mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-28 10:55:40 +00:00
237 lines
9.2 KiB
Swift
237 lines
9.2 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import TelegramPresentationData
|
|
import ComponentFlow
|
|
import GlassBackgroundComponent
|
|
import PlainButtonComponent
|
|
import BundleIconComponent
|
|
import MultilineTextComponent
|
|
|
|
public final class GlassControlGroupComponent: Component {
|
|
public final class Item: Equatable {
|
|
public enum Content: Hashable {
|
|
case icon(String)
|
|
case text(String)
|
|
}
|
|
|
|
public let id: AnyHashable
|
|
public let content: Content
|
|
public let action: (() -> Void)?
|
|
|
|
public init(id: AnyHashable, content: Content, action: (() -> Void)?) {
|
|
self.id = id
|
|
self.content = content
|
|
self.action = action
|
|
}
|
|
|
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
|
if lhs.id != rhs.id {
|
|
return false
|
|
}
|
|
if lhs.content != rhs.content {
|
|
return false
|
|
}
|
|
if (lhs.action == nil) != (rhs.action == nil) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public enum Background {
|
|
case panel
|
|
case activeTint
|
|
}
|
|
|
|
public let theme: PresentationTheme
|
|
public let background: Background
|
|
public let items: [Item]
|
|
public let minWidth: CGFloat
|
|
|
|
public init(
|
|
theme: PresentationTheme,
|
|
background: Background,
|
|
items: [Item],
|
|
minWidth: CGFloat
|
|
) {
|
|
self.theme = theme
|
|
self.background = background
|
|
self.items = items
|
|
self.minWidth = minWidth
|
|
}
|
|
|
|
public static func ==(lhs: GlassControlGroupComponent, rhs: GlassControlGroupComponent) -> Bool {
|
|
if lhs.theme !== rhs.theme {
|
|
return false
|
|
}
|
|
if lhs.background != rhs.background {
|
|
return false
|
|
}
|
|
if lhs.items != rhs.items {
|
|
return false
|
|
}
|
|
if lhs.minWidth != rhs.minWidth {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private let backgroundView: GlassBackgroundView
|
|
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
|
|
|
private var component: GlassControlGroupComponent?
|
|
private weak var state: EmptyComponentState?
|
|
|
|
override public init(frame: CGRect) {
|
|
self.backgroundView = GlassBackgroundView()
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.addSubview(self.backgroundView)
|
|
}
|
|
|
|
required public init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
public func itemView(id: AnyHashable) -> UIView? {
|
|
return self.itemViews[id]?.view
|
|
}
|
|
|
|
func update(component: GlassControlGroupComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
|
|
|
self.component = component
|
|
self.state = state
|
|
|
|
struct ItemId: Hashable {
|
|
var id: AnyHashable
|
|
var contentId: AnyHashable
|
|
|
|
init(id: AnyHashable, contentId: AnyHashable) {
|
|
self.id = id
|
|
self.contentId = contentId
|
|
}
|
|
}
|
|
|
|
var contentsWidth: CGFloat = 0.0
|
|
var validIds: [AnyHashable] = []
|
|
var isInteractive = false
|
|
for item in component.items {
|
|
let itemId = ItemId(id: item.id, contentId: item.content)
|
|
|
|
validIds.append(itemId)
|
|
|
|
let itemView: ComponentView<Empty>
|
|
var itemTransition = transition
|
|
if let current = self.itemViews[itemId] {
|
|
itemView = current
|
|
} else {
|
|
itemView = ComponentView()
|
|
self.itemViews[itemId] = itemView
|
|
itemTransition = itemTransition.withAnimation(.none)
|
|
}
|
|
|
|
if item.action != nil {
|
|
isInteractive = true
|
|
}
|
|
|
|
let content: AnyComponent<Empty>
|
|
var itemInsets = UIEdgeInsets()
|
|
switch item.content {
|
|
case let .icon(name):
|
|
content = AnyComponent(BundleIconComponent(
|
|
name: name,
|
|
tintColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor
|
|
))
|
|
case let .text(string):
|
|
content = AnyComponent(MultilineTextComponent(
|
|
text: .plain(NSAttributedString(string: string, font: Font.semibold(15.0), textColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor))
|
|
))
|
|
itemInsets.left = 10.0
|
|
itemInsets.right = itemInsets.left
|
|
}
|
|
|
|
var minItemWidth: CGFloat = 40.0
|
|
if component.items.count == 1 {
|
|
minItemWidth = max(minItemWidth, component.minWidth)
|
|
}
|
|
|
|
let itemSize = itemView.update(
|
|
transition: itemTransition,
|
|
component: AnyComponent(PlainButtonComponent(
|
|
content: content,
|
|
minSize: CGSize(width: minItemWidth, height: 40.0),
|
|
contentInsets: itemInsets,
|
|
action: {
|
|
item.action?()
|
|
},
|
|
isEnabled: item.action != nil,
|
|
animateAlpha: false,
|
|
animateScale: false,
|
|
animateContents: false
|
|
)),
|
|
environment: {},
|
|
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
|
)
|
|
let itemFrame = CGRect(origin: CGPoint(x: contentsWidth, y: 0.0), size: itemSize)
|
|
|
|
if let itemComponentView = itemView.view {
|
|
var animateIn = false
|
|
if itemComponentView.superview == nil {
|
|
animateIn = true
|
|
self.backgroundView.contentView.addSubview(itemComponentView)
|
|
itemComponentView.alpha = 0.0
|
|
}
|
|
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
|
if animateIn {
|
|
alphaTransition.setAlpha(view: itemComponentView, alpha: 1.0)
|
|
alphaTransition.animateBlur(layer: itemComponentView.layer, fromRadius: 8.0, toRadius: 0.0)
|
|
}
|
|
}
|
|
|
|
contentsWidth += itemSize.width
|
|
}
|
|
|
|
var removeIds: [AnyHashable] = []
|
|
for (id, itemView) in self.itemViews {
|
|
if !validIds.contains(id) {
|
|
removeIds.append(id)
|
|
if let itemComponentView = itemView.view {
|
|
alphaTransition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
|
|
itemComponentView?.removeFromSuperview()
|
|
})
|
|
alphaTransition.animateBlur(layer: itemComponentView.layer, fromRadius: 0.0, toRadius: 8.0, removeOnCompletion: false)
|
|
}
|
|
}
|
|
}
|
|
for id in removeIds {
|
|
self.itemViews.removeValue(forKey: id)
|
|
}
|
|
|
|
let size = CGSize(width: contentsWidth, height: availableSize.height)
|
|
let tintColor: GlassBackgroundView.TintColor
|
|
switch component.background {
|
|
case .panel:
|
|
tintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
|
|
case .activeTint:
|
|
tintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7), innerColor: component.theme.list.itemCheckColors.fillColor)
|
|
}
|
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
|
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: isInteractive, transition: transition)
|
|
|
|
return size
|
|
}
|
|
}
|
|
|
|
public func makeView() -> View {
|
|
return View(frame: CGRect())
|
|
}
|
|
|
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|