mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Initial implementation of channel overscroll navigation
This commit is contained in:
68
submodules/ComponentFlow/Source/Components/Button.swift
Normal file
68
submodules/ComponentFlow/Source/Components/Button.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final class Button: CombinedComponent, Equatable {
|
||||
let content: AnyComponent<Empty>
|
||||
let insets: UIEdgeInsets
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
content: AnyComponent<Empty>,
|
||||
insets: UIEdgeInsets,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.content = content
|
||||
self.insets = insets
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: Button, rhs: Button) -> Bool {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
if lhs.insets != rhs.insets {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
var isHighlighted = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State()
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let content = Child(environment: Empty.self)
|
||||
|
||||
return { context in
|
||||
let content = content.update(
|
||||
component: context.component.content,
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 44.0), transition: context.transition
|
||||
)
|
||||
|
||||
let size = CGSize(width: content.size.width + context.component.insets.left + context.component.insets.right, height: content.size.height + context.component.insets.top + context.component.insets.bottom)
|
||||
|
||||
let component = context.component
|
||||
|
||||
context.add(content
|
||||
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||
.opacity(context.state.isHighlighted ? 0.2 : 1.0)
|
||||
.update(Transition.Update { component, view, transition in
|
||||
view.frame = component.size.centered(around: component._position ?? CGPoint())
|
||||
})
|
||||
.gesture(.tap {
|
||||
component.action()
|
||||
})
|
||||
)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
}
|
||||
48
submodules/ComponentFlow/Source/Components/List.swift
Normal file
48
submodules/ComponentFlow/Source/Components/List.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public final class List<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
public typealias EnvironmentType = ChildEnvironment
|
||||
|
||||
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
||||
private let appear: Transition.Appear
|
||||
|
||||
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], appear: Transition.Appear = .default()) {
|
||||
self.items = items
|
||||
self.appear = appear
|
||||
}
|
||||
|
||||
public static func ==(lhs: List<ChildEnvironment>, rhs: List<ChildEnvironment>) -> Bool {
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static var body: Body {
|
||||
let children = ChildMap(environment: ChildEnvironment.self, keyedBy: AnyHashable.self)
|
||||
|
||||
return { context in
|
||||
let updatedChildren = context.component.items.map { item in
|
||||
return children[item.id].update(
|
||||
component: item.component, environment: {
|
||||
context.environment[ChildEnvironment.self]
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
}
|
||||
|
||||
var nextOrigin: CGFloat = 0.0
|
||||
for child in updatedChildren {
|
||||
context.add(child
|
||||
.position(CGPoint(x: child.size.width / 2.0, y: nextOrigin + child.size.height / 2.0))
|
||||
.appear(context.component.appear)
|
||||
)
|
||||
nextOrigin += child.size.height
|
||||
}
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
41
submodules/ComponentFlow/Source/Components/Rectangle.swift
Normal file
41
submodules/ComponentFlow/Source/Components/Rectangle.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public final class Rectangle: Component {
|
||||
private let color: UIColor
|
||||
private let width: CGFloat?
|
||||
private let height: CGFloat?
|
||||
|
||||
public init(color: UIColor, width: CGFloat? = nil, height: CGFloat? = nil) {
|
||||
self.color = color
|
||||
self.width = width
|
||||
self.height = height
|
||||
}
|
||||
|
||||
public static func ==(lhs: Rectangle, rhs: Rectangle) -> Bool {
|
||||
if !lhs.color.isEqual(rhs.color) {
|
||||
return false
|
||||
}
|
||||
if lhs.width != rhs.width {
|
||||
return false
|
||||
}
|
||||
if lhs.height != rhs.height {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
var size = availableSize
|
||||
if let width = self.width {
|
||||
size.width = min(size.width, width)
|
||||
}
|
||||
if let height = self.height {
|
||||
size.height = min(size.height, height)
|
||||
}
|
||||
|
||||
view.backgroundColor = self.color
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
101
submodules/ComponentFlow/Source/Components/Text.swift
Normal file
101
submodules/ComponentFlow/Source/Components/Text.swift
Normal file
@@ -0,0 +1,101 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public final class Text: Component {
|
||||
private final class MeasureState: Equatable {
|
||||
let attributedText: NSAttributedString
|
||||
let availableSize: CGSize
|
||||
let size: CGSize
|
||||
|
||||
init(attributedText: NSAttributedString, availableSize: CGSize, size: CGSize) {
|
||||
self.attributedText = attributedText
|
||||
self.availableSize = availableSize
|
||||
self.size = size
|
||||
}
|
||||
|
||||
static func ==(lhs: MeasureState, rhs: MeasureState) -> Bool {
|
||||
if !lhs.attributedText.isEqual(rhs.attributedText) {
|
||||
return false
|
||||
}
|
||||
if lhs.availableSize != rhs.availableSize {
|
||||
return false
|
||||
}
|
||||
if lhs.size != rhs.size {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var measureState: MeasureState?
|
||||
|
||||
func update(component: Text, availableSize: CGSize) -> CGSize {
|
||||
let attributedText = NSAttributedString(string: component.text, attributes: [
|
||||
NSAttributedString.Key.font: component.font,
|
||||
NSAttributedString.Key.foregroundColor: component.color
|
||||
])
|
||||
|
||||
if let measureState = self.measureState {
|
||||
if measureState.attributedText.isEqual(to: attributedText) && measureState.availableSize == availableSize {
|
||||
return measureState.size
|
||||
}
|
||||
}
|
||||
|
||||
var boundingRect = attributedText.boundingRect(with: availableSize, options: .usesLineFragmentOrigin, context: nil)
|
||||
boundingRect.size.width = ceil(boundingRect.size.width)
|
||||
boundingRect.size.height = ceil(boundingRect.size.height)
|
||||
|
||||
let measureState = MeasureState(attributedText: attributedText, availableSize: availableSize, size: boundingRect.size)
|
||||
if #available(iOS 10.0, *) {
|
||||
let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: measureState.size))
|
||||
let image = renderer.image { context in
|
||||
UIGraphicsPushContext(context.cgContext)
|
||||
measureState.attributedText.draw(at: CGPoint())
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
self.layer.contents = image.cgImage
|
||||
} else {
|
||||
UIGraphicsBeginImageContextWithOptions(measureState.size, false, 0.0)
|
||||
measureState.attributedText.draw(at: CGPoint())
|
||||
self.layer.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
self.measureState = measureState
|
||||
|
||||
return boundingRect.size
|
||||
}
|
||||
}
|
||||
|
||||
public let text: String
|
||||
public let font: UIFont
|
||||
public let color: UIColor
|
||||
|
||||
public init(text: String, font: UIFont, color: UIColor) {
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.color = color
|
||||
}
|
||||
|
||||
public static func ==(lhs: Text, rhs: Text) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if !lhs.font.isEqual(rhs.font) {
|
||||
return false
|
||||
}
|
||||
if !lhs.color.isEqual(rhs.color) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user