mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-06-08 19:13:56 +02:00
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:
@@ -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",
|
||||
],
|
||||
)
|
||||
+186
@@ -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",
|
||||
],
|
||||
)
|
||||
+353
@@ -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)
|
||||
}
|
||||
}
|
||||
+27
@@ -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",
|
||||
],
|
||||
)
|
||||
+361
@@ -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",
|
||||
],
|
||||
)
|
||||
+70
@@ -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",
|
||||
],
|
||||
)
|
||||
+126
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user