mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Passkeys
This commit is contained in:
@@ -16,6 +16,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/SwitchNode",
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
|
||||
private final class ContextOptionComponent: Component {
|
||||
let title: String
|
||||
let color: UIColor
|
||||
let isLast: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
title: String,
|
||||
color: UIColor,
|
||||
isLast: Bool,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.color = color
|
||||
self.isLast = isLast
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: ContextOptionComponent, rhs: ContextOptionComponent) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.isLast != rhs.isLast {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
let backgroundView: UIView
|
||||
let title = ComponentView<Empty>()
|
||||
|
||||
var component: ContextOptionComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundView = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.component?.action()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: ContextOptionComponent, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let sideInset: CGFloat = 8.0
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(17.0), textColor: .white))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let size = CGSize(width: sideInset * 2.0 + titleSize.width, height: availableSize.height)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - titleSize.height) * 0.5)), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
transition.setPosition(view: titleView, position: titleFrame.center)
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
}
|
||||
|
||||
self.backgroundView.backgroundColor = component.color
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width + (component.isLast ? 1000.0 : 0.0), height: size.height)))
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class ContentContainer: UIScrollView, UIScrollViewDelegate {
|
||||
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
|
||||
private var ignoreScrollingEvents: Bool = false
|
||||
private var draggingBeganInClosedState: Bool = false
|
||||
|
||||
private var contextOptions: [ListActionItemComponent.ContextOption] = []
|
||||
private var optionsWidth: CGFloat = 0.0
|
||||
|
||||
private var revealedStateTapRecognizer: UITapGestureRecognizer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
let revealedStateTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))
|
||||
self.revealedStateTapRecognizer = revealedStateTapRecognizer
|
||||
revealedStateTapRecognizer.isEnabled = false
|
||||
self.addGestureRecognizer(revealedStateTapRecognizer)
|
||||
|
||||
self.delaysContentTouches = false
|
||||
self.canCancelContentTouches = true
|
||||
self.clipsToBounds = false
|
||||
self.contentInsetAdjustmentBehavior = .never
|
||||
self.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
self.showsVerticalScrollIndicator = false
|
||||
self.showsHorizontalScrollIndicator = false
|
||||
self.alwaysBounceHorizontal = false
|
||||
self.alwaysBounceVertical = false
|
||||
self.scrollsToTop = false
|
||||
self.delegate = self
|
||||
|
||||
self.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.contentOffset.x != 0.0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.revealedStateTapRecognizer?.isEnabled = self.contentOffset.x > 0.0
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
self.draggingBeganInClosedState = self.contentOffset.x == 0.0
|
||||
}
|
||||
|
||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
targetContentOffset.pointee.x = self.contentOffset.x
|
||||
|
||||
if self.contentOffset.x >= self.optionsWidth + 30.0 {
|
||||
self.contextOptions.last?.action()
|
||||
} else {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.draggingBeganInClosedState {
|
||||
if self.contentOffset.x > 20.0 {
|
||||
self.setContentOffset(CGPoint(x: self.optionsWidth, y: 0.0), animated: true)
|
||||
} else {
|
||||
self.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: true)
|
||||
}
|
||||
} else {
|
||||
if self.contentOffset.x < self.optionsWidth - 20.0 {
|
||||
self.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: true)
|
||||
} else {
|
||||
self.setContentOffset(CGPoint(x: self.optionsWidth, y: 0.0), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let revealedStateTapRecognizer = self.revealedStateTapRecognizer, revealedStateTapRecognizer.isEnabled {
|
||||
if self.bounds.contains(point), point.x < self.bounds.width {
|
||||
return self
|
||||
}
|
||||
}
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func update(size: CGSize, contextOptions: [ListActionItemComponent.ContextOption], transition: ComponentTransition) {
|
||||
self.contextOptions = contextOptions
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
var optionsWidth: CGFloat = 0.0
|
||||
for i in 0 ..< contextOptions.count {
|
||||
let option = contextOptions[i]
|
||||
validIds.append(option.id)
|
||||
|
||||
let itemView: ComponentView<Empty>
|
||||
var itemTransition = transition
|
||||
if let current = self.itemViews[option.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = itemTransition.withAnimation(.none)
|
||||
itemView = ComponentView()
|
||||
self.itemViews[option.id] = itemView
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(ContextOptionComponent(
|
||||
title: option.title,
|
||||
color: option.color,
|
||||
isLast: i == contextOptions.count - 1,
|
||||
action: option.action
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 10000.0, height: size.height)
|
||||
)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: size.width + optionsWidth, y: 0.0), size: itemSize)
|
||||
optionsWidth += itemSize.width
|
||||
if let itemComponentView = itemView.view {
|
||||
self.addSubview(itemComponentView)
|
||||
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
}
|
||||
}
|
||||
var removedIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validIds.contains(id) {
|
||||
removedIds.append(id)
|
||||
if let itemComponentView = itemView.view {
|
||||
itemComponentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in removedIds {
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
}
|
||||
self.optionsWidth = optionsWidth
|
||||
|
||||
let contentSize = CGSize(width: size.width + optionsWidth, height: size.height)
|
||||
if self.contentSize != contentSize {
|
||||
self.contentSize = contentSize
|
||||
}
|
||||
self.isScrollEnabled = optionsWidth != 0.0
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,33 @@ public final class ListActionItemComponent: Component {
|
||||
case center
|
||||
}
|
||||
|
||||
public final class ContextOption: Equatable {
|
||||
public let id: AnyHashable
|
||||
public let title: String
|
||||
public let color: UIColor
|
||||
public let action: () -> Void
|
||||
|
||||
public init(id: AnyHashable, title: String, color: UIColor, action: @escaping () -> Void) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.color = color
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: ContextOption, rhs: ContextOption) -> Bool {
|
||||
if lhs.id != rhs.id {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public let theme: PresentationTheme
|
||||
public let style: Style
|
||||
public let background: AnyComponent<Empty>?
|
||||
@@ -147,6 +174,7 @@ public final class ListActionItemComponent: Component {
|
||||
public let leftIcon: LeftIcon?
|
||||
public let icon: Icon?
|
||||
public let accessory: Accessory?
|
||||
public let contextOptions: [ContextOption]
|
||||
public let action: ((UIView) -> Void)?
|
||||
public let highlighting: Highlighting
|
||||
public let updateIsHighlighted: ((UIView, Bool) -> Void)?
|
||||
@@ -161,6 +189,7 @@ public final class ListActionItemComponent: Component {
|
||||
leftIcon: LeftIcon? = nil,
|
||||
icon: Icon? = nil,
|
||||
accessory: Accessory? = .arrow,
|
||||
contextOptions: [ContextOption] = [],
|
||||
action: ((UIView) -> Void)?,
|
||||
highlighting: Highlighting = .default,
|
||||
updateIsHighlighted: ((UIView, Bool) -> Void)? = nil
|
||||
@@ -174,6 +203,7 @@ public final class ListActionItemComponent: Component {
|
||||
self.leftIcon = leftIcon
|
||||
self.icon = icon
|
||||
self.accessory = accessory
|
||||
self.contextOptions = contextOptions
|
||||
self.action = action
|
||||
self.highlighting = highlighting
|
||||
self.updateIsHighlighted = updateIsHighlighted
|
||||
@@ -207,6 +237,9 @@ public final class ListActionItemComponent: Component {
|
||||
if lhs.accessory != rhs.accessory {
|
||||
return false
|
||||
}
|
||||
if lhs.contextOptions != rhs.contextOptions {
|
||||
return false
|
||||
}
|
||||
if (lhs.action == nil) != (rhs.action == nil) {
|
||||
return false
|
||||
}
|
||||
@@ -289,7 +322,9 @@ public final class ListActionItemComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton, ListSectionComponent.ChildView {
|
||||
public final class View: UIView, ListSectionComponent.ChildView {
|
||||
private let container: ContentContainer
|
||||
private let button: HighlightTrackingButton
|
||||
private var background: ComponentView<Empty>?
|
||||
private let title = ComponentView<Empty>()
|
||||
private var leftIcon: ComponentView<Empty>?
|
||||
@@ -316,10 +351,16 @@ public final class ListActionItemComponent: Component {
|
||||
public var separatorInset: CGFloat = 0.0
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
self.container = ContentContainer(frame: CGRect())
|
||||
self.button = HighlightTrackingButton()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
self.internalHighligthedChanged = { [weak self] isHighlighted in
|
||||
self.addSubview(self.container)
|
||||
self.container.addSubview(self.button)
|
||||
|
||||
self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
self.button.internalHighligthedChanged = { [weak self] isHighlighted in
|
||||
guard let self, let component = self.component, component.action != nil else {
|
||||
return
|
||||
}
|
||||
@@ -479,7 +520,7 @@ public final class ListActionItemComponent: Component {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - contentRightInset - iconSize.width + iconOffset, y: floor((contentHeight - iconSize.height) * 0.5)), size: iconSize)
|
||||
if let iconView = icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.addSubview(iconView)
|
||||
self.button.addSubview(iconView)
|
||||
transition.animateAlpha(view: iconView, from: 0.0, to: 1.0)
|
||||
}
|
||||
iconView.isUserInteractionEnabled = iconValue.allowUserInteraction
|
||||
@@ -516,7 +557,7 @@ public final class ListActionItemComponent: Component {
|
||||
animateIn = true
|
||||
leftCheckView = CheckView()
|
||||
self.leftCheckView = leftCheckView
|
||||
self.addSubview(leftCheckView)
|
||||
self.button.addSubview(leftCheckView)
|
||||
|
||||
leftCheckView.action = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
@@ -596,7 +637,7 @@ public final class ListActionItemComponent: Component {
|
||||
if let leftIconView = leftIcon.view {
|
||||
if leftIconView.superview == nil {
|
||||
leftIconView.isUserInteractionEnabled = false
|
||||
self.addSubview(leftIconView)
|
||||
self.button.addSubview(leftIconView)
|
||||
transition.animateAlpha(view: leftIconView, from: 0.0, to: 1.0)
|
||||
}
|
||||
leftIconTransition.setFrame(view: leftIconView, frame: leftIconFrame)
|
||||
@@ -634,7 +675,7 @@ public final class ListActionItemComponent: Component {
|
||||
arrowTransition = arrowTransition.withAnimation(.none)
|
||||
arrowView = UIImageView(image: PresentationResourcesItemList.disclosureArrowImage(component.theme))
|
||||
self.arrowView = arrowView
|
||||
self.addSubview(arrowView)
|
||||
self.button.addSubview(arrowView)
|
||||
}
|
||||
|
||||
if let image = arrowView.image {
|
||||
@@ -660,7 +701,7 @@ public final class ListActionItemComponent: Component {
|
||||
arrowTransition = arrowTransition.withAnimation(.none)
|
||||
arrowView = UIImageView(image: PresentationResourcesItemList.disclosureOptionArrowsImage(component.theme))
|
||||
self.arrowView = arrowView
|
||||
self.addSubview(arrowView)
|
||||
self.button.addSubview(arrowView)
|
||||
}
|
||||
|
||||
if let image = arrowView.image {
|
||||
@@ -689,7 +730,7 @@ public final class ListActionItemComponent: Component {
|
||||
switchNode = SwitchNode()
|
||||
switchNode.setOn(toggle.isOn, animated: false)
|
||||
self.switchNode = switchNode
|
||||
self.addSubview(switchNode.view)
|
||||
self.button.addSubview(switchNode.view)
|
||||
|
||||
switchNode.valueUpdated = { [weak self] value in
|
||||
guard let self, let component = self.component else {
|
||||
@@ -739,7 +780,7 @@ public final class ListActionItemComponent: Component {
|
||||
switchNode.updateIsLocked(toggle.style == .lock)
|
||||
switchNode.setOn(toggle.isOn, animated: false)
|
||||
self.iconSwitchNode = switchNode
|
||||
self.addSubview(switchNode.view)
|
||||
self.button.addSubview(switchNode.view)
|
||||
|
||||
switchNode.valueUpdated = { [weak self] value in
|
||||
guard let self, let component = self.component else {
|
||||
@@ -792,7 +833,7 @@ public final class ListActionItemComponent: Component {
|
||||
activityIndicatorView = UIActivityIndicatorView(style: .gray)
|
||||
}
|
||||
self.activityIndicatorView = activityIndicatorView
|
||||
self.addSubview(activityIndicatorView)
|
||||
self.button.addSubview(activityIndicatorView)
|
||||
activityIndicatorView.sizeToFit()
|
||||
}
|
||||
|
||||
@@ -818,7 +859,7 @@ public final class ListActionItemComponent: Component {
|
||||
if let customAccessoryComponentView = customAccessoryView.view {
|
||||
if customAccessoryComponentView.superview == nil {
|
||||
customAccessoryComponentView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
||||
self.addSubview(customAccessoryComponentView)
|
||||
self.button.addSubview(customAccessoryComponentView)
|
||||
}
|
||||
customAccessoryComponentView.isUserInteractionEnabled = customAccessory.isInteractive
|
||||
customAccessoryTransition.setPosition(view: customAccessoryComponentView, position: CGPoint(x: activityAccessoryFrame.maxX, y: activityAccessoryFrame.minY))
|
||||
@@ -835,7 +876,7 @@ public final class ListActionItemComponent: Component {
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
self.button.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
@@ -879,7 +920,12 @@ public final class ListActionItemComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
let size = CGSize(width: availableSize.width, height: contentHeight)
|
||||
self.container.update(size: size, contextOptions: component.contextOptions, transition: transition)
|
||||
transition.setFrame(view: self.container, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setFrame(view: self.button, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -299,8 +299,15 @@ final class PasskeysScreenComponent: Component {
|
||||
component: AnyComponent(PasskeysScreenListComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
insets: UIEdgeInsets(top: environment.statusBarHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0),
|
||||
passkeys: passkeysData,
|
||||
addPasskeyAction: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.createPasskey()
|
||||
},
|
||||
deletePasskeyAction: { [weak self] id in
|
||||
guard let self else {
|
||||
return
|
||||
|
||||
@@ -17,21 +17,27 @@ import EmojiStatusComponent
|
||||
final class PasskeysScreenListComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let insets: UIEdgeInsets
|
||||
let passkeys: [TelegramPasskey]
|
||||
let addPasskeyAction: () -> Void
|
||||
let deletePasskeyAction: (String) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
insets: UIEdgeInsets,
|
||||
passkeys: [TelegramPasskey],
|
||||
addPasskeyAction: @escaping () -> Void,
|
||||
deletePasskeyAction: @escaping (String) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.insets = insets
|
||||
self.passkeys = passkeys
|
||||
self.addPasskeyAction = addPasskeyAction
|
||||
self.deletePasskeyAction = deletePasskeyAction
|
||||
}
|
||||
|
||||
@@ -42,6 +48,9 @@ final class PasskeysScreenListComponent: Component {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.insets != rhs.insets {
|
||||
return false
|
||||
}
|
||||
@@ -101,6 +110,11 @@ final class PasskeysScreenListComponent: Component {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var maxPasskeys = 5
|
||||
if let data = component.context.currentAppConfiguration.with({ $0 }).data, let maxValue = data["passkeys_account_passkeys_max"] as? Double {
|
||||
maxPasskeys = Int(maxValue)
|
||||
}
|
||||
|
||||
self.backgroundColor = component.theme.list.blocksBackgroundColor
|
||||
|
||||
let sideInset: CGFloat = 16.0 + component.insets.left
|
||||
@@ -227,7 +241,7 @@ final class PasskeysScreenListComponent: Component {
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: passkey.name,
|
||||
string: passkey.name.isEmpty ? "Passkey" : passkey.name, //TODO:localize
|
||||
font: Font.regular(17.0),
|
||||
textColor: component.theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
@@ -247,11 +261,45 @@ final class PasskeysScreenListComponent: Component {
|
||||
false
|
||||
),
|
||||
accessory: nil,
|
||||
contextOptions: [ListActionItemComponent.ContextOption(
|
||||
id: "delete",
|
||||
title: component.strings.Common_Delete,
|
||||
color: component.theme.list.itemDisclosureActions.destructive.fillColor,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.deletePasskeyAction(passkeyId)
|
||||
}
|
||||
)],
|
||||
action: nil,
|
||||
highlighting: .default
|
||||
))))
|
||||
}
|
||||
|
||||
if component.passkeys.count < maxPasskeys {
|
||||
listSectionItems.append(AnyComponentWithIdentity(id: "_add", component: AnyComponent(ListActionItemComponent(
|
||||
theme: component.theme,
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Create Passkey", //TODO:localize
|
||||
font: Font.regular(17.0),
|
||||
textColor: component.theme.list.itemAccentColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)))
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat List/AddIcon",
|
||||
tintColor: component.theme.list.itemAccentColor
|
||||
))), false),
|
||||
accessory: nil,
|
||||
action: { [weak self] _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.deletePasskeyAction(passkeyId)
|
||||
component.addPasskeyAction()
|
||||
},
|
||||
highlighting: .default
|
||||
))))
|
||||
|
||||
Reference in New Issue
Block a user