diff --git a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift index 4fb0c2ff4c..26b078703c 100644 --- a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift @@ -802,7 +802,7 @@ class StatsOverviewItemNode: ListViewItemNode { item.context, params.width, item.presentationData, - presentationStringsFormattedNumber(additionalStats.balances.availableBalance, item.presentationData.dateTimeFormat.groupingSeparator), + formatStarsAmountText(additionalStats.balances.availableBalance, dateTimeFormat: item.presentationData.dateTimeFormat), " ", (additionalStats.balances.availableBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(additionalStats.balances.availableBalance.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic), .stars @@ -812,7 +812,7 @@ class StatsOverviewItemNode: ListViewItemNode { item.context, params.width, item.presentationData, - presentationStringsFormattedNumber(additionalStats.balances.currentBalance, item.presentationData.dateTimeFormat.groupingSeparator), + formatStarsAmountText(additionalStats.balances.currentBalance, dateTimeFormat: item.presentationData.dateTimeFormat), " ", (additionalStats.balances.currentBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(additionalStats.balances.currentBalance.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic), .stars @@ -822,7 +822,7 @@ class StatsOverviewItemNode: ListViewItemNode { item.context, params.width, item.presentationData, - presentationStringsFormattedNumber(additionalStats.balances.overallRevenue, item.presentationData.dateTimeFormat.groupingSeparator), + formatStarsAmountText(additionalStats.balances.overallRevenue, dateTimeFormat: item.presentationData.dateTimeFormat), " ", (additionalStats.balances.overallRevenue == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(additionalStats.balances.overallRevenue.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic), .stars @@ -871,7 +871,7 @@ class StatsOverviewItemNode: ListViewItemNode { item.context, params.width, item.presentationData, - presentationStringsFormattedNumber(stats.balances.availableBalance, item.presentationData.dateTimeFormat.groupingSeparator), + formatStarsAmountText(stats.balances.availableBalance, dateTimeFormat: item.presentationData.dateTimeFormat), item.presentationData.strings.Monetization_StarsProceeds_Available, (stats.balances.availableBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(stats.balances.availableBalance.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic), .stars @@ -881,7 +881,7 @@ class StatsOverviewItemNode: ListViewItemNode { item.context, params.width, item.presentationData, - presentationStringsFormattedNumber(stats.balances.currentBalance, item.presentationData.dateTimeFormat.groupingSeparator), + formatStarsAmountText(stats.balances.currentBalance, dateTimeFormat: item.presentationData.dateTimeFormat), item.presentationData.strings.Monetization_StarsProceeds_Current, (stats.balances.currentBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(stats.balances.currentBalance.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic), .stars @@ -891,7 +891,7 @@ class StatsOverviewItemNode: ListViewItemNode { item.context, params.width, item.presentationData, - presentationStringsFormattedNumber(stats.balances.overallRevenue, item.presentationData.dateTimeFormat.groupingSeparator), + formatStarsAmountText(stats.balances.overallRevenue, dateTimeFormat: item.presentationData.dateTimeFormat), item.presentationData.strings.Monetization_StarsProceeds_Total, (stats.balances.overallRevenue == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(stats.balances.overallRevenue.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic), .stars diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD index 8181c61dd3..cc832add08 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/TelegramUI/Components/MultiAnimationRenderer", "//submodules/TelegramUI/Components/AnimationCache", "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/HierarchyTrackingLayer", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/Utils/LokiRng", "//submodules/TextFormat", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift index 267befaacd..5e79b94f63 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift @@ -11,53 +11,7 @@ import SwiftSignalKit import EmojiTextAttachmentView import LokiRng import TextFormat - -private final class PatternContentsTarget: MultiAnimationRenderTarget { - private let imageUpdated: (Bool) -> Void - - init(imageUpdated: @escaping (Bool) -> Void) { - self.imageUpdated = imageUpdated - - super.init() - } - - required init(coder: NSCoder) { - preconditionFailure() - } - - override func transitionToContents(_ contents: AnyObject, didLoop: Bool) { - let hadContents = self.contents != nil - self.contents = contents - self.imageUpdated(hadContents) - } -} - -private func windowFunction(t: CGFloat) -> CGFloat { - return bezierPoint(0.6, 0.0, 0.4, 1.0, t) -} - -private func patternScaleValueAt(fraction: CGFloat, t: CGFloat, reverse: Bool) -> CGFloat { - let windowSize: CGFloat = 0.8 - - let effectiveT: CGFloat - let windowStartOffset: CGFloat - let windowEndOffset: CGFloat - if reverse { - effectiveT = 1.0 - t - windowStartOffset = 1.0 - windowEndOffset = -windowSize - } else { - effectiveT = t - windowStartOffset = -windowSize - windowEndOffset = 1.0 - } - - let windowPosition = (1.0 - fraction) * windowStartOffset + fraction * windowEndOffset - let windowT = max(0.0, min(windowSize, effectiveT - windowPosition)) / windowSize - let localT = 1.0 - windowFunction(t: windowT) - - return localT -} +import HierarchyTrackingLayer public final class PeerInfoGiftsCoverComponent: Component { public let context: AccountContext @@ -65,11 +19,13 @@ public final class PeerInfoGiftsCoverComponent: Component { public let giftsContext: ProfileGiftsContext public let hasBackground: Bool public let avatarCenter: CGPoint - public let avatarScale: CGFloat - public let defaultHeight: CGFloat public let avatarTransitionFraction: CGFloat - public let patternTransitionFraction: CGFloat + public let statusBarHeight: CGFloat + public let topLeftButtonsSize: CGSize + public let topRightButtonsSize: CGSize + public let titleWidth: CGFloat public let hasButtons: Bool + public let action: (ProfileGiftsContext.State.StarGift) -> Void public init( context: AccountContext, @@ -77,22 +33,26 @@ public final class PeerInfoGiftsCoverComponent: Component { giftsContext: ProfileGiftsContext, hasBackground: Bool, avatarCenter: CGPoint, - avatarScale: CGFloat, - defaultHeight: CGFloat, avatarTransitionFraction: CGFloat, - patternTransitionFraction: CGFloat, - hasButtons: Bool + statusBarHeight: CGFloat, + topLeftButtonsSize: CGSize, + topRightButtonsSize: CGSize, + titleWidth: CGFloat, + hasButtons: Bool, + action: @escaping (ProfileGiftsContext.State.StarGift) -> Void ) { self.context = context self.peerId = peerId self.giftsContext = giftsContext self.hasBackground = hasBackground self.avatarCenter = avatarCenter - self.avatarScale = avatarScale - self.defaultHeight = defaultHeight self.avatarTransitionFraction = avatarTransitionFraction - self.patternTransitionFraction = patternTransitionFraction + self.statusBarHeight = statusBarHeight + self.topLeftButtonsSize = topLeftButtonsSize + self.topRightButtonsSize = topRightButtonsSize + self.titleWidth = titleWidth self.hasButtons = hasButtons + self.action = action } public static func ==(lhs: PeerInfoGiftsCoverComponent, rhs: PeerInfoGiftsCoverComponent) -> Bool { @@ -108,16 +68,19 @@ public final class PeerInfoGiftsCoverComponent: Component { if lhs.avatarCenter != rhs.avatarCenter { return false } - if lhs.avatarScale != rhs.avatarScale { - return false - } - if lhs.defaultHeight != rhs.defaultHeight { - return false - } if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction { return false } - if lhs.patternTransitionFraction != rhs.patternTransitionFraction { + if lhs.statusBarHeight != rhs.statusBarHeight { + return false + } + if lhs.topLeftButtonsSize != rhs.topLeftButtonsSize { + return false + } + if lhs.topRightButtonsSize != rhs.topRightButtonsSize { + return false + } + if lhs.titleWidth != rhs.titleWidth { return false } if lhs.hasButtons != rhs.hasButtons { @@ -127,11 +90,6 @@ public final class PeerInfoGiftsCoverComponent: Component { } public final class View: UIView { - private let avatarBackgroundPatternContentsLayer: SimpleGradientLayer - private let avatarBackgroundPatternMaskLayer: SimpleLayer - private let avatarBackgroundGradientLayer: SimpleGradientLayer - private let backgroundPatternContainer: UIView - private var currentSize: CGSize? private var component: PeerInfoGiftsCoverComponent? private var state: EmptyComponentState? @@ -141,33 +99,37 @@ public final class PeerInfoGiftsCoverComponent: Component { private var appliedGiftIds: [Int64] = [] private var iconLayers: [AnyHashable: GiftIconLayer] = [:] - private var iconPositions: [PositionGenerator.Position] = [] + private let seed = UInt(Date().timeIntervalSince1970) + + private let trackingLayer = HierarchyTrackingLayer() + private var isCurrentlyInHierarchy = false + + private var isUpdating = false override public init(frame: CGRect) { - self.avatarBackgroundGradientLayer = SimpleGradientLayer() - self.avatarBackgroundGradientLayer.opacity = 0.0 - - self.avatarBackgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5) - self.avatarBackgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) - self.avatarBackgroundGradientLayer.type = .radial - - self.avatarBackgroundPatternContentsLayer = SimpleGradientLayer() - self.avatarBackgroundPatternContentsLayer.startPoint = CGPoint(x: 0.5, y: 0.5) - self.avatarBackgroundPatternContentsLayer.endPoint = CGPoint(x: 1.0, y: 1.0) - self.avatarBackgroundPatternContentsLayer.type = .radial - - self.avatarBackgroundPatternMaskLayer = SimpleLayer() - self.backgroundPatternContainer = UIView() - super.init(frame: frame) self.clipsToBounds = true - - self.avatarBackgroundPatternContentsLayer.mask = self.avatarBackgroundPatternMaskLayer - self.layer.addSublayer(self.avatarBackgroundPatternContentsLayer) - self.addSubview(self.backgroundPatternContainer) + self.layer.addSublayer(self.trackingLayer) + + self.trackingLayer.didEnterHierarchy = { [weak self] in + guard let self else { + return + } + self.isCurrentlyInHierarchy = true + self.updateAnimations() + } + + self.trackingLayer.didExitHierarchy = { [weak self] in + guard let self else { + return + } + self.isCurrentlyInHierarchy = false + } + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapped(_:)))) } required public init?(coder aDecoder: NSCoder) { @@ -178,7 +140,38 @@ public final class PeerInfoGiftsCoverComponent: Component { self.giftsDisposable?.dispose() } - private var isUpdating = false + @objc private func tapped(_ gestureRecognizer: UITapGestureRecognizer) { + guard let component = self.component else { + return + } + let location = gestureRecognizer.location(in: self) + for (_, iconLayer) in self.iconLayers { + if iconLayer.frame.contains(location) { + component.action(iconLayer.gift) + break + } + } + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + for (_, iconLayer) in self.iconLayers { + if iconLayer.frame.contains(point) { + return true + } + } + return false + } + + func updateAnimations() { + var index = 0 + for (_, iconLayer) in self.iconLayers { + if self.isCurrentlyInHierarchy { + iconLayer.startAnimations(index: index) + } + index += 1 + } + } + func update(component: PeerInfoGiftsCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -202,26 +195,30 @@ public final class PeerInfoGiftsCoverComponent: Component { } } - if previousCurrentSize?.width != availableSize.width || (previousComponent != nil && previousComponent?.hasBackground != component.hasBackground) || self.appliedGiftIds != giftIds { + if !giftIds.isEmpty && (self.iconPositions.isEmpty || previousCurrentSize?.width != availableSize.width || (previousComponent != nil && previousComponent?.hasBackground != component.hasBackground) || self.appliedGiftIds != giftIds) { var excludeRects: [CGRect] = [] - excludeRects.append(CGRect(origin: .zero, size: CGSize(width: 50.0, height: 90.0))) - excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - 105.0, y: 0.0), size: CGSize(width: 105.0, height: 90.0))) - excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - 390.0) / 2.0), y: 0.0), size: CGSize(width: 390.0, height: 50.0))) - excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - 280.0) / 2.0), y: component.avatarCenter.y + 56.0), size: CGSize(width: 280.0, height: 65.0))) + if component.statusBarHeight > 0.0 { + excludeRects.append(CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: component.statusBarHeight + 4.0))) + } + excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.statusBarHeight), size: component.topLeftButtonsSize)) + excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - component.topRightButtonsSize.width, y: component.statusBarHeight), size: component.topRightButtonsSize)) + excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: component.avatarCenter.y + 56.0), size: CGSize(width: component.titleWidth, height: 72.0))) if component.hasButtons { excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - 81.0), size: CGSize(width: availableSize.width, height: 81.0))) } - + let positionGenerator = PositionGenerator( containerSize: availableSize, - avatarFrame: CGSize(width: 100, height: 100).centered(around: component.avatarCenter), - minDistance: 75.0, - maxDistance: availableSize.width / 2.0, - padding: 12.0, - seed: UInt(Date().timeIntervalSince1970), - excludeRects: excludeRects + centerFrame: CGSize(width: 100, height: 100).centered(around: component.avatarCenter), + exclusionZones: excludeRects, + minimumDistance: 42.0, + edgePadding: 5.0, + seed: self.seed ) - self.iconPositions = positionGenerator.generatePositions(count: 9, viewSize: iconSize) + + let start = CACurrentMediaTime() + self.iconPositions = positionGenerator.generatePositions(count: 12, itemSize: iconSize) + print("generated icon positions in \( CACurrentMediaTime() - start )s") } self.appliedGiftIds = giftIds @@ -257,22 +254,13 @@ public final class PeerInfoGiftsCoverComponent: Component { } }) } - - let avatarPatternFrame = CGSize(width: 380.0, height: floor(component.defaultHeight * 1.0)).centered(around: component.avatarCenter) - transition.setFrame(layer: self.avatarBackgroundPatternContentsLayer, frame: avatarPatternFrame) - - self.avatarBackgroundPatternContentsLayer.colors = [ - UIColor.red.withAlphaComponent(0.6).cgColor, - UIColor.red.withAlphaComponent(0.0).cgColor - ] - - let backgroundPatternContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height), size: CGSize(width: availableSize.width, height: 0.0)) - transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame) - transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction) - + var validIds = Set() var index = 0 - for gift in self.gifts.prefix(9) { + for gift in self.gifts.prefix(12) { + guard index < self.iconPositions.count else { + break + } let id: AnyHashable if case let .unique(uniqueGift) = gift.gift { id = uniqueGift.slug @@ -289,12 +277,13 @@ public final class PeerInfoGiftsCoverComponent: Component { } else { iconTransition = .immediate iconLayer = GiftIconLayer(context: component.context, gift: gift, size: iconSize, glowing: component.hasBackground) - iconLayer.startHovering() self.iconLayers[id] = iconLayer self.layer.addSublayer(iconLayer) iconLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) iconLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2) + + iconLayer.startAnimations(index: index) } iconLayer.glowing = component.hasBackground @@ -350,202 +339,6 @@ public final class PeerInfoGiftsCoverComponent: Component { } } - -private class PositionGenerator { - private let containerSize: CGSize - private let avatarFrame: CGRect - private let padding: CGFloat - private let minDistance: CGFloat - private let maxDistance: CGFloat - private let rng: LokiRng - - private let excludeRects: [CGRect] - - struct Position { - let center: CGPoint - let scale: CGFloat - } - - init( - containerSize: CGSize, - avatarFrame: CGRect, - minDistance: CGFloat, - maxDistance: CGFloat, - padding: CGFloat, - seed: UInt, - excludeRects: [CGRect] = [] - ) { - self.containerSize = containerSize - self.avatarFrame = avatarFrame - self.minDistance = minDistance - self.maxDistance = maxDistance - self.padding = padding - self.rng = LokiRng(seed0: seed, seed1: 0, seed2: 0) - self.excludeRects = excludeRects - } - - func generatePositions(count: Int, viewSize: CGSize) -> [Position] { - let safeCount = min(max(count, 1), 12) // Ensure between 1 and 12 - var positions: [Position] = [] - - let distanceRanges = calculateDistanceRanges(count: safeCount) - - for i in 0.. minRequiredDistance - } - if isFarEnough { - result = position - break - } - } - - if attempts % 5 == 0 && result == nil { - currentMaxDist *= 1.2 - } - } - - if result == nil { - if let lastChancePosition = self.generateSinglePosition( - viewSize: viewSize, - minDist: minDist, - maxDist: maxDist * 2.0, - rightSide: !isEven - ) { - result = lastChancePosition - } else { - let defaultX = self.avatarFrame.center.x + (isEven ? -1 : 1) * (minDist + CGFloat(i * 20)) - let defaultY = self.avatarFrame.center.y + CGFloat(i * 15) - let defaultPosition = CGPoint(x: defaultX, y: defaultY) - - result = defaultPosition - } - } - - if let result { - let distance = hypot(result.x - self.avatarFrame.center.x, result.y - self.avatarFrame.center.y) - let baseScale = min(1.0, max(0.77, 1.0 - (distance - 75.0) / 75.0)) - - let randomFactor = 0.14 + (1.0 - baseScale) * 0.2 - let randomValue = -randomFactor + CGFloat(self.rng.next()) * 2.0 * randomFactor - - let finalScale = min(1.2, max(baseScale * 0.65, baseScale + randomValue)) - positions.append(Position(center: result, scale: finalScale)) - } - } - - return positions.map { - Position(center: $0.center.offsetBy(dx: -self.avatarFrame.center.x, dy: -self.avatarFrame.center.y), scale: $0.scale) - } - } - - private func calculateDistanceRanges(count: Int) -> [(CGFloat, CGFloat)] { - var ranges: [(CGFloat, CGFloat)] = [] - - let totalRange = self.maxDistance - self.minDistance - for _ in 0..<4 { - let min = self.minDistance - let max = self.minDistance + (totalRange * 0.12) - ranges.append((min, max)) - } - - for _ in 0..<4 { - let min = self.minDistance + (totalRange * 0.19) - let max = self.minDistance + (totalRange * 0.55) - ranges.append((min, max)) - } - - for _ in 0..<4 { - let min = self.minDistance + (totalRange * 0.6) - let max = self.minDistance + (totalRange * 0.9) - ranges.append((min, max)) - } - - return ranges - } - - private func generateSinglePosition(viewSize: CGSize, minDist: CGFloat, maxDist: CGFloat, rightSide: Bool) -> CGPoint? { - let avatarCenter = avatarFrame.center - - for _ in 0..<50 { - let baseAngle: CGFloat - let angleSpread: CGFloat - - if rightSide { - baseAngle = 0 - angleSpread = .pi / 2 - } else { - baseAngle = .pi - angleSpread = .pi / 2 - } - - let angleOffset = (CGFloat(rng.next()) * 2.0 - 1.0) * angleSpread - let angle = baseAngle + angleOffset - - let distance = minDist + CGFloat(rng.next()) * (maxDist - minDist) - - let x = avatarCenter.x + cos(angle) * distance - let y = avatarCenter.y + sin(angle) * distance - - let position = CGPoint(x: x, y: y) - - let viewFrame = CGRect( - x: position.x - viewSize.width / 2, - y: position.y - viewSize.height / 2, - width: viewSize.width, - height: viewSize.height - ) - - if isFrameWithinBounds(viewFrame) && !isFrameInExclusionZone(viewFrame) { - return CGPoint(x: round(position.x), y: round(position.y)) - } - } - - return nil - } - - private func isFrameWithinBounds(_ frame: CGRect) -> Bool { - return frame.minX >= self.padding && - frame.minY >= self.padding && - frame.maxX <= self.containerSize.width - self.padding && - frame.maxY <= self.containerSize.height - self.padding - } - - private func isFrameInExclusionZone(_ frame: CGRect) -> Bool { - if frame.intersects(avatarFrame) { - return true - } - let padding: CGFloat = -8.0 - for excludeRect in self.excludeRects { - if frame.intersects(excludeRect.insetBy(dx: padding, dy: padding)) { - return true - } - } - return false - } -} - private var shadowImage: UIImage? = { return generateImage(CGSize(width: 44.0, height: 44.0), rotatedContext: { size, context in context.clear(CGRect(origin: .zero, size: size)) @@ -616,10 +409,9 @@ private final class StarsEffectLayer: SimpleLayer { } } - private class GiftIconLayer: SimpleLayer { private let context: AccountContext - private let gift: ProfileGiftsContext.State.StarGift + let gift: ProfileGiftsContext.State.StarGift private let size: CGSize var glowing: Bool { didSet { @@ -780,26 +572,273 @@ private class GiftIconLayer: SimpleLayer { self.animationLayer.frame = CGRect(origin: .zero, size: self.bounds.size) } - func startHovering(distance: CGFloat = 3.0, duration: TimeInterval = 4.0, timingFunction: CAMediaTimingFunction = CAMediaTimingFunction(name: .easeInEaseOut)) { - let hoverAnimation = CABasicAnimation(keyPath: "transform.translation.y") - hoverAnimation.duration = duration - hoverAnimation.fromValue = -distance - hoverAnimation.toValue = distance - hoverAnimation.autoreverses = true - hoverAnimation.repeatCount = .infinity - hoverAnimation.timingFunction = timingFunction - hoverAnimation.beginTime = Double.random(in: 0.0 ..< 12.0) - hoverAnimation.isAdditive = true - self.add(hoverAnimation, forKey: "hover") + func startAnimations(index: Int) { + let beginTime = Double(index) * 1.5 - let glowAnimation = CABasicAnimation(keyPath: "transform.scale") - glowAnimation.duration = duration - glowAnimation.fromValue = 1.0 - glowAnimation.toValue = 1.2 - glowAnimation.autoreverses = true - glowAnimation.repeatCount = .infinity - glowAnimation.timingFunction = timingFunction - glowAnimation.beginTime = Double.random(in: 0.0 ..< 12.0) - self.shadowLayer.add(glowAnimation, forKey: "glow") + if self.animation(forKey: "hover") == nil { + let upDistance = CGFloat.random(in: 1.0 ..< 2.0) + let downDistance = CGFloat.random(in: 1.0 ..< 2.0) + let hoverDuration = TimeInterval.random(in: 3.5 ..< 4.5) + + let hoverAnimation = CABasicAnimation(keyPath: "transform.translation.y") + hoverAnimation.duration = duration + hoverAnimation.fromValue = -upDistance + hoverAnimation.toValue = downDistance + hoverAnimation.autoreverses = true + hoverAnimation.repeatCount = .infinity + hoverAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + hoverAnimation.beginTime = beginTime + hoverAnimation.isAdditive = true + self.add(hoverAnimation, forKey: "hover") + } + + if self.animationLayer.animation(forKey: "wiggle") == nil { + let fromRotationAngle = CGFloat.random(in: 0.025 ..< 0.05) + let toRotationAngle = CGFloat.random(in: 0.025 ..< 0.05) + let wiggleDuration = TimeInterval.random(in: 2.0 ..< 3.0) + + let wiggleAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + wiggleAnimation.duration = wiggleDuration + wiggleAnimation.fromValue = -fromRotationAngle + wiggleAnimation.toValue = toRotationAngle + wiggleAnimation.autoreverses = true + wiggleAnimation.repeatCount = .infinity + wiggleAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + wiggleAnimation.beginTime = beginTime + wiggleAnimation.isAdditive = true + self.animationLayer.add(wiggleAnimation, forKey: "wiggle") + } + + if self.shadowLayer.animation(forKey: "glow") == nil { + let glowDuration = TimeInterval.random(in: 2.0 ..< 3.0) + + let glowAnimation = CABasicAnimation(keyPath: "transform.scale") + glowAnimation.duration = glowDuration + glowAnimation.fromValue = 1.0 + glowAnimation.toValue = 1.2 + glowAnimation.autoreverses = true + glowAnimation.repeatCount = .infinity + glowAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + glowAnimation.beginTime = beginTime + self.shadowLayer.add(glowAnimation, forKey: "glow") + } } } + +private struct PositionGenerator { + struct Position { + let center: CGPoint + let scale: CGFloat + } + + let containerSize: CGSize + let centerFrame: CGRect + let exclusionZones: [CGRect] + let minimumDistance: CGFloat + let edgePadding: CGFloat + let scaleRange: (min: CGFloat, max: CGFloat) + + let innerOrbitRange: (min: CGFloat, max: CGFloat) + let outerOrbitRange: (min: CGFloat, max: CGFloat) + let innerOrbitCount: Int + + private let lokiRng: LokiRng + + init( + containerSize: CGSize, + centerFrame: CGRect, + exclusionZones: [CGRect], + minimumDistance: CGFloat, + edgePadding: CGFloat, + seed: UInt, + scaleRange: (min: CGFloat, max: CGFloat) = (0.7, 1.15), + innerOrbitRange: (min: CGFloat, max: CGFloat) = (1.4, 2.2), + outerOrbitRange: (min: CGFloat, max: CGFloat) = (2.5, 3.6), + innerOrbitCount: Int = 4 + ) { + self.containerSize = containerSize + self.centerFrame = centerFrame + self.exclusionZones = exclusionZones + self.minimumDistance = minimumDistance + self.edgePadding = edgePadding + self.scaleRange = scaleRange + self.innerOrbitRange = innerOrbitRange + self.outerOrbitRange = outerOrbitRange + self.innerOrbitCount = innerOrbitCount + self.lokiRng = LokiRng(seed0: seed, seed1: 0, seed2: 0) + } + + func generatePositions(count: Int, itemSize: CGSize) -> [Position] { + var positions: [Position] = [] + + let centerPoint = CGPoint(x: self.centerFrame.midX, y: self.centerFrame.midY) + let centerRadius = min(self.centerFrame.width, self.centerFrame.height) / 2.0 + + let maxAttempts = count * 200 + var attempts = 0 + + var leftPositions = 0 + var rightPositions = 0 + + let innerCount = min(self.innerOrbitCount, count) + + while positions.count < innerCount && attempts < maxAttempts { + attempts += 1 + + let placeOnLeftSide = rightPositions > leftPositions + + let orbitRangeSize = self.innerOrbitRange.max - self.innerOrbitRange.min + let orbitDistanceFactor = self.innerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next()) + let orbitDistance = orbitDistanceFactor * centerRadius + + let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi + let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2) + let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next()) + + let absoluteX = centerPoint.x + orbitDistance * cos(angle) + let absoluteY = centerPoint.y + orbitDistance * sin(angle) + let absolutePosition = CGPoint(x: absoluteX, y: absoluteY) + + if absolutePosition.x - itemSize.width/2 < self.edgePadding || + absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding || + absolutePosition.y - itemSize.height/2 < self.edgePadding || + absolutePosition.y + itemSize.height/2 > self.containerSize.height - self.edgePadding { + continue + } + + let relativePosition = CGPoint( + x: absolutePosition.x - centerPoint.x, + y: absolutePosition.y - centerPoint.y + ) + + let itemRect = CGRect( + x: absolutePosition.x - itemSize.width/2, + y: absolutePosition.y - itemSize.height/2, + width: itemSize.width, + height: itemSize.height + ) + + if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) { + let scaleRangeSize = max(self.scaleRange.min + 0.1, 0.75) - self.scaleRange.max + let scale = self.scaleRange.max + scaleRangeSize * CGFloat(self.lokiRng.next()) + positions.append(Position(center: relativePosition, scale: scale)) + + if absolutePosition.x < centerPoint.x { + leftPositions += 1 + } else { + rightPositions += 1 + } + } + } + + let maxPossibleDistance = hypot(self.containerSize.width, self.containerSize.height) / 2 + + while positions.count < count && attempts < maxAttempts { + attempts += 1 + + let placeOnLeftSide = rightPositions >= leftPositions + + let orbitRangeSize = self.outerOrbitRange.max - self.outerOrbitRange.min + let orbitDistanceFactor = self.outerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next()) + let orbitDistance = orbitDistanceFactor * centerRadius + + let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi + let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2) + let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next()) + + let absoluteX = centerPoint.x + orbitDistance * cos(angle) + let absoluteY = centerPoint.y + orbitDistance * sin(angle) + let absolutePosition = CGPoint(x: absoluteX, y: absoluteY) + + if absolutePosition.x - itemSize.width/2 < self.edgePadding || + absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding || + absolutePosition.y - itemSize.height/2 < self.edgePadding || + absolutePosition.y + itemSize.height/2 > self.containerSize.height - self.edgePadding { + continue + } + + let relativePosition = CGPoint( + x: absolutePosition.x - centerPoint.x, + y: absolutePosition.y - centerPoint.y + ) + + let itemRect = CGRect( + x: absolutePosition.x - itemSize.width/2, + y: absolutePosition.y - itemSize.height/2, + width: itemSize.width, + height: itemSize.height + ) + + if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) { + let distance = hypot(absolutePosition.x - centerPoint.x, absolutePosition.y - centerPoint.y) + + let normalizedDistance = min(distance / maxPossibleDistance, 1.0) + let scale = self.scaleRange.max - normalizedDistance * (self.scaleRange.max - self.scaleRange.min) + positions.append(Position(center: relativePosition, scale: scale)) + + if absolutePosition.x < centerPoint.x { + leftPositions += 1 + } else { + rightPositions += 1 + } + } + } + + return positions + } + + private func posToAbsolute(_ relativePos: CGPoint, centerPoint: CGPoint) -> CGPoint { + return CGPoint(x: relativePos.x + centerPoint.x, y: relativePos.y + centerPoint.y) + } + + private func isValidPosition(_ rect: CGRect, existingPositions: [CGPoint], itemSize: CGSize) -> Bool { + if rect.minX < self.edgePadding || rect.maxX > self.containerSize.width - self.edgePadding || + rect.minY < self.edgePadding || rect.maxY > self.containerSize.height - self.edgePadding { + return false + } + + for zone in self.exclusionZones { + if rect.intersects(zone) { + return false + } + } + + let effectiveMinDistance = existingPositions.count > 5 ? max(self.minimumDistance * 0.7, 10.0) : self.minimumDistance + + for existingPosition in existingPositions { + let distance = hypot(existingPosition.x - rect.midX, existingPosition.y - rect.midY) + if distance < effectiveMinDistance { + return false + } + } + + return true + } +} + +private func windowFunction(t: CGFloat) -> CGFloat { + return bezierPoint(0.6, 0.0, 0.4, 1.0, t) +} + +private func patternScaleValueAt(fraction: CGFloat, t: CGFloat, reverse: Bool) -> CGFloat { + let windowSize: CGFloat = 0.8 + + let effectiveT: CGFloat + let windowStartOffset: CGFloat + let windowEndOffset: CGFloat + if reverse { + effectiveT = 1.0 - t + windowStartOffset = 1.0 + windowEndOffset = -windowSize + } else { + effectiveT = t + windowStartOffset = -windowSize + windowEndOffset = 1.0 + } + + let windowPosition = (1.0 - fraction) * windowStartOffset + fraction * windowEndOffset + let windowT = max(0.0, min(windowSize, effectiveT - windowPosition)) / windowSize + let localT = 1.0 - windowFunction(t: windowT) + + return localT +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 0c1f1d805e..dad267cb29 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -154,6 +154,7 @@ swift_library( "//submodules/TelegramUI/Components/MediaEditorScreen", "//submodules/TelegramUI/Components/CameraScreen", "//submodules/TelegramUI/Components/PeerInfo/VerifyAlertController", + "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index a33fa58dab..a7ac9b2b51 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -924,13 +924,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { isVisibleForAnimations: true, useSharedAnimation: true, action: { [weak self] in - guard let strongSelf = self else { + guard let self else { return } - if let uniqueGiftSlug { - strongSelf.openUniqueGift?(strongSelf.titleStatusIconView, uniqueGiftSlug) + if let uniqueGiftSlug, !self.isSettings { + self.openUniqueGift?(self.titleStatusIconView, uniqueGiftSlug) } else { - strongSelf.displayPremiumIntro?(strongSelf.titleStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false) + self.displayPremiumIntro?(self.titleStatusIconView, currentEmojiStatus, self.emojiStatusFileAndPackTitle.get(), false) } }, emojiFileUpdated: { [weak self] emojiFile in @@ -985,13 +985,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { isVisibleForAnimations: true, useSharedAnimation: true, action: { [weak self] in - guard let strongSelf = self else { + guard let self else { return } - if let uniqueGiftSlug { - strongSelf.openUniqueGift?(strongSelf.titleExpandedStatusIconView, uniqueGiftSlug) + if let uniqueGiftSlug, !self.isSettings { + self.openUniqueGift?(self.titleExpandedStatusIconView, uniqueGiftSlug) } else { - strongSelf.displayPremiumIntro?(strongSelf.titleExpandedStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true) + self.displayPremiumIntro?(self.titleExpandedStatusIconView, currentEmojiStatus, self.emojiStatusFileAndPackTitle.get(), true) } } )), @@ -2306,7 +2306,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { subject: backgroundCoverSubject, files: [:], isDark: presentationData.theme.overallDarkAppearance, - avatarCenter: apparentAvatarFrame.center, + avatarCenter: apparentAvatarFrame.center.offsetBy(dx: bannerInset, dy: 0.0), avatarScale: avatarScale, defaultHeight: backgroundDefaultHeight, gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45), @@ -2321,9 +2321,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.backgroundBannerView.addSubview(backgroundCoverView) } if additive { - transition.updateFrameAdditive(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: -3.0, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize)) + transition.updateFrameAdditive(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: -bannerInset, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize)) } else { - transition.updateFrame(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: 0.0, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize)) + transition.updateFrame(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: -bannerInset, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize)) } if backgroundCoverAnimateIn { if !self.isAvatarExpanded { @@ -2351,24 +2351,32 @@ final class PeerInfoHeaderNode: ASDisplayNode { giftsContext: profileGiftsContext, hasBackground: hasBackground, avatarCenter: apparentAvatarFrame.center, - avatarScale: avatarScale, - defaultHeight: backgroundDefaultHeight, avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)), - patternTransitionFraction: buttonsTransitionFraction * backgroundTransitionFraction, - hasButtons: !buttonKeys.isEmpty + statusBarHeight: statusBarHeight, + topLeftButtonsSize: CGSize(width: (self.isSettings ? 57.0 : 47.0), height: 46.0), + topRightButtonsSize: CGSize(width: 76.0 + (self.isMyProfile ? 38.0 : 0.0), height: 46.0), + titleWidth: titleFrame.width + 42.0, + hasButtons: !buttonKeys.isEmpty, + action: { [weak self] gift in + guard let self, case let .unique(gift) = gift.gift else { + return + } + self.openUniqueGift?(self.view, gift.slug) + } )), environment: {}, - containerSize: CGSize(width: width + bannerInset * 2.0, height: apparentBackgroundHeight + bannerInset) + containerSize: CGSize(width: width, height: apparentBackgroundHeight) ) if let giftsCoverView = self.giftsCover.view as? PeerInfoGiftsCoverComponent.View { if giftsCoverView.superview == nil { - self.backgroundBannerView.addSubview(giftsCoverView) + self.view.insertSubview(giftsCoverView, aboveSubview: self.backgroundBannerView) } if additive { - transition.updateFrameAdditive(view: giftsCoverView, frame: CGRect(origin: CGPoint(x: -3.0, y: bannerFrame.height - giftsCoverSize.height - bannerInset), size: giftsCoverSize)) + transition.updateFrameAdditive(view: giftsCoverView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: giftsCoverSize)) } else { - transition.updateFrame(view: giftsCoverView, frame: CGRect(origin: CGPoint(x: 0.0, y: bannerFrame.height - giftsCoverSize.height - bannerInset), size: giftsCoverSize)) + transition.updateFrame(view: giftsCoverView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: giftsCoverSize)) } + navigationTransition.updateAlpha(layer: giftsCoverView.layer, alpha: backgroundBannerAlpha) } } @@ -2490,6 +2498,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { return result } + if let giftsCoverView = self.giftsCover.view, giftsCoverView.alpha > 0.0, giftsCoverView.point(inside: self.view.convert(point, to: giftsCoverView), with: event) { + return giftsCoverView + } + if result == self.view || result == self.regularContentNode.view || result == self.editingContentNode.view { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 39f2d35800..68b0aa655c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -111,6 +111,7 @@ import UIKitRuntimeUtils import OldChannelsController import UrlHandling import VerifyAlertController +import GiftViewScreen public enum PeerInfoAvatarEditingMode { case generic @@ -4641,10 +4642,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.emojiStatusSelectionController = emojiStatusSelectionController strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root)) } - - self.headerNode.openUniqueGift = { [weak self] sourceView, _ in - self?.headerNode.displayPremiumIntro?(sourceView, nil, .single(nil), false) - } } else { if peerId == context.account.peerId { self.privacySettings.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) @@ -4731,13 +4728,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro controller.present(tooltipController, in: .current) } - self.headerNode.openUniqueGift = { [weak self] _, slug in - guard let self else { - return - } - self.openUrl(url: "https://t.me/nft/\(slug)", concealed: false, external: false) - } - self.headerNode.displayStatusPremiumIntro = { [weak self] in guard let self else { return @@ -4834,6 +4824,64 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + self.headerNode.openUniqueGift = { [weak self] _, slug in + guard let self, let profileGifts = self.data?.profileGiftsContext else { + return + } + var found = false + if let state = profileGifts.currentState { + for gift in state.gifts { + if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug { + found = true + + let controller = GiftViewScreen( + context: self.context, + subject: .profileGift(self.peerId, gift), + updateSavedToProfile: { [weak profileGifts] reference, added in + guard let profileGifts else { + return + } + profileGifts.updateStarGiftAddedToProfile(reference: reference, added: added) + }, + convertToStars: { [weak profileGifts] in + guard let profileGifts, let reference = gift.reference else { + return + } + profileGifts.convertStarGift(reference: reference) + }, + transferGift: { [weak profileGifts] prepaid, peerId in + guard let profileGifts, let reference = gift.reference else { + return + } + profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId) + }, + upgradeGift: { [weak profileGifts] formId, keepOriginalInfo in + guard let profileGifts, let reference = gift.reference else { + return .never() + } + return profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo) + }, + shareStory: { [weak self] uniqueGift in + guard let self, let controller = self.controller else { + return + } + Queue.mainQueue().after(0.15) { + let shareController = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: controller) + controller.push(shareController) + } + } + ) + self.controller?.push(controller) + + break + } + } + } + if !found { + self.openUrl(url: "https://t.me/nft/\(slug)", concealed: false, external: false) + } + } + self.headerNode.avatarListNode.listContainerNode.currentIndexUpdated = { [weak self] in self?.updateNavigation(transition: .immediate, additive: true, animateHeader: true) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 30b66f1639..12ecd981dc 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -676,7 +676,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let buttonSideInset = sideInset + 16.0 let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) - var bottomPanelHeight = bottomInset + buttonSize.height + 8.0 + var bottomPanelHeight = max(8.0, bottomInset) + buttonSize.height + 8.0 if params.visibleHeight < 110.0 { scrollOffset -= bottomPanelHeight } @@ -902,7 +902,6 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr ) if let view = footerText.view { if view.superview == nil { - view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonPressed))) self.scrollNode.view.addSubview(view) } transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floor((size.width - footerTextSize.width) / 2.0), y: contentHeight), size: footerTextSize)) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsOverviewItemComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsOverviewItemComponent.swift index e8c00e3a20..34fea1b120 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsOverviewItemComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsOverviewItemComponent.swift @@ -81,7 +81,7 @@ final class StarsOverviewItemComponent: Component { valueOffset += icon.size.width } - let valueString = presentationStringsFormattedNumber(component.value, component.dateTimeFormat.groupingSeparator) + let valueString = formatStarsAmountText(component.value, dateTimeFormat: component.dateTimeFormat) let usdValueString = formatTonUsdValue(component.value.value, divide: false, rate: component.rate, dateTimeFormat: component.dateTimeFormat) let valueSize = self.value.update( diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 18fcdf4e72..21853370d7 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -2517,7 +2517,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.actionButtons.updateAbsoluteRect(CGRect(x: rect.origin.x + actionButtonsFrame.origin.x, y: rect.origin.y + actionButtonsFrame.origin.y, width: actionButtonsFrame.width, height: actionButtonsFrame.height), within: containerSize, transition: transition) } - let slowModeButtonFrame = CGRect(origin: CGPoint(x: hideOffset.x + width - rightInset - 5.0 - slowModeButtonSize.width + composeButtonsOffset, y: hideOffset.y + panelHeight - minimalHeight + 6.0), size: slowModeButtonSize) transition.updateFrame(node: self.slowModeButton, frame: slowModeButtonFrame) @@ -2767,12 +2766,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch mediaInputDisabled = false } - var mediaInputIsActive = false - if case .media = interfaceState.inputMode { - mediaInputIsActive = true - } - - self.actionButtons.micButton.fadeDisabled = mediaInputDisabled || mediaInputIsActive + self.actionButtons.micButton.fadeDisabled = mediaInputDisabled var viewOnceIsVisible = false if let recordingState = interfaceState.inputTextPanelState.mediaRecordingState {