chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
@@ -0,0 +1,28 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AlertCheckComponent",
module_name = "AlertCheckComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TextFormat",
"//submodules/Markdown",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/CheckComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,186 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramPresentationData
import AlertComponent
import PlainButtonComponent
import MultilineTextComponent
import CheckComponent
import TextFormat
import Markdown
public final class AlertCheckComponent: Component {
public typealias EnvironmentType = AlertComponentEnvironment
public class ExternalState {
public fileprivate(set) var value: Bool
fileprivate var valuePromise = Promise<Bool>()
public var valueSignal: Signal<Bool, NoError>
public init() {
self.value = false
self.valueSignal = self.valuePromise.get()
}
}
let title: String
let initialValue: Bool
let externalState: ExternalState
let linkAction: (() -> Void)?
public init(
title: String,
initialValue: Bool,
externalState: ExternalState,
linkAction: (() -> Void)? = nil
) {
self.title = title
self.initialValue = initialValue
self.externalState = externalState
self.linkAction = linkAction
}
public static func ==(lhs: AlertCheckComponent, rhs: AlertCheckComponent) -> Bool {
return true
}
public final class View: UIView {
private let button = ComponentView<Empty>()
private var component: AlertCheckComponent?
private weak var state: EmptyComponentState?
private var isUpdating = false
private var valuePromise = ValuePromise<Bool>(false)
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
func findTextView(view: UIView?) -> ImmediateTextView? {
if let view {
if let view = view as? ImmediateTextView {
return view
}
for view in view.subviews {
if let result = findTextView(view: view) {
return result
}
}
}
return nil
}
let result = super.hitTest(point, with: event)
if let textView = findTextView(view: result) {
if let (_, attributes) = textView.attributesAtPoint(self.convert(point, to: textView)) {
if attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] != nil {
return textView
}
}
}
return result
}
func update(component: AlertCheckComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
if self.component == nil {
component.externalState.value = component.initialValue
component.externalState.valuePromise.set(self.valuePromise.get())
}
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let checkTheme = CheckComponent.Theme(
backgroundColor: environment.theme.list.itemCheckColors.fillColor,
strokeColor: environment.theme.list.itemCheckColors.foregroundColor,
borderColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.15),
overlayBorder: false,
hasInset: false,
hasShadow: false
)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = environment.theme.actionSheet.primaryTextColor
let linkColor = environment.theme.actionSheet.controlAccentColor
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)
}
)
let buttonSize = self.button.update(
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
theme: checkTheme,
size: CGSize(width: 18.0, height: 18.0),
selected: component.externalState.value
))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .markdown(text: component.title, attributes: markdownAttributes),
maximumNumberOfLines: 2,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
component.linkAction?()
}
}
)))
], spacing: 10.0)),
effectAlignment: .center,
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.externalState.value = !component.externalState.value
self.valuePromise.set(component.externalState.value)
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
},
animateAlpha: false,
animateScale: false
)),
environment: {
},
containerSize: CGSize(width: availableSize.width + 20.0, height: 1000.0)
)
let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0), y: 7.0), size: buttonSize)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
transition.setFrame(view: buttonView, frame: buttonFrame)
}
return CGSize(width: availableSize.width, height: buttonSize.height + 7.0)
}
}
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)
}
}
@@ -0,0 +1,28 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AlertInputFieldComponent",
module_name = "AlertInputFieldComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/AccountContext",
"//submodules/ComponentFlow",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/TextFormat",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,353 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import AlertComponent
import MultilineTextComponent
import AccountContext
import TextFormat
import PlainButtonComponent
import BundleIconComponent
public final class AlertInputFieldComponent: Component {
public typealias EnvironmentType = AlertComponentEnvironment
public class ExternalState {
public fileprivate(set) var value: String = ""
public fileprivate(set) var animateError: () -> Void = {}
public fileprivate(set) var activateInput: () -> Void = {}
fileprivate let valuePromise = ValuePromise<String>("")
public var valueSignal: Signal<String, NoError> {
return self.valuePromise.get()
}
public init() {
}
}
let context: AccountContext
let initialValue: String?
let placeholder: String
let characterLimit: Int?
let hasClearButton: Bool
let isSecureTextEntry: Bool
let returnKeyType: UIReturnKeyType
let keyboardType: UIKeyboardType
let autocapitalizationType: UITextAutocapitalizationType
let autocorrectionType: UITextAutocorrectionType
let isInitiallyFocused: Bool
let externalState: ExternalState
let shouldChangeText: ((String) -> Bool)?
let returnKeyAction: (() -> Void)?
public init(
context: AccountContext,
initialValue: String? = nil,
placeholder: String,
characterLimit: Int? = nil,
hasClearButton: Bool = false,
isSecureTextEntry: Bool = false,
returnKeyType: UIReturnKeyType = .done,
keyboardType: UIKeyboardType = .default,
autocapitalizationType: UITextAutocapitalizationType = .sentences,
autocorrectionType: UITextAutocorrectionType = .default,
isInitiallyFocused: Bool = false,
externalState: ExternalState,
shouldChangeText: ((String) -> Bool)? = nil,
returnKeyAction: (() -> Void)? = nil
) {
self.context = context
self.initialValue = initialValue
self.placeholder = placeholder
self.characterLimit = characterLimit
self.hasClearButton = hasClearButton
self.isSecureTextEntry = isSecureTextEntry
self.returnKeyType = returnKeyType
self.keyboardType = keyboardType
self.autocapitalizationType = autocapitalizationType
self.autocorrectionType = autocorrectionType
self.isInitiallyFocused = isInitiallyFocused
self.externalState = externalState
self.shouldChangeText = shouldChangeText
self.returnKeyAction = returnKeyAction
}
public static func ==(lhs: AlertInputFieldComponent, rhs: AlertInputFieldComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.initialValue != rhs.initialValue {
return false
}
if lhs.placeholder != rhs.placeholder {
return false
}
if lhs.characterLimit != rhs.characterLimit {
return false
}
if lhs.hasClearButton != rhs.hasClearButton {
return false
}
if lhs.isSecureTextEntry != rhs.isSecureTextEntry {
return false
}
if lhs.returnKeyType != rhs.returnKeyType {
return false
}
if lhs.keyboardType != rhs.keyboardType {
return false
}
if lhs.autocapitalizationType != rhs.autocapitalizationType {
return false
}
if lhs.autocorrectionType != rhs.autocorrectionType {
return false
}
if lhs.isInitiallyFocused != rhs.isInitiallyFocused {
return false
}
return true
}
private final class TextField: UITextField {
var sideInset: CGFloat = 0.0
override func textRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: CGPoint(x: self.sideInset, y: 0.0), size: CGSize(width: bounds.width - self.sideInset * 2.0, height: bounds.height))
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: CGPoint(x: self.sideInset, y: 0.0), size: CGSize(width: bounds.width - self.sideInset * 2.0, height: bounds.height))
}
}
public final class View: UIView, UITextFieldDelegate {
private let background = ComponentView<Empty>()
private let textField = TextField()
private let placeholder = ComponentView<Empty>()
private let clearButton = ComponentView<Empty>()
private var component: AlertInputFieldComponent?
private weak var state: EmptyComponentState?
private var isUpdating = false
var currentText: String {
return self.textField.text ?? ""
}
private var clearOnce: Bool = false
func activateInput() {
self.textField.becomeFirstResponder()
}
func animateError() {
if let component = self.component, component.isInitiallyFocused {
self.clearOnce = true
}
self.textField.layer.addShakeAnimation()
HapticFeedback().error()
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.component?.returnKeyAction?()
return false
}
@objc private func textDidChange() {
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
}
public func textFieldDidBeginEditing(_ textField: UITextField) {
self.clearButton.view?.isHidden = self.currentText.isEmpty
}
public func textFieldDidEndEditing(_ textField: UITextField) {
self.clearButton.view?.isHidden = true
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let component = self.component else {
return true
}
if self.clearOnce {
self.clearOnce = false
if range.length > string.count {
textField.text = ""
return false
}
}
let updatedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
if let shouldChangeText = component.shouldChangeText {
return shouldChangeText(updatedText)
}
return true
}
public func setText(text: String) {
self.textField.text = text
if !self.isUpdating {
self.state?.updated(transition: .immediate, isLocal: true)
}
}
func update(component: AlertInputFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
var resetText: String?
if self.component == nil {
resetText = component.initialValue
component.externalState.animateError = { [weak self] in
self?.animateError()
}
component.externalState.activateInput = { [weak self] in
self?.activateInput()
}
}
let isFirstTime = self.component == nil
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let topInset: CGFloat = 15.0
if self.textField.superview == nil {
self.addSubview(self.textField)
self.textField.delegate = self
self.textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
}
if self.textField.autocapitalizationType != component.autocapitalizationType {
self.textField.autocapitalizationType = component.autocapitalizationType
}
if self.textField.autocorrectionType != component.autocorrectionType {
self.textField.autocorrectionType = component.autocorrectionType
}
if self.textField.isSecureTextEntry != component.isSecureTextEntry {
self.textField.isSecureTextEntry = component.isSecureTextEntry
}
if self.textField.returnKeyType != component.returnKeyType {
self.textField.returnKeyType = component.returnKeyType
}
self.textField.keyboardAppearance = environment.theme.overallDarkAppearance ? .dark : .light
if let resetText {
self.textField.text = resetText
}
self.textField.font = Font.regular(17.0)
self.textField.textColor = environment.theme.actionSheet.primaryTextColor
self.textField.tintColor = environment.theme.actionSheet.controlAccentColor
self.textField.sideInset = 16.0
let backgroundPadding: CGFloat = 14.0
let size = CGSize(width: availableSize.width, height: 50.0)
let backgroundSize = self.background.update(
transition: transition,
component: AnyComponent(
FilledRoundedRectangleComponent(color: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), cornerRadius: .value(25.0), smoothCorners: false)
),
environment: {},
containerSize: CGSize(width: size.width + backgroundPadding * 2.0, height: size.height)
)
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) / 2.0), y: topInset ), size: backgroundSize)
if let backgroundView = self.background.view {
if backgroundView.superview == nil {
self.addSubview(backgroundView)
}
transition.setFrame(view: backgroundView, frame: backgroundFrame)
}
let textFieldSize = CGSize(width: availableSize.width - 24.0, height: 50.0)
let textFieldFrame = CGRect(origin: CGPoint(x: -12.0, y: topInset), size: textFieldSize)
transition.setFrame(view: self.textField, frame: textFieldFrame)
let placeholderSize = self.placeholder.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(
string: component.placeholder,
font: Font.regular(17.0),
textColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.4)
)))
),
environment: {},
containerSize: CGSize(width: size.width, height: 50.0)
)
let placeholderFrame = CGRect(origin: CGPoint(x: 4.0, y: floorToScreenPixels(textFieldFrame.midY - placeholderSize.height / 2.0)), size: placeholderSize)
if let placeholderView = self.placeholder.view {
if placeholderView.superview == nil {
placeholderView.isUserInteractionEnabled = false
self.addSubview(placeholderView)
}
placeholderView.frame = placeholderFrame
placeholderView.isHidden = !self.currentText.isEmpty
}
if component.hasClearButton {
let clearButtonSize = self.clearButton.update(
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(BundleIconComponent(
name: "Components/Search Bar/Clear",
tintColor: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4)
)),
effectAlignment: .center,
minSize: CGSize(width: 44.0, height: 44.0),
action: { [weak self] in
guard let self else {
return
}
self.setText(text: "")
},
animateAlpha: false,
animateScale: true
)),
environment: {},
containerSize: CGSize(width: 44.0, height: 44.0)
)
if let clearButtonView = self.clearButton.view {
if clearButtonView.superview == nil {
self.addSubview(clearButtonView)
}
transition.setFrame(view: clearButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - clearButtonSize.width + 11.0, y: topInset + floor((size.height - clearButtonSize.height) * 0.5)), size: clearButtonSize))
clearButtonView.isHidden = self.currentText.isEmpty || !self.textField.isFirstResponder
}
} else if let clearButtonView = self.clearButton.view, clearButtonView.superview != nil {
clearButtonView.removeFromSuperview()
}
if isFirstTime && component.isInitiallyFocused {
self.activateInput()
}
component.externalState.value = self.currentText
component.externalState.valuePromise.set(self.currentText)
return CGSize(width: availableSize.width, height: size.height + topInset)
}
}
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)
}
}
@@ -0,0 +1,27 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AlertMultilineInputFieldComponent",
module_name = "AlertMultilineInputFieldComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/AccountContext",
"//submodules/ComponentFlow",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/TextFormat",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/TextFieldComponent",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,361 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import AlertComponent
import TextFieldComponent
import MultilineTextComponent
import AccountContext
import TextFormat
public final class AlertMultilineInputFieldComponent: Component {
public typealias EnvironmentType = AlertComponentEnvironment
public class ExternalState {
public fileprivate(set) var value: NSAttributedString = NSAttributedString()
public fileprivate(set) var animateError: () -> Void = {}
public fileprivate(set) var activateInput: () -> Void = {}
fileprivate let valuePromise = ValuePromise<NSAttributedString>(NSAttributedString())
public var valueSignal: Signal<NSAttributedString, NoError> {
return self.valuePromise.get()
}
public var textAndEntities: (String, [MessageTextEntity]) {
let text = self.value.string
let entities = generateChatInputTextEntities(self.value)
return (text, entities)
}
public init() {
}
}
public enum FormatMenuAvailability: Equatable {
public enum Action: CaseIterable {
case bold
case italic
case monospace
case link
case strikethrough
case underline
case spoiler
case quote
case code
public static var all: [Action] = [
.bold,
.italic,
.monospace,
.link,
.strikethrough,
.underline,
.spoiler,
.quote,
.code
]
var textFieldValue: TextFieldComponent.FormatMenuAvailability.Action {
switch self {
case .bold:
return .bold
case .italic:
return .italic
case .monospace:
return .monospace
case .link:
return .link
case .strikethrough:
return .strikethrough
case .underline:
return .underline
case .spoiler:
return .spoiler
case .quote:
return .quote
case .code:
return .code
}
}
}
case available([Action])
case none
var textFieldValue: TextFieldComponent.FormatMenuAvailability {
switch self {
case let .available(actions):
return .available(actions.map { $0.textFieldValue })
case .none:
return .none
}
}
}
public enum EmptyLineHandling {
case allowed
case oneConsecutive
case notAllowed
var textFieldValue: TextFieldComponent.EmptyLineHandling {
switch self {
case .allowed:
return .allowed
case .oneConsecutive:
return .oneConsecutive
case .notAllowed:
return .notAllowed
}
}
}
let context: AccountContext
let initialValue: NSAttributedString?
let placeholder: String
let prefix: NSAttributedString?
let characterLimit: Int?
let returnKeyType: UIReturnKeyType
let keyboardType: UIKeyboardType
let autocapitalizationType: UITextAutocapitalizationType
let autocorrectionType: UITextAutocorrectionType
let formatMenuAvailability: FormatMenuAvailability
let emptyLineHandling: EmptyLineHandling
let isInitiallyFocused: Bool
let externalState: ExternalState
let present: (ViewController) -> Void
let returnKeyAction: (() -> Void)?
public init(
context: AccountContext,
initialValue: NSAttributedString? = nil,
placeholder: String,
prefix: NSAttributedString? = nil,
characterLimit: Int? = nil,
returnKeyType: UIReturnKeyType = .default,
keyboardType: UIKeyboardType = .default,
autocapitalizationType: UITextAutocapitalizationType = .sentences,
autocorrectionType: UITextAutocorrectionType = .default,
formatMenuAvailability: FormatMenuAvailability = .none,
emptyLineHandling: EmptyLineHandling = .allowed,
isInitiallyFocused: Bool = false,
externalState: ExternalState,
present: @escaping (ViewController) -> Void = { _ in },
returnKeyAction: (() -> Void)? = nil
) {
self.context = context
self.initialValue = initialValue
self.placeholder = placeholder
self.prefix = prefix
self.characterLimit = characterLimit
self.returnKeyType = returnKeyType
self.keyboardType = keyboardType
self.autocapitalizationType = autocapitalizationType
self.autocorrectionType = autocorrectionType
self.formatMenuAvailability = formatMenuAvailability
self.emptyLineHandling = emptyLineHandling
self.isInitiallyFocused = isInitiallyFocused
self.externalState = externalState
self.present = present
self.returnKeyAction = returnKeyAction
}
public static func ==(lhs: AlertMultilineInputFieldComponent, rhs: AlertMultilineInputFieldComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.initialValue != rhs.initialValue {
return false
}
if lhs.placeholder != rhs.placeholder {
return false
}
if lhs.prefix != rhs.prefix {
return false
}
if lhs.returnKeyType != rhs.returnKeyType {
return false
}
if lhs.characterLimit != rhs.characterLimit {
return false
}
if lhs.keyboardType != rhs.keyboardType {
return false
}
if lhs.autocapitalizationType != rhs.autocapitalizationType {
return false
}
if lhs.autocorrectionType != rhs.autocorrectionType {
return false
}
if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
return false
}
if lhs.emptyLineHandling != rhs.emptyLineHandling {
return false
}
if lhs.isInitiallyFocused != rhs.isInitiallyFocused {
return false
}
return true
}
public final class View: UIView {
private let background = ComponentView<Empty>()
private let textField = ComponentView<Empty>()
private let textFieldExternalState = TextFieldComponent.ExternalState()
private let placeholder = ComponentView<Empty>()
private var component: AlertMultilineInputFieldComponent?
private weak var state: EmptyComponentState?
func activateInput() {
if let textFieldView = self.textField.view as? TextFieldComponent.View {
textFieldView.activateInput()
}
}
func animateError() {
if let textFieldView = self.textField.view {
textFieldView.layer.addShakeAnimation()
}
HapticFeedback().error()
}
func update(component: AlertMultilineInputFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
var resetText: NSAttributedString?
if self.component == nil {
resetText = component.initialValue
component.externalState.animateError = { [weak self] in
self?.animateError()
}
component.externalState.activateInput = { [weak self] in
self?.activateInput()
}
}
let isFirstTime = self.component == nil
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let topInset: CGFloat = 15.0
let horizontalInset: CGFloat = 4.0
let verticalInset: CGFloat = 11.0 - UIScreenPixel
let textFieldSize = self.textField.update(
transition: transition,
component: AnyComponent(TextFieldComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
externalState: self.textFieldExternalState,
fontSize: 17.0,
textColor: environment.theme.actionSheet.primaryTextColor,
accentColor: environment.theme.actionSheet.controlAccentColor,
insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0),
hideKeyboard: false,
customInputView: nil,
resetText: resetText,
isOneLineWhenUnfocused: false,
characterLimit: component.characterLimit,
emptyLineHandling: component.emptyLineHandling.textFieldValue,
formatMenuAvailability: component.formatMenuAvailability.textFieldValue,
returnKeyType: component.returnKeyType,
keyboardType: component.keyboardType,
autocapitalizationType: component.autocapitalizationType,
autocorrectionType: component.autocorrectionType,
lockedFormatAction: {
},
present: { [weak self] c in
guard let self, let component = self.component else {
return
}
component.present(c)
},
paste: { _ in
},
returnKeyAction: { [weak self] in
guard let self, let component = self.component else {
return
}
component.returnKeyAction?()
},
backspaceKeyAction: {
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width + horizontalInset * 2.0, height: availableSize.height)
)
component.externalState.value = self.textFieldExternalState.text
component.externalState.valuePromise.set(component.externalState.value)
let backgroundPadding: CGFloat = 14.0
let size = CGSize(width: availableSize.width, height: max(50.0, floor(textFieldSize.height + verticalInset * 2.0)))
let backgroundSize = self.background.update(
transition: transition,
component: AnyComponent(
FilledRoundedRectangleComponent(color: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), cornerRadius: .value(25.0), smoothCorners: false)
),
environment: {},
containerSize: CGSize(width: size.width + backgroundPadding * 2.0, height: size.height)
)
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) / 2.0), y: topInset ), size: backgroundSize)
if let backgroundView = self.background.view {
if backgroundView.superview == nil {
self.addSubview(backgroundView)
}
transition.setFrame(view: backgroundView, frame: backgroundFrame)
}
let textFieldFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - textFieldSize.width) / 2.0), y: topInset + 11.0 - UIScreenPixel), size: textFieldSize)
if let textFieldView = self.textField.view {
if textFieldView.superview == nil {
self.addSubview(textFieldView)
self.textField.parentState = state
}
transition.setFrame(view: textFieldView, frame: textFieldFrame)
}
let placeholderSize = self.placeholder.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(
string: component.placeholder,
font: Font.regular(17.0),
textColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.4)
)))
),
environment: {},
containerSize: CGSize(width: size.width, height: 50.0)
)
let placeholderFrame = CGRect(origin: CGPoint(x: 4.0, y: floorToScreenPixels(textFieldFrame.midY - placeholderSize.height / 2.0)), size: placeholderSize)
if let placeholderView = self.placeholder.view {
if placeholderView.superview == nil {
placeholderView.isUserInteractionEnabled = false
self.addSubview(placeholderView)
}
placeholderView.frame = placeholderFrame
placeholderView.isHidden = self.textFieldExternalState.hasText
}
if isFirstTime && component.isInitiallyFocused {
self.activateInput()
}
return CGSize(width: availableSize.width, height: size.height + topInset)
}
}
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)
}
}
@@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AlertTableComponent",
module_name = "AlertTableComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/Gifts/TableComponent",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,70 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import TelegramPresentationData
import AlertComponent
import TableComponent
public final class AlertTableComponent: Component {
public typealias EnvironmentType = AlertComponentEnvironment
let items: [TableComponent.Item]
public init(
items: [TableComponent.Item]
) {
self.items = items
}
public static func ==(lhs: AlertTableComponent, rhs: AlertTableComponent) -> Bool {
if lhs.items != rhs.items {
return false
}
return true
}
public final class View: UIView {
private let table = ComponentView<Empty>()
private var component: AlertTableComponent?
private weak var state: EmptyComponentState?
func update(component: AlertTableComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let tableSize = self.table.update(
transition: transition,
component: AnyComponent(
TableComponent(
theme: environment.theme,
items: component.items,
semiTransparent: true
)
),
environment: {},
containerSize: CGSize(width: availableSize.width + 20.0, height: availableSize.height)
)
let tableFrame = CGRect(origin: CGPoint(x: -10.0, y: 5.0), size: tableSize)
if let tableView = self.table.view {
if tableView.superview == nil {
self.addSubview(tableView)
}
transition.setFrame(view: tableView, frame: tableFrame)
}
return CGSize(width: availableSize.width, height: tableSize.height + 10.0)
}
}
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)
}
}
@@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AlertTransferHeaderComponent",
module_name = "AlertTransferHeaderComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/AlertComponent",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,126 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import TelegramPresentationData
import AlertComponent
import BundleIconComponent
public final class AlertTransferHeaderComponent: Component {
public typealias EnvironmentType = AlertComponentEnvironment
public enum IconType {
case transfer
case take
}
let fromComponent: AnyComponentWithIdentity<Empty>
let toComponent: AnyComponentWithIdentity<Empty>
let type: IconType
public init(
fromComponent: AnyComponentWithIdentity<Empty>,
toComponent: AnyComponentWithIdentity<Empty>,
type: IconType
) {
self.fromComponent = fromComponent
self.toComponent = toComponent
self.type = type
}
public static func ==(lhs: AlertTransferHeaderComponent, rhs: AlertTransferHeaderComponent) -> Bool {
if lhs.fromComponent != rhs.fromComponent {
return false
}
if lhs.toComponent != rhs.toComponent {
return false
}
if lhs.type != rhs.type {
return false
}
return true
}
public final class View: UIView {
private let from = ComponentView<Empty>()
private let to = ComponentView<Empty>()
private let arrow = ComponentView<Empty>()
private var component: AlertTransferHeaderComponent?
private weak var state: EmptyComponentState?
func update(component: AlertTransferHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let size: CGSize
let iconName: String
switch component.type {
case .transfer:
iconName = "Peer Info/AlertArrow"
size = CGSize(width: 148.0, height: 60.0)
case .take:
iconName = "Media Editor/CutoutUndo"
size = CGSize(width: 154.0, height: 60.0)
}
let sideInset = floorToScreenPixels((availableSize.width - size.width) / 2.0)
let fromSize = self.from.update(
transition: transition,
component: component.fromComponent.component,
environment: {},
containerSize: CGSize(width: 60.0, height: 60.0)
)
let fromFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: fromSize)
if let fromView = self.from.view {
if fromView.superview == nil {
self.addSubview(fromView)
}
transition.setFrame(view: fromView, frame: fromFrame)
}
let arrowSize = self.arrow.update(
transition: transition,
component: AnyComponent(
BundleIconComponent(name: iconName, tintColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.2))
),
environment: {},
containerSize: availableSize
)
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - arrowSize.width) / 2.0), y: floorToScreenPixels((size.height - arrowSize.height) / 2.0)), size: arrowSize)
if let arrowView = self.arrow.view {
if arrowView.superview == nil {
self.addSubview(arrowView)
}
transition.setFrame(view: arrowView, frame: arrowFrame)
}
let toSize = self.to.update(
transition: transition,
component: component.toComponent.component,
environment: {},
containerSize: CGSize(width: 60.0, height: 60.0)
)
let toFrame = CGRect(origin: CGPoint(x: availableSize.width - toSize.width - sideInset, y: 0.0), size: toSize)
if let toView = self.to.view {
if toView.superview == nil {
self.addSubview(toView)
}
transition.setFrame(view: toView, frame: toFrame)
}
return CGSize(width: availableSize.width, height: size.height + 11.0)
}
}
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)
}
}
@@ -12,9 +12,19 @@ swift_library(
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/TelegramCore",
"//submodules/AccountContext",
"//submodules/Markdown",
"//submodules/TextFormat",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/Components/ActivityIndicatorComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",
@@ -0,0 +1,228 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import MultilineTextComponent
import GlassBackgroundComponent
import ActivityIndicatorComponent
private let titleFont = Font.medium(17.0)
private let boldTitleFont = Font.semibold(17.0)
final class AlertActionComponent: Component {
typealias EnvironmentType = AlertComponentEnvironment
static let actionHeight: CGFloat = 48.0
struct Theme: Equatable {
enum Font {
case regular
case bold
}
let background: UIColor
let foreground: UIColor
let secondary: UIColor
let font: Font
}
let theme: Theme
let title: String
let isHighlighted: Bool
let isEnabled: Signal<Bool, NoError>
let progress: Signal<Bool, NoError>
init(
theme: Theme,
title: String,
isHighlighted: Bool,
isEnabled: Signal<Bool, NoError>,
progress: Signal<Bool, NoError>
) {
self.theme = theme
self.title = title
self.isHighlighted = isHighlighted
self.isEnabled = isEnabled
self.progress = progress
}
static func ==(lhs: AlertActionComponent, rhs: AlertActionComponent) -> Bool {
if lhs.theme != rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.isHighlighted != rhs.isHighlighted {
return false
}
return true
}
final class View: UIView {
private let backgroundView = UIView()
private let title = ComponentView<Empty>()
private var activity: ComponentView<Empty>?
private var component: AlertActionComponent?
private weak var state: EmptyComponentState?
private var isEnabledDisposable: Disposable?
private var isEnabled = true
private var progressDisposable: Disposable?
private var hasProgress = false
private var isUpdating = false
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundView.clipsToBounds = true
self.addSubview(self.backgroundView)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
deinit {
self.isEnabledDisposable?.dispose()
self.progressDisposable?.dispose()
}
func update(component: AlertActionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
if self.component == nil {
self.isEnabledDisposable = (component.isEnabled
|> deliverOnMainQueue).start(next: { [weak self] isEnabled in
guard let self else {
return
}
self.isEnabled = isEnabled
if !self.isUpdating {
self.state?.updated(transition: .easeInOut(duration: 0.25))
}
})
self.progressDisposable = (component.progress
|> deliverOnMainQueue).start(next: { [weak self] hasProgress in
guard let self else {
return
}
self.hasProgress = hasProgress
if !self.isUpdating {
self.state?.updated(transition: .easeInOut(duration: 0.25))
}
})
}
self.component = component
self.state = state
let attributedString = NSMutableAttributedString(string: component.title, font: component.theme.font == .bold ? boldTitleFont : titleFont, textColor: .white, paragraphAlignment: .center)
if let range = attributedString.string.range(of: "$") {
attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string))
}
let titlePadding: CGFloat = 16.0
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(attributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
tintColor: component.theme.foreground
)),
environment: {},
containerSize: CGSize(width: availableSize.width - titlePadding * 2.0, height: availableSize.height)
)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.bounds = CGRect(origin: .zero, size: titleSize)
transition.setAlpha(view: titleView, alpha: self.hasProgress ? 0.0 : 1.0)
}
if self.hasProgress {
let activity: ComponentView<Empty>
if let current = self.activity {
activity = current
} else {
activity = ComponentView()
self.activity = activity
}
let activitySize = CGSize(width: 18.0, height: 18.0)
let _ = activity.update(
transition: transition,
component: AnyComponent(ActivityIndicatorComponent(color: component.theme.secondary)),
environment: {},
containerSize: activitySize
)
if let activityView = activity.view {
activityView.bounds = CGRect(origin: .zero, size: activitySize)
}
} else if let activity = self.activity {
self.activity = nil
if let activityView = activity.view {
transition.setAlpha(view: activityView, alpha: 0.0, completion: { _ in
activityView.removeFromSuperview()
})
}
}
let buttonAlpha: CGFloat
if self.isEnabled {
buttonAlpha = component.isHighlighted ? 0.35 : 1.0
} else {
buttonAlpha = 0.2
}
transition.setBackgroundColor(view: self.backgroundView, color: component.theme.background)
transition.setAlpha(view: self.backgroundView, alpha: buttonAlpha)
self.backgroundView.layer.cornerRadius = availableSize.height * 0.5
return CGSize(width: titleSize.width + titlePadding * 2.0, height: availableSize.height)
}
func applySize(size: CGSize, transition: ComponentTransition) {
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
if let titleView = self.title.view {
let titleSize = titleView.bounds.size
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
transition.setFrame(view: titleView, frame: titleFrame)
}
if let activityView = self.activity?.view {
var activityTransition = transition
if activityView.superview == nil {
self.addSubview(activityView)
transition.animateAlpha(view: activityView, from: 0.0, to: 1.0)
activityTransition = .immediate
}
let activitySize = activityView.bounds.size
let activityFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - activitySize.width) / 2.0), y: floorToScreenPixels((size.height - activitySize.height) / 2.0)), size: activitySize)
activityTransition.setPosition(view: activityView, position: activityFrame.center)
activityView.transform = CGAffineTransformMakeScale(0.7, 0.7)
}
}
}
func makeView() -> View {
return View(frame: CGRect())
}
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)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,366 @@
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)
}
}