Storage improvements

This commit is contained in:
Ali
2022-12-24 00:04:48 +04:00
parent dd06922e85
commit 808f5b80ff
25 changed files with 3384 additions and 655 deletions

View File

@@ -113,7 +113,7 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer {
guard let file = self.file else {
return
}
let frameManager = SoftwareVideoLayerFrameManager(account: self.context.account, userLocation: self.userLocation, userContentType: .gif, fileReference: .savedGif(media: file), layerHolder: nil, layer: self)
let frameManager = SoftwareVideoLayerFrameManager(account: self.context.account, userLocation: self.userLocation, userContentType: .other, fileReference: .savedGif(media: file), layerHolder: nil, layer: self)
self.frameManager = frameManager
frameManager.started = { [weak self] in
guard let strongSelf = self else {

View File

@@ -464,7 +464,7 @@ public final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
layerHolder.layer.frame = item.frame
self.scrollNode.layer.addSublayer(layerHolder.layer)
let manager = SoftwareVideoLayerFrameManager(account: self.account, userLocation: .other, userContentType: .gif, fileReference: item.file.file, layerHolder: layerHolder)
let manager = SoftwareVideoLayerFrameManager(account: self.account, userLocation: .other, userContentType: .other, fileReference: item.file.file, layerHolder: layerHolder)
self.visibleLayers[item.id] = (manager, layerHolder)
self.visibleThumbnailLayers[item.id]?.ready = { [weak self] in
if let strongSelf = self {

View File

@@ -31,6 +31,8 @@ swift_library(
"//submodules/ContextUI",
"//submodules/AnimatedAvatarSetNode",
"//submodules/AvatarNode",
"//submodules/PhotoResources",
"//submodules/SemanticStatusNode",
],
visibility = [
"//visibility:public",

View File

@@ -13,15 +13,88 @@ import MultilineTextComponent
import EmojiStatusComponent
import Postbox
private func interpolateChartData(start: PieChartComponent.ChartData, end: PieChartComponent.ChartData, progress: CGFloat) -> PieChartComponent.ChartData {
if start.items.count != end.items.count {
return start
}
var result = end
for i in 0 ..< result.items.count {
result.items[i].value = (1.0 - progress) * start.items[i].value + progress * end.items[i].value
result.items[i].color = start.items[i].color.interpolateTo(end.items[i].color, fraction: progress) ?? end.items[i].color
}
return result
}
private func processChartData(data: PieChartComponent.ChartData) -> PieChartComponent.ChartData {
var data = data
let minValue: Double = 0.01
var totalSum: CGFloat = 0.0
for i in 0 ..< data.items.count {
if data.items[i].value > 0.00001 {
data.items[i].value = max(data.items[i].value, minValue)
}
totalSum += data.items[i].value
}
var hasOneItem = false
for i in 0 ..< data.items.count {
if data.items[i].value != 0 && totalSum == data.items[i].value {
data.items[i].value = 1.0
hasOneItem = true
break
}
}
if !hasOneItem {
if abs(totalSum - 1.0) > 0.0001 {
let deltaValue = totalSum - 1.0
var availableSum: Double = 0.0
for i in 0 ..< data.items.count {
let itemValue = data.items[i].value
let availableItemValue = max(0.0, itemValue - minValue)
if availableItemValue > 0.0 {
availableSum += availableItemValue
}
}
totalSum = 0.0
let itemFraction = deltaValue / availableSum
for i in 0 ..< data.items.count {
let itemValue = data.items[i].value
let availableItemValue = max(0.0, itemValue - minValue)
if availableItemValue > 0.0 {
let itemDelta = availableItemValue * itemFraction
data.items[i].value -= itemDelta
}
totalSum += data.items[i].value
}
}
if totalSum > 0.0 && totalSum < 1.0 - 0.0001 {
for i in 0 ..< data.items.count {
data.items[i].value /= totalSum
}
}
}
return data
}
final class PieChartComponent: Component {
struct ChartData: Equatable {
struct Item: Equatable {
var id: AnyHashable
var displayValue: Double
var value: Double
var color: UIColor
init(id: AnyHashable, value: Double, color: UIColor) {
init(id: AnyHashable, displayValue: Double, value: Double, color: UIColor) {
self.id = id
self.displayValue = displayValue
self.value = value
self.color = color
}
@@ -55,96 +128,98 @@ final class PieChartComponent: Component {
return true
}
class View: UIView {
private var shapeLayers: [AnyHashable: SimpleShapeLayer] = [:]
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
var selectedKey: AnyHashable?
private final class ChartDataView: UIView {
private(set) var theme: PresentationTheme?
private(set) var data: ChartData?
private(set) var selectedKey: AnyHashable?
private weak var state: EmptyComponentState?
private var currentAnimation: (start: ChartData, end: ChartData, current: ChartData, progress: CGFloat)?
private var animator: DisplayLinkAnimator?
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
self.backgroundColor = nil
self.isOpaque = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let point = recognizer.location(in: self)
for (key, layer) in self.shapeLayers {
if layer.frame.contains(point), let path = layer.path {
if path.contains(self.layer.convert(point, to: layer)) {
if self.selectedKey == key {
self.selectedKey = nil
} else {
self.selectedKey = key
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
break
}
deinit {
self.animator?.invalidate()
}
func setItems(theme: PresentationTheme, data: ChartData, selectedKey: AnyHashable?, animated: Bool) {
let data = processChartData(data: data)
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
self.theme = theme
self.selectedKey = selectedKey
if animated, let previous = self.data {
var initialState = previous
if let currentAnimation = self.currentAnimation {
initialState = currentAnimation.current
}
self.currentAnimation = (initialState, data, initialState, 0.0)
self.animator?.invalidate()
self.animator = DisplayLinkAnimator(duration: 0.4, from: 0.0, to: 1.0, update: { [weak self] progress in
guard let self else {
return
}
let progress = listViewAnimationCurveSystem(progress)
if let currentAnimationValue = self.currentAnimation {
self.currentAnimation = (currentAnimationValue.start, currentAnimationValue.end, interpolateChartData(start: currentAnimationValue.start, end: currentAnimationValue.end, progress: progress), progress)
self.setNeedsDisplay()
}
}, completion: { [weak self] in
guard let self else {
return
}
self.currentAnimation = nil
self.setNeedsDisplay()
})
}
self.data = data
self.setNeedsDisplay()
}
}
func update(component: PieChartComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.state = state
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
guard let theme = self.theme, let data = self.currentAnimation?.current ?? self.data else {
return
}
if data.items.isEmpty {
return
}
let innerDiameter: CGFloat = 100.0
let spacing: CGFloat = 2.0
let innerAngleSpacing: CGFloat = spacing / (innerDiameter * 0.5)
let minAngle: CGFloat = innerAngleSpacing * 2.0 + 2.0 / (innerDiameter * 0.5)
//let minAngle: CGFloat = innerAngleSpacing * 2.0 + 2.0 / (innerDiameter * 0.5)
var valueSum: Double = 0.0
for item in component.chartData.items {
valueSum += item.value
}
var angles: [Double] = []
var totalAngle: Double = 0.0
for i in 0 ..< component.chartData.items.count {
let item = component.chartData.items[i]
var angle = item.value / valueSum * CGFloat.pi * 2.0
if angle > .ulpOfOne {
if angle < minAngle {
angle = minAngle
}
totalAngle += angle
}
for i in 0 ..< data.items.count {
let item = data.items[i]
let angle = item.value * CGFloat.pi * 2.0
angles.append(angle)
}
if totalAngle > CGFloat.pi * 2.0 {
let deltaAngle = totalAngle - CGFloat.pi * 2.0
var availableAngleSum: Double = 0.0
for i in 0 ..< angles.count {
let itemAngle = angles[i]
let availableItemAngle = max(0.0, itemAngle - minAngle)
if availableItemAngle > 0.0 {
availableAngleSum += availableItemAngle
}
}
let itemFraction = deltaAngle / availableAngleSum
for i in 0 ..< angles.count {
let availableItemAngle = max(0.0, angles[i] - minAngle)
if availableItemAngle > 0.0 {
let itemDelta = availableItemAngle * itemFraction
angles[i] -= itemDelta
}
}
}
let diameter: CGFloat = 200.0
let reducedDiameter: CGFloat = 170.0
var startAngle: CGFloat = 0.0
for i in 0 ..< component.chartData.items.count {
let item = component.chartData.items[i]
for i in 0 ..< data.items.count {
let item = data.items[i]
let itemOuterDiameter: CGFloat
if let selectedKey = self.selectedKey {
@@ -157,21 +232,10 @@ final class PieChartComponent: Component {
itemOuterDiameter = diameter
}
let shapeLayerFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: 0.0), size: CGSize(width: diameter, height: diameter))
let shapeLayerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: diameter, height: diameter))
let angleSpacing: CGFloat = spacing / (itemOuterDiameter * 0.5)
let shapeLayer: SimpleShapeLayer
if let current = self.shapeLayers[item.id] {
shapeLayer = current
} else {
shapeLayer = SimpleShapeLayer()
self.shapeLayers[item.id] = shapeLayer
self.layer.insertSublayer(shapeLayer, at: 0)
}
transition.setFrame(layer: shapeLayer, frame: shapeLayerFrame)
let angleValue: CGFloat = angles[i]
let innerStartAngle = startAngle + innerAngleSpacing * 0.5
@@ -187,14 +251,17 @@ final class PieChartComponent: Component {
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: innerDiameter * 0.5, startAngle: innerEndAngle, endAngle: innerStartAngle, clockwise: true)
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: itemOuterDiameter * 0.5, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: false)
transition.setShapeLayerPath(layer: shapeLayer, path: path)
context.addPath(path)
context.setFillColor(item.color.cgColor)
context.fillPath()
startAngle += angleValue
shapeLayer.fillColor = item.color.cgColor
let fractionValue: Double = floor(item.value * 100.0 * 10.0) / 10.0
let fractionValue: Double = floor(item.displayValue * 100.0 * 10.0) / 10.0
let fractionString: String
if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
if fractionValue < 0.1 {
fractionString = "<0.1"
} else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
fractionString = "\(Int(fractionValue))"
} else {
fractionString = "\(fractionValue)"
@@ -207,16 +274,125 @@ final class PieChartComponent: Component {
label = ComponentView<Empty>()
self.labels[item.id] = label
}
let labelSize = label.update(transition: .immediate, component: AnyComponent(Text(text: "\(fractionString)%", font: Font.with(size: 16.0, design: .round, weight: .semibold), color: component.theme.list.itemCheckColors.foregroundColor)), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0))
let labelSize = label.update(transition: .immediate, component: AnyComponent(Text(text: "\(fractionString)%", font: Font.with(size: 16.0, design: .round, weight: .semibold), color: theme.list.itemCheckColors.foregroundColor)), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0))
var labelFrame: CGRect?
for step in 0 ... 6 {
let stepFraction: CGFloat = CGFloat(step) / 6.0
let centerOffset: CGFloat = 0.5 * (1.0 - stepFraction) + 0.65 * stepFraction
if angleValue >= 0.001 {
for step in 0 ... 20 {
let stepFraction: CGFloat = CGFloat(step) / 20.0
let centerOffset: CGFloat = 0.5 * (1.0 - stepFraction) + 0.65 * stepFraction
let midAngle: CGFloat = (innerStartAngle + innerEndAngle) * 0.5
let centerDistance: CGFloat = (innerDiameter * 0.5 + (diameter * 0.5 - innerDiameter * 0.5) * centerOffset)
let relLabelCenter = CGPoint(
x: cos(midAngle) * centerDistance,
y: sin(midAngle) * centerDistance
)
let labelCenter = CGPoint(
x: shapeLayerFrame.midX + relLabelCenter.x,
y: shapeLayerFrame.midY + relLabelCenter.y
)
func lineCircleIntersection(_ center: CGPoint, _ p1: CGPoint, _ p2: CGPoint, _ r: CGFloat) -> CGFloat {
let dx: CGFloat = p2.x - p1.x
let dy: CGFloat = p2.y - p1.y
let dr: CGFloat = sqrt(dx * dx + dy * dy)
let D: CGFloat = p1.x * p2.y - p2.x * p1.y
var minDistance: CGFloat = 10000.0
for i in 0 ..< 2 {
let signFactor: CGFloat = i == 0 ? 1.0 : (-1.0)
let dysign: CGFloat = dy < 0.0 ? -1.0 : 1.0
let ix: CGFloat = (D * dy + signFactor * dysign * dx * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
let iy: CGFloat = (-D * dx + signFactor * abs(dy) * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
let distance: CGFloat = sqrt(pow(ix - center.x, 2.0) + pow(iy - center.y, 2.0))
minDistance = min(minDistance, distance)
}
return minDistance
}
func lineLineIntersection(_ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint, _ p4: CGPoint) -> CGFloat {
let x1 = p1.x
let y1 = p1.y
let x2 = p2.x
let y2 = p2.y
let x3 = p3.x
let y3 = p3.y
let x4 = p4.x
let y4 = p4.y
let d: CGFloat = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if abs(d) <= 0.00001 {
return 10000.0
}
let px: CGFloat = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d
let py: CGFloat = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d
let distance: CGFloat = sqrt(pow(px - p1.x, 2.0) + pow(py - p1.y, 2.0))
return distance
}
let intersectionOuterTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), diameter * 0.5)
let intersectionInnerTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), innerDiameter * 0.5)
let intersectionOuterBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), diameter * 0.5)
let intersectionInnerBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), innerDiameter * 0.5)
let intersectionLine1TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
let intersectionLine1BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
let intersectionLine2TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
let intersectionLine2BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
var distances: [CGFloat] = [
intersectionOuterTopRight,
intersectionInnerTopRight,
intersectionOuterBottomRight,
intersectionInnerBottomRight
]
if angleValue < CGFloat.pi / 2.0 {
distances.append(contentsOf: [
intersectionLine1TopRight,
intersectionLine1BottomRight,
intersectionLine2TopRight,
intersectionLine2BottomRight
] as [CGFloat])
}
var minDistance: CGFloat = 1000.0
for distance in distances {
minDistance = min(minDistance, distance + 1.0)
}
let diagonalAngle = atan2(labelSize.height, labelSize.width)
let maxHalfWidth = cos(diagonalAngle) * minDistance
let maxHalfHeight = sin(diagonalAngle) * minDistance
let maxSize = CGSize(width: maxHalfWidth * 2.0, height: maxHalfHeight * 2.0)
let finalSize = CGSize(width: min(labelSize.width, maxSize.width), height: min(labelSize.height, maxSize.height))
let currentFrame = CGRect(origin: CGPoint(x: labelCenter.x - finalSize.width * 0.5, y: labelCenter.y - finalSize.height * 0.5), size: finalSize)
if finalSize.width >= labelSize.width {
labelFrame = currentFrame
break
}
if let labelFrame {
if labelFrame.width > finalSize.width {
continue
}
}
labelFrame = currentFrame
}
} else {
let midAngle: CGFloat = (innerStartAngle + innerEndAngle) * 0.5
let centerDistance: CGFloat = (innerDiameter * 0.5 + (diameter * 0.5 - innerDiameter * 0.5) * centerOffset)
let centerDistance: CGFloat = (innerDiameter * 0.5 + (diameter * 0.5 - innerDiameter * 0.5) * 0.5)
let relLabelCenter = CGPoint(
x: cos(midAngle) * centerDistance,
@@ -228,103 +404,14 @@ final class PieChartComponent: Component {
y: shapeLayerFrame.midY + relLabelCenter.y
)
func lineCircleIntersection(_ center: CGPoint, _ p1: CGPoint, _ p2: CGPoint, _ r: CGFloat) -> CGFloat {
let dx: CGFloat = p2.x - p1.x
let dy: CGFloat = p2.y - p1.y
let dr: CGFloat = sqrt(dx * dx + dy * dy)
let D: CGFloat = p1.x * p2.y - p2.x * p1.y
var minDistance: CGFloat = 10000.0
for i in 0 ..< 2 {
let signFactor: CGFloat = i == 0 ? 1.0 : (-1.0)
let dysign: CGFloat = dy < 0.0 ? -1.0 : 1.0
let ix: CGFloat = (D * dy + signFactor * dysign * dx * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
let iy: CGFloat = (-D * dx + signFactor * abs(dy) * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
let distance: CGFloat = sqrt(pow(ix - center.x, 2.0) + pow(iy - center.y, 2.0))
minDistance = min(minDistance, distance)
}
return minDistance
}
func lineLineIntersection(_ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint, _ p4: CGPoint) -> CGFloat {
let x1 = p1.x
let y1 = p1.y
let x2 = p2.x
let y2 = p2.y
let x3 = p3.x
let y3 = p3.y
let x4 = p4.x
let y4 = p4.y
let d: CGFloat = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if abs(d) <= 0.00001 {
return 10000.0
}
let px: CGFloat = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d
let py: CGFloat = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d
let distance: CGFloat = sqrt(pow(px - p1.x, 2.0) + pow(py - p1.y, 2.0))
return distance
}
let intersectionOuterTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), diameter * 0.5)
let intersectionInnerTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), innerDiameter * 0.5)
let intersectionOuterBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), diameter * 0.5)
let intersectionInnerBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), innerDiameter * 0.5)
let intersectionLine1TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
let intersectionLine1BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
let intersectionLine2TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
let intersectionLine2BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
var distances: [CGFloat] = [
intersectionOuterTopRight,
intersectionInnerTopRight,
intersectionOuterBottomRight,
intersectionInnerBottomRight
]
if angleValue < CGFloat.pi / 2.0 {
distances.append(contentsOf: [
intersectionLine1TopRight,
intersectionLine1BottomRight,
intersectionLine2TopRight,
intersectionLine2BottomRight
] as [CGFloat])
}
var minDistance: CGFloat = 1000.0
for distance in distances {
minDistance = min(minDistance, distance + 1.0)
}
let diagonalAngle = atan2(labelSize.height, labelSize.width)
let maxHalfWidth = cos(diagonalAngle) * minDistance
let maxHalfHeight = sin(diagonalAngle) * minDistance
let maxSize = CGSize(width: maxHalfWidth * 2.0, height: maxHalfHeight * 2.0)
let finalSize = CGSize(width: min(labelSize.width, maxSize.width), height: min(labelSize.height, maxSize.height))
let currentFrame = CGRect(origin: CGPoint(x: labelCenter.x - finalSize.width * 0.5, y: labelCenter.y - finalSize.height * 0.5), size: finalSize)
if finalSize.width >= labelSize.width {
labelFrame = currentFrame
break
}
if let labelFrame {
if labelFrame.width > finalSize.width {
continue
}
}
labelFrame = currentFrame
let minSize = labelSize.aspectFitted(CGSize(width: 4.0, height: 4.0))
labelFrame = CGRect(origin: CGPoint(x: labelCenter.x - minSize.width * 0.5, y: labelCenter.y - minSize.height * 0.5), size: minSize)
}
if let labelView = label.view, let labelFrame {
var animateIn: Bool = false
if labelView.superview == nil {
animateIn = true
self.addSubview(labelView)
}
@@ -338,11 +425,12 @@ final class PieChartComponent: Component {
y: labelFrame.midY - shapeLayerFrame.midY
)
let labelAlpha: CGFloat
if let selectedKey = self.selectedKey {
if selectedKey == item.id {
transition.setAlpha(view: labelView, alpha: normalAlpha)
labelAlpha = normalAlpha
} else {
transition.setAlpha(view: labelView, alpha: 0.0)
labelAlpha = 0.0
let reducedFactor: CGFloat = (reducedDiameter - innerDiameter) / (diameter - innerDiameter)
let reducedDiameterFactor: CGFloat = reducedDiameter / diameter
@@ -353,7 +441,16 @@ final class PieChartComponent: Component {
relLabelCenter.y *= reducedDiameterFactor
}
} else {
transition.setAlpha(view: labelView, alpha: normalAlpha)
labelAlpha = normalAlpha
}
if labelView.alpha != labelAlpha {
let transition: Transition
if animateIn {
transition = .immediate
} else {
transition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
}
transition.setAlpha(view: labelView, alpha: labelAlpha)
}
let labelCenter = CGPoint(
@@ -361,10 +458,61 @@ final class PieChartComponent: Component {
y: shapeLayerFrame.midY + relLabelCenter.y
)
transition.setPosition(view: labelView, position: labelCenter)
transition.setScale(view: labelView, scale: labelScale)
labelView.center = labelCenter
labelView.transform = CGAffineTransformMakeScale(labelScale, labelScale)
}
}
}
}
class View: UIView {
private let dataView: ChartDataView
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
var selectedKey: AnyHashable?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.dataView = ChartDataView()
super.init(frame: frame)
self.addSubview(self.dataView)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let point = recognizer.location(in: self)
let _ = point
/*for (key, layer) in self.shapeLayers {
if layer.frame.contains(point), let path = layer.path {
if path.contains(self.layer.convert(point, to: layer)) {
if self.selectedKey == key {
self.selectedKey = nil
} else {
self.selectedKey = key
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
break
}
}
}*/
}
}
func update(component: PieChartComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.state = state
transition.setFrame(view: self.dataView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - 200.0) / 2.0), y: 0.0), size: CGSize(width: 200.0, height: 200.0)))
self.dataView.setItems(theme: component.theme, data: component.chartData, selectedKey: self.selectedKey, animated: !transition.animation.isImmediate)
return CGSize(width: availableSize.width, height: 200.0)
}

View File

@@ -17,7 +17,7 @@ import SolidRoundedButtonComponent
final class StorageCategoriesComponent: Component {
struct CategoryData: Equatable {
var key: AnyHashable
var key: StorageUsageScreenComponent.Category
var color: UIColor
var title: String
var size: Int64
@@ -25,7 +25,7 @@ final class StorageCategoriesComponent: Component {
var isSelected: Bool
var subcategories: [CategoryData]
init(key: AnyHashable, color: UIColor, title: String, size: Int64, sizeFraction: Double, isSelected: Bool, subcategories: [CategoryData]) {
init(key: StorageUsageScreenComponent.Category, color: UIColor, title: String, size: Int64, sizeFraction: Double, isSelected: Bool, subcategories: [CategoryData]) {
self.key = key
self.title = title
self.color = color
@@ -39,18 +39,27 @@ final class StorageCategoriesComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let categories: [CategoryData]
let toggleCategorySelection: (AnyHashable) -> Void
let isOtherExpanded: Bool
let toggleCategorySelection: (StorageUsageScreenComponent.Category) -> Void
let toggleOtherExpanded: () -> Void
let clearAction: () -> Void
init(
theme: PresentationTheme,
strings: PresentationStrings,
categories: [CategoryData],
toggleCategorySelection: @escaping (AnyHashable) -> Void
isOtherExpanded: Bool,
toggleCategorySelection: @escaping (StorageUsageScreenComponent.Category) -> Void,
toggleOtherExpanded: @escaping () -> Void,
clearAction: @escaping () -> Void
) {
self.theme = theme
self.strings = strings
self.categories = categories
self.isOtherExpanded = isOtherExpanded
self.toggleCategorySelection = toggleCategorySelection
self.toggleOtherExpanded = toggleOtherExpanded
self.clearAction = clearAction
}
static func ==(lhs: StorageCategoriesComponent, rhs: StorageCategoriesComponent) -> Bool {
@@ -63,14 +72,16 @@ final class StorageCategoriesComponent: Component {
if lhs.categories != rhs.categories {
return false
}
if lhs.isOtherExpanded != rhs.isOtherExpanded {
return false
}
return true
}
class View: UIView {
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
private var itemViews: [StorageUsageScreenComponent.Category: ComponentView<Empty>] = [:]
private let button = ComponentView<Empty>()
private var expandedCategory: AnyHashable?
private var component: StorageCategoriesComponent?
private weak var state: EmptyComponentState?
@@ -89,6 +100,8 @@ final class StorageCategoriesComponent: Component {
self.component = component
self.state = state
let expandedCategory: StorageUsageScreenComponent.Category? = component.isOtherExpanded ? .other : nil
var totalSelectedSize: Int64 = 0
var hasDeselected = false
for category in component.categories {
@@ -111,7 +124,7 @@ final class StorageCategoriesComponent: Component {
var contentHeight: CGFloat = 0.0
var validKeys = Set<AnyHashable>()
var validKeys = Set<StorageUsageScreenComponent.Category>()
for i in 0 ..< component.categories.count {
let category = component.categories[i]
validKeys.insert(category.key)
@@ -134,7 +147,7 @@ final class StorageCategoriesComponent: Component {
strings: component.strings,
category: category,
isExpandedLevel: false,
isExpanded: self.expandedCategory == category.key,
isExpanded: expandedCategory == category.key,
hasNext: i != component.categories.count - 1,
action: { [weak self] key, actionType in
guard let self, let component = self.component else {
@@ -144,12 +157,7 @@ final class StorageCategoriesComponent: Component {
switch actionType {
case .generic:
if let category = component.categories.first(where: { $0.key == key }), !category.subcategories.isEmpty {
if self.expandedCategory == category.key {
self.expandedCategory = nil
} else {
self.expandedCategory = category.key
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
component.toggleOtherExpanded()
} else {
component.toggleCategorySelection(key)
}
@@ -172,7 +180,7 @@ final class StorageCategoriesComponent: Component {
contentHeight += itemSize.height
}
var removeKeys: [AnyHashable] = []
var removeKeys: [StorageUsageScreenComponent.Category] = []
for (key, itemView) in self.itemViews {
if !validKeys.contains(key) {
if let itemComponentView = itemView.view {
@@ -221,7 +229,11 @@ final class StorageCategoriesComponent: Component {
animationName: nil,
iconPosition: .right,
iconSpacing: 4.0,
action: {
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.clearAction()
}
)),
environment: {},

View File

@@ -27,7 +27,7 @@ final class StorageCategoryItemComponent: Component {
let isExpandedLevel: Bool
let isExpanded: Bool
let hasNext: Bool
let action: (AnyHashable, ActionType) -> Void
let action: (StorageUsageScreenComponent.Category, ActionType) -> Void
init(
theme: PresentationTheme,
@@ -36,7 +36,7 @@ final class StorageCategoryItemComponent: Component {
isExpandedLevel: Bool,
isExpanded: Bool,
hasNext: Bool,
action: @escaping (AnyHashable, ActionType) -> Void
action: @escaping (StorageUsageScreenComponent.Category, ActionType) -> Void
) {
self.theme = theme
self.strings = strings
@@ -80,7 +80,7 @@ final class StorageCategoryItemComponent: Component {
private let checkButtonArea: HighlightTrackingButton
private let subcategoryClippingContainer: UIView
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
private var itemViews: [StorageUsageScreenComponent.Category: ComponentView<Empty>] = [:]
private var component: StorageCategoryItemComponent?
@@ -306,7 +306,7 @@ final class StorageCategoryItemComponent: Component {
self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height + ((component.isExpanded || component.hasNext) ? UIScreenPixel : 0.0)))
var validKeys = Set<AnyHashable>()
var validKeys = Set<StorageUsageScreenComponent.Category>()
if component.isExpanded {
for i in 0 ..< component.category.subcategories.count {
let category = component.category.subcategories[i]
@@ -358,7 +358,7 @@ final class StorageCategoryItemComponent: Component {
}
}
var removeKeys: [AnyHashable] = []
var removeKeys: [StorageUsageScreenComponent.Category] = []
for (key, itemView) in self.itemViews {
if !validKeys.contains(key) {
if let itemComponentView = itemView.view {

View File

@@ -19,13 +19,20 @@ import AvatarNode
private let avatarFont = avatarPlaceholderFont(size: 15.0)
private final class PeerListItemComponent: Component {
enum SelectionState: Equatable {
case none
case editing(isSelected: Bool)
}
let context: AccountContext
let theme: PresentationTheme
let sideInset: CGFloat
let title: String
let peer: EnginePeer?
let label: String
let selectionState: SelectionState
let hasNext: Bool
let action: (EnginePeer) -> Void
init(
context: AccountContext,
@@ -34,7 +41,9 @@ private final class PeerListItemComponent: Component {
title: String,
peer: EnginePeer?,
label: String,
hasNext: Bool
selectionState: SelectionState,
hasNext: Bool,
action: @escaping (EnginePeer) -> Void
) {
self.context = context
self.theme = theme
@@ -42,7 +51,9 @@ private final class PeerListItemComponent: Component {
self.title = title
self.peer = peer
self.label = label
self.selectionState = selectionState
self.hasNext = hasNext
self.action = action
}
static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool {
@@ -64,6 +75,9 @@ private final class PeerListItemComponent: Component {
if lhs.label != rhs.label {
return false
}
if lhs.selectionState != rhs.selectionState {
return false
}
if lhs.hasNext != rhs.hasNext {
return false
}
@@ -76,6 +90,11 @@ private final class PeerListItemComponent: Component {
private let separatorLayer: SimpleLayer
private let avatarNode: AvatarNode
private var checkLayer: CheckLayer?
private var highlightBackgroundFrame: CGRect?
private var highlightBackgroundLayer: SimpleLayer?
private var component: PeerListItemComponent?
override init(frame: CGRect) {
@@ -87,23 +106,115 @@ private final class PeerListItemComponent: Component {
self.layer.addSublayer(self.separatorLayer)
self.layer.addSublayer(self.avatarNode.layer)
self.highligthedChanged = { [weak self] isHighlighted in
guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else {
return
}
if isHighlighted, case .none = component.selectionState {
self.superview?.bringSubviewToFront(self)
let highlightBackgroundLayer: SimpleLayer
if let current = self.highlightBackgroundLayer {
highlightBackgroundLayer = current
} else {
highlightBackgroundLayer = SimpleLayer()
self.highlightBackgroundLayer = highlightBackgroundLayer
self.layer.insertSublayer(highlightBackgroundLayer, above: self.separatorLayer)
highlightBackgroundLayer.backgroundColor = component.theme.list.itemHighlightedBackgroundColor.cgColor
}
highlightBackgroundLayer.frame = highlightBackgroundFrame
highlightBackgroundLayer.opacity = 1.0
} else {
if let highlightBackgroundLayer = self.highlightBackgroundLayer {
self.highlightBackgroundLayer = nil
highlightBackgroundLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak highlightBackgroundLayer] _ in
highlightBackgroundLayer?.removeFromSuperlayer()
})
}
}
}
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
guard let component = self.component, let peer = component.peer else {
return
}
component.action(peer)
}
func update(component: PeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
var hasSelectionUpdated = false
if let previousComponent = self.component {
switch previousComponent.selectionState {
case .none:
if case .none = component.selectionState {
} else {
hasSelectionUpdated = true
}
case .editing:
if case .editing = component.selectionState {
} else {
hasSelectionUpdated = true
}
}
}
self.component = component
let height: CGFloat = 52.0
let leftInset: CGFloat = 62.0 + component.sideInset
var leftInset: CGFloat = 62.0 + component.sideInset
var avatarLeftInset: CGFloat = component.sideInset + 10.0
if case let .editing(isSelected) = component.selectionState {
leftInset += 48.0
avatarLeftInset += 48.0
let checkSize: CGFloat = 22.0
let checkLayer: CheckLayer
if let current = self.checkLayer {
checkLayer = current
if themeUpdated {
checkLayer.theme = CheckNodeTheme(theme: component.theme, style: .plain)
}
checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate)
} else {
checkLayer = CheckLayer(theme: CheckNodeTheme(theme: component.theme, style: .plain))
self.checkLayer = checkLayer
self.layer.addSublayer(checkLayer)
checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))
checkLayer.setSelected(isSelected, animated: false)
checkLayer.setNeedsDisplay()
}
transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: 20.0, y: floor((height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
} else {
if let checkLayer = self.checkLayer {
self.checkLayer = nil
transition.setPosition(layer: checkLayer, position: CGPoint(x: -checkLayer.bounds.width * 0.5, y: checkLayer.position.y), completion: { [weak checkLayer] _ in
checkLayer?.removeFromSuperlayer()
})
}
}
let rightInset: CGFloat = 16.0 + component.sideInset
let avatarSize: CGFloat = 40.0
self.avatarNode.frame = CGRect(origin: CGPoint(x: component.sideInset + 10.0, y: floor((height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
let avatarFrame = CGRect(origin: CGPoint(x: avatarLeftInset, y: floor((height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
if self.avatarNode.bounds.isEmpty {
self.avatarNode.frame = avatarFrame
} else {
transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame)
}
if let peer = component.peer {
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
}
@@ -117,6 +228,12 @@ private final class PeerListItemComponent: Component {
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let previousTitleFrame = self.title.view?.frame
var previousTitleContents: UIView?
if hasSelectionUpdated {
previousTitleContents = self.title.view?.snapshotView(afterScreenUpdates: false)
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
@@ -125,15 +242,31 @@ private final class PeerListItemComponent: Component {
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset - labelSize.width - 4.0, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize))
titleView.frame = titleFrame
if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x {
transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true)
}
if let previousTitleFrame, let previousTitleContents, previousTitleFrame.size != titleSize {
previousTitleContents.frame = CGRect(origin: previousTitleFrame.origin, size: previousTitleFrame.size)
self.addSubview(previousTitleContents)
transition.setFrame(view: previousTitleContents, frame: CGRect(origin: titleFrame.origin, size: previousTitleFrame.size))
transition.setAlpha(view: previousTitleContents, alpha: 0.0, completion: { [weak previousTitleContents] _ in
previousTitleContents?.removeFromSuperview()
})
transition.animateAlpha(view: titleView, from: 0.0, to: 1.0)
}
}
if let labelView = self.label.view {
if labelView.superview == nil {
labelView.isUserInteractionEnabled = false
self.addSubview(labelView)
}
transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset - labelSize.width, y: floor((height - labelSize.height) / 2.0)), size: labelSize))
@@ -145,6 +278,8 @@ private final class PeerListItemComponent: Component {
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
self.separatorLayer.isHidden = !component.hasNext
self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height + ((component.hasNext) ? UIScreenPixel : 0.0)))
return CGSize(width: availableSize.width, height: height)
}
}
@@ -201,13 +336,19 @@ final class StoragePeerListPanelComponent: Component {
let context: AccountContext
let items: Items?
let selectionState: StorageUsageScreenComponent.SelectionState?
let peerAction: (EnginePeer) -> Void
init(
context: AccountContext,
items: Items?
items: Items?,
selectionState: StorageUsageScreenComponent.SelectionState?,
peerAction: @escaping (EnginePeer) -> Void
) {
self.context = context
self.items = items
self.selectionState = selectionState
self.peerAction = peerAction
}
static func ==(lhs: StoragePeerListPanelComponent, rhs: StoragePeerListPanelComponent) -> Bool {
@@ -217,6 +358,9 @@ final class StoragePeerListPanelComponent: Component {
if lhs.items != rhs.items {
return false
}
if lhs.selectionState != rhs.selectionState {
return false
}
return true
}
@@ -337,6 +481,13 @@ final class StoragePeerListPanelComponent: Component {
self.visibleItems[id] = itemView
}
let itemSelectionState: PeerListItemComponent.SelectionState
if let selectionState = component.selectionState {
itemSelectionState = .editing(isSelected: selectionState.selectedPeers.contains(id))
} else {
itemSelectionState = .none
}
let _ = itemView.update(
transition: itemTransition,
component: AnyComponent(PeerListItemComponent(
@@ -346,7 +497,9 @@ final class StoragePeerListPanelComponent: Component {
title: item.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
peer: item.peer,
label: dataSizeString(item.size, formatting: dataSizeFormatting),
hasNext: index != items.items.count - 1
selectionState: itemSelectionState,
hasNext: index != items.items.count - 1,
action: component.peerAction
)),
environment: {},
containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight)
@@ -392,7 +545,10 @@ final class StoragePeerListPanelComponent: Component {
title: "ABCDEF",
peer: nil,
label: "1000",
hasNext: false
selectionState: .none,
hasNext: false,
action: { _ in
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 1000.0)
@@ -408,7 +564,14 @@ final class StoragePeerListPanelComponent: Component {
self.ignoreScrolling = true
let contentOffset = self.scrollView.bounds.minY
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
transition.setPosition(view: self.scrollView, position: CGRect(origin: CGPoint(), size: availableSize).center)
var scrollBounds = self.scrollView.bounds
scrollBounds.size = availableSize
if !environment.isScrollable {
scrollBounds.origin = CGPoint()
}
transition.setBounds(view: self.scrollView, bounds: scrollBounds)
self.scrollView.isScrollEnabled = environment.isScrollable
let contentSize = CGSize(width: availableSize.width, height: itemLayout.contentHeight)
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize
@@ -419,7 +582,7 @@ final class StoragePeerListPanelComponent: Component {
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: -deltaOffset), to: CGPoint(), additive: true)
}
self.ignoreScrolling = false
self.updateScrolling(transition: .immediate)
self.updateScrolling(transition: transition)
return availableSize
}

View File

@@ -5,19 +5,42 @@ import ComponentFlow
import ComponentDisplayAdapters
import TelegramPresentationData
final class StorageUsagePanelContainerEnvironment: Equatable {
let isScrollable: Bool
init(
isScrollable: Bool
) {
self.isScrollable = isScrollable
}
static func ==(lhs: StorageUsagePanelContainerEnvironment, rhs: StorageUsagePanelContainerEnvironment) -> Bool {
if lhs.isScrollable != rhs.isScrollable {
return false
}
return true
}
}
final class StorageUsagePanelEnvironment: Equatable {
let theme: PresentationTheme
let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat
let containerInsets: UIEdgeInsets
let isScrollable: Bool
init(
theme: PresentationTheme,
strings: PresentationStrings,
containerInsets: UIEdgeInsets
dateTimeFormat: PresentationDateTimeFormat,
containerInsets: UIEdgeInsets,
isScrollable: Bool
) {
self.theme = theme
self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.containerInsets = containerInsets
self.isScrollable = isScrollable
}
static func ==(lhs: StorageUsagePanelEnvironment, rhs: StorageUsagePanelEnvironment) -> Bool {
@@ -27,9 +50,15 @@ final class StorageUsagePanelEnvironment: Equatable {
if lhs.strings !== rhs.strings {
return false
}
if lhs.dateTimeFormat != rhs.dateTimeFormat {
return false
}
if lhs.containerInsets != rhs.containerInsets {
return false
}
if lhs.isScrollable != rhs.isScrollable {
return false
}
return true
}
}
@@ -37,13 +66,16 @@ final class StorageUsagePanelEnvironment: Equatable {
private final class StorageUsageHeaderItemComponent: CombinedComponent {
let theme: PresentationTheme
let title: String
let activityFraction: CGFloat
init(
theme: PresentationTheme,
title: String
title: String,
activityFraction: CGFloat
) {
self.theme = theme
self.title = title
self.activityFraction = activityFraction
}
static func ==(lhs: StorageUsageHeaderItemComponent, rhs: StorageUsageHeaderItemComponent) -> Bool {
@@ -53,22 +85,38 @@ private final class StorageUsageHeaderItemComponent: CombinedComponent {
if lhs.title != rhs.title {
return false
}
if lhs.activityFraction != rhs.activityFraction {
return false
}
return true
}
static var body: Body {
let text = Child(Text.self)
let activeText = Child(Text.self)
let inactiveText = Child(Text.self)
return { context in
let text = text.update(
component: Text(text: context.component.title, font: Font.semibold(15.0), color: context.component.theme.list.itemAccentColor),
let activeText = activeText.update(
component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemAccentColor),
availableSize: context.availableSize,
transition: .immediate
)
let inactiveText = inactiveText.update(
component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemSecondaryTextColor),
availableSize: context.availableSize,
transition: .immediate
)
context.add(text.position(CGPoint(x: text.size.width * 0.5, y: text.size.height * 0.5)))
context.add(activeText
.position(CGPoint(x: activeText.size.width * 0.5, y: activeText.size.height * 0.5))
.opacity(context.component.activityFraction)
)
context.add(inactiveText
.position(CGPoint(x: inactiveText.size.width * 0.5, y: inactiveText.size.height * 0.5))
.opacity(1.0 - context.component.activityFraction)
)
return text.size
return activeText.size
}
}
}
@@ -89,13 +137,22 @@ private final class StorageUsageHeaderComponent: Component {
let theme: PresentationTheme
let items: [Item]
let activeIndex: Int
let transitionFraction: CGFloat
let switchToPanel: (AnyHashable) -> Void
init(
theme: PresentationTheme,
items: [Item]
items: [Item],
activeIndex: Int,
transitionFraction: CGFloat,
switchToPanel: @escaping (AnyHashable) -> Void
) {
self.theme = theme
self.items = items
self.activeIndex = activeIndex
self.transitionFraction = transitionFraction
self.switchToPanel = switchToPanel
}
static func ==(lhs: StorageUsageHeaderComponent, rhs: StorageUsageHeaderComponent) -> Bool {
@@ -105,6 +162,12 @@ private final class StorageUsageHeaderComponent: Component {
if lhs.items != rhs.items {
return false
}
if lhs.activeIndex != rhs.activeIndex {
return false
}
if lhs.transitionFraction != rhs.transitionFraction {
return false
}
return true
}
@@ -122,19 +185,46 @@ private final class StorageUsageHeaderComponent: Component {
super.init(frame: frame)
self.layer.addSublayer(self.activeItemLayer)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let point = recognizer.location(in: self)
var closestId: (CGFloat, AnyHashable)?
if self.bounds.contains(point) {
for (id, item) in self.visibleItems {
if let itemView = item.view {
let distance: CGFloat = min(abs(point.x - itemView.frame.minX), abs(point.x - itemView.frame.maxX))
if let closestIdValue = closestId {
if distance < closestIdValue.0 {
closestId = (distance, id)
}
} else {
closestId = (distance, id)
}
}
}
}
if let closestId = closestId, let component = self.component {
component.switchToPanel(closestId.1)
}
}
}
func update(component: StorageUsageHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
self.component = component
var validIds = Set<AnyHashable>()
for item in component.items {
for i in 0 ..< component.items.count {
let item = component.items[i]
validIds.insert(item.id)
let itemView: ComponentView<Empty>
@@ -146,24 +236,59 @@ private final class StorageUsageHeaderComponent: Component {
itemView = ComponentView()
self.visibleItems[item.id] = itemView
}
let activeIndex: CGFloat = CGFloat(component.activeIndex) - component.transitionFraction
let activityDistance: CGFloat = abs(activeIndex - CGFloat(i))
let activityFraction: CGFloat
if activityDistance < 1.0 {
activityFraction = 1.0 - activityDistance
} else {
activityFraction = 0.0
}
let itemSize = itemView.update(
transition: itemTransition,
component: AnyComponent(StorageUsageHeaderItemComponent(
theme: component.theme,
title: item.title
title: item.title,
activityFraction: activityFraction
)),
environment: {},
containerSize: availableSize
)
let itemFrame = CGRect(origin: CGPoint(x: 34.0, y: floor((availableSize.height - itemSize.height) / 2.0)), size: itemSize)
let itemHorizontalSpace = availableSize.width / CGFloat(component.items.count)
let itemX: CGFloat
if component.items.count == 1 {
itemX = 37.0
} else {
itemX = itemHorizontalSpace * CGFloat(i) + floor((itemHorizontalSpace - itemSize.width) / 2.0)
}
let itemFrame = CGRect(origin: CGPoint(x: itemX, y: floor((availableSize.height - itemSize.height) / 2.0)), size: itemSize)
if let itemComponentView = itemView.view {
if itemComponentView.superview == nil {
self.addSubview(itemComponentView)
itemComponentView.isUserInteractionEnabled = false
}
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
}
transition.setFrame(layer: self.activeItemLayer, frame: CGRect(origin: CGPoint(x: itemFrame.minX, y: availableSize.height - 3.0), size: CGSize(width: itemFrame.width, height: 3.0)))
}
if component.activeIndex < component.items.count {
let activeView = self.visibleItems[component.items[component.activeIndex].id]?.view
let nextIndex: Int
if component.transitionFraction > 0.0 {
nextIndex = max(0, component.activeIndex - 1)
} else {
nextIndex = min(component.items.count - 1, component.activeIndex + 1)
}
let nextView = self.visibleItems[component.items[nextIndex].id]?.view
if let activeView = activeView, let nextView = nextView {
let mergedFrame = activeView.frame.interpolate(to: nextView.frame, amount: abs(component.transitionFraction))
transition.setFrame(layer: self.activeItemLayer, frame: CGRect(origin: CGPoint(x: mergedFrame.minX, y: availableSize.height - 3.0), size: CGSize(width: mergedFrame.width, height: 3.0)))
}
}
if themeUpdated {
@@ -197,6 +322,8 @@ private final class StorageUsageHeaderComponent: Component {
}
final class StorageUsagePanelContainerComponent: Component {
typealias EnvironmentType = StorageUsagePanelContainerEnvironment
struct Item: Equatable {
let id: AnyHashable
let title: String
@@ -215,17 +342,20 @@ final class StorageUsagePanelContainerComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat
let insets: UIEdgeInsets
let items: [Item]
init(
theme: PresentationTheme,
strings: PresentationStrings,
dateTimeFormat: PresentationDateTimeFormat,
insets: UIEdgeInsets,
items: [Item]
) {
self.theme = theme
self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.insets = insets
self.items = items
}
@@ -237,6 +367,9 @@ final class StorageUsagePanelContainerComponent: Component {
if lhs.strings !== rhs.strings {
return false
}
if lhs.dateTimeFormat != rhs.dateTimeFormat {
return false
}
if lhs.insets != rhs.insets {
return false
}
@@ -246,47 +379,211 @@ final class StorageUsagePanelContainerComponent: Component {
return true
}
class View: UIView {
private let topPanelBackgroundView: BlurredBackgroundView
class View: UIView, UIGestureRecognizerDelegate {
private let topPanelBackgroundView: UIView
private let topPanelSeparatorLayer: SimpleLayer
private let header = ComponentView<Empty>()
private var component: StorageUsagePanelContainerComponent?
private weak var state: EmptyComponentState?
private let panelsBackgroundLayer: SimpleLayer
private var visiblePanels: [AnyHashable: ComponentView<StorageUsagePanelEnvironment>] = [:]
private var actualVisibleIds = Set<AnyHashable>()
private var currentId: AnyHashable?
private var transitionFraction: CGFloat = 0.0
private var animatingTransition: Bool = false
override init(frame: CGRect) {
self.topPanelBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
self.topPanelBackgroundView = UIView()
self.topPanelSeparatorLayer = SimpleLayer()
self.panelsBackgroundLayer = SimpleLayer()
super.init(frame: frame)
self.layer.addSublayer(self.panelsBackgroundLayer)
self.addSubview(self.topPanelBackgroundView)
self.layer.addSublayer(self.topPanelSeparatorLayer)
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
guard let self, let component = self.component, let currentId = self.currentId else {
return []
}
guard let index = component.items.firstIndex(where: { $0.id == currentId }) else {
return []
}
/*if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) {
return []
}*/
if index == 0 {
return .left
}
return [.left, .right]
})
panRecognizer.delegate = self
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
self.addGestureRecognizer(panRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: StorageUsagePanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
return false
}
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
return true
}
return false
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
func cancelContextGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for gesture in gestureRecognizers {
if let gesture = gesture as? ContextGesture {
gesture.cancel()
}
}
}
for subview in view.subviews {
cancelContextGestures(view: subview)
}
}
cancelContextGestures(view: self)
//self.animatingTransition = true
case .changed:
guard let component = self.component, let currentId = self.currentId else {
return
}
guard let index = component.items.firstIndex(where: { $0.id == currentId }) else {
return
}
let translation = recognizer.translation(in: self)
var transitionFraction = translation.x / self.bounds.width
if index <= 0 {
transitionFraction = min(0.0, transitionFraction)
}
if index >= component.items.count - 1 {
transitionFraction = max(0.0, transitionFraction)
}
self.transitionFraction = transitionFraction
self.state?.updated(transition: .immediate)
// let nextKey = availablePanes[updatedIndex]
// print(transitionFraction)
//self.paneTransitionPromise.set(transitionFraction)
//self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .immediate)
//self.currentPaneUpdated?(false)
case .cancelled, .ended:
guard let component = self.component, let currentId = self.currentId else {
return
}
guard let index = component.items.firstIndex(where: { $0.id == currentId }) else {
return
}
let translation = recognizer.translation(in: self)
let velocity = recognizer.velocity(in: self)
var directionIsToRight: Bool?
if abs(velocity.x) > 10.0 {
directionIsToRight = velocity.x < 0.0
} else {
if abs(translation.x) > self.bounds.width / 2.0 {
directionIsToRight = translation.x > self.bounds.width / 2.0
}
}
if let directionIsToRight = directionIsToRight {
var updatedIndex = index
if directionIsToRight {
updatedIndex = min(updatedIndex + 1, component.items.count - 1)
} else {
updatedIndex = max(updatedIndex - 1, 0)
}
self.currentId = component.items[updatedIndex].id
}
self.transitionFraction = 0.0
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
self.animatingTransition = false
//self.currentPaneUpdated?(false)
//self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil))
default:
break
}
}
func update(component: StorageUsagePanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StorageUsagePanelContainerEnvironment>, transition: Transition) -> CGSize {
let environment = environment[StorageUsagePanelContainerEnvironment.self].value
let themeUpdated = self.component?.theme !== component.theme
self.component = component
self.state = state
if themeUpdated {
self.backgroundColor = component.theme.list.itemBlocksBackgroundColor
self.topPanelBackgroundView.updateColor(color: component.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.8), transition: .immediate)
self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor
self.topPanelSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
}
let topPanelFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: 44.0))
let topPanelCoverHeight: CGFloat = 10.0
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -topPanelCoverHeight), size: CGSize(width: availableSize.width, height: 44.0))
transition.setFrame(view: self.topPanelBackgroundView, frame: topPanelFrame)
self.topPanelBackgroundView.update(size: topPanelFrame.size, transition: transition.containedViewLayoutTransition)
let lockScrollingTransition: Transition
if themeUpdated {
lockScrollingTransition = transition
} else {
lockScrollingTransition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
}
lockScrollingTransition.setBackgroundColor(view: self.topPanelBackgroundView, color: environment.isScrollable ? component.theme.rootController.navigationBar.blurredBackgroundColor : component.theme.list.itemBlocksBackgroundColor)
transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY)))
transition.setFrame(layer: self.topPanelSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
if let currentIdValue = self.currentId, !component.items.contains(where: { $0.id == currentIdValue }) {
self.currentId = nil
}
if self.currentId == nil {
self.currentId = component.items.first?.id
}
var visibleIds = Set<AnyHashable>()
var currentIndex: Int?
if let currentId = self.currentId {
visibleIds.insert(currentId)
if let index = component.items.firstIndex(where: { $0.id == currentId }) {
currentIndex = index
if index != 0 {
visibleIds.insert(component.items[index - 1].id)
}
if index != component.items.count - 1 {
visibleIds.insert(component.items[index + 1].id)
}
}
}
let _ = self.header.update(
transition: transition,
component: AnyComponent(StorageUsageHeaderComponent(
@@ -296,6 +593,17 @@ final class StorageUsagePanelContainerComponent: Component {
id: item.id,
title: item.title
)
},
activeIndex: currentIndex ?? 0,
transitionFraction: self.transitionFraction,
switchToPanel: { [weak self] id in
guard let self, let component = self.component else {
return
}
if component.items.contains(where: { $0.id == id }) {
self.currentId = id
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
}
}
)),
environment: {},
@@ -308,45 +616,114 @@ final class StorageUsagePanelContainerComponent: Component {
transition.setFrame(view: headerView, frame: topPanelFrame)
}
if let currentIdValue = self.currentId, !component.items.contains(where: { $0.id == currentIdValue }) {
self.currentId = nil
}
if self.currentId == nil {
self.currentId = component.items.first?.id
}
let childEnvironment = StorageUsagePanelEnvironment(
theme: component.theme,
strings: component.strings,
containerInsets: UIEdgeInsets(top: topPanelFrame.height, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right)
dateTimeFormat: component.dateTimeFormat,
containerInsets: UIEdgeInsets(top: 0.0, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right),
isScrollable: environment.isScrollable
)
let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY))
if self.animatingTransition {
visibleIds = visibleIds.filter({ self.visiblePanels[$0] != nil })
}
self.actualVisibleIds = visibleIds
for (id, _) in self.visiblePanels {
visibleIds.insert(id)
}
var validIds = Set<AnyHashable>()
if let currentId = self.currentId, let panelItem = component.items.first(where: { $0.id == currentId }) {
validIds.insert(panelItem.id)
let panel: ComponentView<StorageUsagePanelEnvironment>
var panelTransition = transition
if let current = self.visiblePanels[panelItem.id] {
panel = current
} else {
panelTransition = .immediate
panel = ComponentView()
self.visiblePanels[panelItem.id] = panel
}
let _ = panel.update(
transition: panelTransition,
component: panelItem.panel,
environment: {
childEnvironment
},
containerSize: availableSize
)
if let panelView = panel.view {
if panelView.superview == nil {
self.insertSubview(panelView, belowSubview: self.topPanelBackgroundView)
if let currentIndex {
var anyAnchorOffset: CGFloat = 0.0
for (id, panel) in self.visiblePanels {
guard let itemIndex = component.items.firstIndex(where: { $0.id == id }), let panelView = panel.view else {
continue
}
var itemFrame = centralPanelFrame.offsetBy(dx: self.transitionFraction * availableSize.width, dy: 0.0)
if itemIndex < currentIndex {
itemFrame.origin.x -= itemFrame.width
} else if itemIndex > currentIndex {
itemFrame.origin.x += itemFrame.width
}
anyAnchorOffset = itemFrame.minX - panelView.frame.minX
break
}
for id in visibleIds {
guard let itemIndex = component.items.firstIndex(where: { $0.id == id }) else {
continue
}
let panelItem = component.items[itemIndex]
var itemFrame = centralPanelFrame.offsetBy(dx: self.transitionFraction * availableSize.width, dy: 0.0)
if itemIndex < currentIndex {
itemFrame.origin.x -= itemFrame.width
} else if itemIndex > currentIndex {
itemFrame.origin.x += itemFrame.width
}
validIds.insert(panelItem.id)
let panel: ComponentView<StorageUsagePanelEnvironment>
var panelTransition = transition
var animateInIfNeeded = false
if let current = self.visiblePanels[panelItem.id] {
panel = current
if let panelView = panel.view, !panelView.bounds.isEmpty {
var wasHidden = false
if abs(panelView.frame.minX - availableSize.width) < .ulpOfOne || abs(panelView.frame.maxX - 0.0) < .ulpOfOne {
wasHidden = true
}
var isHidden = false
if abs(itemFrame.minX - availableSize.width) < .ulpOfOne || abs(itemFrame.maxX - 0.0) < .ulpOfOne {
isHidden = true
}
if wasHidden && isHidden {
panelTransition = .immediate
}
}
} else {
panelTransition = .immediate
animateInIfNeeded = true
panel = ComponentView()
self.visiblePanels[panelItem.id] = panel
}
let _ = panel.update(
transition: panelTransition,
component: panelItem.panel,
environment: {
childEnvironment
},
containerSize: centralPanelFrame.size
)
if let panelView = panel.view {
if panelView.superview == nil {
self.insertSubview(panelView, belowSubview: self.topPanelBackgroundView)
}
panelTransition.setFrame(view: panelView, frame: itemFrame, completion: { [weak self] _ in
guard let self else {
return
}
if !self.actualVisibleIds.contains(id) {
if let panel = self.visiblePanels[id] {
self.visiblePanels.removeValue(forKey: id)
panel.view?.removeFromSuperview()
}
}
})
if animateInIfNeeded && anyAnchorOffset != 0.0 {
transition.animatePosition(view: panelView, from: CGPoint(x: -anyAnchorOffset, y: 0.0), to: CGPoint(), additive: true)
}
}
panelTransition.setFrame(view: panelView, frame: CGRect(origin: CGPoint(), size: availableSize))
}
}
@@ -371,7 +748,7 @@ final class StorageUsagePanelContainerComponent: Component {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StorageUsagePanelContainerEnvironment>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@@ -0,0 +1,157 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import ViewControllerComponent
import ComponentDisplayAdapters
import TelegramPresentationData
import AccountContext
import TelegramCore
import MultilineTextComponent
import EmojiStatusComponent
import Postbox
import TelegramStringFormatting
import CheckNode
import SolidRoundedButtonComponent
final class StorageUsageScreenSelectionPanelComponent: Component {
let theme: PresentationTheme
let title: String
let label: String?
let isEnabled: Bool
let insets: UIEdgeInsets
let action: () -> Void
init(
theme: PresentationTheme,
title: String,
label: String?,
isEnabled: Bool,
insets: UIEdgeInsets,
action: @escaping () -> Void
) {
self.theme = theme
self.title = title
self.label = label
self.isEnabled = isEnabled
self.insets = insets
self.action = action
}
static func ==(lhs: StorageUsageScreenSelectionPanelComponent, rhs: StorageUsageScreenSelectionPanelComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.label != rhs.label {
return false
}
if lhs.isEnabled != rhs.isEnabled {
return false
}
if lhs.insets != rhs.insets {
return false
}
return true
}
class View: UIView {
private let backgroundView: BlurredBackgroundView
private let separatorLayer: SimpleLayer
private let actionButton = ComponentView<Empty>()
private var component: StorageUsageScreenSelectionPanelComponent?
override init(frame: CGRect) {
self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
self.separatorLayer = SimpleLayer()
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.layer.addSublayer(self.separatorLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: StorageUsageScreenSelectionPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
self.component = component
let topInset: CGFloat = 8.0
let bottomInset: CGFloat
if component.insets.bottom == 0.0 {
bottomInset = topInset
} else {
bottomInset = component.insets.bottom + 10.0
}
let height: CGFloat = topInset + 50.0 + bottomInset
if themeUpdated {
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor
}
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.height, height: height))
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition)
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
let actionButtonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(SolidRoundedButtonComponent(
title: component.title,
label: component.label,
theme: SolidRoundedButtonComponent.Theme(
backgroundColor: component.theme.list.itemCheckColors.fillColor,
backgroundColors: [],
foregroundColor: component.theme.list.itemCheckColors.foregroundColor
),
font: .bold,
fontSize: 17.0,
height: 50.0,
cornerRadius: 10.0,
gloss: false,
isEnabled: component.isEnabled,
animationName: nil,
iconPosition: .right,
iconSpacing: 4.0,
action: { [weak self] in
guard let self else {
return
}
self.component?.action()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - component.insets.left - component.insets.right, height: 50.0)
)
if let actionButtonView = self.actionButton.view {
if actionButtonView.superview == nil {
self.addSubview(actionButtonView)
}
transition.setFrame(view: actionButtonView, frame: CGRect(origin: CGPoint(x: component.insets.left, y: topInset), size: actionButtonSize))
}
return CGSize(width: availableSize.width, height: height)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}