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 TelegramStringFormatting import CheckNode import SegmentedControlNode final class SegmentControlComponent: Component { struct Item: Equatable { var id: AnyHashable var title: String } let theme: PresentationTheme let items: [Item] let selectedId: AnyHashable? let action: (AnyHashable) -> Void init( theme: PresentationTheme, items: [Item], selectedId: AnyHashable?, action: @escaping (AnyHashable) -> Void ) { self.theme = theme self.items = items self.selectedId = selectedId self.action = action } static func ==(lhs: SegmentControlComponent, rhs: SegmentControlComponent) -> Bool { if lhs.theme !== rhs.theme { return false } if lhs.items != rhs.items { return false } if lhs.selectedId != rhs.selectedId { return false } return true } class SegmentedControlView: UISegmentedControl { var foregroundColor: UIColor? { didSet { self.resetChrome() } } override func touchesBegan(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) self.resetChrome() } override func touchesEnded(_ touches: Set, with event: UIEvent?) { super.touchesEnded(touches, with: event) self.resetChrome() } func resetChrome() { for case let view as UIImageView in self.subviews { view.isHidden = true } if let selectorView = self.subviews.last, let loupe = selectorView.subviews.first, let innerLoupe = loupe.subviews.first { for view in innerLoupe.subviews { if type(of: view) == UIView.self { view.backgroundColor = self.foregroundColor } } } } override func layoutSubviews() { super.layoutSubviews() self.resetChrome() } } class View: UIView { private let title = ComponentView() private var component: SegmentControlComponent? private var nativeSegmentedView: SegmentedControlView? private var legacySegmentedNode: SegmentedControlNode? override init(frame: CGRect) { super.init(frame: frame) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(component: SegmentControlComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let themeUpdated = self.component?.theme !== component.theme self.component = component var controlSize = CGSize() if #available(iOS 26.0, *) { let segmentedView: SegmentedControlView if let current = self.nativeSegmentedView { segmentedView = current } else { let mappedActions: [UIAction] = component.items.map { item -> UIAction in return UIAction(title: item.title, handler: { [weak self] _ in guard let self, let component = self.component else { return } component.action(item.id) }) } segmentedView = SegmentedControlView(frame: .zero, actions: mappedActions) segmentedView.selectedSegmentIndex = component.items.firstIndex(where: { $0.id == component.selectedId }) ?? 0 self.nativeSegmentedView = segmentedView self.addSubview(segmentedView) } if themeUpdated { let backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.itemBlocksBackgroundColor : component.theme.rootController.navigationBar.segmentedBackgroundColor segmentedView.setTitleTextAttributes([ .font: UIFont.boldSystemFont(ofSize: 15.0), .foregroundColor: component.theme.rootController.navigationBar.segmentedTextColor ], for: .normal) segmentedView.foregroundColor = component.theme.rootController.navigationBar.segmentedForegroundColor segmentedView.backgroundColor = backgroundColor } controlSize = segmentedView.sizeThatFits(availableSize) controlSize.width = min(availableSize.width - 32.0, max(300.0, controlSize.width)) controlSize.height = 36.0 segmentedView.frame = CGRect(origin: .zero, size: controlSize) } else { let segmentedNode: SegmentedControlNode if let current = self.legacySegmentedNode { segmentedNode = current if themeUpdated { let backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.itemBlocksBackgroundColor : component.theme.rootController.navigationBar.segmentedBackgroundColor let controlTheme = SegmentedControlTheme(backgroundColor: backgroundColor, foregroundColor: component.theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .clear, textColor: component.theme.rootController.navigationBar.segmentedTextColor, dividerColor: component.theme.rootController.navigationBar.segmentedDividerColor) segmentedNode.updateTheme(controlTheme) } } else { let mappedItems: [SegmentedControlItem] = component.items.map { item -> SegmentedControlItem in return SegmentedControlItem(title: item.title) } let backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.itemBlocksBackgroundColor : component.theme.rootController.navigationBar.segmentedBackgroundColor let controlTheme = SegmentedControlTheme(backgroundColor: backgroundColor, foregroundColor: component.theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .clear, textColor: component.theme.rootController.navigationBar.segmentedTextColor, dividerColor: component.theme.rootController.navigationBar.segmentedDividerColor) segmentedNode = SegmentedControlNode(theme: controlTheme, items: mappedItems, selectedIndex: component.items.firstIndex(where: { $0.id == component.selectedId }) ?? 0, cornerRadius: 18.0) self.legacySegmentedNode = segmentedNode self.addSubnode(segmentedNode) segmentedNode.selectedIndexChanged = { [weak self] index in guard let self, let component = self.component else { return } component.action(component.items[index].id) } } controlSize = segmentedNode.updateLayout(SegmentedControlLayout.sizeToFit(maximumWidth: availableSize.width, minimumWidth: min(availableSize.width, 300.0), height: 36.0), transition: transition.containedViewLayoutTransition) transition.containedViewLayoutTransition.updateFrame(node: segmentedNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: controlSize)) } return controlSize } } func makeView() -> View { return View(frame: CGRect()) } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } }