mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-02-09 04:44:55 +00:00
367 lines
14 KiB
Swift
367 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import ComponentFlow
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import MultilineTextComponent
|
|
import MultilineTextWithEntitiesComponent
|
|
import Markdown
|
|
import TextFormat
|
|
import AccountContext
|
|
|
|
private let titleFont = Font.bold(17.0)
|
|
private let defaultTextFont = Font.regular(15.0)
|
|
private let defaultBoldTextFont = Font.semibold(15.0)
|
|
private let defaultItalicTextFont = Font.italic(15.0)
|
|
private let defaultBoldItalicTextFont = Font.with(size: 15.0, weight: .semibold, traits: [.italic])
|
|
private let defaultFixedTextFont = Font.monospace(15.0)
|
|
private let smallTextFont = Font.regular(14.0)
|
|
private let smallBoldTextFont = Font.semibold(14.0)
|
|
private let smallItalicTextFont = Font.italic(14.0)
|
|
private let smallBoldItalicTextFont = Font.with(size: 14.0, weight: .semibold, traits: [.italic])
|
|
private let smallFixedTextFont = Font.monospace(14.0)
|
|
private let backgroundInset: CGFloat = 8.0
|
|
|
|
public final class AlertTitleComponent: Component {
|
|
public typealias EnvironmentType = AlertComponentEnvironment
|
|
|
|
public enum Alignment {
|
|
case `default`
|
|
case center
|
|
}
|
|
|
|
let title: String
|
|
let alignment: Alignment
|
|
|
|
public init(
|
|
title: String,
|
|
alignment: Alignment = .default
|
|
) {
|
|
self.title = title
|
|
self.alignment = alignment
|
|
}
|
|
|
|
public static func ==(lhs: AlertTitleComponent, rhs: AlertTitleComponent) -> Bool {
|
|
if lhs.title != rhs.title {
|
|
return false
|
|
}
|
|
if lhs.alignment != rhs.alignment {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private let title = ComponentView<Empty>()
|
|
|
|
private var component: AlertTitleComponent?
|
|
private weak var state: EmptyComponentState?
|
|
|
|
func update(component: AlertTitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
|
self.component = component
|
|
self.state = state
|
|
|
|
let environment = environment[AlertComponentEnvironment.self]
|
|
|
|
let titleSize = self.title.update(
|
|
transition: transition,
|
|
component: AnyComponent(MultilineTextComponent(
|
|
text: .plain(NSAttributedString(
|
|
string: component.title,
|
|
font: titleFont,
|
|
textColor: environment.theme.actionSheet.primaryTextColor
|
|
)),
|
|
horizontalAlignment: component.alignment == .center ? .center : .natural,
|
|
maximumNumberOfLines: 0
|
|
)),
|
|
environment: {},
|
|
containerSize: availableSize
|
|
)
|
|
|
|
let titleOriginX: CGFloat
|
|
switch component.alignment {
|
|
case .default:
|
|
titleOriginX = 0.0
|
|
case .center:
|
|
titleOriginX = floorToScreenPixels((availableSize.width - titleSize.width) / 2.0)
|
|
}
|
|
let titleFrame = CGRect(origin: CGPoint(x: titleOriginX, y: 0.0), size: titleSize)
|
|
if let titleView = self.title.view {
|
|
if titleView.superview == nil {
|
|
self.addSubview(titleView)
|
|
}
|
|
transition.setFrame(view: titleView, frame: titleFrame)
|
|
}
|
|
return CGSize(width: availableSize.width, height: titleSize.height)
|
|
}
|
|
}
|
|
|
|
public func makeView() -> View {
|
|
return View(frame: CGRect())
|
|
}
|
|
|
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|
|
|
|
public final class AlertTextComponent: Component {
|
|
public typealias EnvironmentType = AlertComponentEnvironment
|
|
|
|
public enum Content: Equatable {
|
|
case plain(String)
|
|
case attributed(NSAttributedString)
|
|
case textWithEntities(AccountContext, String, [MessageTextEntity])
|
|
|
|
public static func ==(lhs: Content, rhs: Content) -> Bool {
|
|
switch lhs {
|
|
case let .plain(text):
|
|
if case .plain(text) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .attributed(text):
|
|
if case .attributed(text) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .textWithEntities(_, lhsText, lhsEntities):
|
|
if case let .textWithEntities(_, rhsText, rhsEntities) = rhs {
|
|
return lhsText == rhsText && lhsEntities == rhsEntities
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum Alignment: Equatable {
|
|
case `default`
|
|
case center
|
|
}
|
|
|
|
public enum Color: Equatable {
|
|
case primary
|
|
case secondary
|
|
case destructive
|
|
}
|
|
|
|
public enum TextStyle: Equatable {
|
|
case `default`
|
|
case small
|
|
case bold
|
|
}
|
|
|
|
public enum Style: Equatable {
|
|
case plain(TextStyle)
|
|
case background(TextStyle)
|
|
}
|
|
|
|
let content: Content
|
|
let alignment: Alignment
|
|
let color: Color
|
|
let style: Style
|
|
let insets: UIEdgeInsets
|
|
let action: ([NSAttributedString.Key: Any]) -> Void
|
|
|
|
public init(
|
|
content: Content,
|
|
alignment: Alignment = .default,
|
|
color: Color = .primary,
|
|
style: Style = .plain(.default),
|
|
insets: UIEdgeInsets = .zero,
|
|
action: @escaping ([NSAttributedString.Key: Any]) -> Void = { _ in }
|
|
) {
|
|
self.content = content
|
|
self.alignment = alignment
|
|
self.color = color
|
|
self.style = style
|
|
self.insets = insets
|
|
self.action = action
|
|
}
|
|
|
|
public static func ==(lhs: AlertTextComponent, rhs: AlertTextComponent) -> Bool {
|
|
if lhs.content != rhs.content {
|
|
return false
|
|
}
|
|
if lhs.alignment != rhs.alignment {
|
|
return false
|
|
}
|
|
if lhs.color != rhs.color {
|
|
return false
|
|
}
|
|
if lhs.style != rhs.style {
|
|
return false
|
|
}
|
|
if lhs.insets != rhs.insets {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private let background = ComponentView<Empty>()
|
|
private let text = ComponentView<Empty>()
|
|
|
|
private var component: AlertTextComponent?
|
|
private weak var state: EmptyComponentState?
|
|
|
|
func update(component: AlertTextComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
|
self.component = component
|
|
self.state = state
|
|
|
|
let environment = environment[AlertComponentEnvironment.self]
|
|
|
|
let textColor: UIColor
|
|
switch component.color {
|
|
case .primary:
|
|
textColor = environment.theme.actionSheet.primaryTextColor
|
|
case .secondary:
|
|
textColor = environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.35)
|
|
case .destructive:
|
|
textColor = environment.theme.actionSheet.destructiveActionTextColor
|
|
}
|
|
let linkColor = environment.theme.actionSheet.controlAccentColor
|
|
|
|
let textFont: UIFont
|
|
let boldTextFont: UIFont
|
|
let italicTextFont: UIFont
|
|
let fixedTextFont: UIFont
|
|
switch component.style {
|
|
case let .plain(textStyle), let .background(textStyle):
|
|
switch textStyle {
|
|
case .default:
|
|
textFont = defaultTextFont
|
|
boldTextFont = defaultBoldTextFont
|
|
italicTextFont = defaultItalicTextFont
|
|
fixedTextFont = defaultFixedTextFont
|
|
case .small:
|
|
textFont = smallTextFont
|
|
boldTextFont = smallBoldTextFont
|
|
italicTextFont = smallItalicTextFont
|
|
fixedTextFont = smallFixedTextFont
|
|
case .bold:
|
|
textFont = defaultBoldTextFont
|
|
boldTextFont = defaultBoldTextFont
|
|
italicTextFont = defaultBoldItalicTextFont
|
|
fixedTextFont = defaultFixedTextFont
|
|
}
|
|
}
|
|
|
|
var finalText: NSAttributedString
|
|
var context: AccountContext?
|
|
switch component.content {
|
|
case let .plain(text):
|
|
let markdownAttributes = MarkdownAttributes(
|
|
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
|
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
|
link: MarkdownAttributeSet(font: textFont, textColor: linkColor),
|
|
linkAttribute: { contents in
|
|
return (TelegramTextAttributes.URL, contents)
|
|
}
|
|
)
|
|
finalText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes)
|
|
case let .attributed(attributedText):
|
|
finalText = attributedText
|
|
case let .textWithEntities(accountContext, text, entities):
|
|
context = accountContext
|
|
finalText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: italicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil)
|
|
}
|
|
|
|
var hasCenterAlignment = component.alignment == .center
|
|
switch component.style {
|
|
case .background:
|
|
hasCenterAlignment = true
|
|
default:
|
|
break
|
|
}
|
|
|
|
let textConstrainedSize = CGSize(width: availableSize.width, height: availableSize.height)
|
|
|
|
let textSize = self.text.update(
|
|
transition: transition,
|
|
component: AnyComponent(
|
|
MultilineTextWithEntitiesComponent(
|
|
context: context,
|
|
animationCache: context?.animationCache,
|
|
animationRenderer: context?.animationRenderer,
|
|
placeholderColor: textColor.withMultipliedAlpha(0.1),
|
|
text: .plain(finalText),
|
|
horizontalAlignment: hasCenterAlignment ? .center : .natural,
|
|
maximumNumberOfLines: 0,
|
|
lineSpacing: 0.2,
|
|
spoilerColor: textColor,
|
|
highlightColor: linkColor.withAlphaComponent(0.2),
|
|
manualVisibilityControl: true,
|
|
resetAnimationsOnVisibilityChange: true,
|
|
highlightAction: { attributes in
|
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
|
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
|
} else {
|
|
return nil
|
|
}
|
|
},
|
|
tapAction: { attributes, _ in
|
|
component.action(attributes)
|
|
}
|
|
)
|
|
),
|
|
environment: {},
|
|
containerSize: textConstrainedSize
|
|
)
|
|
|
|
var textOffset = CGPoint()
|
|
if hasCenterAlignment {
|
|
textOffset.x = floorToScreenPixels((availableSize.width - textSize.width) / 2.0)
|
|
}
|
|
var size = CGSize(width: availableSize.width, height: textSize.height)
|
|
if case .background = component.style {
|
|
let backgroundSize = CGSize(width: availableSize.width + 20.0, height: textSize.height + backgroundInset * 2.0)
|
|
size = backgroundSize
|
|
textOffset = CGPoint(x: textOffset.x, y: backgroundInset)
|
|
|
|
let _ = self.background.update(
|
|
transition: transition,
|
|
component: AnyComponent(
|
|
FilledRoundedRectangleComponent(
|
|
color: textColor.withMultipliedAlpha(0.1),
|
|
cornerRadius: .value(10.0),
|
|
smoothCorners: true
|
|
)
|
|
),
|
|
environment: {},
|
|
containerSize: backgroundSize
|
|
)
|
|
let backgroundFrame = CGRect(origin: CGPoint(x: -10.0, y: component.insets.top), size: backgroundSize)
|
|
if let backgroundView = self.background.view {
|
|
if backgroundView.superview == nil {
|
|
self.addSubview(backgroundView)
|
|
}
|
|
transition.setFrame(view: backgroundView, frame: backgroundFrame)
|
|
}
|
|
}
|
|
|
|
let textFrame = CGRect(origin: textOffset.offsetBy(dx: 0.0, dy: component.insets.top), size: textSize)
|
|
if let textView = self.text.view {
|
|
if textView.superview == nil {
|
|
self.addSubview(textView)
|
|
}
|
|
transition.setFrame(view: textView, frame: textFrame)
|
|
}
|
|
return CGSize(width: size.width, height: size.height + component.insets.top + component.insets.bottom)
|
|
}
|
|
}
|
|
|
|
public func makeView() -> View {
|
|
return View(frame: CGRect())
|
|
}
|
|
|
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|