This commit is contained in:
Isaac 2026-01-30 18:50:28 +08:00
commit 87e914d1d2
87 changed files with 3466 additions and 2436 deletions

View file

@ -15669,3 +15669,55 @@ Error: %8$@";
"Conversation.Summary.Limit.Title" = "AI Summary";
"Conversation.Summary.Limit.Text" = "Summarize large messages with AI unlimited with Telegram Premium.";
"Appearance.SendWithCmdEnter" = "Send Messages with ⌘+Enter";
"Notification.StarsGift.Crafted" = "You crafted a new unique collectible";
"Gift.Craft.Title" = "Craft Gift";
"Gift.Craft.Description" = "Add up to **4 gifts** to craft new\n**$ %@**.\n\nIf crafting fails, all used gifts\nwill be lost.";
"Gift.Craft.ViewVariants" = "View all new variants";
"Gift.Craft.BackdropTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** backdrop.";
"Gift.Craft.SymbolTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** symbol.";
"Gift.Craft.SuccessChanceSuffix" = "% Success Chance";
"Gift.Craft.Craft" = "Craft %@";
"Gift.Craft.Crafting.Title" = "Crafting";
"Gift.Craft.Crafting.Description" = "If crafting fails, all used gifts\nwill be lost.";
"Gift.Craft.Crafting.SuccessChance" = "%@% Success Chance";
"Gift.Craft.CraftingFailed.Title" = "Crafting Failed";
"Gift.Craft.CraftingFailed.Text_1" = "This crafting attempt was unsuccessful.\n**%@ gift** was lost.";
"Gift.Craft.CraftingFailed.Text_any" = "This crafting attempt was unsuccessful.\n**%@ gifts** were lost.";
"Gift.Craft.CraftingFailed.CraftAnotherGift" = "Craft Another Gift";
"Gift.Craft.Info.Title" = "Gift Crafting";
"Gift.Craft.Info.Description" = "Turn your gifts intro rare, epic,\nuncommon and legendary versions.";
"Gift.Craft.Info.Paragraph1.Title" = "Get Rare Models";
"Gift.Craft.Info.Paragraph1.Text" = "Select up to 4 gifts to craft a new exclusive model.";
"Gift.Craft.Info.Paragraph2.Title" = "Maximize Chances";
"Gift.Craft.Info.Paragraph2.Text" = "Combine more gifts to increase your odds of success.";
"Gift.Craft.Info.Paragraph3.Title" = "Affect the Result";
"Gift.Craft.Info.Paragraph3.Text" = "Use gifts with the same attribute to boost it's chance.";
"Gift.Craft.Info.Understood" = "Understood";
"Gift.Craft.Unavailable.Title" = "Try Later";
"Gift.Craft.Unavailable.Text" = "You will be able to craft this gift on %@.";
"Gift.Craft.Select.Title" = "Select Gifts";
"Gift.Craft.Select.YourGifts" = "Your Gifts";
"Gift.Craft.Select.SaleGifts" = "Suitable Gifts On Sale";
"Gift.Craft.Select.NoGiftsFromCollection" = "You don't have other gifts\nfrom this collection";
"Gift.Attribute.Rare" = "rare";
"Gift.Attribute.Legendary" = "legendary";
"Gift.Attribute.Epic" = "epic";
"Gift.Attribute.Uncommon" = "uncommon";
"SocksProxySetup.CheckStatus" = "Check Status";
"SocksProxySetup.Warning.Title" = "Warning";
"SocksProxySetup.Warning.Text" = "This action will expose your IP address to the admin of the proxy server.";
"SocksProxySetup.Warning.Proceed" = "Proceed";
"Gift.Variants.CollectionInfo.CraftableModel_1" = "**%@** craftable model";
"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

@ -1300,6 +1300,7 @@ public protocol SharedAccountContext: AnyObject {
var currentMediaInputSettings: Atomic<MediaInputSettings> { get }
var currentStickerSettings: Atomic<StickerSettings> { get }
var currentMediaDisplaySettings: Atomic<MediaDisplaySettings> { get }
var currentChatSettings: Atomic<ChatSettings> { get }
var energyUsageSettings: EnergyUsageSettings { get }
@ -1459,15 +1460,15 @@ public protocol SharedAccountContext: AnyObject {
func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: ((StarGift.UniqueGift) -> Void)?) -> ViewController
func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, openChatTheme: (() -> Void)?, dismissed: (() -> Void)?) -> ViewController
func makeGiftWearPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]?) -> ViewController
func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController
func makeGiftUpgradePreviewScreen(context: AccountContext, gift: StarGift.Gift, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController
func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController
func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?) -> ViewController
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, peerId: EnginePeer.Id?, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController
func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController
func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController
func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, onlyCrafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController
func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, crafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController
func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController
func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController
func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift, profileGiftsContext: ProfileGiftsContext?) -> ViewController
func makeGiftDemoScreen(context: AccountContext) -> ViewController
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?)

View file

@ -1489,7 +1489,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
}
public func chatInputTextNodeShouldReturn() -> Bool {
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendButton.alpha.isZero {
self.sendButtonPressed()
}
@ -1497,7 +1497,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
return self.chatInputTextNodeShouldReturn()
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
}
private func applyUpdateSendButtonIcon() {

View file

@ -19,6 +19,7 @@ public final class ResizableSheetComponentEnvironment: Equatable {
public let screenSize: CGSize
public let regularMetricsSize: CGSize?
public let dismiss: (Bool) -> Void
public let boundsUpdated: ActionSlot<CGRect>
public init(
theme: PresentationTheme,
@ -30,7 +31,8 @@ public final class ResizableSheetComponentEnvironment: Equatable {
isCentered: Bool,
screenSize: CGSize,
regularMetricsSize: CGSize?,
dismiss: @escaping (Bool) -> Void
dismiss: @escaping (Bool) -> Void,
boundsUpdated: ActionSlot<CGRect> = ActionSlot<CGRect>()
) {
self.theme = theme
self.statusBarHeight = statusBarHeight
@ -42,6 +44,7 @@ public final class ResizableSheetComponentEnvironment: Equatable {
self.screenSize = screenSize
self.regularMetricsSize = regularMetricsSize
self.dismiss = dismiss
self.boundsUpdated = boundsUpdated
}
public static func ==(lhs: ResizableSheetComponentEnvironment, rhs: ResizableSheetComponentEnvironment) -> Bool {
@ -179,7 +182,7 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
}
}
public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView {
public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView, UIGestureRecognizerDelegate {
public final class Tag {
public init() {
}
@ -213,6 +216,10 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
private let backgroundHandleView: UIImageView
private var ignoreScrolling: Bool = false
private var isDismissingInteractively: Bool = false
private var dismissTranslation: CGFloat = 0.0
private var dismissStartTranslation: CGFloat?
private var dismissPanGesture: UIPanGestureRecognizer?
private var component: ResizableSheetComponent?
private weak var state: EmptyComponentState?
@ -284,6 +291,12 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
self.containerView.addSubview(self.bottomContainer)
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
let dismissPanGesture = UIPanGestureRecognizer(target: self, action: #selector(self.dismissPanGesture(_:)))
dismissPanGesture.maximumNumberOfTouches = 1
dismissPanGesture.delegate = self
self.addGestureRecognizer(dismissPanGesture)
self.dismissPanGesture = dismissPanGesture
}
required init?(coder: NSCoder) {
@ -314,6 +327,26 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
return result
}
override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === self.dismissPanGesture {
let pan = gestureRecognizer as! UIPanGestureRecognizer
let velocity = pan.velocity(in: self)
if abs(velocity.y) <= abs(velocity.x) {
return false
}
}
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === self.dismissPanGesture {
if otherGestureRecognizer === self.scrollView.panGestureRecognizer {
return true
}
}
return false
}
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.dismissAnimated()
@ -328,6 +361,79 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
environment.dismiss(true)
}
private func updateDismissTranslation(_ translation: CGFloat) {
self.dismissTranslation = translation
self.updateScrolling(transition: .immediate)
let maxAlphaDistance = max(1.0, self.bounds.height * 0.9)
let alpha = 1.0 - min(1.0, translation / maxAlphaDistance)
self.dimView.alpha = alpha
}
private func resetDismissTranslation(animated: Bool) {
self.dismissTranslation = 0.0
if animated {
let transition: ComponentTransition = .easeInOut(duration: 0.2)
transition.setAlpha(view: self.dimView, alpha: 1.0)
self.updateScrolling(transition: transition)
} else {
self.dimView.alpha = 1.0
self.updateScrolling(transition: .immediate)
}
}
@objc private func dismissPanGesture(_ recognizer: UIPanGestureRecognizer) {
guard let component = self.component else {
return
}
let translation = recognizer.translation(in: self)
switch recognizer.state {
case .began:
self.dismissStartTranslation = nil
case .changed:
let shouldStartDismiss = self.scrollView.contentOffset.y <= 0.0 && translation.y > 0.0
if shouldStartDismiss {
if !self.isDismissingInteractively {
self.isDismissingInteractively = true
self.dismissStartTranslation = translation.y
self.scrollView.isScrollEnabled = false
}
let start = self.dismissStartTranslation ?? translation.y
let dismissOffset = max(0.0, translation.y - start)
self.scrollView.contentOffset = .zero
self.updateDismissTranslation(dismissOffset)
} else if self.isDismissingInteractively {
let start = self.dismissStartTranslation ?? translation.y
let dismissOffset = max(0.0, translation.y - start)
self.updateDismissTranslation(dismissOffset)
}
case .ended, .cancelled:
if self.isDismissingInteractively {
let velocityY = recognizer.velocity(in: self).y
let currentOffset = self.dismissTranslation
let threshold = min(180.0, self.bounds.height * 0.25)
let shouldDismiss = currentOffset > threshold || velocityY > 1000.0
self.isDismissingInteractively = false
self.scrollView.isScrollEnabled = !component.isFullscreen
if shouldDismiss {
let animateOffset = self.bounds.height - self.backgroundLayer.frame.minY
let initialVelocity = animateOffset > 0.0 ? max(0.0, velocityY) / animateOffset : 0.0
self.animateOut(initialVelocity: initialVelocity, completion: { [weak self] in
self?.environment?.dismiss(false)
})
} else {
self.resetDismissTranslation(animated: true)
}
}
default:
break
}
}
private func updateScrolling(transition: ComponentTransition) {
guard let itemLayout = self.itemLayout, let component = self.component else {
return
@ -356,6 +462,7 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
var containerTransform = CATransform3DIdentity
containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0)
containerTransform = CATransform3DScale(containerTransform, scale, scale, scale)
containerTransform = CATransform3DTranslate(containerTransform, 0.0, self.dismissTranslation, 0.0)
transition.setTransform(view: self.containerView, transform: containerTransform)
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: scaledCornerRadius)
@ -363,8 +470,10 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: .zero, size: self.scrollView.bounds.size))
self.scrollView.isScrollEnabled = false
} else {
self.scrollView.isScrollEnabled = true
self.scrollView.isScrollEnabled = !self.isDismissingInteractively
}
self.environment?.boundsUpdated.invoke(self.scrollView.bounds)
}
private var didPlayAppearanceAnimation = false
@ -379,16 +488,28 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
self.bottomContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
func animateOut(completion: @escaping () -> Void) {
func animateOut(initialVelocity: CGFloat? = nil, completion: @escaping () -> Void) {
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
self.bottomContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
self.dimView.layer.animateAlpha(from: self.dimView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false)
if let initialVelocity = initialVelocity {
let transition = ContainedViewLayoutTransition.animated(duration: 0.35, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
transition.updatePosition(layer: self.scrollContentClippingView.layer, position: CGPoint(x: self.scrollContentClippingView.layer.position.x, y: self.scrollContentClippingView.layer.position.y + animateOffset), completion: { _ in
completion()
})
transition.updatePosition(layer: self.backgroundLayer, position: CGPoint(x: self.backgroundLayer.position.x, y: self.backgroundLayer.position.y + animateOffset))
transition.updatePosition(layer: self.navigationBarContainer.layer, position: CGPoint(x: self.navigationBarContainer.layer.position.x, y: self.navigationBarContainer.layer.position.y + animateOffset))
transition.updatePosition(layer: self.bottomContainer.layer, position: CGPoint(x: self.bottomContainer.layer.position.x, y: self.bottomContainer.layer.position.y + animateOffset))
} else {
let duration: Double = 0.25
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
self.bottomContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
}
}
func update(component: ResizableSheetComponent<ChildEnvironmentType>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
@ -530,6 +651,11 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
if let view = leftItemView.view {
if view.superview == nil {
self.navigationBarContainer.addSubview(view)
if !transition.animation.isImmediate {
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
}
leftItemTransition.setFrame(view: view, frame: leftItemFrame)
}
@ -587,17 +713,19 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
}
if let bottomItem = component.bottomItem {
var bottomItemTransition = transition
let bottomItemView: ComponentView<Empty>
if let current = self.bottomItemView {
bottomItemView = current
} else {
bottomItemTransition = .immediate
bottomItemView = ComponentView<Empty>()
self.bottomItemView = bottomItemView
}
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: sheetEnvironment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
let bottomItemSize = bottomItemView.update(
transition: transition,
transition: bottomItemTransition,
component: bottomItem,
environment: {},
containerSize: CGSize(width: containerSize.width - bottomInsets.left - bottomInsets.right, height: 52.0)
@ -606,12 +734,24 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
if let view = bottomItemView.view {
if view.superview == nil {
self.bottomContainer.addSubview(view)
if !transition.animation.isImmediate {
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
}
transition.setFrame(view: view, frame: bottomItemFrame)
bottomItemTransition.setFrame(view: view, frame: bottomItemFrame)
}
} else if let bottomItemView = self.bottomItemView {
self.bottomItemView = nil
bottomItemView.view?.removeFromSuperview()
if !transition.animation.isImmediate {
bottomItemView.view?.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
bottomItemView.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
bottomItemView.view?.removeFromSuperview()
})
} else {
bottomItemView.view?.removeFromSuperview()
}
}
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: availableSize.height - edgeEffectHeight), size: CGSize(width: fillingSize, height: edgeEffectHeight))

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

@ -89,6 +89,7 @@ public class ItemListActionItem: ListViewItem, ItemListItem {
public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let highlightNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
@ -107,7 +108,10 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white
self.highlightNode = ASDisplayNode()
self.highlightNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
@ -132,6 +136,20 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
self.addSubnode(self.activateArea)
}
public func displayHighlight() {
if self.backgroundNode.supernode != nil {
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
} else {
self.insertSubnode(self.highlightNode, at: 0)
}
Queue.mainQueue().after(1.2, {
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
self.highlightNode.removeFromSupernode()
})
})
}
public func asyncLayout() -> (_ item: ItemListActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
@ -211,6 +229,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
strongSelf.highlightNode.backgroundColor = item.presentationData.theme.list.itemSearchHighlightColor
}
let _ = titleApply()
@ -273,6 +292,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))

View file

@ -139,6 +139,7 @@ swift_library(
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/Utils/DeviceModel",
],
visibility = [
"//visibility:public",

View file

@ -224,7 +224,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { peer, navigation in
}, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in
pushControllerImpl?(controller)
presentControllerImpl?(controller, nil)
}, dismissInput: {}, contentContext: nil, progress: nil, completion: nil)
})
}

View file

@ -623,15 +623,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
let removeSessionImpl: (Int64, @escaping () -> Void) -> Void = { sessionId, completion in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateSessionText),
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateSession, color: .destructive, action: {
dismissAction()
let controller = textAlertController(
context: context,
title: nil,
text: presentationData.strings.AuthSessions_TerminateSessionText,
actions: [
TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateSession, action: {
completion()
updateState {
@ -649,11 +646,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
}
context.sharedContext.updateNotificationTokensRegistration()
}))
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
],
actionLayout: .vertical
)
presentControllerImpl?(controller, nil)
}
let removeWebSessionImpl: (Int64) -> Void = { sessionId in
@ -690,16 +688,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
removeSessionImpl(sessionId, {})
}, terminateOtherSessions: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateOtherSessionsText),
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateOtherSessions, color: .destructive, action: {
dismissAction()
let controller = textAlertController(
context: context,
title: nil,
text: presentationData.strings.AuthSessions_TerminateOtherSessionsText,
actions: [
TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateOtherSessions, action: {
updateState {
return $0.withUpdatedTerminatingOtherSessions(true)
}
@ -715,11 +709,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
}
context.sharedContext.updateNotificationTokensRegistration()
}))
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
],
actionLayout: .vertical
)
presentControllerImpl?(controller, nil)
}, openSession: { session in
let controller = RecentSessionScreen(context: context, subject: .session(session), updateAcceptSecretChats: { value in
updateSessionDisposable.set(activeSessionsContext.updateSessionAcceptsSecretChats(session, accepts: value).start())

View file

@ -30,6 +30,7 @@ import ContextUI
import QuickReactionSetupController
import AvatarEditorScreen
import PeerSelectionScreen
import DeviceModel
enum SettingsSearchableItemIcon {
case profile
@ -3895,7 +3896,7 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
present(.push, themeSettingsController(context: context, focusOnItemTag: itemTag))
}
return [
var items: [SettingsSearchableItem] = [
SettingsSearchableItem(
id: "appearance",
title: strings.Settings_Appearance,
@ -4094,14 +4095,31 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
),
SettingsSearchableItem(
id: "appearance/tap-for-next-media",
title: strings.Appearance_ShowNextMediaOnTap,
icon: icon,
breadcrumbs: [strings.Settings_Appearance],
isVisible: false,
present: { context, _, present in
presentAppearanceSettings(context, present, .tapForNextMedia)
}
),
)
]
if DeviceModel.current.isIpad {
items.append(
SettingsSearchableItem(
id: "appearance/send-with-cmd-enter",
title: strings.Appearance_SendWithCmdEnter,
icon: icon,
breadcrumbs: [strings.Settings_Appearance],
isVisible: false,
present: { context, _, present in
presentAppearanceSettings(context, present, .sendWithCmdEnter)
}
)
)
}
return items
}
private func languageSearchableItems(context: AccountContext, localizations: [LocalizationInfo]) -> [SettingsSearchableItem] {

View file

@ -12,12 +12,7 @@ import AppBundle
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
context.fill(bounds)
context.setBlendMode(.clear)
context.fillEllipse(in: bounds)
context.setBlendMode(.normal)
context.clear(bounds)
let lineWidth: CGFloat
if selected {
@ -113,8 +108,10 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
override init() {
self.iconNode = ASImageNode()
self.iconNode.clipsToBounds = true
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
self.iconNode.isLayerBacked = true
self.iconNode.cornerRadius = 15.0
self.overlayNode = ASImageNode()
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
@ -199,6 +196,7 @@ private let selectedTextFont = Font.medium(12.0)
class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let highlightNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
@ -219,6 +217,9 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.highlightNode = ASDisplayNode()
self.highlightNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
@ -234,6 +235,20 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
self.addSubnode(self.containerNode)
}
public func displayHighlight() {
if self.backgroundNode.supernode != nil {
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
} else {
self.insertSubnode(self.highlightNode, at: 0)
}
Queue.mainQueue().after(1.2, {
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
self.highlightNode.removeFromSupernode()
})
})
}
func asyncLayout() -> (_ item: ThemeSettingsAppIconItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
return { item, params, neighbors in
let contentSize: CGSize
@ -280,6 +295,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.highlightNode.backgroundColor = item.theme.list.itemSearchHighlightColor
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
@ -321,6 +337,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))

View file

@ -23,6 +23,7 @@ import ThemeCarouselItem
import ThemeAccentColorScreen
import WallpaperGridScreen
import PeerNameColorItem
import DeviceModel
private final class ThemeSettingsControllerArguments {
let context: AccountContext
@ -38,13 +39,34 @@ private final class ThemeSettingsControllerArguments {
let openBubbleSettings: () -> Void
let openPowerSavingSettings: () -> Void
let openStickersAndEmoji: () -> Void
let toggleSendWithCmdEnter: (Bool) -> Void
let toggleShowNextMediaOnTap: (Bool) -> Void
let selectAppIcon: (PresentationAppIcon) -> Void
let editTheme: (PresentationCloudTheme) -> Void
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, openNameColorSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
init(
context: AccountContext,
selectTheme: @escaping (PresentationThemeReference) -> Void,
openThemeSettings: @escaping () -> Void,
openWallpaperSettings: @escaping () -> Void,
openNameColorSettings: @escaping () -> Void,
selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void,
openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void,
toggleNightTheme: @escaping (Bool) -> Void,
openAutoNightTheme: @escaping () -> Void,
openTextSize: @escaping () -> Void,
openBubbleSettings: @escaping () -> Void,
openPowerSavingSettings: @escaping () -> Void,
openStickersAndEmoji: @escaping () -> Void,
toggleSendWithCmdEnter: @escaping (Bool) -> Void,
toggleShowNextMediaOnTap: @escaping (Bool) -> Void,
selectAppIcon: @escaping (PresentationAppIcon) -> Void,
editTheme: @escaping (PresentationCloudTheme) -> Void,
themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void,
colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
) {
self.context = context
self.selectTheme = selectTheme
self.openThemeSettings = openThemeSettings
@ -58,6 +80,7 @@ private final class ThemeSettingsControllerArguments {
self.openBubbleSettings = openBubbleSettings
self.openPowerSavingSettings = openPowerSavingSettings
self.openStickersAndEmoji = openStickersAndEmoji
self.toggleSendWithCmdEnter = toggleSendWithCmdEnter
self.toggleShowNextMediaOnTap = toggleShowNextMediaOnTap
self.selectAppIcon = selectAppIcon
self.editTheme = editTheme
@ -84,6 +107,7 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
case powerSaving
case stickersAndEmoji
case animations
case sendWithCmdEnter
case tapForNextMedia
case nightMode
@ -112,6 +136,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case powerSaving
case stickersAndEmoji
case otherHeader(PresentationTheme, String)
case sendWithCmdEnter(PresentationTheme, String, Bool)
case showNextMediaOnTap(PresentationTheme, String, Bool)
case showNextMediaOnTapInfo(PresentationTheme, String)
@ -127,7 +152,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsControllerSection.icon.rawValue
case .powerSaving, .stickersAndEmoji:
return ThemeSettingsControllerSection.message.rawValue
case .otherHeader, .showNextMediaOnTap, .showNextMediaOnTapInfo:
case .otherHeader, .sendWithCmdEnter, .showNextMediaOnTap, .showNextMediaOnTapInfo:
return ThemeSettingsControllerSection.other.rawValue
}
}
@ -164,10 +189,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 13
case .otherHeader:
return 14
case .showNextMediaOnTap:
case .sendWithCmdEnter:
return 15
case .showNextMediaOnTapInfo:
case .showNextMediaOnTap:
return 16
case .showNextMediaOnTapInfo:
return 17
}
}
@ -263,6 +290,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .sendWithCmdEnter(lhsTheme, lhsTitle, lhsValue):
if case let .sendWithCmdEnter(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .showNextMediaOnTap(lhsTheme, lhsTitle, lhsValue):
if case let .showNextMediaOnTap(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
@ -351,6 +384,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
})
case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .sendWithCmdEnter(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleSendWithCmdEnter(value)
}, tag: ThemeSettingsEntryTag.sendWithCmdEnter)
case let .showNextMediaOnTap(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleShowNextMediaOnTap(value)
@ -361,7 +398,21 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
}
}
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], accountPeer: EnginePeer?, nameColors: PeerNameColors) -> [ThemeSettingsControllerEntry] {
private func themeSettingsControllerEntries(
presentationData: PresentationData,
presentationThemeSettings: PresentationThemeSettings,
chatSettings: ChatSettings,
mediaSettings: MediaDisplaySettings,
themeReference: PresentationThemeReference,
availableThemes: [PresentationThemeReference],
availableAppIcons: [PresentationAppIcon],
currentAppIconName: String?,
isPremium: Bool,
chatThemes: [PresentationThemeReference],
animatedEmojiStickers: [String: [StickerPackItem]],
accountPeer: EnginePeer?,
nameColors: PeerNameColors
) -> [ThemeSettingsControllerEntry] {
var entries: [ThemeSettingsControllerEntry] = []
let strings = presentationData.strings
@ -437,6 +488,9 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
}
entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased()))
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))
@ -557,6 +611,10 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
pushControllerImpl?(installedStickerPacksController(context: context, mode: .general, archivedPacks: archivedStickerPacks, updatedPacks: { _ in
}))
})
}, toggleSendWithCmdEnter: { value in
let _ = updateChatSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedSendWithCmdEnter(value)
}).start()
}, toggleShowNextMediaOnTap: { value in
let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedShowNextMediaOnTap(value)
@ -1035,9 +1093,26 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})
})
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)))
let signal = combineLatest(
queue: .mainQueue(),
context.sharedContext.presentationData,
context.sharedContext.accountManager.sharedData(keys: [
ApplicationSpecificSharedDataKeys.presentationThemeSettings,
ApplicationSpecificSharedDataKeys.chatSettings,
ApplicationSpecificSharedDataKeys.mediaDisplaySettings,
SharedDataKeys.chatThemes
]),
cloudThemes.get(),
availableAppIcons,
currentAppIconName.get(),
removedThemeIndexesPromise.get(),
animatedEmojiStickers,
context.account.postbox.peerView(id: context.account.peerId),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
)
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
let chatSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.chatSettings]?.get(ChatSettings.self) ?? ChatSettings.defaultSettings
let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false
@ -1077,7 +1152,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
chatThemes.insert(.builtin(.dayClassic), at: 0)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer, nameColors: context.peerNameColors), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, chatSettings: chatSettings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer, nameColors: context.peerNameColors), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
return (controllerState, (listState, arguments))
}

View file

@ -289,9 +289,9 @@ private final class StickerPackContainer: ASDisplayNode {
self.addSubnode(self.gridNode)
self.titleContainer.addSubnode(self.titleNode)
self.addSubnode(self.topContainerNode)
self.addSubnode(self.titleContainer)
self.addSubnode(self.topContainerNode)
self.addSubnode(self.bottomContainerNode)
self.gridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
@ -654,6 +654,13 @@ private final class StickerPackContainer: ASDisplayNode {
self.reorderingGestureRecognizer = reorderingGestureRecognizer
self.gridNode.view.addGestureRecognizer(reorderingGestureRecognizer)
self.gridNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.gridNode.clipsToBounds = true
self.gridNode.layer.cornerRadius = 40.0
self.topEdgeEffectView.clipsToBounds = true
self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.topEdgeEffectView.layer.cornerRadius = 40.0
self.topContainerNode.view.addSubview(self.topEdgeEffectView)
self.bottomContainerNode.view.addSubview(self.bottomEdgeEffectView)
}
@ -1891,7 +1898,7 @@ private final class StickerPackContainer: ASDisplayNode {
transition.updateFrame(node: self.bottomContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: 90.0)))
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top + titleAreaInset), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - titleAreaInset))
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top))
let itemsPerRow = 5
let fillingWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
@ -1923,12 +1930,13 @@ private final class StickerPackContainer: ASDisplayNode {
let initialRevealedRowCount: CGFloat = 4.5
let topInset: CGFloat
var topInset: CGFloat
if case .regular = layout.metrics.widthClass {
topInset = 0.0
topInset = titleAreaInset
} else {
topInset = insets.top + max(0.0, layout.size.height - floor(initialRevealedRowCount * itemWidth) - insets.top - actionAreaHeight - titleAreaInset)
}
let additionalGridBottomInset = max(0.0, gridFrame.size.height - actionAreaHeight - contentHeight)
let gridInsets = UIEdgeInsets(top: topInset, left: gridLeftInset, bottom: actionAreaHeight + additionalGridBottomInset, right: layout.size.width - fillingWidth - gridLeftInset)
@ -2112,6 +2120,10 @@ private final class StickerPackContainer: ASDisplayNode {
buttonView.frame = buttonFrame
}
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 90.0))
transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
self.topEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: actionAreaHeight - 90.0), size: CGSize(width: layout.size.width, height: 90.0))
transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
self.bottomEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
@ -2133,7 +2145,7 @@ private final class StickerPackContainer: ASDisplayNode {
return
}
let minBackgroundY = gridFrame.minY - titleAreaInset
let minBackgroundY = gridFrame.minY
let unclippedBackgroundY = gridFrame.minY - presentationLayout.contentOffset.y - titleAreaInset
let offsetFromInitialPosition = presentationLayout.contentOffset.y + gridInsets.top
@ -2173,7 +2185,7 @@ private final class StickerPackContainer: ASDisplayNode {
var titleContainerFrame: CGRect
if case .regular = layout.metrics.widthClass {
backgroundFrame.origin.y = min(0.0, backgroundFrame.origin.y)
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0)), size: CGSize())
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0) + 10.0), size: CGSize())
} else {
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: backgroundFrame.minY + floor((56.0) / 2.0) + 10.0), size: CGSize())
}
@ -3103,12 +3115,12 @@ private final class StickerPackContextReferenceContentSource: ContextReferenceCo
private func generateShadowImage() -> UIImage? {
return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in
return generateImage(CGSize(width: 220.0, height: 220.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 10.0).cgPath
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 100.0, height: 100.0), cornerRadius: 40.0).cgPath
context.addPath(path)
context.fillPath()
@ -3117,7 +3129,7 @@ private func generateShadowImage() -> UIImage? {
context.setBlendMode(.clear)
context.addPath(path)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70)
})?.stretchableImage(withLeftCapWidth: 110, topCapHeight: 110)
}
private func generateArrowImage(color: UIColor) -> UIImage? {
@ -3290,38 +3302,6 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer {
}
}
private func generateShadowImage(corners: CACornerMask, radius: CGFloat) -> UIImage? {
return generateImage(CGSize(width: 120.0, height: 120), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
// context.saveGState()
context.setShadow(offset: CGSize(), blur: 28.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
var rectCorners: UIRectCorner = []
if corners.contains(.layerMinXMinYCorner) {
rectCorners.insert(.topLeft)
}
if corners.contains(.layerMaxXMinYCorner) {
rectCorners.insert(.topRight)
}
if corners.contains(.layerMinXMaxYCorner) {
rectCorners.insert(.bottomLeft)
}
if corners.contains(.layerMaxXMaxYCorner) {
rectCorners.insert(.bottomRight)
}
let path = UIBezierPath(roundedRect: CGRect(x: 30.0, y: 30.0, width: 60.0, height: 60.0), byRoundingCorners: rectCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath
context.addPath(path)
context.fillPath()
// context.restoreGState()
// context.setBlendMode(.clear)
// context.addPath(path)
// context.fillPath()
})?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 60)
}
private final class CopyView: UIView {
let shadow: UIImageView
var snapshotView: UIView?

View file

@ -997,6 +997,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2029777832] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityEpic($0) }
dict[-822614104] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityLegendary($0) }
dict[-259174037] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityRare($0) }
dict[-607231095] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityUncommon($0) }
dict[1118831432] = { return Api.StarGiftAuctionAcquiredGift.parse_starGiftAuctionAcquiredGift($0) }
dict[984483112] = { return Api.StarGiftAuctionRound.parse_starGiftAuctionRound($0) }
dict[178266597] = { return Api.StarGiftAuctionRound.parse_starGiftAuctionRoundExtendable($0) }

View file

@ -1318,6 +1318,7 @@ public extension Api {
case starGiftAttributeRarityEpic
case starGiftAttributeRarityLegendary
case starGiftAttributeRarityRare
case starGiftAttributeRarityUncommon
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -1342,6 +1343,11 @@ public extension Api {
buffer.appendInt32(-259174037)
}
break
case .starGiftAttributeRarityUncommon:
if boxed {
buffer.appendInt32(-607231095)
}
break
}
}
@ -1355,6 +1361,8 @@ public extension Api {
return ("starGiftAttributeRarityLegendary", [])
case .starGiftAttributeRarityRare:
return ("starGiftAttributeRarityRare", [])
case .starGiftAttributeRarityUncommon:
return ("starGiftAttributeRarityUncommon", [])
}
}
@ -1378,6 +1386,9 @@ public extension Api {
public static func parse_starGiftAttributeRarityRare(_ reader: BufferReader) -> StarGiftAttributeRarity? {
return Api.StarGiftAttributeRarity.starGiftAttributeRarityRare
}
public static func parse_starGiftAttributeRarityUncommon(_ reader: BufferReader) -> StarGiftAttributeRarity? {
return Api.StarGiftAttributeRarity.starGiftAttributeRarityUncommon
}
}
}
public extension Api {
@ -2127,76 +2138,3 @@ public extension Api {
}
}
}
public extension Api {
enum StarsAmount: TypeConstructorDescription {
public class Cons_starsAmount {
public var amount: Int64
public var nanos: Int32
public init(amount: Int64, nanos: Int32) {
self.amount = amount
self.nanos = nanos
}
}
public class Cons_starsTonAmount {
public var amount: Int64
public init(amount: Int64) {
self.amount = amount
}
}
case starsAmount(Cons_starsAmount)
case starsTonAmount(Cons_starsTonAmount)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsAmount(let _data):
if boxed {
buffer.appendInt32(-1145654109)
}
serializeInt64(_data.amount, buffer: buffer, boxed: false)
serializeInt32(_data.nanos, buffer: buffer, boxed: false)
break
case .starsTonAmount(let _data):
if boxed {
buffer.appendInt32(1957618656)
}
serializeInt64(_data.amount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsAmount(let _data):
return ("starsAmount", [("amount", _data.amount as Any), ("nanos", _data.nanos as Any)])
case .starsTonAmount(let _data):
return ("starsTonAmount", [("amount", _data.amount as Any)])
}
}
public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StarsAmount.starsAmount(Cons_starsAmount(amount: _1!, nanos: _2!))
}
else {
return nil
}
}
public static func parse_starsTonAmount(_ reader: BufferReader) -> StarsAmount? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.StarsAmount.starsTonAmount(Cons_starsTonAmount(amount: _1!))
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,76 @@
public extension Api {
enum StarsAmount: TypeConstructorDescription {
public class Cons_starsAmount {
public var amount: Int64
public var nanos: Int32
public init(amount: Int64, nanos: Int32) {
self.amount = amount
self.nanos = nanos
}
}
public class Cons_starsTonAmount {
public var amount: Int64
public init(amount: Int64) {
self.amount = amount
}
}
case starsAmount(Cons_starsAmount)
case starsTonAmount(Cons_starsTonAmount)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsAmount(let _data):
if boxed {
buffer.appendInt32(-1145654109)
}
serializeInt64(_data.amount, buffer: buffer, boxed: false)
serializeInt32(_data.nanos, buffer: buffer, boxed: false)
break
case .starsTonAmount(let _data):
if boxed {
buffer.appendInt32(1957618656)
}
serializeInt64(_data.amount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsAmount(let _data):
return ("starsAmount", [("amount", _data.amount as Any), ("nanos", _data.nanos as Any)])
case .starsTonAmount(let _data):
return ("starsTonAmount", [("amount", _data.amount as Any)])
}
}
public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StarsAmount.starsAmount(Cons_starsAmount(amount: _1!, nanos: _2!))
}
else {
return nil
}
}
public static func parse_starsTonAmount(_ reader: BufferReader) -> StarsAmount? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.StarsAmount.starsTonAmount(Cons_starsTonAmount(amount: _1!))
}
else {
return nil
}
}
}
}
public extension Api {
enum StarsGiftOption: TypeConstructorDescription {
public class Cons_starsGiftOption {
@ -1662,186 +1735,3 @@ public extension Api {
}
}
}
public extension Api {
enum StickerSetCovered: TypeConstructorDescription {
public class Cons_stickerSetCovered {
public var set: Api.StickerSet
public var cover: Api.Document
public init(set: Api.StickerSet, cover: Api.Document) {
self.set = set
self.cover = cover
}
}
public class Cons_stickerSetFullCovered {
public var set: Api.StickerSet
public var packs: [Api.StickerPack]
public var keywords: [Api.StickerKeyword]
public var documents: [Api.Document]
public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) {
self.set = set
self.packs = packs
self.keywords = keywords
self.documents = documents
}
}
public class Cons_stickerSetMultiCovered {
public var set: Api.StickerSet
public var covers: [Api.Document]
public init(set: Api.StickerSet, covers: [Api.Document]) {
self.set = set
self.covers = covers
}
}
public class Cons_stickerSetNoCovered {
public var set: Api.StickerSet
public init(set: Api.StickerSet) {
self.set = set
}
}
case stickerSetCovered(Cons_stickerSetCovered)
case stickerSetFullCovered(Cons_stickerSetFullCovered)
case stickerSetMultiCovered(Cons_stickerSetMultiCovered)
case stickerSetNoCovered(Cons_stickerSetNoCovered)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stickerSetCovered(let _data):
if boxed {
buffer.appendInt32(1678812626)
}
_data.set.serialize(buffer, true)
_data.cover.serialize(buffer, true)
break
case .stickerSetFullCovered(let _data):
if boxed {
buffer.appendInt32(1087454222)
}
_data.set.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.packs.count))
for item in _data.packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.keywords.count))
for item in _data.keywords {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.documents.count))
for item in _data.documents {
item.serialize(buffer, true)
}
break
case .stickerSetMultiCovered(let _data):
if boxed {
buffer.appendInt32(872932635)
}
_data.set.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.covers.count))
for item in _data.covers {
item.serialize(buffer, true)
}
break
case .stickerSetNoCovered(let _data):
if boxed {
buffer.appendInt32(2008112412)
}
_data.set.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stickerSetCovered(let _data):
return ("stickerSetCovered", [("set", _data.set as Any), ("cover", _data.cover as Any)])
case .stickerSetFullCovered(let _data):
return ("stickerSetFullCovered", [("set", _data.set as Any), ("packs", _data.packs as Any), ("keywords", _data.keywords as Any), ("documents", _data.documents as Any)])
case .stickerSetMultiCovered(let _data):
return ("stickerSetMultiCovered", [("set", _data.set as Any), ("covers", _data.covers as Any)])
case .stickerSetNoCovered(let _data):
return ("stickerSetNoCovered", [("set", _data.set as Any)])
}
}
public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
var _2: Api.Document?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Document
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StickerSetCovered.stickerSetCovered(Cons_stickerSetCovered(set: _1!, cover: _2!))
}
else {
return nil
}
}
public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.StickerKeyword]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self)
}
var _4: [Api.Document]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.StickerSetCovered.stickerSetFullCovered(Cons_stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!))
}
else {
return nil
}
}
public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
var _2: [Api.Document]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StickerSetCovered.stickerSetMultiCovered(Cons_stickerSetMultiCovered(set: _1!, covers: _2!))
}
else {
return nil
}
}
public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
let _c1 = _1 != nil
if _c1 {
return Api.StickerSetCovered.stickerSetNoCovered(Cons_stickerSetNoCovered(set: _1!))
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,186 @@
public extension Api {
enum StickerSetCovered: TypeConstructorDescription {
public class Cons_stickerSetCovered {
public var set: Api.StickerSet
public var cover: Api.Document
public init(set: Api.StickerSet, cover: Api.Document) {
self.set = set
self.cover = cover
}
}
public class Cons_stickerSetFullCovered {
public var set: Api.StickerSet
public var packs: [Api.StickerPack]
public var keywords: [Api.StickerKeyword]
public var documents: [Api.Document]
public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) {
self.set = set
self.packs = packs
self.keywords = keywords
self.documents = documents
}
}
public class Cons_stickerSetMultiCovered {
public var set: Api.StickerSet
public var covers: [Api.Document]
public init(set: Api.StickerSet, covers: [Api.Document]) {
self.set = set
self.covers = covers
}
}
public class Cons_stickerSetNoCovered {
public var set: Api.StickerSet
public init(set: Api.StickerSet) {
self.set = set
}
}
case stickerSetCovered(Cons_stickerSetCovered)
case stickerSetFullCovered(Cons_stickerSetFullCovered)
case stickerSetMultiCovered(Cons_stickerSetMultiCovered)
case stickerSetNoCovered(Cons_stickerSetNoCovered)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stickerSetCovered(let _data):
if boxed {
buffer.appendInt32(1678812626)
}
_data.set.serialize(buffer, true)
_data.cover.serialize(buffer, true)
break
case .stickerSetFullCovered(let _data):
if boxed {
buffer.appendInt32(1087454222)
}
_data.set.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.packs.count))
for item in _data.packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.keywords.count))
for item in _data.keywords {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.documents.count))
for item in _data.documents {
item.serialize(buffer, true)
}
break
case .stickerSetMultiCovered(let _data):
if boxed {
buffer.appendInt32(872932635)
}
_data.set.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.covers.count))
for item in _data.covers {
item.serialize(buffer, true)
}
break
case .stickerSetNoCovered(let _data):
if boxed {
buffer.appendInt32(2008112412)
}
_data.set.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stickerSetCovered(let _data):
return ("stickerSetCovered", [("set", _data.set as Any), ("cover", _data.cover as Any)])
case .stickerSetFullCovered(let _data):
return ("stickerSetFullCovered", [("set", _data.set as Any), ("packs", _data.packs as Any), ("keywords", _data.keywords as Any), ("documents", _data.documents as Any)])
case .stickerSetMultiCovered(let _data):
return ("stickerSetMultiCovered", [("set", _data.set as Any), ("covers", _data.covers as Any)])
case .stickerSetNoCovered(let _data):
return ("stickerSetNoCovered", [("set", _data.set as Any)])
}
}
public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
var _2: Api.Document?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Document
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StickerSetCovered.stickerSetCovered(Cons_stickerSetCovered(set: _1!, cover: _2!))
}
else {
return nil
}
}
public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.StickerKeyword]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self)
}
var _4: [Api.Document]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.StickerSetCovered.stickerSetFullCovered(Cons_stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!))
}
else {
return nil
}
}
public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
var _2: [Api.Document]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StickerSetCovered.stickerSetMultiCovered(Cons_stickerSetMultiCovered(set: _1!, covers: _2!))
}
else {
return nil
}
}
public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? {
var _1: Api.StickerSet?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
}
let _c1 = _1 != nil
if _c1 {
return Api.StickerSetCovered.stickerSetNoCovered(Cons_stickerSetNoCovered(set: _1!))
}
else {
return nil
}
}
}
}
public extension Api {
enum StoriesStealthMode: TypeConstructorDescription {
public class Cons_storiesStealthMode {
@ -1532,64 +1715,3 @@ public extension Api {
}
}
}
public extension Api {
enum TopPeerCategoryPeers: TypeConstructorDescription {
public class Cons_topPeerCategoryPeers {
public var category: Api.TopPeerCategory
public var count: Int32
public var peers: [Api.TopPeer]
public init(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) {
self.category = category
self.count = count
self.peers = peers
}
}
case topPeerCategoryPeers(Cons_topPeerCategoryPeers)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .topPeerCategoryPeers(let _data):
if boxed {
buffer.appendInt32(-75283823)
}
_data.category.serialize(buffer, true)
serializeInt32(_data.count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.peers.count))
for item in _data.peers {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .topPeerCategoryPeers(let _data):
return ("topPeerCategoryPeers", [("category", _data.category as Any), ("count", _data.count as Any), ("peers", _data.peers as Any)])
}
}
public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? {
var _1: Api.TopPeerCategory?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory
}
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.TopPeer]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.TopPeerCategoryPeers.topPeerCategoryPeers(Cons_topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!))
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,64 @@
public extension Api {
enum TopPeerCategoryPeers: TypeConstructorDescription {
public class Cons_topPeerCategoryPeers {
public var category: Api.TopPeerCategory
public var count: Int32
public var peers: [Api.TopPeer]
public init(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) {
self.category = category
self.count = count
self.peers = peers
}
}
case topPeerCategoryPeers(Cons_topPeerCategoryPeers)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .topPeerCategoryPeers(let _data):
if boxed {
buffer.appendInt32(-75283823)
}
_data.category.serialize(buffer, true)
serializeInt32(_data.count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.peers.count))
for item in _data.peers {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .topPeerCategoryPeers(let _data):
return ("topPeerCategoryPeers", [("category", _data.category as Any), ("count", _data.count as Any), ("peers", _data.peers as Any)])
}
}
public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? {
var _1: Api.TopPeerCategory?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory
}
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.TopPeer]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.TopPeerCategoryPeers.topPeerCategoryPeers(Cons_topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!))
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum Update: TypeConstructorDescription {
public class Cons_updateBotBusinessConnect {

View file

@ -248,7 +248,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
guard let gift = StarGift(apiStarGift: apiGift) else {
return nil
}
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0, canCraftAt: canCraftAt))
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0, canCraftAt: canCraftAt, isCrafted: (flags & (1 << 16)) != 0))
case let .messageActionPaidMessagesRefunded(messageActionPaidMessagesRefundedData):
let (count, stars) = (messageActionPaidMessagesRefundedData.count, messageActionPaidMessagesRefundedData.stars)
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))

View file

@ -256,7 +256,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool, isAuctionAcquired: Bool, toPeerId: EnginePeer.Id?, number: Int32?)
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool, fromOffer: Bool, canCraftAt: Int32?)
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool, fromOffer: Bool, canCraftAt: Int32?, isCrafted: Bool)
case paidMessagesRefunded(count: Int32, stars: Int64)
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
case conferenceCall(ConferenceCall)
@ -404,7 +404,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else if let stars = decoder.decodeOptionalInt64ForKey("resaleStars") {
resaleAmount = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars)
}
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false), fromOffer: decoder.decodeBoolForKey("fromOffer", orElse: false), canCraftAt: decoder.decodeOptionalInt32ForKey("canCraftAt"))
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false), fromOffer: decoder.decodeBoolForKey("fromOffer", orElse: false), canCraftAt: decoder.decodeOptionalInt32ForKey("canCraftAt"), isCrafted: decoder.decodeBoolForKey("isCrafted", orElse: false))
case 46:
self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0))
case 47:
@ -803,7 +803,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "number")
}
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned, fromOffer, canCraftAt):
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned, fromOffer, canCraftAt, isCrafted):
encoder.encodeInt32(45, forKey: "_rawValue")
encoder.encodeObject(gift, forKey: "gift")
encoder.encodeBool(isUpgrade, forKey: "isUpgrade")
@ -864,6 +864,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "canCraftAt")
}
encoder.encodeBool(isCrafted, forKey: "isCrafted")
case let .paidMessagesRefunded(count, stars):
encoder.encodeInt32(46, forKey: "_rawValue")
encoder.encodeInt32(count, forKey: "count")
@ -971,7 +973,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
peerIds.append(toPeerId)
}
return peerIds
case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _, _, _):
case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _, _, _, _):
var peerIds: [PeerId] = []
if let peerId {
peerIds.append(peerId)

View file

@ -460,12 +460,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
case rare
case epic
case legendary
case uncommon
public var permilleValue: Int32 {
switch self {
case let .permille(value):
return value
case .rare, .epic, .legendary:
case .rare, .epic, .legendary, .uncommon:
return 0
}
}
@ -501,6 +502,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
return .epic
case 3:
return .legendary
case 4:
return .uncommon
default:
let permille = try container.decodeIfPresent(Int32.self, forKey: .rarity) ?? 0
return .permille(permille)
@ -555,6 +558,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
return .epic
case 3:
return .legendary
case 4:
return .uncommon
default:
let permille = decoder.decodeInt32ForKey(CodingKeys.rarity.rawValue, orElse: 0)
return .permille(permille)
@ -614,6 +619,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
try container.encode(Int32(2), forKey: .rarityType)
case .legendary:
try container.encode(Int32(3), forKey: .rarityType)
case .uncommon:
try container.encode(Int32(4), forKey: .rarityType)
}
}
@ -660,6 +667,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
encoder.encodeInt32(2, forKey: CodingKeys.rarityType.rawValue)
case .legendary:
encoder.encodeInt32(3, forKey: CodingKeys.rarityType.rawValue)
case .uncommon:
encoder.encodeInt32(4, forKey: CodingKeys.rarityType.rawValue)
}
}
@ -772,7 +781,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
public let title: String
public let number: Int32
public let slug: String
public let owner: Owner
public let owner: Owner?
public let attributes: [Attribute]
public let availability: Availability
public let giftAddress: String?
@ -789,7 +798,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
public let minOfferStars: Int64?
public let craftChancePermille: Int32?
public init(id: Int64, giftId: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?, valueAmount: Int64?, valueCurrency: String?, valueUsdAmount: Int64?, flags: Flags, themePeerId: EnginePeer.Id?, peerColor: PeerCollectibleColor?, hostPeerId: EnginePeer.Id?, minOfferStars: Int64?, craftChancePermille: Int32?) {
public init(id: Int64, giftId: Int64, title: String, number: Int32, slug: String, owner: Owner?, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?, valueAmount: Int64?, valueCurrency: String?, valueUsdAmount: Int64?, flags: Flags, themePeerId: EnginePeer.Id?, peerColor: PeerCollectibleColor?, hostPeerId: EnginePeer.Id?, minOfferStars: Int64?, craftChancePermille: Int32?) {
self.id = id
self.giftId = giftId
self.title = title
@ -827,7 +836,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
} else if let ownerName = try container.decodeIfPresent(String.self, forKey: .ownerName) {
self.owner = .name(ownerName)
} else {
self.owner = .name("Unknown")
self.owner = nil
}
self.attributes = try container.decode([UniqueGift.Attribute].self, forKey: .attributes)
self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability)
@ -865,7 +874,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
} else if let ownerName = decoder.decodeOptionalStringForKey(CodingKeys.ownerName.rawValue) {
self.owner = .name(ownerName)
} else {
self.owner = .name("Unknown")
self.owner = nil
}
self.attributes = (try? decoder.decodeObjectArrayWithCustomDecoderForKey(CodingKeys.attributes.rawValue, decoder: { UniqueGift.Attribute(decoder: $0) })) ?? []
self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability
@ -904,6 +913,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
try container.encode(name, forKey: .ownerName)
case let .address(address):
try container.encode(address, forKey: .ownerAddress)
default:
break
}
try container.encode(self.attributes, forKey: .attributes)
try container.encode(self.availability, forKey: .availability)
@ -935,6 +946,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
encoder.encodeString(name, forKey: CodingKeys.ownerName.rawValue)
case let .address(address):
encoder.encodeString(address, forKey: CodingKeys.ownerAddress.rawValue)
default:
break
}
encoder.encodeObjectArray(self.attributes, forKey: CodingKeys.attributes.rawValue)
encoder.encodeObject(self.availability, forKey: CodingKeys.availability.rawValue)
@ -1257,7 +1270,7 @@ extension StarGift {
))
case let .starGiftUnique(starGiftUniqueData):
let (apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, apiResellAmount, releasedBy, valueAmount, valueCurrency, valueUsdAmount, themePeer, peerColor, hostPeerId, minOfferStars, craftChancePermille) = (starGiftUniqueData.flags, starGiftUniqueData.id, starGiftUniqueData.giftId, starGiftUniqueData.title, starGiftUniqueData.slug, starGiftUniqueData.num, starGiftUniqueData.ownerId, starGiftUniqueData.ownerName, starGiftUniqueData.ownerAddress, starGiftUniqueData.attributes, starGiftUniqueData.availabilityIssued, starGiftUniqueData.availabilityTotal, starGiftUniqueData.giftAddress, starGiftUniqueData.resellAmount, starGiftUniqueData.releasedBy, starGiftUniqueData.valueAmount, starGiftUniqueData.valueCurrency, starGiftUniqueData.valueUsdAmount, starGiftUniqueData.themePeer, starGiftUniqueData.peerColor, starGiftUniqueData.hostId, starGiftUniqueData.offerMinStars, starGiftUniqueData.craftChancePermille)
let owner: StarGift.UniqueGift.Owner
let owner: StarGift.UniqueGift.Owner?
if let ownerAddress {
owner = .address(ownerAddress)
} else if let ownerId = ownerPeerId?.peerId {
@ -1265,7 +1278,7 @@ extension StarGift {
} else if let ownerName {
owner = .name(ownerName)
} else {
return nil
owner = nil
}
let resellAmounts = apiResellAmount?.compactMap { CurrencyAmount(apiAmount: $0) }
var flags = StarGift.UniqueGift.Flags()
@ -1296,8 +1309,7 @@ extension StarGift {
number: num,
slug: slug,
owner: owner,
attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0)
},
attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) },
availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal),
giftAddress: giftAddress,
resellAmounts: resellAmounts,
@ -1312,7 +1324,7 @@ extension StarGift {
hostPeerId: hostPeerId?.peerId,
minOfferStars: minOfferStars.flatMap { Int64($0) },
craftChancePermille: craftChancePermille
))
))
}
}
}
@ -1524,7 +1536,7 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
var media = currentMessage.media
if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt) = action.action, case let .unique(uniqueGift) = gift {
if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt, isCrafted) = action.action, case let .unique(uniqueGift) = gift {
let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo }
media = [
TelegramMediaAction(
@ -1546,7 +1558,8 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift
dropOriginalDetailsStars: nil,
assigned: assigned,
fromOffer: fromOffer,
canCraftAt: canCraftAt
canCraftAt: canCraftAt,
isCrafted: isCrafted
)
)
]
@ -1653,7 +1666,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
let message = updateNewMessageData.message
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
for media in message.media {
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
let reference: StarGiftReference
if let peerId, let savedId {
reference = .peer(peerId: peerId, id: savedId)
@ -2370,12 +2383,16 @@ private final class ProfileGiftsContextImpl {
}
func removeStarGifts(references: [StarGiftReference]) {
self.gifts.removeAll(where: {
if let reference = $0.reference {
return references.contains(reference)
} else {
return false
self.gifts.removeAll(where: { gift in
if let reference = gift.reference, references.contains(reference) {
return true
}
for reference in references {
if case let .slug(slug) = reference, case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
}
return false
})
self.pushState()
@ -2390,13 +2407,17 @@ private final class ProfileGiftsContextImpl {
} else {
updatedGifts = []
}
updatedGifts = updatedGifts.filter { gift in
if let reference = gift.reference {
return !references.contains(reference)
} else {
updatedGifts.removeAll(where: { gift in
if let reference = gift.reference, references.contains(reference) {
return true
}
}
for reference in references {
if case let .slug(slug) = reference, case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
}
return false
})
updatedCount -= Int32(references.count)
if let entry = CodableEntry(CachedProfileGifts(gifts: updatedGifts, count: updatedCount, notificationsEnabled: nil)) {
transaction.putItemCacheEntry(id: giftsEntryId(peerId: peerId, collectionId: collectionId), entry: entry)
@ -3186,12 +3207,6 @@ private final class CraftGiftsContextImpl {
return _internal_craftStarGift(account: self.account, references: references)
}
func addGift(gift: ProfileGiftsContext.State.StarGift) {
self.gifts.insert(gift, at: 0)
self.count = self.count + 1
self.pushState()
}
func removeGifts(references: [StarGiftReference]) {
let referencesSet = Set(references)
self.gifts.removeAll { gift in
@ -3325,12 +3340,6 @@ public final class CraftGiftsContext {
}
}
public func addGift(gift: ProfileGiftsContext.State.StarGift) {
self.impl.with { impl in
impl.addGift(gift: gift)
}
}
public func removeGifts(references: [StarGiftReference]) {
self.impl.with { impl in
impl.removeGifts(references: references)
@ -3395,7 +3404,7 @@ func _internal_craftStarGift(account: Account, references: [StarGiftReference])
let message = updateNewMessageData.message
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
for media in message.media {
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
let reference: StarGiftReference
if let peerId, let savedId {
reference = .peer(peerId: peerId, id: savedId)
@ -3514,6 +3523,8 @@ extension StarGift.UniqueGift.Attribute {
return .epic
case .starGiftAttributeRarityLegendary:
return .legendary
case .starGiftAttributeRarityUncommon:
return .uncommon
}
}

View file

@ -1658,7 +1658,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid:
receiptMessageId = nil
}
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
let reference: StarGiftReference
if let peerId, let savedId {
reference = .peer(peerId: peerId, id: savedId)

View file

@ -217,9 +217,10 @@ public final class InitialPresentationDataAndSettings {
public let mediaInputSettings: MediaInputSettings
public let mediaDisplaySettings: MediaDisplaySettings
public let stickerSettings: StickerSettings
public let chatSettings: ChatSettings
public let experimentalUISettings: ExperimentalUISettings
public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) {
public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, chatSettings: ChatSettings, experimentalUISettings: ExperimentalUISettings) {
self.presentationData = presentationData
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
self.autodownloadSettings = autodownloadSettings
@ -228,6 +229,7 @@ public final class InitialPresentationDataAndSettings {
self.mediaInputSettings = mediaInputSettings
self.mediaDisplaySettings = mediaDisplaySettings
self.stickerSettings = stickerSettings
self.chatSettings = chatSettings
self.experimentalUISettings = experimentalUISettings
}
}
@ -245,6 +247,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
var experimentalUISettings: PreferencesEntry?
var contactSynchronizationSettings: PreferencesEntry?
var stickerSettings: PreferencesEntry?
var chatSettings: PreferencesEntry?
init(
localizationSettings: PreferencesEntry?,
@ -257,7 +260,8 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
mediaDisplaySettings: PreferencesEntry?,
experimentalUISettings: PreferencesEntry?,
contactSynchronizationSettings: PreferencesEntry?,
stickerSettings: PreferencesEntry?
stickerSettings: PreferencesEntry?,
chatSettings: PreferencesEntry?
) {
self.localizationSettings = localizationSettings
self.presentationThemeSettings = presentationThemeSettings
@ -270,6 +274,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
self.experimentalUISettings = experimentalUISettings
self.contactSynchronizationSettings = contactSynchronizationSettings
self.stickerSettings = stickerSettings
self.chatSettings = chatSettings
}
}
@ -285,6 +290,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
let experimentalUISettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings)
let contactSynchronizationSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings)
let stickerSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings)
let chatSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.chatSettings)
return InternalData(
localizationSettings: localizationSettings,
@ -297,7 +303,8 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
mediaDisplaySettings: mediaDisplaySettings,
experimentalUISettings: experimentalUISettings,
contactSynchronizationSettings: contactSynchronizationSettings,
stickerSettings: stickerSettings
stickerSettings: stickerSettings,
chatSettings: chatSettings
)
}
|> deliverOn(Queue(name: "PresentationData-Load", qos: .userInteractive))
@ -365,6 +372,13 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
stickerSettings = StickerSettings.defaultSettings
}
let chatSettings: ChatSettings
if let value = internalData.chatSettings?.get(ChatSettings.self) {
chatSettings = value
} else {
chatSettings = ChatSettings.defaultSettings
}
let experimentalUISettings: ExperimentalUISettings = internalData.experimentalUISettings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
let contactSettings: ContactSynchronizationSettings = internalData.contactSynchronizationSettings?.get(ContactSynchronizationSettings.self) ?? ContactSynchronizationSettings.defaultSettings
@ -413,7 +427,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
let chatBubbleCorners = PresentationChatBubbleCorners(mainRadius: CGFloat(themeSettings.chatBubbleSettings.mainRadius), auxiliaryRadius: CGFloat(themeSettings.chatBubbleSettings.auxiliaryRadius), mergeBubbleCorners: themeSettings.chatBubbleSettings.mergeBubbleCorners)
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, mediaDisplaySettings: mediaDisplaySettings, stickerSettings: stickerSettings, experimentalUISettings: experimentalUISettings)
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, mediaDisplaySettings: mediaDisplaySettings, stickerSettings: stickerSettings, chatSettings: chatSettings, experimentalUISettings: experimentalUISettings)
}
}

View file

@ -1262,7 +1262,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
}
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned, fromOffer, _):
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned, fromOffer, _, isCrafted):
if case let .unique(gift) = gift {
if !forAdditionalServiceMessage && !"".isEmpty {
attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor)
@ -1338,7 +1338,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_BoughtYou(giftTitle, starsString)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes])
}
} else {
attributedString = NSAttributedString(string: strings.Notification_StarsGift_TransferYou, font: titleFont, textColor: primaryTextColor)
if isCrafted {
attributedString = NSAttributedString(string: strings.Notification_StarsGift_Crafted, font: titleFont, textColor: primaryTextColor)
} else {
attributedString = NSAttributedString(string: strings.Notification_StarsGift_TransferYou, font: titleFont, textColor: primaryTextColor)
}
}
} else if let senderId, let peer = message.peers[senderId] {
if let peerId, let targetPeer = message.peers[peerId] {
@ -1776,7 +1780,27 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
rawString = strings.Conversation_EmojiStake_WonYou(value).string
}
} else {
let compactPeerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? ""
var compactPeerName = ""
if let authorSignature = message.forwardInfo?.authorSignature {
compactPeerName = authorSignature
} else if let author = message.forwardInfo?.author {
compactPeerName = EnginePeer(author).compactDisplayTitle
} else if let source = message.forwardInfo?.source {
compactPeerName = EnginePeer(source).compactDisplayTitle
} else {
if let author = message.author, case .user = author {
compactPeerName = author.compactDisplayTitle
} else {
for attribute in message.attributes {
if let attribute = attribute as? AuthorSignatureMessageAttribute {
compactPeerName = attribute.signature
}
}
if compactPeerName.isEmpty {
compactPeerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? ""
}
}
}
if value == 1, let tonAmount = dice.tonAmount {
let value = formatTonAmountText(tonAmount, dateTimeFormat: dateTimeFormat)
rawString = strings.Conversation_EmojiStake_Lost(compactPeerName, value).string

View file

@ -51,6 +51,8 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
private let context: AccountContext
private let subject: MessageActionUrlAuthResult
var peer: EnginePeer?
fileprivate var inProgress = false
var allowWrite = true
weak var controller: ViewController?
@ -58,8 +60,17 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
init(context: AccountContext, subject: MessageActionUrlAuthResult) {
self.context = context
self.subject = subject
super.init()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
self.peer = peer
self.updated()
})
}
func displayPhoneNumberConfirmation(commit: @escaping (Bool) -> Void) {
@ -78,7 +89,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
let alertController = textAlertController(
context: self.context,
title: "Phone Number",
text: "**\(domain)** wants to access your phone number **\(phoneNumber)**. Allow access?",
text: "**\(domain)** wants to access your phone number **\(phoneNumber)**.\n\nAllow access?",
actions: [
TextAlertAction(type: .genericAction, title: "Deny", action: {
commit(false)
@ -99,13 +110,12 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
static var body: Body {
let closeButton = Child(GlassBarButtonComponent.self)
let peerButton = Child(AvatarComponent.self)
let avatar = Child(AvatarComponent.self)
let title = Child(MultilineTextComponent.self)
let description = Child(MultilineTextComponent.self)
let clientSection = Child(ListSectionComponent.self)
let optionsSection = Child(ListSectionComponent.self)
let cancelButton = Child(ButtonComponent.self)
let doneButton = Child(ButtonComponent.self)
@ -147,6 +157,24 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
availableSize: CGSize(width: 44.0, height: 44.0),
transition: .immediate
)
context.add(closeButton
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
)
if let peer = state.peer {
let peerButton = peerButton.update(
component: AvatarComponent(
context: component.context,
theme: environment.theme,
peer: peer
),
availableSize: CGSize(width: 44.0, height: 44.0),
transition: .immediate
)
context.add(peerButton
.position(CGPoint(x: context.availableSize.width - 16.0 - peerButton.size.width / 2.0, y: 16.0 + peerButton.size.height / 2.0))
)
}
var contentHeight: CGFloat = 32.0
let avatar = avatar.update(
@ -320,7 +348,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
contentHeight += clientSection.size.height
if flags.contains(.requestWriteAccess) {
contentHeight += 22.0
contentHeight += 38.0
var optionsSectionItems: [AnyComponentWithIdentity<Empty>] = []
optionsSectionItems.append(AnyComponentWithIdentity(id: "allowWrite", component: AnyComponent(ListActionItemComponent(
@ -441,10 +469,6 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
contentHeight += doneButton.size.height
contentHeight += buttonInsets.bottom
context.add(closeButton
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
)
return CGSize(width: context.availableSize.width, height: contentHeight)
}
}

View file

@ -466,6 +466,9 @@ public final class ButtonComponent: Component {
super.init(frame: frame)
self.button.isExclusiveTouch = true
self.layer.rasterizationScale = UIScreenScale
self.addSubview(self.containerView)
self.addSubview(self.button)
@ -477,12 +480,24 @@ public final class ButtonComponent: Component {
case .glass:
let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring))
if highlighted {
self.layer.shouldRasterize = true
let highlightedColor = component.background.color.withMultiplied(hue: 1.0, saturation: 0.77, brightness: 1.01)
transition.setBackgroundColor(view: self.containerView, color: highlightedColor)
transition.setScale(view: self.containerView, scale: 1.05)
transition.setScale(view: self.containerView, scale: 1.05, completion: { finished in
if finished {
self.layer.shouldRasterize = false
}
})
} else {
self.layer.shouldRasterize = true
transition.setBackgroundColor(view: self.containerView, color: component.background.color)
transition.setScale(view: self.containerView, scale: 1.0)
transition.setScale(view: self.containerView, scale: 1.0, completion: { finished in
if finished {
self.layer.shouldRasterize = false
}
})
}
case .legacy:
if highlighted {
@ -598,6 +613,7 @@ public final class ButtonComponent: Component {
if contentView.superview == nil {
contentTransition = .immediate
animateIn = true
contentView.layer.rasterizationScale = UIScreenScale
contentView.isUserInteractionEnabled = false
contentContainerView.addSubview(contentView)
@ -609,7 +625,10 @@ public final class ButtonComponent: Component {
contentTransition.setAlpha(view: contentView, alpha: contentAlpha)
if animateIn && previousContentItem != nil && !transition.animation.isImmediate {
contentView.layer.animateScale(from: 0.4, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring)
contentView.layer.shouldRasterize = true
contentView.layer.animateScale(from: 0.4, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
contentView.layer.shouldRasterize = false
})
contentView.layer.animateAlpha(from: 0.0, to: contentAlpha, duration: 0.1)
contentView.layer.animatePosition(from: CGPoint(x: 0.0, y: -size.height * 0.15), to: CGPoint(), duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
@ -617,6 +636,7 @@ public final class ButtonComponent: Component {
if let previousContentItem, let previousContentView = previousContentItem.view.view {
if !transition.animation.isImmediate {
previousContentView.layer.shouldRasterize = true
previousContentView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
previousContentView.layer.animateAlpha(from: contentAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousContentView] _ in
previousContentView?.removeFromSuperview()

View file

@ -18,7 +18,7 @@
@property (nonatomic, copy) bool (^ _Nullable shouldPaste)();
@property (nonatomic, copy) bool (^ _Nullable shouldRespondToAction)(SEL _Nullable);
@property (nonatomic, copy) ChatInputTextViewImplTargetForAction * _Nullable (^ _Nullable targetForAction)(SEL _Nullable);
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)();
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)(UIKeyModifierFlags);
@property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)();
@property (nonatomic, copy) void (^ _Nullable dropAutocorrectioniOS16)();

View file

@ -136,15 +136,22 @@
}
- (NSArray *)keyCommands {
UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handlePlainReturn:)];
UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handleReturn:)];
UIKeyCommand *cmdReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierCommand action:@selector(handleReturn:)];
return @[
plainReturn
plainReturn,
cmdReturn
];
}
- (void)handlePlainReturn:(id)__unused sender {
- (void)handleReturn:(UIKeyCommand *)__unused sender {
UIKeyModifierFlags modifierFlags = 0;
if ([sender isKindOfClass:[UIKeyCommand class]]) {
modifierFlags = sender.modifierFlags;
}
if (_shouldReturn) {
_shouldReturn();
_shouldReturn(modifierFlags);
}
}

View file

@ -11,7 +11,7 @@ import TextNodeWithEntities
public protocol ChatInputTextNodeDelegate: AnyObject {
func chatInputTextNodeDidUpdateText()
func chatInputTextNodeShouldReturn() -> Bool
func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool
func chatInputTextNodeDidChangeSelection(dueToEditing: Bool)
func chatInputTextNodeDidBeginEditing()
func chatInputTextNodeDidFinishEditing()
@ -1180,11 +1180,11 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate,
}
return self.customDelegate?.chatInputTextNodeShouldPaste() ?? true
}
self.shouldReturn = { [weak self] in
self.shouldReturn = { [weak self] modifierFlags in
guard let self else {
return true
}
return self.customDelegate?.chatInputTextNodeShouldReturn() ?? true
return self.customDelegate?.chatInputTextNodeShouldReturn(modifierFlags: modifierFlags) ?? true
}
self.backspaceWhileEmpty = { [weak self] in
guard let self else {

View file

@ -149,8 +149,7 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
dayText = strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").string
}
dateText = strings.Message_FullDateFormat(dayText, stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)).string
}
else if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
} else if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
dateText = strings.Message_ImportedDateFormat(dateStringForDay(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: forwardInfo.date), stringForMessageTimestamp(timestamp: forwardInfo.date, dateTimeFormat: dateTimeFormat), dateText).string
}

View file

@ -282,7 +282,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
switch action.action {
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
releasedBy = gift.releasedBy
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
releasedBy = gift.releasedBy
default:
break
@ -690,7 +690,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
}
}
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _, fromOffer, _):
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _, fromOffer, _, _):
if case let .unique(uniqueGift) = gift {
isStarGift = true

View file

@ -4530,15 +4530,29 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
}
public func chatInputTextNodeShouldReturn() -> Bool {
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
var shouldSendMessage = false
if self.sendActionButtons.sendButton.supernode != nil && !self.sendActionButtons.sendButton.isHidden && !self.sendActionButtons.sendContainerNode.alpha.isZero {
self.sendButtonPressed()
if let context = self.context, context.sharedContext.currentChatSettings.with({ $0 }).sendWithCmdEnter {
if modifierFlags.contains(.command) {
shouldSendMessage = true
}
} else {
if modifierFlags.isEmpty {
shouldSendMessage = true
}
}
}
return false
if shouldSendMessage {
self.sendButtonPressed()
return false
}
return true
}
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
return self.chatInputTextNodeShouldReturn()
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
}
private func applyUpdateSendButtonIcon() {

View file

@ -18,6 +18,7 @@ import UIKitRuntimeUtils
public final class GiftCompositionComponent: Component {
public class ExternalState {
public fileprivate(set) var previewPatternColor: UIColor?
public fileprivate(set) var backgroundColor: UIColor?
public fileprivate(set) var previewModel: StarGift.UniqueGift.Attribute?
public fileprivate(set) var previewBackdrop: StarGift.UniqueGift.Attribute?
public fileprivate(set) var previewSymbol: StarGift.UniqueGift.Attribute?
@ -766,6 +767,7 @@ public final class GiftCompositionComponent: Component {
}
}
component.externalState?.backgroundColor = backgroundColor
component.externalState?.previewPatternColor = secondBackgroundColor
var animateBackdropSwipe = false

View file

@ -49,6 +49,7 @@ swift_library(
"//submodules/TelegramUI/Components/SpaceWarpView",
"//submodules/ConfettiEffect",
"//submodules/TelegramNotices",
"//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView",
],
visibility = [
"//visibility:public",

View file

@ -108,7 +108,7 @@ final class CraftTableComponent: Component {
fatalError("init(coder:) has not been implemented")
}
func setupFailAnimation() {
func setupFailureAnimation() {
guard !self.didSetupFinishAnimation else {
return
}
@ -127,41 +127,29 @@ final class CraftTableComponent: Component {
}
for i in 0 ..< min(2, availableStickers.count) {
if let sticker = availableStickers[i].view {
self.animationView.setSticker(sticker, face: 3 - i, mirror: isUpsideDown)
let face: Int
if isUpsideDown {
face = i + 1
} else {
face = 3 - i
}
self.animationView.setSticker(sticker, face: face, mirror: isUpsideDown)
}
}
self.state?.updated()
self.flipFaces = isUpsideDown
Queue.mainQueue().after(0.3, {
self.failWillFinish = true
self.component?.willFinish(false)
})
if let failOverlayView = self.failOverlay.view as? LottieComponent.View {
failOverlayView.isHidden = false
failOverlayView.onFrameUpdate = { [weak self] frameIndex in
guard let self else {
return
}
if frameIndex >= 5 && !self.failDidStartCrossAnimation {
self.failDidStartCrossAnimation = true
self.craftFailPlayOnce.invoke(Void())
}
if frameIndex >= 65 && !self.failDidBringToFront {
self.failDidBringToFront = true
failOverlayView.superview?.bringSubviewToFront(failOverlayView)
self.animationView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
if frameIndex >= 75 && !self.failWillFinish {
self.failWillFinish = true
self.component?.willFinish(false)
}
if frameIndex >= 82 && !self.failDidFinish {
self.failDidFinish = true
failOverlayView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.component?.finished(nil)
}
}
}
self.craftFailOverlayPlayOnce.invoke(Void())
Queue.mainQueue().after(0.5, {
self.failDidFinish = true
self.component?.finished(nil)
})
self.state?.updated(transition: .easeInOut(duration: 0.4))
}
}
@ -171,6 +159,8 @@ final class CraftTableComponent: Component {
}
self.didSetupFinishAnimation = true
self.animationView.isSuccess = true
self.animationView.onFinishApproach = { [weak self] isUpsideDown in
guard let self else {
return
@ -270,10 +260,10 @@ final class CraftTableComponent: Component {
if index == 0 {
faceItems.append(
AnyComponentWithIdentity(id: "background", component: AnyComponent(
RoundedRectangle(color: component.buttonColor, cornerRadius: 28.0)
FilledRoundedRectangleComponent(color: component.buttonColor, cornerRadius: .value(28.0), smoothCorners: true)
))
)
if !component.isCrafting {
if !component.isCrafting || self.isFailed {
faceItems.append(
AnyComponentWithIdentity(id: "glass", component: AnyComponent(
GlassBackgroundComponent(size: CGSize(width: cubeSide, height: cubeSide), cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)))
@ -282,11 +272,20 @@ final class CraftTableComponent: Component {
}
if self.isFailed {
faceItems.append(
AnyComponentWithIdentity(id: "fail", component: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(name: "CraftFail"),
size: CGSize(width: 96.0, height: 96.0),
playOnce: self.craftFailPlayOnce
AnyComponentWithIdentity(id: "faildial", component: AnyComponent(
DialIndicatorComponent(
content: AnyComponentWithIdentity(id: "gift", component: AnyComponent(BundleIconComponent(name: "Premium/GiftCrash", tintColor: .white))),
backgroundColor: .white.withAlphaComponent(0.1),
foregroundColor: .white,
diameter: 84.0,
contentSize: CGSize(width: 44.0, height: 44.0),
lineWidth: 5.0,
fontSize: 18.0,
progress: 0.0,
value: component.gifts.count,
suffix: "",
isVisible: true,
isFlipped: self.flipFaces
)
))
)
@ -300,7 +299,9 @@ final class CraftTableComponent: Component {
diameter: 84.0,
lineWidth: 5.0,
fontSize: 18.0,
percentage: permilleValue / 10,
progress: CGFloat(permilleValue / 10 / 100),
value: permilleValue / 10,
suffix: "%",
isVisible: !component.isCrafting
)
))
@ -318,7 +319,7 @@ final class CraftTableComponent: Component {
} else {
faceItems.append(
AnyComponentWithIdentity(id: "background", component: AnyComponent(
RoundedRectangle(color: component.buttonColor, cornerRadius: 28.0)
FilledRoundedRectangleComponent(color: component.buttonColor, cornerRadius: .value(28.0), smoothCorners: true)
))
)
faceItems.append(
@ -412,35 +413,36 @@ final class CraftTableComponent: Component {
self.setupSuccessAnimation(uniqueGift)
}
case .fail:
self.setupFailAnimation()
self.setupFailureAnimation()
default:
break
}
})
}
if self.isFailed {
let failOverlaySize = self.failOverlay.update(
transition: .immediate,
component: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(name: "CraftFailOverlay"),
size: CGSize(width: availableSize.width, height: availableSize.width),
playOnce: self.craftFailOverlayPlayOnce
)
),
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.width)
)
let failOverlayFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - failOverlaySize.width) / 2.0), y: floor((availableSize.height - failOverlaySize.height) / 2.0)), size: failOverlaySize)
if let failOverlayView = self.failOverlay.view {
if failOverlayView.superview == nil {
failOverlayView.isHidden = true
self.insertSubview(failOverlayView, belowSubview: self.animationView)
}
failOverlayView.frame = failOverlayFrame
}
}
// if self.isFailed {
// let failOverlaySize = self.failOverlay.update(
// transition: .immediate,
// component: AnyComponent(
// LottieComponent(
// content: LottieComponent.AppBundleContent(name: "CraftFailOverlay"),
// size: CGSize(width: availableSize.width, height: availableSize.width),
// playOnce: self.craftFailOverlayPlayOnce
// )
// ),
// environment: {},
// containerSize: CGSize(width: availableSize.width, height: availableSize.width)
// )
// let failOverlayFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - failOverlaySize.width) / 2.0), y: floor((availableSize.height - failOverlaySize.height) / 2.0)), size: failOverlaySize)
// if let failOverlayView = self.failOverlay.view {
// if failOverlayView.superview == nil {
// failOverlayView.isHidden = true
// self.insertSubview(failOverlayView, belowSubview: self.animationView)
// }
// failOverlayView.frame = failOverlayFrame
// }
// }
return availableSize
}
@ -544,9 +546,9 @@ final class GiftSlotComponent: Component {
self.state = state
let backgroundFrame = CGRect(origin: .zero, size: availableSize).insetBy(dx: 1.0, dy: 1.0)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)), transition: .immediate)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)), isInteractive: true, transition: .immediate)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
if component.gift == nil && component.isCrafting {
if component.gift == nil && component.isCrafting && previousComponent?.isCrafting == false {
transition.setBlur(layer: self.backgroundView.layer, radius: 10.0)
self.backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false)
transition.setBlur(layer: self.addIcon.layer, radius: 10.0)

View file

@ -79,6 +79,8 @@ final class CubeAnimationView: UIView {
private var finishDelayTimerY: Timer?
private var cubeScale: Float = 1.0
private var hasFiredFinishApproach = false
var isSuccess = false
var onFinishApproach: ((Bool) -> Void)?
@ -485,7 +487,7 @@ final class CubeAnimationView: UIView {
let duration: TimeInterval = 0.2
let startQuad = Quad(rect: sticker.frame)
let animationView: UIView
if let snapshot = sticker.snapshotView(afterScreenUpdates: true) {
if let snapshot = sticker.snapshotView(afterScreenUpdates: false) {
self.warpSnapshot?.removeFromSuperview()
self.warpSnapshot = snapshot
@ -606,7 +608,7 @@ final class CubeAnimationView: UIView {
}
let stickerIndex = indices[index]
if self.stickers.indices.contains(stickerIndex) {
let isLast = stickerIndex == indices.count - 1
let isLast = index == indices.count - 1
self.launchStickerView(self.stickers[stickerIndex], emphasized: isLast, willFinish: isLast)
}
self.scheduleStickerSequence(from: index + 1, indices: indices)
@ -697,10 +699,12 @@ final class CubeAnimationView: UIView {
let upsideDown = abs(shortestAngleDelta(from: self.rotation.x, to: Float.pi)) < (Float.pi / 2)
self.onFinishApproach?(upsideDown)
}
if absRemaining <= self.finishSuccessScaleTriggerAngle {
if self.isSuccess, absRemaining <= self.finishSuccessScaleTriggerAngle {
let raw = (self.finishSuccessScaleTriggerAngle - absRemaining) / self.finishSuccessScaleTriggerAngle
let eased = raw * raw * (3 - 2 * raw)
self.cubeScale = 1.0 + (self.finishSuccessScale - 1.0) * eased
} else if !self.isSuccess {
self.cubeScale = 1.0
}
if abs(remaining) < 0.0008 && abs(self.angularVelocity.y) < 0.0015 {
self.finishRotationY = self.finishTargetYUnwrapped

View file

@ -13,29 +13,41 @@ final class DialIndicatorComponent: Component {
let backgroundColor: UIColor
let foregroundColor: UIColor
let diameter: CGFloat
let contentSize: CGSize?
let lineWidth: CGFloat
let fontSize: CGFloat
let percentage: Int
let progress: CGFloat
let value: Int
let suffix: String
let isVisible: Bool
let isFlipped: Bool
public init(
content: AnyComponentWithIdentity<Empty>,
backgroundColor: UIColor,
foregroundColor: UIColor,
diameter: CGFloat,
contentSize: CGSize? = nil,
lineWidth: CGFloat,
fontSize: CGFloat,
percentage: Int,
isVisible: Bool = true
progress: CGFloat,
value: Int,
suffix: String,
isVisible: Bool = true,
isFlipped: Bool = false,
) {
self.content = content
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
self.diameter = diameter
self.contentSize = contentSize
self.lineWidth = lineWidth
self.fontSize = fontSize
self.percentage = percentage
self.progress = progress
self.value = value
self.suffix = suffix
self.isVisible = isVisible
self.isFlipped = isFlipped
}
public static func ==(lhs: DialIndicatorComponent, rhs: DialIndicatorComponent) -> Bool {
@ -51,18 +63,30 @@ final class DialIndicatorComponent: Component {
if lhs.diameter != rhs.diameter {
return false
}
if lhs.contentSize != rhs.contentSize {
return false
}
if lhs.lineWidth != rhs.lineWidth {
return false
}
if lhs.fontSize != rhs.fontSize {
return false
}
if lhs.percentage != rhs.percentage {
if lhs.progress != rhs.progress {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.suffix != rhs.suffix {
return false
}
if lhs.isVisible != rhs.isVisible {
return false
}
if lhs.isFlipped != rhs.isFlipped {
return false
}
return true
}
@ -82,7 +106,7 @@ final class DialIndicatorComponent: Component {
self.backgroundLayer.lineCap = .round
self.foregroundLayer.lineCap = .round
self.addSubview(self.containerView)
self.containerView.layer.addSublayer(self.backgroundLayer)
@ -119,7 +143,7 @@ final class DialIndicatorComponent: Component {
self.foregroundLayer.path = CGPath(ellipseIn: pathFrame, transform: nil)
self.foregroundLayer.transform = CATransform3DMakeRotation(.pi / 2.0, 0.0, 0.0, 1.0)
self.foregroundLayer.strokeStart = strokeStart
transition.setShapeLayerStrokeEnd(layer: self.foregroundLayer, strokeEnd: strokeStart + (strokeEnd - strokeStart) * (CGFloat(component.percentage) / 100.0))
transition.setShapeLayerStrokeEnd(layer: self.foregroundLayer, strokeEnd: strokeStart + (strokeEnd - strokeStart) * component.progress)
self.foregroundLayer.frame = CGRect(origin: .zero, size: pathSize)
if previousComponent?.content.id != component.content.id {
@ -136,7 +160,8 @@ final class DialIndicatorComponent: Component {
self.content = ComponentView()
}
let contentFrame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: component.diameter - 16.0, height: component.diameter - 16.0))
let contentSize = component.contentSize ?? CGSize(width: component.diameter - 16.0, height: component.diameter - 16.0)
let contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((pathSize.width - contentSize.width) / 2.0), y: floorToScreenPixels((pathSize.height - contentSize.height) / 2.0)), size: contentSize)
let _ = self.content.update(
transition: .immediate,
component: component.content.component,
@ -154,10 +179,12 @@ final class DialIndicatorComponent: Component {
contentView.frame = contentFrame
}
let labelItems: [AnimatedTextComponent.Item] = [
AnimatedTextComponent.Item(id: "percent", content: .number(component.percentage, minDigits: 1)),
AnimatedTextComponent.Item(id: "suffix", content: .text("%"))
var labelItems: [AnimatedTextComponent.Item] = [
AnimatedTextComponent.Item(id: "percent", content: .number(component.value, minDigits: 1))
]
if !component.suffix.isEmpty {
labelItems.append(AnimatedTextComponent.Item(id: "suffix", content: .text(component.suffix)))
}
let labelSize = self.label.update(
transition: transition,
@ -181,6 +208,8 @@ final class DialIndicatorComponent: Component {
transition.setAlpha(view: self.containerView, alpha: component.isVisible ? 1.0 : 0.0)
transition.setBlur(layer: self.containerView.layer, radius: component.isVisible ? 0.0 : 10.0)
self.containerView.transform = CGAffineTransform(rotationAngle: component.isFlipped ? .pi : 0.0)
self.containerView.frame = CGRect(origin: .zero, size: pathSize)
return pathSize

View file

@ -21,6 +21,7 @@ import ResizableSheetComponent
import TooltipUI
import GlassBarButtonComponent
import ConfettiEffect
import GiftLoadingShimmerView
final class SelectGiftPageContent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -34,6 +35,7 @@ final class SelectGiftPageContent: Component {
let starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>
let selectGift: (GiftItem) -> Void
let dismiss: () -> Void
let boundsUpdated: ActionSlot<CGRect>
init(
context: AccountContext,
@ -44,7 +46,8 @@ final class SelectGiftPageContent: Component {
selectedGiftIds: Set<Int64>,
starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>,
selectGift: @escaping (GiftItem) -> Void,
dismiss: @escaping () -> Void
dismiss: @escaping () -> Void,
boundsUpdated: ActionSlot<CGRect>
) {
self.context = context
self.craftContext = craftContext
@ -55,6 +58,7 @@ final class SelectGiftPageContent: Component {
self.starsTopUpOptions = starsTopUpOptions
self.selectGift = selectGift
self.dismiss = dismiss
self.boundsUpdated = boundsUpdated
}
static func ==(lhs: SelectGiftPageContent, rhs: SelectGiftPageContent) -> Bool {
@ -74,6 +78,7 @@ final class SelectGiftPageContent: Component {
private let myGiftsTitle = ComponentView<Empty>()
private var gifts: [AnyHashable: ComponentView<Empty>] = [:]
private let myGiftsPlaceholder = ComponentView<Empty>()
private let loadingView = GiftLoadingShimmerView()
private let storeGiftsTitle = ComponentView<Empty>()
private let storeGifts = ComponentView<Empty>()
@ -84,6 +89,9 @@ final class SelectGiftPageContent: Component {
private var availableGifts: [GiftItem] = []
private var giftMap: [Int64: GiftItem] = [:]
private var availableSize: CGSize?
private var currentBounds: CGRect?
private var component: SelectGiftPageContent?
private weak var state: EmptyComponentState?
private var environment: ViewControllerComponentContainer.Environment?
@ -94,6 +102,8 @@ final class SelectGiftPageContent: Component {
self.layer.cornerRadius = 40.0
self.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.addSubview(self.loadingView)
}
required init?(coder: NSCoder) {
@ -103,6 +113,187 @@ final class SelectGiftPageContent: Component {
deinit {
self.craftStateDisposable?.dispose()
}
func updateScrolling(interactive: Bool, transition: ComponentTransition) -> CGFloat {
guard let bounds = self.currentBounds, let availableSize = self.availableSize, let component = self.component, let environment = self.environment else {
return 0.0
}
let visibleBounds = bounds.insetBy(dx: 0.0, dy: -10.0)
var contentHeight: CGFloat = 88.0 + 32.0
let itemSpacing: CGFloat = 10.0
let itemSideInset = 16.0
let itemsInRow: Int
if availableSize.width > availableSize.height || availableSize.width > 480.0 {
if case .tablet = environment.deviceMetrics.type {
itemsInRow = 4
} else {
itemsInRow = 5
}
} else {
itemsInRow = 3
}
let itemWidth = (availableSize.width - itemSideInset * 2.0 - itemSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
let itemSize = CGSize(width: itemWidth, height: itemWidth)
var isLoading = false
if self.availableGifts.isEmpty, case .loading = (self.craftState?.dataState ?? .loading) {
isLoading = true
}
let loadingTransition: ComponentTransition = .easeInOut(duration: 0.25)
let loadingSize = CGSize(width: availableSize.width, height: 180.0)
if isLoading {
contentHeight += 120.0
self.loadingView.update(size: loadingSize, theme: environment.theme, itemSize: itemSize, showFilters: false, isPlain: true, transition: .immediate)
loadingTransition.setAlpha(view: self.loadingView, alpha: 1.0)
} else {
loadingTransition.setAlpha(view: self.loadingView, alpha: 0.0)
}
transition.setFrame(view: self.loadingView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight - 170.0), size: loadingSize))
var itemFrame = CGRect(origin: CGPoint(x: itemSideInset, y: contentHeight), size: itemSize)
var itemsHeight: CGFloat = 0.0
var validIds: [AnyHashable] = []
for gift in self.availableGifts {
var isVisible = false
if visibleBounds.intersects(itemFrame) {
isVisible = true
}
if isVisible {
let itemId = AnyHashable(gift.gift.id)
validIds.append(itemId)
var itemTransition = transition
let visibleItem: ComponentView<Empty>
if let current = self.gifts[itemId] {
visibleItem = current
} else {
visibleItem = ComponentView()
self.gifts[itemId] = visibleItem
itemTransition = .immediate
}
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
let ribbonText = "#\(gift.gift.number)"
for attribute in gift.gift.attributes {
if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
ribbonColor = .custom(outerColor, innerColor)
break
}
}
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(
GiftItemComponent(
context: component.context,
style: .glass,
theme: environment.theme,
strings: environment.strings,
peer: nil,
subject: .uniqueGift(gift: gift.gift, price: nil),
ribbon: GiftItemComponent.Ribbon(text: ribbonText, font: .monospaced, color: ribbonColor, outline: nil),
badge: gift.gift.craftChancePermille.flatMap { "+\($0 / 10)%" },
resellPrice: nil,
isHidden: false,
isSelected: false,
isPinned: false,
isEditing: false,
mode: .grid,
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.selectGift(gift)
component.dismiss()
},
contextAction: { _, _ in }
)
),
environment: {},
containerSize: itemSize
)
if let itemView = visibleItem.view {
if itemView.superview == nil {
if let _ = self.loadingView.superview {
self.insertSubview(itemView, belowSubview: self.loadingView)
} else {
self.addSubview(itemView)
}
if !transition.animation.isImmediate {
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
}
itemsHeight = itemFrame.maxY - contentHeight
itemFrame.origin.x += itemFrame.width + itemSpacing
if itemFrame.maxX > availableSize.width {
itemFrame.origin.x = itemSideInset
itemFrame.origin.y += itemSize.height + itemSpacing
}
}
var removeIds: [AnyHashable] = []
for (id, item) in self.gifts {
if !validIds.contains(id) {
removeIds.append(id)
if let itemView = item.view {
if !transition.animation.isImmediate {
itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
itemView.removeFromSuperview()
})
} else {
itemView.removeFromSuperview()
}
}
}
}
for id in removeIds {
self.gifts.removeValue(forKey: id)
}
if let state = self.craftState, case .ready = state.dataState, self.availableGifts.isEmpty {
contentHeight += 10.0
let myGiftsPlaceholderSize = self.myGiftsPlaceholder.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_NoGiftsFromCollection, font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 3,
lineSpacing: 0.1
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - 32.0, height: .greatestFiniteMagnitude)
)
let myGiftsPlaceholderFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - myGiftsPlaceholderSize.width) / 2.0), y: contentHeight), size: myGiftsPlaceholderSize)
if let myGiftsPlaceholderView = self.myGiftsPlaceholder.view {
if myGiftsPlaceholderView.superview == nil {
self.addSubview(myGiftsPlaceholderView)
}
myGiftsPlaceholderView.frame = myGiftsPlaceholderFrame
}
contentHeight += myGiftsPlaceholderSize.height
contentHeight += 32.0
} else {
contentHeight += itemsHeight
contentHeight += 24.0
}
if let storeGiftsView = self.storeGifts.view as? GiftStoreContentComponent.View {
storeGiftsView.updateScrolling(bounds: bounds.offsetBy(dx: 0.0, dy: -contentHeight), interactive: interactive, transition: .immediate)
}
return contentHeight
}
func update(component: SelectGiftPageContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
@ -110,7 +301,18 @@ final class SelectGiftPageContent: Component {
self.isUpdating = false
}
self.availableSize = availableSize
if self.component == nil {
self.currentBounds = CGRect(origin: .zero, size: availableSize)
component.boundsUpdated.connect { [weak self] bounds in
guard let self else {
return
}
self.currentBounds = bounds
let _ = self.updateScrolling(interactive: true, transition: .immediate)
}
let initialGiftItem = GiftItem(
gift: component.gift,
reference: .slug(slug: component.gift.slug)
@ -125,7 +327,6 @@ final class SelectGiftPageContent: Component {
guard let self else {
return
}
//let isFirstTime = self.craftState == nil
self.craftState = state
var items: [GiftItem] = []
@ -175,7 +376,7 @@ final class SelectGiftPageContent: Component {
let myGiftsTitleSize = self.myGiftsTitle.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "Your Gifts".uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor)))
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_YourGifts.uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor)))
),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
@ -190,165 +391,12 @@ final class SelectGiftPageContent: Component {
contentHeight += 32.0
let itemSpacing: CGFloat = 10.0
let itemSideInset = 16.0
let itemsInRow: Int
if availableSize.width > availableSize.height || availableSize.width > 480.0 {
if case .tablet = environment.deviceMetrics.type {
itemsInRow = 4
} else {
itemsInRow = 5
}
} else {
itemsInRow = 3
}
let itemWidth = (availableSize.width - itemSideInset * 2.0 - itemSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
let itemSize = CGSize(width: itemWidth, height: itemWidth)
var itemFrame = CGRect(origin: CGPoint(x: itemSideInset, y: contentHeight), size: itemSize)
var itemsHeight: CGFloat = 0.0
var validIds: [AnyHashable] = []
for gift in self.availableGifts {
let isVisible = "".isEmpty
// if visibleBounds.intersects(itemFrame) {
// isVisible = true
// }
if isVisible {
let itemId = AnyHashable(gift.gift.id)
validIds.append(itemId)
var itemTransition = transition
let visibleItem: ComponentView<Empty>
if let current = self.gifts[itemId] {
visibleItem = current
} else {
visibleItem = ComponentView()
self.gifts[itemId] = visibleItem
itemTransition = .immediate
}
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
let ribbonText = "#\(gift.gift.number)"
for attribute in gift.gift.attributes {
if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
ribbonColor = .custom(outerColor, innerColor)
break
}
}
let badge: String? = gift.gift.craftChancePermille.flatMap { "+\($0 / 10)%" }
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(
GiftItemComponent(
context: component.context,
style: .glass,
theme: environment.theme,
strings: environment.strings,
peer: nil,
subject: .uniqueGift(gift: gift.gift, price: nil),
ribbon: GiftItemComponent.Ribbon(text: ribbonText, font: .monospaced, color: ribbonColor, outline: nil),
badge: badge,
resellPrice: nil,
isHidden: false,
isSelected: false,
isPinned: false,
isEditing: false,
mode: .grid,
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.selectGift(gift)
component.dismiss()
},
contextAction: { _, _ in }
)
),
environment: {},
containerSize: itemSize
)
if let itemView = visibleItem.view {
if itemView.superview == nil {
self.addSubview(itemView)
if !transition.animation.isImmediate {
let delay = ((itemFrame.minY - contentHeight) / itemSize.height) * 0.07
itemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25, delay: delay)
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: delay)
}
}
itemView.isUserInteractionEnabled = !component.selectedGiftIds.contains(gift.gift.id)
itemView.alpha = component.selectedGiftIds.contains(gift.gift.id) ? 0.4 : 1.0
itemView.layer.allowsGroupOpacity = itemView.alpha < 1.0
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
}
itemsHeight = itemFrame.maxY - contentHeight
itemFrame.origin.x += itemFrame.width + itemSpacing
if itemFrame.maxX > availableSize.width {
itemFrame.origin.x = itemSideInset
itemFrame.origin.y += itemSize.height + itemSpacing
}
}
var removeIds: [AnyHashable] = []
for (id, item) in self.gifts {
if !validIds.contains(id) {
removeIds.append(id)
if let itemView = item.view {
if !transition.animation.isImmediate {
itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
itemView.removeFromSuperview()
})
} else {
itemView.removeFromSuperview()
}
}
}
}
for id in removeIds {
self.gifts.removeValue(forKey: id)
}
if let state = self.craftState, case .ready = state.dataState, self.availableGifts.isEmpty {
contentHeight += 10.0
let myGiftsPlaceholderSize = self.myGiftsPlaceholder.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: "You don't have other gifts\nfrom this collection", font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 3,
lineSpacing: 0.1
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - 32.0, height: .greatestFiniteMagnitude)
)
let myGiftsPlaceholderFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - myGiftsPlaceholderSize.width) / 2.0), y: contentHeight), size: myGiftsPlaceholderSize)
if let myGiftsPlaceholderView = self.myGiftsPlaceholder.view {
if myGiftsPlaceholderView.superview == nil {
self.addSubview(myGiftsPlaceholderView)
}
myGiftsPlaceholderView.frame = myGiftsPlaceholderFrame
}
contentHeight += myGiftsPlaceholderSize.height
contentHeight += 32.0
} else {
contentHeight += itemsHeight
contentHeight += 24.0
}
contentHeight = self.updateScrolling(interactive: false, transition: transition)
let storeGiftsTitleSize = self.storeGiftsTitle.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "SUITABLE GIFTS ON SALE".uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor)))
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_SaleGifts.uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor)))
),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
@ -379,6 +427,7 @@ final class SelectGiftPageContent: Component {
starsContext: component.context.starsContext!,
peerId: component.context.account.peerId,
gift: component.genericGift,
isPlain: true,
confirmPurchaseImmediately: true,
starsTopUpOptions: component.starsTopUpOptions,
scrollToTop: {},
@ -479,6 +528,8 @@ private final class SheetContainerComponent: CombinedComponent {
static var body: Body {
let sheet = Child(ResizableSheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let boundsUpdated = ActionSlot<CGRect>()
return { context in
let component = context.component
@ -518,11 +569,12 @@ private final class SheetContainerComponent: CombinedComponent {
selectGift: component.selectGift,
dismiss: {
dismiss(true)
}
},
boundsUpdated: boundsUpdated
)
),
titleItem: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "Select Gifts", font: Font.semibold(17.0), textColor: environment.theme.actionSheet.primaryTextColor)))
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_Title, font: Font.semibold(17.0), textColor: environment.theme.actionSheet.primaryTextColor)))
),
leftItem: AnyComponent(
GlassBarButtonComponent(
@ -530,9 +582,9 @@ private final class SheetContainerComponent: CombinedComponent {
backgroundColor: nil,
isDark: theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "back", component: AnyComponent(
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Back",
name: "Navigation/Close",
tintColor: theme.chat.inputPanel.panelControlColor
)
)),
@ -561,7 +613,8 @@ private final class SheetContainerComponent: CombinedComponent {
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in
dismiss(animated)
}
},
boundsUpdated: boundsUpdated
)
},
availableSize: context.availableSize,

View file

@ -792,9 +792,7 @@ public final class GiftItemComponent: Component {
func formatPercentage(_ value: Float) -> String {
return String(format: "%0.1f", value).replacingOccurrences(of: ".0", with: "").replacingOccurrences(of: ",0", with: "") + "%"
}
//TODO:localize
let badgeString: String
var badgeColor: UIColor?
switch rarity {
@ -805,21 +803,28 @@ public final class GiftItemComponent: Component {
badgeString = formatPercentage(Float(value) * 0.1)
}
case .epic:
badgeString = "epic"
badgeString = component.strings.Gift_Attribute_Epic
badgeColor = UIColor(rgb: 0xaf52de)
case .legendary:
badgeString = "legendary"
badgeString = component.strings.Gift_Attribute_Legendary
badgeColor = UIColor(rgb: 0xd57e32)
case .rare:
badgeString = "rare"
badgeColor = UIColor(rgb: 0x79993d)
badgeString = component.strings.Gift_Attribute_Rare
badgeColor = UIColor(rgb: 0x25a3b9)
case .uncommon:
badgeString = component.strings.Gift_Attribute_Uncommon
badgeColor = UIColor(rgb: 0x22b447)
}
var badgeTextColor = isColored ? .white : component.theme.list.itemSecondaryTextColor
var badgeBackgroundColor = isColored ? UIColor(white: 0.0, alpha: 0.2) : component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.06)
if let badgeColor {
badgeTextColor = badgeColor
badgeBackgroundColor = badgeColor.withMultipliedAlpha(0.1)
if let backgroundColor {
badgeBackgroundColor = badgeColor.mixedWith(backgroundColor, alpha: 0.9)
} else {
badgeBackgroundColor = badgeColor.withMultipliedAlpha(0.1)
}
}
let badgeTextSize = self.badgeText.update(
transition: .spring(duration: 0.2),

View file

@ -139,7 +139,7 @@ public final class GiftLoadingShimmerView: UIView {
required init?(coder: NSCoder) { fatalError() }
public func update(size: CGSize, theme: PresentationTheme, showFilters: Bool = false, isPlain: Bool = false, transition: ContainedViewLayoutTransition) {
public func update(size: CGSize, theme: PresentationTheme, itemSize: CGSize? = nil, showFilters: Bool = false, isPlain: Bool = false, transition: ContainedViewLayoutTransition) {
let backgroundColor = isPlain ? theme.list.itemBlocksBackgroundColor : theme.list.blocksBackgroundColor
let color = theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
@ -169,7 +169,7 @@ public final class GiftLoadingShimmerView: UIView {
let optionSpacing: CGFloat = 10.0
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
let itemSize = CGSize(width: optionWidth, height: 154.0)
let itemSize = itemSize ?? CGSize(width: optionWidth, height: 154.0)
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)

View file

@ -1605,7 +1605,7 @@ private final class GiftSetupScreenComponent: Component {
guard let self, let component = self.component, let controller = self.environment?.controller(), let upgradePreview else {
return
}
let previewController = component.context.sharedContext.makeGiftUpgradePreviewScreen(context: component.context, attributes: upgradePreview.attributes, peerName: peerName)
let previewController = component.context.sharedContext.makeGiftUpgradePreviewScreen(context: component.context, gift: gift, attributes: upgradePreview.attributes, peerName: peerName)
controller.push(previewController)
})
}

View file

@ -24,6 +24,7 @@ swift_library(
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/ItemListUI",
"//submodules/ContextUI",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/Components/SheetComponent",

View file

@ -0,0 +1,174 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import AccountContext
import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import TextFormat
import TelegramStringFormatting
import Markdown
final class BalanceComponent: Component {
private let context: AccountContext
private let theme: PresentationTheme
private let action: () -> Void
init(
context: AccountContext,
theme: PresentationTheme,
action: @escaping () -> Void
) {
self.context = context
self.theme = theme
self.action = action
}
static func ==(lhs: BalanceComponent, rhs: BalanceComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
return true
}
public final class View: HighlightTrackingButton {
private var text = ComponentView<Empty>()
private var component: BalanceComponent?
private var componentState: EmptyComponentState?
private var starsBalance: Int64 = 0
private var tonBalance: Int64 = 0
private var balanceDisposable: Disposable?
override public init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.balanceDisposable?.dispose()
}
@objc private func pressed() {
guard let component = self.component else {
return
}
component.action()
}
private var isUpdating = false
func update(component: BalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
self.component = component
self.componentState = state
if self.balanceDisposable == nil {
if let starsContext = component.context.starsContext, let tonContext = component.context.tonContext {
self.balanceDisposable = combineLatest(queue: Queue.mainQueue(),
starsContext.state,
tonContext.state
).start(next: { [weak self] starsState, tonState in
guard let self else {
return
}
self.starsBalance = starsState?.balance.value ?? 0
self.tonBalance = tonState?.balance.value ?? 0
if !self.isUpdating {
self.componentState?.updated()
}
})
}
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
var rawString: String = ""
let starsBalanceString = "**⭐️\(presentationStringsFormattedNumber(Int32(clamping: self.starsBalance), presentationData.dateTimeFormat.groupingSeparator))**"
if self.tonBalance > 0 {
let tonBalanceString = "**💎\(formatTonAmountText(self.tonBalance, dateTimeFormat: presentationData.dateTimeFormat))**"
rawString = starsBalanceString + "\n" + tonBalanceString
} else {
rawString = presentationData.strings.Stars_Purchase_Balance + "\n" + starsBalanceString
}
let attributedText = parseMarkdownIntoAttributedString(
rawString,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
link: MarkdownAttributeSet(font: Font.regular(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
linkAttribute: { _ in
return nil
}
),
textAlignment: .right
).mutableCopy() as! NSMutableAttributedString
let starRange = (attributedText.string as NSString).range(of: "⭐️")
if starRange.location != NSNotFound {
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: starRange)
attributedText.addAttribute(.baselineOffset, value: 1.0, range: starRange)
}
let tonRange = (attributedText.string as NSString).range(of: "💎")
if tonRange.location != NSNotFound {
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: tonRange)
attributedText.addAttribute(.baselineOffset, value: 1.0, range: tonRange)
}
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(
MultilineTextWithEntitiesComponent(
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: .white,
text: .plain(attributedText),
horizontalAlignment: .right,
maximumNumberOfLines: 2,
lineSpacing: 0.1,
displaysAsynchronously: false
)
),
environment: {},
containerSize: availableSize
)
let inset: CGFloat = 12.0
let size = CGSize(width: textSize.width + inset * 2.0, height: 44.0)
if let textView = self.text.view {
if textView.superview == nil {
textView.isUserInteractionEnabled = false
self.addSubview(textView)
if !transition.animation.isImmediate {
transition.animateAlpha(view: textView, from: 0.0, to: 1.0)
}
}
textView.frame = CGRect(origin: CGPoint(x: inset, y: 8.0 - UIScreenPixel), size: textSize)
}
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public 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)
}
}

View file

@ -29,6 +29,7 @@ import LottieComponent
import GiftLoadingShimmerView
import EdgeEffect
import GlassBackgroundComponent
import ContextUI
private let minimumCountToDisplayFilters = 18
@ -45,6 +46,7 @@ public final class GiftStoreContentComponent: Component {
let starsContext: StarsContext
let peerId: EnginePeer.Id
let gift: StarGift.Gift
let isPlain: Bool
let confirmPurchaseImmediately: Bool
let starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>?
let scrollToTop: () -> Void
@ -64,6 +66,7 @@ public final class GiftStoreContentComponent: Component {
starsContext: StarsContext,
peerId: EnginePeer.Id,
gift: StarGift.Gift,
isPlain: Bool,
confirmPurchaseImmediately: Bool,
starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>?,
scrollToTop: @escaping () -> Void,
@ -82,6 +85,7 @@ public final class GiftStoreContentComponent: Component {
self.starsContext = starsContext
self.peerId = peerId
self.gift = gift
self.isPlain = isPlain
self.confirmPurchaseImmediately = confirmPurchaseImmediately
self.starsTopUpOptions = starsTopUpOptions
self.scrollToTop = scrollToTop
@ -1075,7 +1079,7 @@ public final class GiftStoreContentComponent: Component {
let loadingSize = CGSize(width: availableSize.width, height: min(1000.0, availableSize.height))
if isLoading && self.showLoading {
self.loadingView.update(size: loadingSize, theme: component.theme, showFilters: !showingFilters, isPlain: true, transition: .immediate)
self.loadingView.update(size: loadingSize, theme: component.theme, showFilters: !showingFilters, isPlain: component.isPlain, transition: .immediate)
loadingTransition.setAlpha(view: self.loadingView, alpha: 1.0)
} else {
loadingTransition.setAlpha(view: self.loadingView, alpha: 0.0)
@ -1145,10 +1149,8 @@ final class GiftStoreScreenComponent: Component {
private let edgeEffectView: EdgeEffectView
private let balanceBackgroundView: GlassBackgroundView
private let balanceTitle = ComponentView<Empty>()
private let balanceValue = ComponentView<Empty>()
private let balanceIcon = ComponentView<Empty>()
private let balance = ComponentView<Empty>()
private let balanceBackgroundView: GlassContextExtractableContainer
private let title = ComponentView<Empty>()
private let subtitle = ComponentView<Empty>()
@ -1165,7 +1167,7 @@ final class GiftStoreScreenComponent: Component {
private var isUpdating: Bool = false
override init(frame: CGRect) {
self.balanceBackgroundView = GlassBackgroundView()
self.balanceBackgroundView = GlassContextExtractableContainer()
self.scrollView = ScrollView()
self.scrollView.showsVerticalScrollIndicator = true
@ -1211,6 +1213,68 @@ final class GiftStoreScreenComponent: Component {
contentView.updateScrolling(bounds: bounds, interactive: interactive, transition: transition)
}
}
func presentBalanceMenu() {
guard let component = self.component, let starsContext = component.context.starsContext, let tonContext = component.context.tonContext, let controller = self.environment?.controller() else {
return
}
let tonBalance = tonContext.currentState?.balance.value ?? 0
if tonBalance == 0 {
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
controller.push(controller)
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let sourceView = self.balanceBackgroundView
let items: Signal<[ContextMenuItem], NoError> = combineLatest(
queue: Queue.mainQueue(),
starsContext.state,
tonContext.state
)
|> take(1)
|> map { starsState, tonState -> [ContextMenuItem] in
let starsBalance = starsState?.balance ?? .zero
let tonBalance = tonState?.balance.value ?? 0
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(
text: "My Stars",
textLayout: .secondLineWithValue(formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat)),
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stars"), color: theme.contextMenu.primaryColor) },
action: { [weak self] _, f in
f(.dismissWithoutContent)
guard let self, let component = self.component, let environment = self.environment else {
return
}
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: starsContext)
environment.controller()?.push(controller)
}
)))
items.append(.action(ContextMenuActionItem(
text: "My TON",
textLayout: .secondLineWithValue(formatTonAmountText(tonBalance, dateTimeFormat: presentationData.dateTimeFormat)),
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Ton"), color: theme.contextMenu.primaryColor) },
action: { [weak self] _, f in
f(.dismissWithoutContent)
guard let self, let component = self.component, let environment = self.environment else {
return
}
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
environment.controller()?.push(controller)
}
)))
return items
}
let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
func update(component: GiftStoreScreenComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
@ -1255,42 +1319,37 @@ final class GiftStoreScreenComponent: Component {
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: min(30, edgeEffectFrame.height), transition: transition)
let balanceTitleSize = self.balanceTitle.update(
let balanceSize = self.balance.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: strings.Stars_Purchase_Balance,
font: Font.regular(14.0),
textColor: environment.theme.actionSheet.primaryTextColor
)),
maximumNumberOfLines: 1
)),
component: AnyComponent(
BalanceComponent(
context: component.context,
theme: environment.theme,
action: { [weak self] in
guard let self else {
return
}
self.presentBalanceMenu()
}
)
),
environment: {},
containerSize: availableSize
)
let formattedBalance = formatStarsAmountText(self.starsState?.balance ?? StarsAmount.zero, dateTimeFormat: environment.dateTimeFormat)
let smallLabelFont = Font.regular(11.0)
let labelFont = Font.semibold(14.0)
let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: environment.theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator)
let balanceValueSize = self.balanceValue.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(balanceText),
maximumNumberOfLines: 1
)),
environment: {},
containerSize: availableSize
)
let balanceIconSize = self.balanceIcon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil)),
environment: {},
containerSize: availableSize
)
let balanceFrame = CGRect(origin: .zero, size: balanceSize)
if let balanceView = self.balance.view {
if balanceView.superview == nil {
self.balanceBackgroundView.contentView.addSubview(balanceView)
}
balanceView.frame = balanceFrame
let balanceBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - balanceSize.width, y: environment.navigationHeight - 60.0 + 2.0 + floor((60.0 - 44.0) * 0.5)), size: balanceSize)
transition.setFrame(view: self.balanceBackgroundView, frame: balanceBackgroundFrame)
self.balanceBackgroundView.update(size: balanceBackgroundFrame.size, cornerRadius: balanceBackgroundFrame.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
}
if self.balanceBackgroundView.superview == nil {
component.overNavigationContainer.addSubview(self.balanceBackgroundView)
}
@ -1300,32 +1359,6 @@ final class GiftStoreScreenComponent: Component {
topInset = environment.statusBarHeight - 6.0
}
if let balanceTitleView = self.balanceTitle.view, let balanceValueView = self.balanceValue.view, let balanceIconView = self.balanceIcon.view {
if balanceTitleView.superview == nil {
self.balanceBackgroundView.contentView.addSubview(balanceTitleView)
self.balanceBackgroundView.contentView.addSubview(balanceValueView)
self.balanceBackgroundView.contentView.addSubview(balanceIconView)
}
let topBalanceOriginY = (44.0 - balanceTitleSize.height - balanceValueSize.height) / 2.0
let balanceSideInset: CGFloat = 12.0
var balanceBackgroundSize = CGSize(width: balanceTitleSize.width + balanceSideInset * 2.0, height: 44.0)
balanceBackgroundSize.width = max(balanceBackgroundSize.width, balanceValueSize.width + balanceIconSize.width + 2.0 + balanceSideInset * 2.0)
let balanceBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - balanceBackgroundSize.width, y: environment.navigationHeight - 60.0 + 2.0 + floor((60.0 - 44.0) * 0.5)), size: balanceBackgroundSize)
transition.setFrame(view: self.balanceBackgroundView, frame: balanceBackgroundFrame)
self.balanceBackgroundView.update(size: balanceBackgroundFrame.size, cornerRadius: balanceBackgroundFrame.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: transition)
balanceTitleView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0)
balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize)
balanceValueView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0)
balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize)
balanceIconView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel)
balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize)
}
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
@ -1360,6 +1393,7 @@ final class GiftStoreScreenComponent: Component {
starsContext: component.starsContext,
peerId: component.peerId,
gift: component.gift,
isPlain: false,
confirmPurchaseImmediately: false,
starsTopUpOptions: nil,
scrollToTop: { [weak self] in

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

@ -746,8 +746,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
} else {
return false
}
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
secondaryTextColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
}
@ -1083,7 +1083,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
guard let state, let attributes = state.giftUpgradeAttributes else {
return
}
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsScreen(context: component.context, gift: .generic(gift), onlyCrafted: false, attributes: attributes, selectedAttributes: nil, focusedAttribute: nil)
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsScreen(context: component.context, gift: .generic(gift), crafted: false, attributes: attributes, selectedAttributes: nil, focusedAttribute: nil)
environment.controller()?.push(variantsController)
}, animateScale: false),
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),

View file

@ -294,8 +294,8 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
} else {
return false
}
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
secondaryTextColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
}

View file

@ -29,7 +29,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
let context: AccountContext
let gift: StarGift
let onlyCrafted: Bool
let crafted: Bool
let attributes: [StarGift.UniqueGift.Attribute]
let selectedAttributes: [StarGift.UniqueGift.Attribute]?
let focusedAttribute: StarGift.UniqueGift.Attribute?
@ -37,14 +37,14 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
init(
context: AccountContext,
gift: StarGift,
onlyCrafted: Bool,
crafted: Bool,
attributes: [StarGift.UniqueGift.Attribute],
selectedAttributes: [StarGift.UniqueGift.Attribute]?,
focusedAttribute: StarGift.UniqueGift.Attribute?
) {
self.context = context
self.gift = gift
self.onlyCrafted = onlyCrafted
self.crafted = crafted
self.attributes = attributes
self.selectedAttributes = selectedAttributes
self.focusedAttribute = focusedAttribute
@ -112,6 +112,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
private var giftItems: [AnyHashable: ComponentView<Empty>] = [:]
private var selectedSection: SelectedSection = .models
private var displayCraftableModels = false
private let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
@ -122,7 +123,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
private var previewBackdropIndex: Int = 0
private var previewSymbolIndex: Int = 0
private var previewModels: [StarGift.UniqueGift.Attribute] = []
private var previewPrimaryModels: [StarGift.UniqueGift.Attribute] = []
private var previewCraftableModels: [StarGift.UniqueGift.Attribute] = []
private var previewBackdrops: [StarGift.UniqueGift.Attribute] = []
private var previewSymbols: [StarGift.UniqueGift.Attribute] = []
@ -130,10 +132,15 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
private var selectedBackdrop: StarGift.UniqueGift.Attribute?
private var selectedSymbol: StarGift.UniqueGift.Attribute?
private var modelCount: Int32 = 0
private var craftableModelCount: Int32 = 0
private var primaryModelCount: Int32 = 0
private var backdropCount: Int32 = 0
private var symbolCount: Int32 = 0
private var currentDescriptionHeight: CGFloat = 0.0
private var cachedSmallChevronImage: (UIImage, PresentationTheme)?
private var ignoreScrolling: Bool = false
private var component: GiftUpgradeVariantsScreenComponent?
@ -202,6 +209,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
self.containerView.addSubview(self.navigationBarContainer)
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
self.alpha = 0.0
}
required init?(coder: NSCoder) {
@ -271,6 +280,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
}
func animateIn() {
self.alpha = 1.0
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
@ -290,8 +300,9 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
}
private func previewTimerTick() {
guard !self.previewModels.isEmpty else { return }
self.previewModelIndex = (self.previewModelIndex + 1) % self.previewModels.count
let previewModels = self.displayCraftableModels ? self.previewCraftableModels : self.previewPrimaryModels
guard !previewModels.isEmpty else { return }
self.previewModelIndex = (self.previewModelIndex + 1) % previewModels.count
let previousSymbolIndex = self.previewSymbolIndex
var randomSymbolIndex = previousSymbolIndex
@ -326,13 +337,15 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
}
private var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = []
private func updateEffectiveGifts(attributes: [StarGift.UniqueGift.Attribute], onlyCrafted: Bool) {
private func updateEffectiveGifts(attributes: [StarGift.UniqueGift.Attribute]) {
var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = []
switch self.selectedSection {
case .models:
let models = Array(attributes.filter({ attribute in
if case let .model(_, _, _, crafted) = attribute {
if onlyCrafted && !crafted {
if self.displayCraftableModels && !crafted {
return false
} else if !self.displayCraftableModels && crafted {
return false
}
return true
@ -344,7 +357,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
effectiveGifts.append([model])
}
case .backdrops:
let selectedModel = self.selectedModel ?? self.previewModels[self.previewModelIndex]
let previewModels = self.displayCraftableModels ? self.previewCraftableModels : previewPrimaryModels
let selectedModel = self.selectedModel ?? previewModels[self.previewModelIndex]
let selectedSymbol = self.selectedSymbol ?? self.previewSymbols[self.previewSymbolIndex]
let backdrops = Array(attributes.filter({ attribute in
if case .backdrop = attribute {
@ -400,7 +414,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
let optionWidth = (fillingSize - 16.0 * 2.0 - optionSpacing * 2.0) / 3.0
let optionSize = CGSize(width: optionWidth, height: 126.0)
let topInset: CGFloat = 393.0
let topInset: CGFloat = 375.0 + self.currentDescriptionHeight
var validIds: [AnyHashable] = []
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 9.0), size: optionSize)
@ -604,15 +618,19 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
let sideInset: CGFloat = rawSideInset + 16.0
if self.component == nil {
var modelCount: Int32 = 0
self.displayCraftableModels = component.crafted
var primaryModelCount: Int32 = 0
var craftableModelCount: Int32 = 0
var backdropCount: Int32 = 0
var symbolCount: Int32 = 0
for attribute in component.attributes {
switch attribute {
case let .model(_, _, _, crafted):
if component.onlyCrafted && !crafted {
if crafted {
craftableModelCount += 1
} else {
modelCount += 1
primaryModelCount += 1
}
case .backdrop:
backdropCount += 1
@ -622,13 +640,14 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
break
}
}
self.modelCount = modelCount
self.primaryModelCount = primaryModelCount
self.craftableModelCount = craftableModelCount
self.backdropCount = backdropCount
self.symbolCount = symbolCount
let randomModels = Array(component.attributes.filter({ attribute in
let randomPrimaryModels = Array(component.attributes.filter({ attribute in
if case let .model(_, _, _, crafted) = attribute {
if component.onlyCrafted && !crafted {
if crafted {
return false
}
return true
@ -636,7 +655,19 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
return false
}
}).shuffled().prefix(15))
self.previewModels = randomModels
self.previewPrimaryModels = randomPrimaryModels
let randomCraftableModels = Array(component.attributes.filter({ attribute in
if case let .model(_, _, _, crafted) = attribute {
if !crafted {
return false
}
return true
} else {
return false
}
}).shuffled().prefix(15))
self.previewCraftableModels = randomCraftableModels
let randomBackdrops = Array(component.attributes.filter({ attribute in
if case .backdrop = attribute {
@ -684,7 +715,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
}
}
self.updateEffectiveGifts(attributes: component.attributes, onlyCrafted: component.onlyCrafted)
self.updateEffectiveGifts(attributes: component.attributes)
}
self.component = component
@ -705,14 +736,15 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
var badgeColor: UIColor = .white.withAlphaComponent(0.4)
var attributes: [StarGift.UniqueGift.Attribute] = []
if !self.previewModels.isEmpty {
let previewModels = self.displayCraftableModels ? self.previewCraftableModels : self.previewPrimaryModels
if !previewModels.isEmpty {
if self.isPlaying {
attributes.append(self.previewModels[self.previewModelIndex])
attributes.append(previewModels[self.previewModelIndex])
attributes.append(self.previewBackdrops[self.previewBackdropIndex])
attributes.append(self.previewSymbols[self.previewSymbolIndex])
} else {
if self.selectedModel == nil {
self.selectedModel = self.previewModels[self.previewModelIndex]
self.selectedModel = previewModels[self.previewModelIndex]
}
if self.selectedBackdrop == nil {
self.selectedBackdrop = self.previewBackdrops[self.previewBackdropIndex]
@ -739,7 +771,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
return false
}
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
badgeColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
let outer = UIColor(rgb: UInt32(bitPattern: outerColor))
@ -786,8 +818,11 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
contentHeight += headerSize.height
var titleText: String = ""
if case let .generic(gift) = component.gift {
switch component.gift {
case let .generic(gift):
titleText = gift.title ?? ""
case let .unique(gift):
titleText = gift.title
}
let titleSize = self.title.update(
@ -838,7 +873,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
}
let attributeSpacing: CGFloat = 10.0
let attributeWidth: CGFloat = floor((fillingSize - attributeSpacing * CGFloat(attributes.count - 1)) / CGFloat(attributes.count))
let attributeWidth: CGFloat = floor((fillingSize - 32.0 - attributeSpacing * CGFloat(attributes.count - 1)) / CGFloat(attributes.count))
let attributeHeight: CGFloat = 45.0
for i in 0 ..< attributes.count {
@ -911,7 +946,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
self.selectedSection = id
self.isPlaying = false
self.updateEffectiveGifts(attributes: component.attributes, onlyCrafted: component.onlyCrafted)
self.updateEffectiveGifts(attributes: component.attributes)
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)))
})),
environment: {},
@ -932,17 +967,22 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
let itemHeight: CGFloat = 126.0
let itemSpacing: CGFloat = 10.0
let descriptionText: String
var descriptionText: String
let itemCount: Int32
switch self.selectedSection {
case .models:
//TODO:localize
if component.onlyCrafted {
descriptionText = environment.strings.Gift_Variants_CollectionInfo("**\(self.modelCount)** craftable models").string
if self.displayCraftableModels {
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_CraftableModel(self.craftableModelCount)).string
itemCount = self.craftableModelCount
descriptionText += "\n[\(environment.strings.Gift_Variants_ViewPrimaryModels) >]()"
} else {
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Model(self.modelCount)).string
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Model(self.primaryModelCount)).string
itemCount = self.primaryModelCount
if self.craftableModelCount > 0 {
descriptionText += "\n[\(environment.strings.Gift_Variants_ViewCraftableModels) >]()"
}
}
itemCount = self.modelCount
case .backdrops:
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Backdrop(self.backdropCount)).string
itemCount = self.backdropCount
@ -954,14 +994,45 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
let descriptionFont = Font.regular(13.0)
let descriptionBoldFont = Font.semibold(13.0)
let descriptionTextColor = theme.list.itemSecondaryTextColor
let descriptionMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionTextColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), linkAttribute: { contents in
let descriptionLinkColor = theme.list.itemAccentColor
let descriptionMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionTextColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionLinkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
if self.cachedSmallChevronImage == nil || self.cachedSmallChevronImage?.1 !== environment.theme {
self.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: descriptionLinkColor)!, theme)
}
let descriptionAttributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: descriptionMarkdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
if let range = descriptionAttributedString.string.range(of: ">"), let chevronImage = self.cachedSmallChevronImage?.0 {
descriptionAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: descriptionAttributedString.string))
}
let descriptionSize = self.descriptionText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .markdown(text: descriptionText, attributes: descriptionMarkdownAttributes)
text: .plain(descriptionAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 3,
lineSpacing: 0.2,
highlightColor: descriptionLinkColor.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: { [weak self] attributes, _ in
if let self, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
self.displayCraftableModels = !self.displayCraftableModels
self.isPlaying = false
self.updateEffectiveGifts(attributes: component.attributes)
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)))
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
@ -973,6 +1044,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
}
descriptionView.frame = descriptionFrame
}
self.currentDescriptionHeight = descriptionSize.height
contentHeight += descriptionSize.height
contentHeight += 26.0
@ -1061,6 +1133,13 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
self.previewTimerTick()
}
self.state?.updated(transition: .easeInOut(duration: 0.25))
if let buttonView = self.playbackButton.view {
buttonView.isUserInteractionEnabled = false
Queue.mainQueue().after(0.3, {
buttonView.isUserInteractionEnabled = true
})
}
}
)),
environment: {},
@ -1156,7 +1235,7 @@ public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer {
public init(
context: AccountContext,
gift: StarGift,
onlyCrafted: Bool = false,
crafted: Bool = false,
attributes: [StarGift.UniqueGift.Attribute],
selectedAttributes: [StarGift.UniqueGift.Attribute]?,
focusedAttribute: StarGift.UniqueGift.Attribute?
@ -1166,7 +1245,7 @@ public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer {
super.init(context: context, component: GiftUpgradeVariantsScreenComponent(
context: context,
gift: gift,
onlyCrafted: onlyCrafted,
crafted: crafted,
attributes: attributes,
selectedAttributes: selectedAttributes,
focusedAttribute: focusedAttribute
@ -1194,7 +1273,9 @@ public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer {
self.didPlayAppearAnimation = true
if let componentView = self.node.hostView.componentView as? GiftUpgradeVariantsScreenComponent.View {
componentView.animateIn()
Queue.mainQueue().justDispatch {
componentView.animateIn()
}
}
}
}
@ -1352,8 +1433,8 @@ private final class AttributeInfoComponent: Component {
}
var badgeString = ""
var badgeColor = component.badgeColor
if let rarity {
//TODO:localize
switch rarity {
case let .permille(value):
if value == 0 {
@ -1362,11 +1443,17 @@ private final class AttributeInfoComponent: Component {
badgeString = formatPercentage(Float(value) * 0.1)
}
case .epic:
badgeString = "epic"
badgeString = component.strings.Gift_Attribute_Epic
badgeColor = UIColor(rgb: 0xaf52de)
case .legendary:
badgeString = "legendary"
badgeString = component.strings.Gift_Attribute_Legendary
badgeColor = UIColor(rgb: 0xd57e32)
case .rare:
badgeString = "rare"
badgeString = component.strings.Gift_Attribute_Rare
badgeColor = UIColor(rgb: 0x25a3b9)
case .uncommon:
badgeString = component.strings.Gift_Attribute_Uncommon
badgeColor = UIColor(rgb: 0x22b447)
}
}
@ -1406,8 +1493,7 @@ private final class AttributeInfoComponent: Component {
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -5.5, dy: -2.0)
transition.setFrame(layer: self.badgeBackground, frame: badgeBackgroundFrame)
transition.setBackgroundColor(layer: self.badgeBackground, color: component.badgeColor)
transition.setBackgroundColor(layer: self.badgeBackground, color: badgeColor)
return availableSize
}

View file

@ -37,6 +37,8 @@ public func buyStarGiftImpl(
parseMarkdown: true
)
controller.present(alertController, in: .window(.root))
beforeCompletion()
return
}
@ -207,6 +209,8 @@ public func buyStarGiftImpl(
context.sharedContext.applicationBindings.openUrl(fragmentUrl)
}
))
beforeCompletion()
} else {
proceed()
}

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

@ -3734,7 +3734,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
}
} else if case let .gift(gift) = subject {
isGift = true
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil, dropOriginalDetailsStars: nil, assigned: false, fromOffer: false, canCraftAt: nil))]
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil, dropOriginalDetailsStars: nil, assigned: false, fromOffer: false, canCraftAt: nil, isCrafted: false))]
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
messages = .single([message])
} else {

View file

@ -197,6 +197,8 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
}
private let backgroundNode: ASDisplayNode
private let highlightNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
@ -225,6 +227,9 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.highlightNode = ASDisplayNode()
self.highlightNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
@ -297,6 +302,20 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
self.item?.openSetCustom?()
}
public func displayHighlight() {
if self.backgroundNode.supernode != nil {
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
} else {
self.insertSubnode(self.highlightNode, at: 0)
}
Queue.mainQueue().after(1.2, {
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
self.highlightNode.removeFromSupernode()
})
})
}
func asyncLayout() -> (_ item: MessagePriceItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
@ -329,6 +348,7 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.highlightNode.backgroundColor = item.theme.list.itemSearchHighlightColor
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
@ -370,6 +390,7 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))

View file

@ -587,8 +587,7 @@ public final class PeerInfoCoverComponent: Component {
self.avatarPatternContentLayers.append(itemLayer)
}
//itemLayer.frame = itemFrame
transition.setFrame(layer: itemLayer, frame: itemFrame)
itemLayer.frame = itemFrame
itemLayer.layerTintColor = UIColor(white: 0.0, alpha: 0.8).cgColor
transition.setAlpha(layer: itemLayer, alpha: 1.0 - itemScaleFraction)

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),

View file

@ -314,7 +314,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let tipsPeerDisposable = MetaDisposable()
let cachedFaq = Promise<ResolvedUrl?>(nil)
private var didSetCachedFaq = false
var didSetCachedFaq = false
weak var copyProtectionTooltipController: TooltipController?
weak var emojiStatusSelectionController: ViewController?
@ -2370,6 +2370,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let controller = GiftViewScreen(
context: self.context,
subject: .profileGift(self.peerId, gift),
profileGiftsContext: profileGifts,
updateSavedToProfile: { [weak profileGifts] reference, added in
guard let profileGifts else {
return
@ -4807,10 +4808,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
if self.isSettings {
if !self.didSetCachedFaq {
self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init)))
self.didSetCachedFaq = true
}
self.setupFaqIfNeeded()
if let settings = self.data?.globalSettings {
self.searchDisplayController = SearchDisplayController(

View file

@ -15,6 +15,7 @@ import PremiumUI
import TelegramPresentationData
import PresentationDataUtils
import PasswordSetupUI
import InstantPageCache
extension PeerInfoScreenNode {
func openSettings(section: PeerInfoSettingsSection) {
@ -298,7 +299,16 @@ extension PeerInfoScreenNode {
}
}
func setupFaqIfNeeded() {
if !self.didSetCachedFaq {
self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init)))
self.didSetCachedFaq = true
}
}
func openFaq(anchor: String? = nil) {
self.setupFaqIfNeeded()
let presentationData = self.presentationData
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
@ -314,6 +324,7 @@ extension PeerInfoScreenNode {
let progressDisposable = progressSignal.start()
let _ = (self.cachedFaq.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] resolvedUrl in
progressDisposable.dispose()

View file

@ -606,6 +606,7 @@ final class GiftsListView: UIView {
subject: .profileGift(self.peerId, product),
allSubjects: allSubjects,
index: index,
profileGiftsContext: self.profileGifts,
updateSavedToProfile: { [weak self] reference, added in
guard let self else {
return

View file

@ -108,6 +108,7 @@ public final class PlainButtonComponent: Component {
super.init(frame: frame)
self.isExclusiveTouch = true
self.layer.rasterizationScale = UIScreenScale
self.contentContainer.isUserInteractionEnabled = false
self.addSubview(self.contentContainer)
@ -130,8 +131,13 @@ public final class PlainButtonComponent: Component {
self.contentContainer.alpha = 0.7
}
if animateScale {
self.layer.shouldRasterize = true
let transition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))
transition.setScale(layer: self.contentContainer.layer, scale: topScale)
transition.setScale(layer: self.contentContainer.layer, scale: topScale, completion: { finished in
if finished {
self.layer.shouldRasterize = false
}
})
}
} else {
if animateAlpha {
@ -140,15 +146,21 @@ public final class PlainButtonComponent: Component {
}
if animateScale {
self.layer.shouldRasterize = true
let transition = ComponentTransition(animation: .none)
transition.setScale(layer: self.contentContainer.layer, scale: 1.0)
self.contentContainer.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
guard let self else {
return
}
self.contentContainer.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
self.contentContainer.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, completion: { finished in
if finished {
self.layer.shouldRasterize = false
}
})
})
}
}

View file

@ -195,15 +195,14 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
commit()
return
}
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let alertController = textAlertController(
context: context,
title: "Warning",
text: "This action will expose your IP address to the admin of the proxy server.",
title: presentationData.strings.SocksProxySetup_Warning_Title,
text: presentationData.strings.SocksProxySetup_Warning_Text,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: "Proceed", action: {
TextAlertAction(type: .defaultAction, title: presentationData.strings.SocksProxySetup_Warning_Proceed, action: {
commit()
})
]
@ -253,14 +252,11 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
availableSize: CGSize(width: 44.0, height: 44.0),
transition: .immediate
)
let titleText: String = "Proxy"
let buttonText: String = "Connect Proxy"
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: titleText,
string: strings.SocksProxySetup_Title,
font: Font.semibold(17.0),
textColor: theme.actionSheet.primaryTextColor,
paragraphAlignment: .center
@ -323,7 +319,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
))
}
var statusText = "Check Status"
var statusText = strings.SocksProxySetup_CheckStatus
var statusColor = tableLinkColor
var statusIsActive = true
if let status = state.status {
@ -378,7 +374,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: buttonText, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.SocksProxySetup_ConnectAndSave, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
displaysProgress: state.inProgress,
action: {

View file

@ -22,6 +22,7 @@ swift_library(
"//submodules/Components/SheetComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
],
visibility = [
"//visibility:public",

View file

@ -12,6 +12,7 @@ import BalancedTextComponent
import Markdown
import TelegramStringFormatting
import BundleIconComponent
import GlassBarButtonComponent
public final class ButtonSubtitleComponent: CombinedComponent {
public let title: String
@ -124,23 +125,29 @@ private final class StoryQualityUpgradeSheetContentComponent: Component {
}
let cancelButtonSize = cancelButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component: AnyComponent(GlassBarButtonComponent(
size: CGSize(width: 44.0, height: 44.0),
backgroundColor: nil,
isDark: environment.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: environment.theme.chat.inputPanel.panelControlColor
)
)),
action: { _ in
component.dismiss()
}
).minSize(CGSize(width: 8.0, height: 44.0))),
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
containerSize: CGSize(width: 44.0, height: 44.0)
)
if let cancelButtonView = cancelButton.view {
if cancelButtonView.superview == nil {
self.addSubview(cancelButtonView)
}
transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 6.0), size: cancelButtonSize))
transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelButtonSize))
}
var contentHeight: CGFloat = 0.0
@ -152,7 +159,8 @@ private final class StoryQualityUpgradeSheetContentComponent: Component {
content: LottieComponent.AppBundleContent(name: "StoryUpgradeSheet"),
color: nil,
startingPosition: .begin,
size: CGSize(width: 100.0, height: 100.0)
size: CGSize(width: 100.0, height: 100.0),
loop: true
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
@ -217,16 +225,18 @@ private final class StoryQualityUpgradeSheetContentComponent: Component {
color: environment.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7)
))))
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
let buttonSize = self.button.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
VStack(buttonContents, spacing: 3.0)
VStack(buttonContents, spacing: 1.0)
)),
isEnabled: true,
allowActionWhenDisabled: true,
@ -240,9 +250,9 @@ private final class StoryQualityUpgradeSheetContentComponent: Component {
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0)
)
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize)
let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
@ -250,12 +260,7 @@ private final class StoryQualityUpgradeSheetContentComponent: Component {
transition.setFrame(view: buttonView, frame: buttonFrame)
}
contentHeight += buttonSize.height
if environment.safeInsets.bottom.isZero {
contentHeight += 16.0
} else {
contentHeight += environment.safeInsets.bottom + 14.0
}
contentHeight += buttonInsets.bottom
return CGSize(width: availableSize.width, height: contentHeight)
}
@ -355,7 +360,8 @@ private final class StoryQualityUpgradeSheetScreenComponent: Component {
})
}
)),
backgroundColor: .color(environment.theme.overallDarkAppearance ? environment.theme.list.itemBlocksBackgroundColor : environment.theme.list.blocksBackgroundColor),
style: .glass,
backgroundColor: .color(environment.theme.list.modalPlainBackgroundColor),
animateOut: self.sheetAnimateOut
)),
environment: {

View file

@ -589,7 +589,7 @@ public final class TextFieldComponent: Component {
}
}
public func chatInputTextNodeShouldReturn() -> Bool {
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
guard let component = self.component else {
return true
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "star (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ton (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "puzzle.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "diamond (2).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "chance.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "giftcrash.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -15,16 +15,15 @@ import ChatControllerInteraction
extension ChatControllerImpl {
var keyShortcutsInternal: [KeyShortcut] {
if !self.traceVisibility() || !isTopmostChatController(self) {
if !isTopmostChatController(self) {
return []
}
let strings = self.presentationData.strings
var inputShortcuts: [KeyShortcut]
var inputShortcuts: [KeyShortcut] = []
if self.chatDisplayNode.isInputViewFocused {
inputShortcuts = [
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: {}),
KeyShortcut(input: "B", modifiers: [.command], action: { [weak self] in
if let strongSelf = self {
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
@ -80,6 +79,16 @@ extension ChatControllerImpl {
}
})
]
if self.context.sharedContext.currentChatSettings.with({ $0 }).sendWithCmdEnter {
inputShortcuts.append(
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", modifiers: [.command], action: {})
)
} else {
inputShortcuts.append(
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: {})
)
}
} else if UIResponder.currentFirst() == nil {
inputShortcuts = [
KeyShortcut(title: strings.KeyCommand_FocusOnInputField, input: "\r", action: { [weak self] in

View file

@ -182,7 +182,7 @@ func textInputAccessoryPanel(
let alertController = AlertScreen(
context: context,
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
configuration: AlertScreen.Configuration(actionAlignment: .vertical, allowInputInset: true),
content: content,
actions: [
.init(title: strings.Conversation_ForwardOptions_ShowOptions, action: {

View file

@ -1136,7 +1136,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
let sendGiftTitle: String
var isIncoming = message.effectivelyIncoming(context.account.peerId)
for media in message.media {
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action {
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action {
if isUpgrade && message.author?.id == context.account.peerId {
isIncoming = true
}

View file

@ -255,6 +255,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public let currentMediaDisplaySettings: Atomic<MediaDisplaySettings>
private var mediaDisplaySettingsDisposable: Disposable?
public let currentChatSettings: Atomic<ChatSettings>
private var chatSettingsDisposable: Disposable?
public let currentStickerSettings: Atomic<StickerSettings>
private var stickerSettingsDisposable: Disposable?
@ -339,6 +342,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.currentMediaDisplaySettings = Atomic(value: initialPresentationDataAndSettings.mediaDisplaySettings)
self.currentStickerSettings = Atomic(value: initialPresentationDataAndSettings.stickerSettings)
self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings)
self.currentChatSettings = Atomic(value: initialPresentationDataAndSettings.chatSettings)
if automaticEnergyUsageShouldBeOnNow(settings: self.currentAutomaticMediaDownloadSettings) {
self.energyUsageSettings = EnergyUsageSettings.powerSavingDefault
@ -485,6 +489,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
})
self.chatSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.chatSettings])
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
if let strongSelf = self {
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.chatSettings]?.get(ChatSettings.self) {
let _ = strongSelf.currentChatSettings.swap(settings)
}
}
})
let immediateExperimentalUISettingsValue = self.immediateExperimentalUISettingsValue
let _ = immediateExperimentalUISettingsValue.swap(initialPresentationDataAndSettings.experimentalUISettings)
@ -1078,6 +1091,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.inAppNotificationSettingsDisposable?.dispose()
self.mediaInputSettingsDisposable?.dispose()
self.mediaDisplaySettingsDisposable?.dispose()
self.chatSettingsDisposable?.dispose()
self.stickerSettingsDisposable?.dispose()
self.callDisposable?.dispose()
self.groupCallDisposable?.dispose()
self.callStateDisposable?.dispose()
@ -3852,8 +3867,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return GiftViewScreen(context: context, subject: .wearPreview(gift, attributes))
}
public func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController {
return GiftViewScreen(context: context, subject: .upgradePreview(attributes, peerName))
public func makeGiftUpgradePreviewScreen(context: AccountContext, gift: StarGift.Gift, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController {
return GiftViewScreen(context: context, subject: .upgradePreview(gift, attributes, peerName))
}
public func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController {
@ -3876,16 +3891,16 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return giftOfferAlertController(context: context, updatedPresentationData: updatedPresentationData, gift: gift, peer: peer, amount: amount, commit: commit)
}
public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, onlyCrafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController {
return GiftUpgradeVariantsScreen(context: context, gift: gift, onlyCrafted: onlyCrafted, attributes: attributes, selectedAttributes: selectedAttributes, focusedAttribute: focusedAttribute)
public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, crafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController {
return GiftUpgradeVariantsScreen(context: context, gift: gift, crafted: crafted, attributes: attributes, selectedAttributes: selectedAttributes, focusedAttribute: focusedAttribute)
}
public func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController {
return GiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, attributes: attributes, completion: completion)
}
public func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController {
return GiftCraftScreen(context: context, gift: gift)
public func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift, profileGiftsContext: ProfileGiftsContext?) -> ViewController {
return GiftCraftScreen(context: context, gift: gift, profileGiftsContext: profileGiftsContext)
}
public func makeGiftDemoScreen(context: AccountContext) -> ViewController {

View file

@ -0,0 +1,49 @@
import Foundation
import TelegramCore
import SwiftSignalKit
public struct ChatSettings: Codable, Equatable {
public let sendWithCmdEnter: Bool
public static var defaultSettings: ChatSettings {
return ChatSettings(sendWithCmdEnter: false)
}
public init(sendWithCmdEnter: Bool) {
self.sendWithCmdEnter = sendWithCmdEnter
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.sendWithCmdEnter = (try container.decode(Int32.self, forKey: "sendWithCmdEnter")) != 0
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode((self.sendWithCmdEnter ? 1 : 0) as Int32, forKey: "sendWithCmdEnter")
}
public static func ==(lhs: ChatSettings, rhs: ChatSettings) -> Bool {
return lhs.sendWithCmdEnter == rhs.sendWithCmdEnter
}
public func withUpdatedSendWithCmdEnter(_ sendWithCmdEnter: Bool) -> ChatSettings {
return ChatSettings(sendWithCmdEnter: sendWithCmdEnter)
}
}
public func updateChatSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (ChatSettings) -> ChatSettings) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.chatSettings, { entry in
let currentSettings: ChatSettings
if let entry = entry?.get(ChatSettings.self) {
currentSettings = entry
} else {
currentSettings = ChatSettings.defaultSettings
}
return SharedPreferencesEntry(f(currentSettings))
})
}
}

View file

@ -43,6 +43,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
case drawingSettings = 19
case mediaDisplaySettings = 20
case updateSettings = 21
case chatSettings = 22
}
public struct ApplicationSpecificSharedDataKeys {
@ -68,6 +69,7 @@ public struct ApplicationSpecificSharedDataKeys {
public static let drawingSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue)
public static let mediaDisplaySettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue)
public static let updateSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.updateSettings.rawValue)
public static let chatSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.chatSettings.rawValue)
}
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {