mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
2adffc2ebc
commit
f541820b46
@ -802,7 +802,7 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||||||
item.context,
|
item.context,
|
||||||
params.width,
|
params.width,
|
||||||
item.presentationData,
|
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),
|
(additionalStats.balances.availableBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(additionalStats.balances.availableBalance.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
|
||||||
.stars
|
.stars
|
||||||
@ -812,7 +812,7 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||||||
item.context,
|
item.context,
|
||||||
params.width,
|
params.width,
|
||||||
item.presentationData,
|
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),
|
(additionalStats.balances.currentBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(additionalStats.balances.currentBalance.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
|
||||||
.stars
|
.stars
|
||||||
@ -822,7 +822,7 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||||||
item.context,
|
item.context,
|
||||||
params.width,
|
params.width,
|
||||||
item.presentationData,
|
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),
|
(additionalStats.balances.overallRevenue == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(additionalStats.balances.overallRevenue.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
|
||||||
.stars
|
.stars
|
||||||
@ -871,7 +871,7 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||||||
item.context,
|
item.context,
|
||||||
params.width,
|
params.width,
|
||||||
item.presentationData,
|
item.presentationData,
|
||||||
presentationStringsFormattedNumber(stats.balances.availableBalance, item.presentationData.dateTimeFormat.groupingSeparator),
|
formatStarsAmountText(stats.balances.availableBalance, dateTimeFormat: item.presentationData.dateTimeFormat),
|
||||||
item.presentationData.strings.Monetization_StarsProceeds_Available,
|
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),
|
(stats.balances.availableBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(stats.balances.availableBalance.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
|
||||||
.stars
|
.stars
|
||||||
@ -881,7 +881,7 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||||||
item.context,
|
item.context,
|
||||||
params.width,
|
params.width,
|
||||||
item.presentationData,
|
item.presentationData,
|
||||||
presentationStringsFormattedNumber(stats.balances.currentBalance, item.presentationData.dateTimeFormat.groupingSeparator),
|
formatStarsAmountText(stats.balances.currentBalance, dateTimeFormat: item.presentationData.dateTimeFormat),
|
||||||
item.presentationData.strings.Monetization_StarsProceeds_Current,
|
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),
|
(stats.balances.currentBalance == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(stats.balances.currentBalance.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
|
||||||
.stars
|
.stars
|
||||||
@ -891,7 +891,7 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||||||
item.context,
|
item.context,
|
||||||
params.width,
|
params.width,
|
||||||
item.presentationData,
|
item.presentationData,
|
||||||
presentationStringsFormattedNumber(stats.balances.overallRevenue, item.presentationData.dateTimeFormat.groupingSeparator),
|
formatStarsAmountText(stats.balances.overallRevenue, dateTimeFormat: item.presentationData.dateTimeFormat),
|
||||||
item.presentationData.strings.Monetization_StarsProceeds_Total,
|
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),
|
(stats.balances.overallRevenue == StarsAmount.zero ? "" : "≈\(formatTonUsdValue(stats.balances.overallRevenue.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
|
||||||
.stars
|
.stars
|
||||||
|
@ -20,6 +20,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||||
"//submodules/TelegramUI/Components/AnimationCache",
|
"//submodules/TelegramUI/Components/AnimationCache",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/Components/HierarchyTrackingLayer",
|
||||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||||
"//submodules/Utils/LokiRng",
|
"//submodules/Utils/LokiRng",
|
||||||
"//submodules/TextFormat",
|
"//submodules/TextFormat",
|
||||||
|
@ -11,53 +11,7 @@ import SwiftSignalKit
|
|||||||
import EmojiTextAttachmentView
|
import EmojiTextAttachmentView
|
||||||
import LokiRng
|
import LokiRng
|
||||||
import TextFormat
|
import TextFormat
|
||||||
|
import HierarchyTrackingLayer
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class PeerInfoGiftsCoverComponent: Component {
|
public final class PeerInfoGiftsCoverComponent: Component {
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
@ -65,11 +19,13 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
public let giftsContext: ProfileGiftsContext
|
public let giftsContext: ProfileGiftsContext
|
||||||
public let hasBackground: Bool
|
public let hasBackground: Bool
|
||||||
public let avatarCenter: CGPoint
|
public let avatarCenter: CGPoint
|
||||||
public let avatarScale: CGFloat
|
|
||||||
public let defaultHeight: CGFloat
|
|
||||||
public let avatarTransitionFraction: 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 hasButtons: Bool
|
||||||
|
public let action: (ProfileGiftsContext.State.StarGift) -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -77,22 +33,26 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
giftsContext: ProfileGiftsContext,
|
giftsContext: ProfileGiftsContext,
|
||||||
hasBackground: Bool,
|
hasBackground: Bool,
|
||||||
avatarCenter: CGPoint,
|
avatarCenter: CGPoint,
|
||||||
avatarScale: CGFloat,
|
|
||||||
defaultHeight: CGFloat,
|
|
||||||
avatarTransitionFraction: CGFloat,
|
avatarTransitionFraction: CGFloat,
|
||||||
patternTransitionFraction: CGFloat,
|
statusBarHeight: CGFloat,
|
||||||
hasButtons: Bool
|
topLeftButtonsSize: CGSize,
|
||||||
|
topRightButtonsSize: CGSize,
|
||||||
|
titleWidth: CGFloat,
|
||||||
|
hasButtons: Bool,
|
||||||
|
action: @escaping (ProfileGiftsContext.State.StarGift) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.giftsContext = giftsContext
|
self.giftsContext = giftsContext
|
||||||
self.hasBackground = hasBackground
|
self.hasBackground = hasBackground
|
||||||
self.avatarCenter = avatarCenter
|
self.avatarCenter = avatarCenter
|
||||||
self.avatarScale = avatarScale
|
|
||||||
self.defaultHeight = defaultHeight
|
|
||||||
self.avatarTransitionFraction = avatarTransitionFraction
|
self.avatarTransitionFraction = avatarTransitionFraction
|
||||||
self.patternTransitionFraction = patternTransitionFraction
|
self.statusBarHeight = statusBarHeight
|
||||||
|
self.topLeftButtonsSize = topLeftButtonsSize
|
||||||
|
self.topRightButtonsSize = topRightButtonsSize
|
||||||
|
self.titleWidth = titleWidth
|
||||||
self.hasButtons = hasButtons
|
self.hasButtons = hasButtons
|
||||||
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PeerInfoGiftsCoverComponent, rhs: PeerInfoGiftsCoverComponent) -> Bool {
|
public static func ==(lhs: PeerInfoGiftsCoverComponent, rhs: PeerInfoGiftsCoverComponent) -> Bool {
|
||||||
@ -108,16 +68,19 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
if lhs.avatarCenter != rhs.avatarCenter {
|
if lhs.avatarCenter != rhs.avatarCenter {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.avatarScale != rhs.avatarScale {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.defaultHeight != rhs.defaultHeight {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction {
|
if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction {
|
||||||
return false
|
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
|
return false
|
||||||
}
|
}
|
||||||
if lhs.hasButtons != rhs.hasButtons {
|
if lhs.hasButtons != rhs.hasButtons {
|
||||||
@ -127,11 +90,6 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIView {
|
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 currentSize: CGSize?
|
||||||
private var component: PeerInfoGiftsCoverComponent?
|
private var component: PeerInfoGiftsCoverComponent?
|
||||||
private var state: EmptyComponentState?
|
private var state: EmptyComponentState?
|
||||||
@ -141,33 +99,37 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
private var appliedGiftIds: [Int64] = []
|
private var appliedGiftIds: [Int64] = []
|
||||||
|
|
||||||
private var iconLayers: [AnyHashable: GiftIconLayer] = [:]
|
private var iconLayers: [AnyHashable: GiftIconLayer] = [:]
|
||||||
|
|
||||||
private var iconPositions: [PositionGenerator.Position] = []
|
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) {
|
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)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.clipsToBounds = true
|
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) {
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
@ -178,7 +140,38 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
self.giftsDisposable?.dispose()
|
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<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: PeerInfoGiftsCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
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] = []
|
var excludeRects: [CGRect] = []
|
||||||
excludeRects.append(CGRect(origin: .zero, size: CGSize(width: 50.0, height: 90.0)))
|
if component.statusBarHeight > 0.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: .zero, size: CGSize(width: availableSize.width, height: component.statusBarHeight + 4.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)))
|
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 {
|
if component.hasButtons {
|
||||||
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - 81.0), size: CGSize(width: availableSize.width, height: 81.0)))
|
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - 81.0), size: CGSize(width: availableSize.width, height: 81.0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
let positionGenerator = PositionGenerator(
|
let positionGenerator = PositionGenerator(
|
||||||
containerSize: availableSize,
|
containerSize: availableSize,
|
||||||
avatarFrame: CGSize(width: 100, height: 100).centered(around: component.avatarCenter),
|
centerFrame: CGSize(width: 100, height: 100).centered(around: component.avatarCenter),
|
||||||
minDistance: 75.0,
|
exclusionZones: excludeRects,
|
||||||
maxDistance: availableSize.width / 2.0,
|
minimumDistance: 42.0,
|
||||||
padding: 12.0,
|
edgePadding: 5.0,
|
||||||
seed: UInt(Date().timeIntervalSince1970),
|
seed: self.seed
|
||||||
excludeRects: excludeRects
|
|
||||||
)
|
)
|
||||||
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
|
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<AnyHashable>()
|
var validIds = Set<AnyHashable>()
|
||||||
var index = 0
|
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
|
let id: AnyHashable
|
||||||
if case let .unique(uniqueGift) = gift.gift {
|
if case let .unique(uniqueGift) = gift.gift {
|
||||||
id = uniqueGift.slug
|
id = uniqueGift.slug
|
||||||
@ -289,12 +277,13 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
iconTransition = .immediate
|
iconTransition = .immediate
|
||||||
iconLayer = GiftIconLayer(context: component.context, gift: gift, size: iconSize, glowing: component.hasBackground)
|
iconLayer = GiftIconLayer(context: component.context, gift: gift, size: iconSize, glowing: component.hasBackground)
|
||||||
iconLayer.startHovering()
|
|
||||||
self.iconLayers[id] = iconLayer
|
self.iconLayers[id] = iconLayer
|
||||||
self.layer.addSublayer(iconLayer)
|
self.layer.addSublayer(iconLayer)
|
||||||
|
|
||||||
iconLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
iconLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
iconLayer.animateScale(from: 0.01, 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
|
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..<safeCount {
|
|
||||||
let minDist = distanceRanges[i].0
|
|
||||||
let maxDist = distanceRanges[i].1
|
|
||||||
let isEven = i % 2 == 0
|
|
||||||
|
|
||||||
var attempts = 0
|
|
||||||
let maxAttempts = 20
|
|
||||||
var currentMaxDist = maxDist
|
|
||||||
|
|
||||||
var result: CGPoint?
|
|
||||||
|
|
||||||
while result == nil && attempts < maxAttempts {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
if let position = generateSinglePosition(
|
|
||||||
viewSize: viewSize,
|
|
||||||
minDist: minDist,
|
|
||||||
maxDist: currentMaxDist,
|
|
||||||
rightSide: !isEven
|
|
||||||
) {
|
|
||||||
let isFarEnough = positions.isEmpty || positions.allSatisfy { existingPosition in
|
|
||||||
let distance = hypot(position.x - existingPosition.center.x, position.y - existingPosition.center.y)
|
|
||||||
let minRequiredDistance = max(viewSize.width, viewSize.height) / 2 + max(viewSize.width, viewSize.height) / 2 + padding
|
|
||||||
return distance > 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? = {
|
private var shadowImage: UIImage? = {
|
||||||
return generateImage(CGSize(width: 44.0, height: 44.0), rotatedContext: { size, context in
|
return generateImage(CGSize(width: 44.0, height: 44.0), rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: .zero, size: size))
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
@ -616,10 +409,9 @@ private final class StarsEffectLayer: SimpleLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class GiftIconLayer: SimpleLayer {
|
private class GiftIconLayer: SimpleLayer {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let gift: ProfileGiftsContext.State.StarGift
|
let gift: ProfileGiftsContext.State.StarGift
|
||||||
private let size: CGSize
|
private let size: CGSize
|
||||||
var glowing: Bool {
|
var glowing: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
@ -780,26 +572,273 @@ private class GiftIconLayer: SimpleLayer {
|
|||||||
self.animationLayer.frame = CGRect(origin: .zero, size: self.bounds.size)
|
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)) {
|
func startAnimations(index: Int) {
|
||||||
let hoverAnimation = CABasicAnimation(keyPath: "transform.translation.y")
|
let beginTime = Double(index) * 1.5
|
||||||
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")
|
|
||||||
|
|
||||||
let glowAnimation = CABasicAnimation(keyPath: "transform.scale")
|
if self.animation(forKey: "hover") == nil {
|
||||||
glowAnimation.duration = duration
|
let upDistance = CGFloat.random(in: 1.0 ..< 2.0)
|
||||||
glowAnimation.fromValue = 1.0
|
let downDistance = CGFloat.random(in: 1.0 ..< 2.0)
|
||||||
glowAnimation.toValue = 1.2
|
let hoverDuration = TimeInterval.random(in: 3.5 ..< 4.5)
|
||||||
glowAnimation.autoreverses = true
|
|
||||||
glowAnimation.repeatCount = .infinity
|
let hoverAnimation = CABasicAnimation(keyPath: "transform.translation.y")
|
||||||
glowAnimation.timingFunction = timingFunction
|
hoverAnimation.duration = duration
|
||||||
glowAnimation.beginTime = Double.random(in: 0.0 ..< 12.0)
|
hoverAnimation.fromValue = -upDistance
|
||||||
self.shadowLayer.add(glowAnimation, forKey: "glow")
|
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
|
||||||
|
}
|
||||||
|
@ -154,6 +154,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/MediaEditorScreen",
|
"//submodules/TelegramUI/Components/MediaEditorScreen",
|
||||||
"//submodules/TelegramUI/Components/CameraScreen",
|
"//submodules/TelegramUI/Components/CameraScreen",
|
||||||
"//submodules/TelegramUI/Components/PeerInfo/VerifyAlertController",
|
"//submodules/TelegramUI/Components/PeerInfo/VerifyAlertController",
|
||||||
|
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -924,13 +924,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
isVisibleForAnimations: true,
|
isVisibleForAnimations: true,
|
||||||
useSharedAnimation: true,
|
useSharedAnimation: true,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let uniqueGiftSlug {
|
if let uniqueGiftSlug, !self.isSettings {
|
||||||
strongSelf.openUniqueGift?(strongSelf.titleStatusIconView, uniqueGiftSlug)
|
self.openUniqueGift?(self.titleStatusIconView, uniqueGiftSlug)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.displayPremiumIntro?(strongSelf.titleStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false)
|
self.displayPremiumIntro?(self.titleStatusIconView, currentEmojiStatus, self.emojiStatusFileAndPackTitle.get(), false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emojiFileUpdated: { [weak self] emojiFile in
|
emojiFileUpdated: { [weak self] emojiFile in
|
||||||
@ -985,13 +985,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
isVisibleForAnimations: true,
|
isVisibleForAnimations: true,
|
||||||
useSharedAnimation: true,
|
useSharedAnimation: true,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let uniqueGiftSlug {
|
if let uniqueGiftSlug, !self.isSettings {
|
||||||
strongSelf.openUniqueGift?(strongSelf.titleExpandedStatusIconView, uniqueGiftSlug)
|
self.openUniqueGift?(self.titleExpandedStatusIconView, uniqueGiftSlug)
|
||||||
} else {
|
} 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,
|
subject: backgroundCoverSubject,
|
||||||
files: [:],
|
files: [:],
|
||||||
isDark: presentationData.theme.overallDarkAppearance,
|
isDark: presentationData.theme.overallDarkAppearance,
|
||||||
avatarCenter: apparentAvatarFrame.center,
|
avatarCenter: apparentAvatarFrame.center.offsetBy(dx: bannerInset, dy: 0.0),
|
||||||
avatarScale: avatarScale,
|
avatarScale: avatarScale,
|
||||||
defaultHeight: backgroundDefaultHeight,
|
defaultHeight: backgroundDefaultHeight,
|
||||||
gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45),
|
gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45),
|
||||||
@ -2321,9 +2321,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
self.backgroundBannerView.addSubview(backgroundCoverView)
|
self.backgroundBannerView.addSubview(backgroundCoverView)
|
||||||
}
|
}
|
||||||
if additive {
|
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 {
|
} 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 backgroundCoverAnimateIn {
|
||||||
if !self.isAvatarExpanded {
|
if !self.isAvatarExpanded {
|
||||||
@ -2351,24 +2351,32 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
giftsContext: profileGiftsContext,
|
giftsContext: profileGiftsContext,
|
||||||
hasBackground: hasBackground,
|
hasBackground: hasBackground,
|
||||||
avatarCenter: apparentAvatarFrame.center,
|
avatarCenter: apparentAvatarFrame.center,
|
||||||
avatarScale: avatarScale,
|
|
||||||
defaultHeight: backgroundDefaultHeight,
|
|
||||||
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
|
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
|
||||||
patternTransitionFraction: buttonsTransitionFraction * backgroundTransitionFraction,
|
statusBarHeight: statusBarHeight,
|
||||||
hasButtons: !buttonKeys.isEmpty
|
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: {},
|
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 let giftsCoverView = self.giftsCover.view as? PeerInfoGiftsCoverComponent.View {
|
||||||
if giftsCoverView.superview == nil {
|
if giftsCoverView.superview == nil {
|
||||||
self.backgroundBannerView.addSubview(giftsCoverView)
|
self.view.insertSubview(giftsCoverView, aboveSubview: self.backgroundBannerView)
|
||||||
}
|
}
|
||||||
if additive {
|
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 {
|
} 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
|
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 {
|
if result == self.view || result == self.regularContentNode.view || result == self.editingContentNode.view {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ import UIKitRuntimeUtils
|
|||||||
import OldChannelsController
|
import OldChannelsController
|
||||||
import UrlHandling
|
import UrlHandling
|
||||||
import VerifyAlertController
|
import VerifyAlertController
|
||||||
|
import GiftViewScreen
|
||||||
|
|
||||||
public enum PeerInfoAvatarEditingMode {
|
public enum PeerInfoAvatarEditingMode {
|
||||||
case generic
|
case generic
|
||||||
@ -4641,10 +4642,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
strongSelf.emojiStatusSelectionController = emojiStatusSelectionController
|
strongSelf.emojiStatusSelectionController = emojiStatusSelectionController
|
||||||
strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root))
|
strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.headerNode.openUniqueGift = { [weak self] sourceView, _ in
|
|
||||||
self?.headerNode.displayPremiumIntro?(sourceView, nil, .single(nil), false)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if peerId == context.account.peerId {
|
if peerId == context.account.peerId {
|
||||||
self.privacySettings.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init)))
|
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)
|
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
|
self.headerNode.displayStatusPremiumIntro = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
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.headerNode.avatarListNode.listContainerNode.currentIndexUpdated = { [weak self] in
|
||||||
self?.updateNavigation(transition: .immediate, additive: true, animateHeader: true)
|
self?.updateNavigation(transition: .immediate, additive: true, animateHeader: true)
|
||||||
}
|
}
|
||||||
|
@ -676,7 +676,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
|
|
||||||
let buttonSideInset = sideInset + 16.0
|
let buttonSideInset = sideInset + 16.0
|
||||||
let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.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 {
|
if params.visibleHeight < 110.0 {
|
||||||
scrollOffset -= bottomPanelHeight
|
scrollOffset -= bottomPanelHeight
|
||||||
}
|
}
|
||||||
@ -902,7 +902,6 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
)
|
)
|
||||||
if let view = footerText.view {
|
if let view = footerText.view {
|
||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonPressed)))
|
|
||||||
self.scrollNode.view.addSubview(view)
|
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))
|
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floor((size.width - footerTextSize.width) / 2.0), y: contentHeight), size: footerTextSize))
|
||||||
|
@ -81,7 +81,7 @@ final class StarsOverviewItemComponent: Component {
|
|||||||
valueOffset += icon.size.width
|
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 usdValueString = formatTonUsdValue(component.value.value, divide: false, rate: component.rate, dateTimeFormat: component.dateTimeFormat)
|
||||||
|
|
||||||
let valueSize = self.value.update(
|
let valueSize = self.value.update(
|
||||||
|
@ -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)
|
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)
|
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)
|
transition.updateFrame(node: self.slowModeButton, frame: slowModeButtonFrame)
|
||||||
|
|
||||||
@ -2767,12 +2766,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
mediaInputDisabled = false
|
mediaInputDisabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaInputIsActive = false
|
self.actionButtons.micButton.fadeDisabled = mediaInputDisabled
|
||||||
if case .media = interfaceState.inputMode {
|
|
||||||
mediaInputIsActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
self.actionButtons.micButton.fadeDisabled = mediaInputDisabled || mediaInputIsActive
|
|
||||||
|
|
||||||
var viewOnceIsVisible = false
|
var viewOnceIsVisible = false
|
||||||
if let recordingState = interfaceState.inputTextPanelState.mediaRecordingState {
|
if let recordingState = interfaceState.inputTextPanelState.mediaRecordingState {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user