mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Folder improvements
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AnimatedCounterComponent",
|
||||
module_name = "AnimatedCounterComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,279 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
final class AnimatedCounterItemComponent: Component {
|
||||
public let font: UIFont
|
||||
public let color: UIColor
|
||||
public let text: String
|
||||
public let numericValue: Int
|
||||
public let alignment: CGFloat
|
||||
|
||||
public init(
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
text: String,
|
||||
numericValue: Int,
|
||||
alignment: CGFloat
|
||||
) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.text = text
|
||||
self.numericValue = numericValue
|
||||
self.alignment = alignment
|
||||
}
|
||||
|
||||
public static func ==(lhs: AnimatedCounterItemComponent, rhs: AnimatedCounterItemComponent) -> Bool {
|
||||
if lhs.font != rhs.font {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.numericValue != rhs.numericValue {
|
||||
return false
|
||||
}
|
||||
if lhs.alignment != rhs.alignment {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let contentView: UIImageView
|
||||
|
||||
private var component: AnimatedCounterItemComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.contentView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.contentView)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: AnimatedCounterItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let previousNumericValue = self.component?.numericValue
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let text = NSAttributedString(string: component.text, font: component.font, textColor: component.color)
|
||||
let textBounds = text.boundingRect(with: availableSize, options: [.usesLineFragmentOrigin], context: nil)
|
||||
let size = CGSize(width: ceil(textBounds.width), height: ceil(textBounds.height))
|
||||
|
||||
let previousContentImage = self.contentView.image
|
||||
let previousContentFrame = self.contentView.frame
|
||||
|
||||
self.contentView.image = generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
text.draw(at: textBounds.origin)
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
self.contentView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
if !transition.animation.isImmediate, let previousContentImage, !previousContentFrame.isEmpty, let previousNumericValue, previousNumericValue != component.numericValue {
|
||||
let previousContentView = UIImageView()
|
||||
previousContentView.image = previousContentImage
|
||||
previousContentView.frame = CGRect(origin: CGPoint(x: size.width * component.alignment - previousContentFrame.width * component.alignment, y: previousContentFrame.minY), size: previousContentFrame.size)
|
||||
self.addSubview(previousContentView)
|
||||
|
||||
let offsetY: CGFloat = size.height * 0.6 * (previousNumericValue < component.numericValue ? -1.0 : 1.0)
|
||||
|
||||
let subTransition = Transition(animation: .curve(duration: 0.16, curve: .easeInOut))
|
||||
|
||||
subTransition.animatePosition(view: self.contentView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
|
||||
subTransition.animateAlpha(view: self.contentView, from: 0.0, to: 1.0)
|
||||
|
||||
subTransition.setPosition(view: previousContentView, position: CGPoint(x: previousContentView.layer.position.x, y: previousContentView.layer.position.y - offsetY))
|
||||
subTransition.setAlpha(view: previousContentView, alpha: 0.0, completion: { [weak previousContentView] _ in
|
||||
previousContentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final class AnimatedCounterComponent: Component {
|
||||
public enum Alignment {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
public struct Item: Equatable {
|
||||
public var id: AnyHashable
|
||||
public var text: String
|
||||
public var numericValue: Int
|
||||
|
||||
public init(id: AnyHashable, text: String, numericValue: Int) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.numericValue = numericValue
|
||||
}
|
||||
}
|
||||
|
||||
public let font: UIFont
|
||||
public let color: UIColor
|
||||
public let alignment: Alignment
|
||||
public let items: [Item]
|
||||
|
||||
public init(
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
alignment: Alignment,
|
||||
items: [Item]
|
||||
) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.alignment = alignment
|
||||
self.items = items
|
||||
}
|
||||
|
||||
public static func ==(lhs: AnimatedCounterComponent, rhs: AnimatedCounterComponent) -> Bool {
|
||||
if lhs.font != rhs.font {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.alignment != rhs.alignment {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class ItemView {
|
||||
let view = ComponentView<Empty>()
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var itemViews: [AnyHashable: ItemView] = [:]
|
||||
|
||||
private var component: AnimatedCounterComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var measuredSpaceWidth: CGFloat?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: AnimatedCounterComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let spaceWidth: CGFloat
|
||||
if let measuredSpaceWidth = self.measuredSpaceWidth, let previousComponent = self.component, previousComponent.font.pointSize == component.font.pointSize {
|
||||
spaceWidth = measuredSpaceWidth
|
||||
} else {
|
||||
spaceWidth = ceil(NSAttributedString(string: " ", font: component.font, textColor: .black).boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil).width)
|
||||
self.measuredSpaceWidth = spaceWidth
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var size = CGSize()
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
for item in component.items {
|
||||
if size.width != 0.0 {
|
||||
size.width += spaceWidth
|
||||
}
|
||||
|
||||
validIds.append(item.id)
|
||||
|
||||
let itemView: ItemView
|
||||
var itemTransition = transition
|
||||
if let current = self.itemViews[item.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = .immediate
|
||||
itemView = ItemView()
|
||||
self.itemViews[item.id] = itemView
|
||||
}
|
||||
|
||||
let itemSize = itemView.view.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(AnimatedCounterItemComponent(
|
||||
font: component.font,
|
||||
color: component.color,
|
||||
text: item.text,
|
||||
numericValue: item.numericValue,
|
||||
alignment: component.alignment == .left ? 0.0 : 1.0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
|
||||
if let itemComponentView = itemView.view.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.addSubview(itemComponentView)
|
||||
}
|
||||
let itemFrame = CGRect(origin: CGPoint(x: size.width, y: 0.0), size: itemSize)
|
||||
switch component.alignment {
|
||||
case .left:
|
||||
itemComponentView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5)
|
||||
itemTransition.setPosition(view: itemComponentView, position: CGPoint(x: itemFrame.minX, y: itemFrame.midY))
|
||||
case .right:
|
||||
itemComponentView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5)
|
||||
itemTransition.setPosition(view: itemComponentView, position: CGPoint(x: itemFrame.maxX, y: itemFrame.midY))
|
||||
}
|
||||
itemComponentView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
}
|
||||
|
||||
size.width += itemSize.width
|
||||
size.height = max(size.height, itemSize.height)
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
if let componentView = itemView.view.view {
|
||||
transition.setAlpha(view: componentView, alpha: 0.0, completion: { [weak componentView] _ in
|
||||
componentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user