Refactor text input

This commit is contained in:
Isaac 2025-09-01 21:45:31 +02:00
parent 980ced37a8
commit 871072ba95
28 changed files with 616 additions and 359 deletions

2
MODULE.bazel.lock generated
View file

@ -159,7 +159,7 @@
"moduleExtensions": {
"@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
"general": {
"bzlTransitiveDigest": "xcBTf2+GaloFpg7YEh/Bv+1yAczRkiCt3DGws4K7kSk=",
"bzlTransitiveDigest": "RjubjYIojbv0PxTpnoknalV9QzT9asbV7elDuN7m2A4=",
"usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},

View file

@ -1096,6 +1096,8 @@ public protocol ChatController: ViewController {
func playShakeAnimation()
func removeAd(opaqueId: Data)
func restrictedSendingContentsText() -> String
}
public protocol ChatMessagePreviewItemNode: AnyObject {

View file

@ -1273,6 +1273,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, displayUndo: { _ in
}, sendEmoji: { _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

@ -22,6 +22,8 @@ swift_library(
"//submodules/ChatContextQuery",
"//submodules/TooltipUI",
"//submodules/AudioWaveform",
"//submodules/UndoUI",
"//submodules/TextFormat",
],
visibility = [
"//visibility:public",

View file

@ -8,6 +8,8 @@ import Display
import AccountContext
import ContextUI
import TooltipUI
import UndoUI
import TextFormat
public enum ChatLoadingMessageSubject {
case generic
@ -187,6 +189,8 @@ public final class ChatPanelInterfaceInteraction {
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
public let dismissAllTooltips: () -> Void
public let editTodoMessage: (MessageId, Int32?, Bool) -> Void
public let displayUndo: (UndoOverlayContent) -> Void
public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void
public let requestLayout: (ContainedViewLayoutTransition) -> Void
public let chatController: () -> ViewController?
public let statuses: ChatPanelInterfaceInteractionStatuses?
@ -306,6 +310,8 @@ public final class ChatPanelInterfaceInteraction {
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
dismissAllTooltips: @escaping () -> Void,
editTodoMessage: @escaping (MessageId, Int32?, Bool) -> Void,
displayUndo: @escaping (UndoOverlayContent) -> Void,
sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void,
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
toggleChatSidebarMode: @escaping () -> Void,
@ -428,6 +434,8 @@ public final class ChatPanelInterfaceInteraction {
self.updateRecordingTrimRange = updateRecordingTrimRange
self.dismissAllTooltips = dismissAllTooltips
self.editTodoMessage = editTodoMessage
self.displayUndo = displayUndo
self.sendEmoji = sendEmoji
self.updateHistoryFilter = updateHistoryFilter
self.updateChatLocationThread = updateChatLocationThread
self.toggleChatSidebarMode = toggleChatSidebarMode
@ -559,6 +567,8 @@ public final class ChatPanelInterfaceInteraction {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, displayUndo: { _ in
}, sendEmoji: { _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

@ -486,7 +486,9 @@ swift_library(
"//submodules/TelegramUI/Components/FaceScanScreen",
"//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist",
"//submodules/TelegramUI/Components/ChatThemeScreen",
"//submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode",
"//submodules/ContactsHelper",
"//submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View file

@ -173,6 +173,8 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, displayUndo: { _ in
}, sendEmoji: { _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatRecordingViewOnceButtonNode",
module_name = "ChatRecordingViewOnceButtonNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramPresentationData",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,113 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
public final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode {
public enum Icon {
case viewOnce
case recordMore
}
private let icon: Icon
private let backgroundNode: ASImageNode
private let iconNode: ASImageNode
private var theme: PresentationTheme?
public init(icon: Icon) {
self.icon = icon
self.backgroundNode = ASImageNode()
self.backgroundNode.isUserInteractionEnabled = false
self.iconNode = ASImageNode()
self.iconNode.isUserInteractionEnabled = false
super.init(pointerStyle: .default)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.iconNode)
self.highligthedChanged = { [weak self] highlighted in
if let self, self.bounds.width > 0.0 {
let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
if highlighted {
self.layer.removeAnimation(forKey: "sublayerTransform")
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
transition.updateTransformScale(node: self, scale: topScale)
} else {
let transition = ContainedViewLayoutTransition.immediate
transition.updateTransformScale(node: self, scale: 1.0)
self.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
guard let self else {
return
}
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
})
}
}
}
}
private var innerIsSelected = false
public func update(isSelected: Bool, animated: Bool = false) {
guard let theme = self.theme else {
return
}
let updated = self.iconNode.image == nil || self.innerIsSelected != isSelected
self.innerIsSelected = isSelected
if animated, updated && self.iconNode.image != nil, let snapshot = self.iconNode.view.snapshotContentTree() {
self.view.addSubview(snapshot)
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
snapshot.removeFromSuperview()
})
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
if updated {
if case .viewOnce = self.icon {
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor)
}
}
}
public func update(theme: PresentationTheme) -> CGSize {
let size = CGSize(width: 44.0, height: 44.0)
let innerSize = CGSize(width: 40.0, height: 40.0)
if self.theme !== theme {
self.theme = theme
self.backgroundNode.image = generateFilledCircleImage(diameter: innerSize.width, color: theme.rootController.navigationBar.opaqueBackgroundColor, strokeColor: theme.chat.inputPanel.panelSeparatorColor, strokeWidth: 0.5, backgroundColor: nil)
switch self.icon {
case .viewOnce:
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor)
case .recordMore:
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: theme.chat.inputPanel.panelControlAccentColor)
}
}
if let backgroundImage = self.backgroundNode.image {
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - backgroundImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - backgroundImage.size.height / 2.0)), size: backgroundImage.size)
self.backgroundNode.frame = backgroundFrame
}
if let iconImage = self.iconNode.image {
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - iconImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - iconImage.size.height / 2.0)), size: iconImage.size)
self.iconNode.frame = iconFrame
}
return size
}
}

View file

@ -0,0 +1,32 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatTextInputActionButtonsNode",
module_name = "ChatTextInputActionButtonsNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramCore",
"//submodules/TelegramPresentationData",
"//submodules/ContextUI",
"//submodules/ChatPresentationInterfaceState",
"//submodules/ChatMessageBackground",
"//submodules/TelegramUI/Components/ChatControllerInteraction",
"//submodules/AccountContext",
"//submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton",
"//submodules/TelegramUI/Components/ChatSendButtonRadialStatusNode",
"//submodules/ChatSendMessageActionUI",
"//submodules/ComponentFlow",
"//submodules/AnimatedCountLabelNode",
],
visibility = [
"//visibility:public",
],
)

View file

@ -126,29 +126,29 @@ private final class EffectBadgeView: UIView {
}
}
final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
private let context: AccountContext
private let presentationContext: ChatPresentationContext?
private let strings: PresentationStrings
let micButton: ChatTextInputMediaRecordingButton
let sendContainerNode: ASDisplayNode
let backdropNode: ChatMessageBubbleBackdrop
let backgroundNode: ASDisplayNode
let sendButton: HighlightTrackingButtonNode
var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
var sendButtonHasApplyIcon = false
var animatingSendButton = false
public let micButton: ChatTextInputMediaRecordingButton
public let sendContainerNode: ASDisplayNode
public let backdropNode: ChatMessageBubbleBackdrop
public let backgroundNode: ASDisplayNode
public let sendButton: HighlightTrackingButtonNode
public var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
public var sendButtonHasApplyIcon = false
public var animatingSendButton = false
let textNode: ImmediateAnimatedCountLabelNode
public let textNode: ImmediateAnimatedCountLabelNode
let expandMediaInputButton: HighlightableButtonNode
public let expandMediaInputButton: HighlightableButtonNode
private var effectBadgeView: EffectBadgeView?
var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
public var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
private var gestureRecognizer: ContextGesture?
var sendButtonLongPressEnabled = false {
public var sendButtonLongPressEnabled = false {
didSet {
self.gestureRecognizer?.isEnabled = self.sendButtonLongPressEnabled
}
@ -159,7 +159,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
private var validLayout: CGSize?
init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
self.context = context
self.presentationContext = presentationContext
let theme = presentationInterfaceState.theme
@ -222,7 +222,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
self.addSubnode(self.expandMediaInputButton)
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
let gestureRecognizer = ContextGesture(target: nil, action: nil)
@ -239,7 +239,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: self.backgroundNode.view, style: .lift)
}
func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
public func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
self.micButton.updateTheme(theme: theme)
self.expandMediaInputButton.setImage(PresentationResourcesChat.chatInputPanelExpandButtonImage(theme), for: [])
@ -256,7 +256,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
}
private var absoluteRect: (CGRect, CGSize)?
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
let previousContaierSize = self.absoluteRect?.1
self.absoluteRect = (rect, containerSize)
self.backdropNode.update(rect: rect, within: containerSize, transition: transition)
@ -268,7 +268,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
}
}
func updateLayout(size: CGSize, isMediaInputExpanded: Bool, showTitle: Bool, currentMessageEffectId: Int64?, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGSize {
public func updateLayout(size: CGSize, isMediaInputExpanded: Bool, showTitle: Bool, currentMessageEffectId: Int64?, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGSize {
self.validLayout = size
var innerSize = size
@ -367,7 +367,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
return innerSize
}
func updateAccessibility() {
public func updateAccessibility() {
self.accessibilityTraits = .button
if !self.micButton.alpha.isZero {
switch self.micButton.mode {
@ -384,7 +384,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
}
}
func makeCustomContents() -> UIView? {
public func makeCustomContents() -> UIView? {
if self.sendButtonHasApplyIcon || self.effectBadgeView != nil {
let result = UIView()
result.frame = self.bounds

View file

@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatTextInputAudioRecordingCancelIndicator",
module_name = "ChatTextInputAudioRecordingCancelIndicator",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramPresentationData",
],
visibility = [
"//visibility:public",
],
)

View file

@ -6,7 +6,7 @@ import TelegramPresentationData
private let cancelFont = Font.regular(17.0)
final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode {
public final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode {
private let cancel: () -> Void
private let arrowNode: ASImageNode
@ -14,9 +14,9 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode {
private let cancelButton: HighlightableButtonNode
private let strings: PresentationStrings
private(set) var isDisplayingCancel = false
public private(set) var isDisplayingCancel = false
init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void) {
public init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void) {
self.cancel = cancel
self.arrowNode = ASImageNode()
@ -59,7 +59,7 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode {
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
}
func updateTheme(theme: PresentationTheme) {
public func updateTheme(theme: PresentationTheme) {
self.arrowNode.image = PresentationResourcesChat.chatInputPanelMediaRecordingCancelArrowImage(theme)
self.cancelButton.setTitle(self.strings.Common_Cancel, with: cancelFont, with: theme.chat.inputPanel.panelControlAccentColor, for: [])
let makeLayout = TextNode.asyncLayout(self.labelNode)
@ -67,7 +67,7 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode {
let _ = labelApply()
}
func updateIsDisplayingCancel(_ isDisplayingCancel: Bool, animated: Bool) {
public func updateIsDisplayingCancel(_ isDisplayingCancel: Bool, animated: Bool) {
if self.isDisplayingCancel != isDisplayingCancel {
self.isDisplayingCancel = isDisplayingCancel
if isDisplayingCancel {
@ -103,22 +103,20 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode {
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.cancelButton.alpha.isZero, self.cancelButton.frame.insetBy(dx: -5.0, dy: -5.0).contains(point) {
return self.cancelButton.view
}
return super.hitTest(point, with: event)
}
@objc func cancelPressed() {
@objc private func cancelPressed() {
self.cancel()
}
func animateIn() {
public func animateIn() {
}
func animateOut() {
public func animateOut() {
}
}

View file

@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatTextInputAudioRecordingTimeNode",
module_name = "ChatTextInputAudioRecordingTimeNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/ChatPresentationInterfaceState",
],
visibility = [
"//visibility:public",
],
)

View file

@ -21,7 +21,7 @@ private final class ChatTextInputAudioRecordingTimeNodeParameters: NSObject {
private let textFont = Font.regular(15.0)
final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
public final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
private let textNode: TextNode
private var timestamp: Double = 0.0 {
@ -32,9 +32,9 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
private let stateDisposable = MetaDisposable()
private var didStart = false
var started = {}
public var started = {}
var audioRecorder: ManagedAudioRecorder? {
public var audioRecorder: ManagedAudioRecorder? {
didSet {
if self.audioRecorder !== oldValue {
if let audioRecorder = self.audioRecorder {
@ -65,7 +65,7 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
private var durationDisposable: MetaDisposable?
var videoRecordingStatus: InstantVideoControllerRecordingStatus? {
public var videoRecordingStatus: InstantVideoControllerRecordingStatus? {
didSet {
if self.videoRecordingStatus !== oldValue {
if self.durationDisposable == nil {
@ -93,7 +93,7 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
private var theme: PresentationTheme
init(theme: PresentationTheme) {
public init(theme: PresentationTheme) {
self.theme = theme
self.textNode = TextNode()
@ -106,13 +106,13 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
self.durationDisposable?.dispose()
}
func updateTheme(theme: PresentationTheme) {
public func updateTheme(theme: PresentationTheme) {
self.theme = theme
self.setNeedsDisplay()
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
let makeLayout = TextNode.asyncLayout(self.textNode)
let (size, apply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "0:00:00,00", font: Font.regular(15.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 200.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let _ = apply()
@ -120,7 +120,7 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
return size.size
}
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return ChatTextInputAudioRecordingTimeNodeParameters(timestamp: self.timestamp, theme: self.theme)
}

View file

@ -0,0 +1,66 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatTextInputPanelNode",
module_name = "ChatTextInputPanelNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/AsyncDisplayKit",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/TelegramPresentationData",
"//submodules/TextFormat",
"//submodules/AccountContext",
"//submodules/TouchDownGesture",
"//submodules/ImageTransparency",
"//submodules/ActivityIndicator",
"//submodules/AnimationUI",
"//submodules/Speak",
"//submodules/ObjCRuntimeUtils",
"//submodules/AvatarNode",
"//submodules/ContextUI",
"//submodules/InvisibleInkDustNode",
"//submodules/TextInputMenu",
"//submodules/Pasteboard",
"//submodules/ChatPresentationInterfaceState",
"//submodules/ManagedAnimationNode",
"//submodules/AttachmentUI",
"//submodules/TelegramUI/Components/EditableChatTextNode",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/Components/LottieAnimationComponent",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
"//submodules/TelegramUI/Components/ChatControllerInteraction",
"//submodules/UndoUI",
"//submodules/PremiumUI",
"//submodules/StickerPeekUI",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/SolidRoundedButtonNode",
"//submodules/TooltipUI",
"//submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton",
"//submodules/ChatContextQuery",
"//submodules/TelegramUI/Components/Chat/ChatInputTextNode",
"//submodules/TelegramUI/Components/Chat/ChatInputPanelNode",
"//submodules/TelegramNotices",
"//submodules/AnimatedCountLabelNode",
"//submodules/TelegramStringFormatting",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/Utils/DeviceModel",
"//submodules/PhotoResources",
"//submodules/TelegramUI/Components/Chat/ChatTextInputSlowmodePlaceholderNode",
"//submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode",
"//submodules/TelegramUI/Components/Chat/ChatTextInputAudioRecordingTimeNode",
"//submodules/TelegramUI/Components/Chat/ChatTextInputAudioRecordingCancelIndicator",
"//submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode",
],
visibility = [
"//visibility:public",
],
)

View file

@ -47,6 +47,26 @@ import TelegramStringFormatting
import TextNodeWithEntities
import DeviceModel
import PhotoResources
import ChatTextInputSlowmodePlaceholderNode
import ChatTextInputActionButtonsNode
import ChatTextInputAudioRecordingTimeNode
import ChatTextInputAudioRecordingCancelIndicator
import ChatRecordingViewOnceButtonNode
public protocol ChatRecordingPreviewInputPanelNode: ASDisplayNode {
var viewOnceButton: ChatRecordingViewOnceButtonNode { get }
var recordMoreButton: ChatRecordingViewOnceButtonNode { get }
var waveformBackgroundNode: ASDisplayNode { get }
var waveformScrubberNode: ASDisplayNode { get }
var scrubber: ComponentView<Empty> { get }
var binNode: AnimationNode { get }
var deleteButton: ASDisplayNode { get }
var sendButton: ASDisplayNode { get }
var playButtonNode: ASDisplayNode { get }
var trimView: UIView { get }
var viewOnce: Bool { get }
var gestureRecognizer: ContextGesture? { get }
}
private let accessoryButtonFont = Font.medium(14.0)
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
@ -344,7 +364,7 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
}
}
let chatTextInputMinFontSize: CGFloat = 5.0
public let chatTextInputMinFontSize: CGFloat = 5.0
private let minInputFontSize = chatTextInputMinFontSize
@ -431,7 +451,7 @@ private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackground
}
}
enum ChatTextInputPanelPasteData {
public enum ChatTextInputPanelPasteData {
case images([UIImage])
case video(Data)
case gif(Data)
@ -529,27 +549,27 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre
)
}
class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate {
let clippingNode: ASDisplayNode
var textPlaceholderNode: ImmediateTextNodeWithEntities
var textLockIconNode: ASImageNode?
var contextPlaceholderNode: TextNode?
var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode?
let textInputContainerBackgroundNode: ASImageNode
let textInputContainer: ASDisplayNode
var textInputNode: ChatInputTextNode?
var dustNode: InvisibleInkDustNode?
var customEmojiContainerView: CustomEmojiContainerView?
public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate {
public let clippingNode: ASDisplayNode
public var textPlaceholderNode: ImmediateTextNodeWithEntities
public var textLockIconNode: ASImageNode?
public var contextPlaceholderNode: TextNode?
public var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode?
public let textInputContainerBackgroundNode: ASImageNode
public let textInputContainer: ASDisplayNode
public var textInputNode: ChatInputTextNode?
public var dustNode: InvisibleInkDustNode?
public var customEmojiContainerView: CustomEmojiContainerView?
let textInputBackgroundNode: ASImageNode
var textInputBackgroundTapRecognizer: TouchDownGestureRecognizer?
public let textInputBackgroundNode: ASImageNode
public var textInputBackgroundTapRecognizer: TouchDownGestureRecognizer?
private var transparentTextInputBackgroundImage: UIImage?
let actionButtons: ChatTextInputActionButtonsNode
public let actionButtons: ChatTextInputActionButtonsNode
private let slowModeButton: BoostSlowModeButton
var mediaRecordingAccessibilityArea: AccessibilityAreaNode?
public var mediaRecordingAccessibilityArea: AccessibilityAreaNode?
private let counterTextNode: ImmediateTextNode
let menuButton: HighlightTrackingButtonNode
public let menuButton: HighlightTrackingButtonNode
private let menuButtonBackgroundNode: ASDisplayNode
private let menuButtonClippingNode: ASDisplayNode
private let menuButtonIconNode: MenuIconNode
@ -557,28 +577,28 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
private let startButton: SolidRoundedButtonNode
let sendAsAvatarButtonNode: HighlightableButtonNode
let sendAsAvatarReferenceNode: ContextReferenceContentNode
let sendAsAvatarContainerNode: ContextControllerSourceNode
public let sendAsAvatarButtonNode: HighlightableButtonNode
public let sendAsAvatarReferenceNode: ContextReferenceContentNode
public let sendAsAvatarContainerNode: ContextControllerSourceNode
private let sendAsAvatarNode: AvatarNode
let attachmentButton: HighlightableButtonNode
let attachmentButtonDisabledNode: HighlightableButtonNode
public let attachmentButton: HighlightableButtonNode
public let attachmentButtonDisabledNode: HighlightableButtonNode
var attachmentImageNode: TransformImageNode?
public var attachmentImageNode: TransformImageNode?
let searchLayoutClearButton: HighlightableButton
public let searchLayoutClearButton: HighlightableButton
private let searchLayoutClearImageNode: ASImageNode
private var searchActivityIndicator: ActivityIndicator?
var audioRecordingInfoContainerNode: ASDisplayNode?
var audioRecordingDotNode: AnimationNode?
var audioRecordingDotNodeDismissed = false
var audioRecordingTimeNode: ChatTextInputAudioRecordingTimeNode?
var audioRecordingCancelIndicator: ChatTextInputAudioRecordingCancelIndicator?
var animatingBinNode: AnimationNode?
public var audioRecordingInfoContainerNode: ASDisplayNode?
public var audioRecordingDotNode: AnimationNode?
public var audioRecordingDotNodeDismissed = false
public var audioRecordingTimeNode: ChatTextInputAudioRecordingTimeNode?
public var audioRecordingCancelIndicator: ChatTextInputAudioRecordingCancelIndicator?
public var animatingBinNode: AnimationNode?
var viewOnce = false
let viewOnceButton: ChatRecordingViewOnceButtonNode
public var viewOnce = false
public let viewOnceButton: ChatRecordingViewOnceButtonNode
private var accessoryItemButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButtonNode)] = []
@ -587,14 +607,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
private var rightSlowModeInset: CGFloat = 0.0
private var currentTextInputBackgroundWidthOffset: CGFloat = 0.0
var displayAttachmentMenu: () -> Void = { }
var sendMessage: () -> Void = { }
var paste: (ChatTextInputPanelPasteData) -> Void = { _ in }
var updateHeight: (Bool) -> Void = { _ in }
var toggleExpandMediaInput: (() -> Void)?
var switchToTextInputIfNeeded: (() -> Void)?
public var displayAttachmentMenu: () -> Void = { }
public var sendMessage: () -> Void = { }
public var paste: (ChatTextInputPanelPasteData) -> Void = { _ in }
public var updateHeight: (Bool) -> Void = { _ in }
public var toggleExpandMediaInput: (() -> Void)?
public var switchToTextInputIfNeeded: (() -> Void)?
var updateActivity: () -> Void = { }
public var updateActivity: () -> Void = { }
private var updatingInputState = false
@ -607,7 +627,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
private var keepSendButtonEnabled = false
private var extendedSearchLayout = false
var isMediaDeleted: Bool = false
public var isMediaDeleted: Bool = false
private var recordingPaused = false
private let inputMenu: TextInputMenu
@ -617,7 +637,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
private let hapticFeedback = HapticFeedback()
var inputTextState: ChatTextInputState {
public var inputTextState: ChatTextInputState {
if let textInputNode = self.textInputNode {
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
return ChatTextInputState(inputText: stateAttributedStringForText(textInputNode.attributedText ?? NSAttributedString()), selectionRange: selectionRange)
@ -626,8 +646,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
var storedInputLanguage: String?
var effectiveInputLanguage: String? {
public var storedInputLanguage: String?
public var effectiveInputLanguage: String? {
if let textInputNode = textInputNode, textInputNode.isFirstResponder() {
return textInputNode.textInputMode?.primaryLanguage
} else {
@ -635,7 +655,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
var enablePredictiveInput: Bool = true {
public var enablePredictiveInput: Bool = true {
didSet {
if let textInputNode = self.textInputNode {
textInputNode.textView.autocorrectionType = self.enablePredictiveInput ? .default : .no
@ -643,18 +663,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
override var context: AccountContext? {
override public var context: AccountContext? {
didSet {
self.actionButtons.micButton.statusBarHost = self.context?.sharedContext.mainWindow?.statusBarHost
}
}
var micButton: ChatTextInputMediaRecordingButton? {
public var micButton: ChatTextInputMediaRecordingButton? {
return self.actionButtons.micButton
}
private let statusDisposable = MetaDisposable()
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
override public var interfaceInteraction: ChatPanelInterfaceInteraction? {
didSet {
if let statuses = self.interfaceInteraction?.statuses {
self.statusDisposable.set((statuses.inlineSearch
@ -666,7 +686,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, accessoryItems: [ChatTextInputAccessoryItem], animated: Bool) {
public func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, accessoryItems: [ChatTextInputAccessoryItem], animated: Bool) {
if let currentState = self.presentationInterfaceState {
var updateAccessoryButtons = false
if accessoryItems.count == self.accessoryItemButtons.count {
@ -740,7 +760,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func updateKeepSendButtonEnabled(keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, animated: Bool) {
public func updateKeepSendButtonEnabled(keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, animated: Bool) {
if keepSendButtonEnabled != self.keepSendButtonEnabled || extendedSearchLayout != self.extendedSearchLayout {
self.keepSendButtonEnabled = keepSendButtonEnabled
self.extendedSearchLayout = extendedSearchLayout
@ -748,7 +768,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
var text: String {
public var text: String {
get {
return self.textInputNode?.attributedText?.string ?? ""
} set(value) {
@ -772,17 +792,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
private var spoilersRevealed = false
private var animatingTransition = false
var finishedTransitionToPreview: Bool?
public var finishedTransitionToPreview: Bool?
private var touchDownGestureRecognizer: TouchDownGestureRecognizer?
var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
public var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
private let presentationContext: ChatPresentationContext?
private var tooltipController: TooltipScreen?
init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
self.presentationInterfaceState = presentationInterfaceState
self.presentationContext = presentationContext
@ -1070,7 +1090,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
recognizer.touchDown = { [weak self] in
if let strongSelf = self {
if strongSelf.sendingTextDisabled {
guard let controller = strongSelf.interfaceInteraction?.chatController() as? ChatControllerImpl else {
guard let controller = (strongSelf.interfaceInteraction?.chatController() as? ChatController) else {
return
}
@ -1079,7 +1099,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return
}
controller.controllerInteraction?.displayUndo(.universal(animation: "premium_unlock", scale: 1.0, colors: ["__allcolors__": UIColor(white: 1.0, alpha: 1.0)], title: nil, text: controller.restrictedSendingContentsText(), customUndoText: nil, timeout: nil))
strongSelf.interfaceInteraction?.displayUndo(.universal(animation: "premium_unlock", scale: 1.0, colors: ["__allcolors__": UIColor(white: 1.0, alpha: 1.0)], title: nil, text: controller.restrictedSendingContentsText(), customUndoText: nil, timeout: nil))
} else {
strongSelf.ensureFocused()
}
@ -1114,7 +1134,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
self.viewOnceButton.addTarget(self, action: #selector(self.viewOncePressed), forControlEvents: [.touchUpInside])
}
required init?(coder aDecoder: NSCoder) {
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -1124,7 +1144,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
self.currentEmojiSuggestion?.disposable.dispose()
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
if let viewForOverlayContent = self.viewForOverlayContent {
@ -1132,7 +1152,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func loadTextInputNodeIfNeeded() {
public func loadTextInputNodeIfNeeded() {
if self.textInputNode == nil {
self.loadTextInputNode()
}
@ -1360,7 +1380,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return result
}
override func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
override public func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics)
var minimalHeight: CGFloat = 14.0 + textFieldMinHeight
if case .regular = metrics.widthClass, case .regular = metrics.heightClass {
@ -1413,7 +1433,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
transition.updatePosition(layer: menuTextSnapshotView.layer, position: CGPoint(x: menuTextSnapshotView.center.x + menuContentDelta, y: self.startButton.position.y))
}
func animateBotButtonOutToMenu(transition: ContainedViewLayoutTransition) {
public func animateBotButtonOutToMenu(transition: ContainedViewLayoutTransition) {
guard !self.animatingTransition else {
return
}
@ -1473,7 +1493,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
private var absoluteRect: (CGRect, CGSize)?
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
self.absoluteRect = (rect, containerSize)
if !self.actionButtons.frame.width.isZero {
@ -1488,14 +1508,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func requestLayout(transition: ContainedViewLayoutTransition = .immediate) {
public func requestLayout(transition: ContainedViewLayoutTransition = .immediate) {
guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else {
return
}
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let previousAdditionalSideInsets = self.validLayout?.4
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
@ -3034,11 +3054,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
parentController.present(tooltipController, in: .current)
}
override func canHandleTransition(from prevInputPanelNode: ChatInputPanelNode?) -> Bool {
override public func canHandleTransition(from prevInputPanelNode: ChatInputPanelNode?) -> Bool {
return prevInputPanelNode is ChatRecordingPreviewInputPanelNode
}
func chatInputTextNodeDidUpdateText() {
public func chatInputTextNodeDidUpdateText() {
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState, let context = self.context {
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
refreshChatTextInputAttributes(context: context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in
@ -3058,7 +3078,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
@objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.chatInputTextNodeDidUpdateText()
}
@ -3642,9 +3662,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
guard let self else {
return
}
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
return
}
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
@ -3665,32 +3682,22 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
if let emojiAttribute {
controller.controllerInteraction?.sendEmoji(text, emojiAttribute, true)
self.interfaceInteraction?.sendEmoji(text, emojiAttribute, true)
}
}
let setStatus: (TelegramMediaFile) -> Void = { file in
guard let self, let context = self.context else {
return
}
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
return
}
let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).startStandalone()
var animateInAsReplacement = false
animateInAsReplacement = false
/*if let currentUndoOverlayController = strongSelf.currentUndoOverlayController {
currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation()
strongSelf.currentUndoOverlayController = nil
animateInAsReplacement = true
}*/
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.EmojiStatus_AppliedText, undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false })
//strongSelf.currentUndoOverlayController = controller
controller.controllerInteraction?.presentController(undoController, nil)
strongSelf.interfaceInteraction?.presentController(undoController, nil)
}
let copyEmoji: (TelegramMediaFile) -> Void = { file in
var text = "."
@ -4023,7 +4030,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func updateIsProcessingInlineRequest(_ value: Bool) {
public func updateIsProcessingInlineRequest(_ value: Bool) {
if value {
if self.searchActivityIndicator == nil, let currentState = self.presentationInterfaceState {
let searchActivityIndicator = ActivityIndicator(type: .custom(currentState.theme.list.itemAccentColor, 20.0, 1.5, true))
@ -4042,14 +4049,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func chatInputTextNodeShouldReturn() -> Bool {
public func chatInputTextNodeShouldReturn() -> Bool {
if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendContainerNode.alpha.isZero {
self.sendButtonPressed()
}
return false
}
@objc func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
return self.chatInputTextNodeShouldReturn()
}
@ -4082,7 +4089,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func chatInputTextNodeDidChangeSelection(dueToEditing: Bool) {
public func chatInputTextNodeDidChangeSelection(dueToEditing: Bool) {
if !dueToEditing && !self.updatingInputState {
let inputTextState = self.inputTextState
self.interfaceInteraction?.updateTextInputStateAndMode({ _, inputMode in return (inputTextState, inputMode) })
@ -4102,11 +4109,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
@objc func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) {
@objc public func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) {
self.chatInputTextNodeDidChangeSelection(dueToEditing: dueToEditing)
}
func chatInputTextNodeDidBeginEditing() {
public func chatInputTextNodeDidBeginEditing() {
guard let interfaceInteraction = self.interfaceInteraction, let presentationInterfaceState = self.presentationInterfaceState else {
return
}
@ -4131,12 +4138,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
@objc func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
@objc public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
self.chatInputTextNodeDidBeginEditing()
}
var skipPresentationInterfaceStateUpdate = false
func chatInputTextNodeDidFinishEditing() {
public var skipPresentationInterfaceStateUpdate = false
public func chatInputTextNodeDidFinishEditing() {
guard let editableTextNode = self.textInputNode else {
return
}
@ -4169,14 +4176,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
public func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
self.chatInputTextNodeDidFinishEditing()
}
func chatInputTextNodeBackspaceWhileEmpty() {
public func chatInputTextNodeBackspaceWhileEmpty() {
}
func editableTextNodeTarget(forAction action: Selector) -> ASEditableTextNodeTargetForAction? {
public func editableTextNodeTarget(forAction action: Selector) -> ASEditableTextNodeTargetForAction? {
if action == makeSelectorFromString("_accessibilitySpeak:") {
if case .format = self.inputMenu.state {
return ASEditableTextNodeTargetForAction(target: nil)
@ -4257,7 +4264,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
var suggestedActionCounter: Int = 0
@available(iOS 13.0, *)
func chatInputTextNodeMenu(forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu {
public func chatInputTextNodeMenu(forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu {
guard let editableTextNode = self.textInputNode else {
return UIMenu(children: [])
}
@ -4345,7 +4352,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
@available(iOS 16.0, *)
func editableTextNodeMenu(_ editableTextNode: ASEditableTextNode, forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu {
public func editableTextNodeMenu(_ editableTextNode: ASEditableTextNode, forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu {
return chatInputTextNodeMenu(forTextRange: textRange, suggestedActions: suggestedActions)
}
@ -4455,7 +4462,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
self.updateSpoilersRevealed(animated: animated)
}
func chatInputTextNode(shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
public func chatInputTextNode(shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard let editableTextNode = self.textInputNode, let context = self.context else {
return false
}
@ -4495,11 +4502,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return true
}
@objc func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
@objc public func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return self.chatInputTextNode(shouldChangeTextIn: range, replacementText: text)
}
func chatInputTextNodeShouldCopy() -> Bool {
public func chatInputTextNodeShouldCopy() -> Bool {
self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
storeInputTextInPasteboard(current.inputText.attributedSubstring(from: NSMakeRange(current.selectionRange.lowerBound, current.selectionRange.count)))
return (current, inputMode)
@ -4507,7 +4514,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return false
}
@objc func editableTextNodeShouldCopy(_ editableTextNode: ASEditableTextNode) -> Bool {
@objc public func editableTextNodeShouldCopy(_ editableTextNode: ASEditableTextNode) -> Bool {
return self.chatInputTextNodeShouldCopy()
}
@ -4523,7 +4530,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func chatInputTextNodeShouldPaste() -> Bool {
public func chatInputTextNodeShouldPaste() -> Bool {
let pasteboard = UIPasteboard.general
var attributedString: NSAttributedString?
@ -4603,7 +4610,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return true
}
@objc func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool {
@objc public func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool {
return self.chatInputTextNodeShouldPaste()
}
@ -4688,15 +4695,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
var isFocused: Bool {
public var isFocused: Bool {
return self.textInputNode?.isFirstResponder() ?? false
}
func ensureUnfocused() {
public func ensureUnfocused() {
self.textInputNode?.resignFirstResponder()
}
func ensureFocused() {
public func ensureFocused() {
if self.sendingTextDisabled {
return
}
@ -4711,7 +4718,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
private var switching = false
func ensureFocusedOnTap() {
public func ensureFocusedOnTap() {
if self.textInputNode == nil {
self.loadTextInputNode()
}
@ -4725,7 +4732,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
func backwardsDeleteText() {
public func backwardsDeleteText() {
guard let textInputNode = self.textInputNode else {
return
}
@ -4788,7 +4795,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let audioRecordingCancelIndicator = self.audioRecordingCancelIndicator {
if let result = audioRecordingCancelIndicator.hitTest(point.offsetBy(dx: -audioRecordingCancelIndicator.frame.minX, dy: -audioRecordingCancelIndicator.frame.minY), with: event) {
return result
@ -4807,7 +4814,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return result
}
func frameForAccessoryButton(_ item: ChatTextInputAccessoryItem) -> CGRect? {
public func frameForAccessoryButton(_ item: ChatTextInputAccessoryItem) -> CGRect? {
for (buttonItem, buttonNode) in self.accessoryItemButtons {
if buttonItem == item {
return buttonNode.frame
@ -4816,21 +4823,21 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return nil
}
func frameForAttachmentButton() -> CGRect? {
public func frameForAttachmentButton() -> CGRect? {
if !self.attachmentButton.alpha.isZero {
return self.attachmentButton.frame.insetBy(dx: 0.0, dy: 6.0).offsetBy(dx: 2.0, dy: 0.0)
}
return nil
}
func frameForMenuButton() -> CGRect? {
public func frameForMenuButton() -> CGRect? {
if !self.menuButton.alpha.isZero {
return self.menuButton.frame
}
return nil
}
func frameForInputActionButton() -> CGRect? {
public func frameForInputActionButton() -> CGRect? {
if !self.actionButtons.alpha.isZero {
if self.actionButtons.micButton.alpha.isZero {
return self.actionButtons.frame.insetBy(dx: 0.0, dy: 6.0).offsetBy(dx: 4.0, dy: 0.0)
@ -4841,7 +4848,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return nil
}
func frameForStickersButton() -> CGRect? {
public func frameForStickersButton() -> CGRect? {
for (item, button) in self.accessoryItemButtons {
if case let .input(_, inputMode) = item, case .stickers = inputMode {
return button.frame.insetBy(dx: 0.0, dy: 6.0)
@ -4850,7 +4857,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return nil
}
func frameForEmojiButton() -> CGRect? {
public func frameForEmojiButton() -> CGRect? {
for (item, button) in self.accessoryItemButtons {
if case let .input(_, inputMode) = item, case .emoji = inputMode {
return button.frame.insetBy(dx: 0.0, dy: 6.0)
@ -4859,7 +4866,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return nil
}
func frameForGiftButton() -> CGRect? {
public func frameForGiftButton() -> CGRect? {
for (item, button) in self.accessoryItemButtons {
if case .gift = item {
return button.frame.insetBy(dx: 0.0, dy: 6.0)
@ -4868,7 +4875,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return nil
}
func makeSnapshotForTransition() -> ChatMessageTransitionNodeImpl.Source.TextInput? {
public func makeSnapshotForTransition() -> (backgroundView: UIView, contentView: UIView, sourceRect: CGRect, scrollOffset: CGFloat)? {
guard let backgroundImage = self.transparentTextInputBackgroundImage else {
return nil
}
@ -4891,7 +4898,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
contentView.frame = textInputNode.frame
return ChatMessageTransitionNodeImpl.Source.TextInput(
return (
backgroundView: backgroundView,
contentView: contentView,
sourceRect: self.view.convert(self.bounds, to: nil),
@ -4899,7 +4906,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
)
}
func makeAttachmentMenuTransition(accessoryPanelNode: ASDisplayNode?) -> AttachmentController.InputPanelTransition {
public func makeAttachmentMenuTransition(accessoryPanelNode: ASDisplayNode?) -> AttachmentController.InputPanelTransition {
return AttachmentController.InputPanelTransition(inputNode: self, accessoryPanelNode: accessoryPanelNode, menuButtonNode: self.menuButton, menuButtonBackgroundNode: self.menuButtonBackgroundNode, menuIconNode: self.menuButtonIconNode, menuTextNode: self.menuButtonTextNode, prepareForDismiss: { self.menuButtonIconNode.enqueueState(.app, animated: false) })
}
}

View file

@ -0,0 +1,24 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatTextInputSlowmodePlaceholderNode",
module_name = "ChatTextInputSlowmodePlaceholderNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/TelegramStringFormatting",
"//submodules/AppBundle",
"//submodules/ChatPresentationInterfaceState",
],
visibility = [
"//visibility:public",
],
)

View file

@ -8,7 +8,7 @@ import TelegramStringFormatting
import AppBundle
import ChatPresentationInterfaceState
final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
public final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
private var theme: PresentationTheme
private let iconNode: ASImageNode
private let iconArrowContainerNode: ASDisplayNode
@ -20,7 +20,7 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
private var timer: SwiftSignalKit.Timer?
init(theme: PresentationTheme) {
public init(theme: PresentationTheme) {
self.theme = theme
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
@ -52,7 +52,7 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
self.timer?.invalidate()
}
func updateState(_ slowmodeState: ChatSlowmodeState) {
public func updateState(_ slowmodeState: ChatSlowmodeState) {
if self.slowmodeState != slowmodeState {
self.slowmodeState = slowmodeState
self.update()
@ -97,7 +97,7 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
}
}
func updateLayout(size: CGSize) {
public func updateLayout(size: CGSize) {
self.validLayout = size
var leftInset: CGFloat = 0.0

View file

@ -439,6 +439,8 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, displayUndo: { _ in
}, sendEmoji: { _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

@ -826,6 +826,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, displayUndo: { _ in
}, sendEmoji: { _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

@ -4340,6 +4340,16 @@ extension ChatControllerImpl {
return
}
self.openTodoEditing(messageId: messageId, itemId: itemId, append: append)
}, displayUndo: { [weak self] content in
guard let self else {
return
}
self.controllerInteraction?.displayUndo(content)
}, sendEmoji: { [weak self] text, attribute, immediately in
guard let self else {
return
}
self.controllerInteraction?.sendEmoji(text, attribute, immediately)
}, updateHistoryFilter: { [weak self] update in
guard let self else {
return

View file

@ -9961,7 +9961,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
transition.updateSublayerTransformScale(node: self.chatDisplayNode.historyNode, scale: scale)
}
func restrictedSendingContentsText() -> String {
public func restrictedSendingContentsText() -> String {
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return self.presentationData.strings.Chat_SendNotAllowedText
}

View file

@ -47,6 +47,7 @@ import ChatEmptyNode
import SpaceWarpView
import ChatSideTopicsPanel
import ChatThemeScreen
import ChatTextInputPanelNode
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
let itemNode: OverlayMediaItemNode
@ -3969,7 +3970,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
func sendButtonFrame() -> CGRect? {
if let mediaPreviewNode = self.inputPanelNode as? ChatRecordingPreviewInputPanelNode {
if let mediaPreviewNode = self.inputPanelNode as? ChatRecordingPreviewInputPanelNodeImpl {
return mediaPreviewNode.convert(mediaPreviewNode.sendButton.frame, to: self)
} else if let frame = self.textInputPanelNode?.actionButtons.frame {
return self.textInputPanelNode?.convert(frame, to: self)
@ -4012,7 +4013,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
return textInputPanelNode.frameForInputActionButton().flatMap {
return $0.offsetBy(dx: textInputPanelNode.frame.minX, dy: textInputPanelNode.frame.minY)
}
} else if let recordingPreviewPanelNode = self.inputPanelNode as? ChatRecordingPreviewInputPanelNode {
} else if let recordingPreviewPanelNode = self.inputPanelNode as? ChatRecordingPreviewInputPanelNodeImpl {
return recordingPreviewPanelNode.frameForInputActionButton().flatMap {
return $0.offsetBy(dx: recordingPreviewPanelNode.frame.minX, dy: recordingPreviewPanelNode.frame.minY)
}
@ -4761,7 +4762,12 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if self.shouldAnimateMessageTransition, let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode, let textInput = inputPanelNode.makeSnapshotForTransition() {
usedCorrelationId = correlationId
let source: ChatMessageTransitionNodeImpl.Source = .textInput(textInput: textInput, replyPanel: replyPanel)
let source: ChatMessageTransitionNodeImpl.Source = .textInput(textInput: ChatMessageTransitionNodeImpl.Source.TextInput(
backgroundView: textInput.backgroundView,
contentView: textInput.contentView,
sourceRect: textInput.sourceRect,
scrollOffset: textInput.scrollOffset
), replyPanel: replyPanel)
self.messageTransitionNode.add(correlationId: correlationId, source: source, initiated: {
})
}

View file

@ -10,6 +10,7 @@ import ChatPresentationInterfaceState
import SwiftSignalKit
import TextFormat
import ChatContextQuery
import ChatTextInputPanelNode
func serviceTasksForChatPresentationIntefaceState(context: AccountContext, chatPresentationInterfaceState: ChatPresentationInterfaceState, updateState: @escaping ((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void) -> [AnyHashable: () -> Disposable] {
var missingEmoji = Set<Int64>()

View file

@ -10,6 +10,7 @@ import ChatInputNode
import ChatEntityKeyboardInputNode
import ChatInputPanelNode
import ChatButtonKeyboardInputNode
import ChatTextInputPanelNode
func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentNode: ChatInputNode?, interfaceInteraction: ChatPanelInterfaceInteraction?, controllerInteraction: ChatControllerInteraction, inputPanelNode: ChatInputPanelNode?, makeMediaInputNode: () -> ChatInputNode?) -> ChatInputNode? {
if let inputPanelNode = inputPanelNode, !(inputPanelNode is ChatTextInputPanelNode) {

View file

@ -8,6 +8,7 @@ import ChatInputPanelNode
import ChatBotStartInputPanelNode
import ChatChannelSubscriberInputPanelNode
import ChatMessageSelectionInputPanelNode
import ChatTextInputPanelNode
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) {
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
@ -417,10 +418,10 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
}
} else {
if let _ = chatPresentationInterfaceState.interfaceState.mediaDraftState {
if let currentPanel = (currentPanel as? ChatRecordingPreviewInputPanelNode) ?? (currentSecondaryPanel as? ChatRecordingPreviewInputPanelNode) {
if let currentPanel = (currentPanel as? ChatRecordingPreviewInputPanelNodeImpl) ?? (currentSecondaryPanel as? ChatRecordingPreviewInputPanelNodeImpl) {
return (currentPanel, nil)
} else {
let panel = ChatRecordingPreviewInputPanelNode(theme: chatPresentationInterfaceState.theme)
let panel = ChatRecordingPreviewInputPanelNodeImpl(theme: chatPresentationInterfaceState.theme)
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return (panel, nil)

View file

@ -20,6 +20,8 @@ import TelegramNotices
import ComponentFlow
import MediaScrubberComponent
import AnimatedCountLabelNode
import ChatTextInputPanelNode
import ChatRecordingViewOnceButtonNode
#if SWIFT_PACKAGE
extension AudioWaveformNode: CustomMediaPlayerScrubbingForegroundNode {
@ -127,20 +129,35 @@ final class PlayButtonNode: ASDisplayNode {
}
}
final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
let deleteButton: HighlightableButtonNode
final class ChatRecordingPreviewInputPanelNodeImpl: ChatInputPanelNode, ChatRecordingPreviewInputPanelNode {
let deleteButtonImpl: HighlightableButtonNode
var deleteButton: ASDisplayNode {
return self.deleteButtonImpl
}
let binNode: AnimationNode
let sendButton: HighlightTrackingButtonNode
let sendButtonImpl: HighlightTrackingButtonNode
var sendButton: ASDisplayNode {
return self.sendButtonImpl
}
let sendBackgroundNode: ASDisplayNode
let sendIconNode: ASImageNode
let textNode: ImmediateAnimatedCountLabelNode
private var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
private let waveformButton: ASButtonNode
let waveformBackgroundNode: ASImageNode
let waveformBackgroundNodeImpl: ASImageNode
var waveformBackgroundNode: ASDisplayNode {
return self.waveformBackgroundNodeImpl
}
let trimView: TrimView
let playButtonNode: PlayButtonNode
let trimViewImpl: TrimView
var trimView: UIView {
return self.trimViewImpl
}
let playButtonNodeImpl: PlayButtonNode
var playButtonNode: ASDisplayNode {
return self.playButtonNodeImpl
}
let scrubber = ComponentView<Empty>()
@ -150,7 +167,10 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
private let waveformNode: AudioWaveformNode
private let waveformForegroundNode: AudioWaveformNode
let waveformScrubberNode: MediaPlayerScrubbingNode
let waveformScrubberNodeImpl: MediaPlayerScrubbingNode
var waveformScrubberNode: ASDisplayNode {
return self.waveformScrubberNodeImpl
}
private var presentationInterfaceState: ChatPresentationInterfaceState?
@ -165,8 +185,8 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
private(set) var gestureRecognizer: ContextGesture?
init(theme: PresentationTheme) {
self.deleteButton = HighlightableButtonNode()
self.deleteButton.displaysAsynchronously = false
self.deleteButtonImpl = HighlightableButtonNode()
self.deleteButtonImpl.displaysAsynchronously = false
self.binNode = AnimationNode(
animation: "BinBlue",
@ -181,9 +201,9 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
]
)
self.sendButton = HighlightTrackingButtonNode()
self.sendButton.displaysAsynchronously = false
self.sendButton.isExclusiveTouch = true
self.sendButtonImpl = HighlightTrackingButtonNode()
self.sendButtonImpl.displaysAsynchronously = false
self.sendButtonImpl.isExclusiveTouch = true
self.sendBackgroundNode = ASDisplayNode()
self.sendBackgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
@ -198,11 +218,11 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.viewOnceButton = ChatRecordingViewOnceButtonNode(icon: .viewOnce)
self.recordMoreButton = ChatRecordingViewOnceButtonNode(icon: .recordMore)
self.waveformBackgroundNode = ASImageNode()
self.waveformBackgroundNode.isLayerBacked = true
self.waveformBackgroundNode.displaysAsynchronously = false
self.waveformBackgroundNode.displayWithoutProcessing = true
self.waveformBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 33.0, color: theme.chat.inputPanel.actionControlFillColor)
self.waveformBackgroundNodeImpl = ASImageNode()
self.waveformBackgroundNodeImpl.isLayerBacked = true
self.waveformBackgroundNodeImpl.displaysAsynchronously = false
self.waveformBackgroundNodeImpl.displayWithoutProcessing = true
self.waveformBackgroundNodeImpl.image = generateStretchableFilledCircleImage(diameter: 33.0, color: theme.chat.inputPanel.actionControlFillColor)
self.waveformButton = ASButtonNode()
self.waveformButton.accessibilityTraits.insert(.startsMediaSession)
@ -212,11 +232,11 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.waveformForegroundNode = AudioWaveformNode()
self.waveformForegroundNode.isLayerBacked = true
self.waveformScrubberNode = MediaPlayerScrubbingNode(content: .custom(backgroundNode: self.waveformNode, foregroundContentNode: self.waveformForegroundNode))
self.waveformScrubberNodeImpl = MediaPlayerScrubbingNode(content: .custom(backgroundNode: self.waveformNode, foregroundContentNode: self.waveformForegroundNode))
self.trimView = TrimView(frame: .zero)
self.trimView.isHollow = true
self.playButtonNode = PlayButtonNode(theme: theme)
self.trimViewImpl = TrimView(frame: .zero)
self.trimViewImpl.isHollow = true
self.playButtonNodeImpl = PlayButtonNode(theme: theme)
super.init()
@ -235,37 +255,37 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
}
)
self.addSubnode(self.deleteButton)
self.deleteButton.addSubnode(self.binNode)
self.addSubnode(self.waveformBackgroundNode)
self.addSubnode(self.sendButton)
self.sendButton.addSubnode(self.sendBackgroundNode)
self.sendButton.addSubnode(self.sendIconNode)
self.sendButton.addSubnode(self.textNode)
self.addSubnode(self.deleteButtonImpl)
self.deleteButtonImpl.addSubnode(self.binNode)
self.addSubnode(self.waveformBackgroundNodeImpl)
self.addSubnode(self.sendButtonImpl)
self.sendButtonImpl.addSubnode(self.sendBackgroundNode)
self.sendButtonImpl.addSubnode(self.sendIconNode)
self.sendButtonImpl.addSubnode(self.textNode)
self.addSubnode(self.waveformScrubberNode)
//self.addSubnode(self.waveformButton)
self.view.addSubview(self.trimView)
self.addSubnode(self.playButtonNode)
self.view.addSubview(self.trimViewImpl)
self.addSubnode(self.playButtonNodeImpl)
self.sendButton.highligthedChanged = { [weak self] highlighted in
self.sendButtonImpl.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.sendButton.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
} else if let presentationLayer = strongSelf.sendButton.layer.presentation() {
strongSelf.sendButton.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
strongSelf.sendButtonImpl.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
} else if let presentationLayer = strongSelf.sendButtonImpl.layer.presentation() {
strongSelf.sendButtonImpl.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
}
}
}
self.playButtonNode.pressed = { [weak self] in
self.playButtonNodeImpl.pressed = { [weak self] in
guard let self else {
return
}
self.waveformPressed()
}
self.waveformScrubberNode.seek = { [weak self] timestamp in
self.waveformScrubberNodeImpl.seek = { [weak self] timestamp in
guard let self else {
return
}
@ -276,17 +296,17 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.mediaPlayer?.seek(timestamp: timestamp)
}
self.scrubbingDisposable = (self.waveformScrubberNode.scrubbingPosition
self.scrubbingDisposable = (self.waveformScrubberNodeImpl.scrubbingPosition
|> deliverOnMainQueue).startStrict(next: { [weak self] value in
guard let self else {
return
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
transition.updateAlpha(node: self.playButtonNode, alpha: value != nil ? 0.0 : 1.0)
transition.updateAlpha(node: self.playButtonNodeImpl, alpha: value != nil ? 0.0 : 1.0)
})
self.deleteButton.addTarget(self, action: #selector(self.deletePressed), forControlEvents: [.touchUpInside])
self.sendButton.addTarget(self, action: #selector(self.sendPressed), forControlEvents: [.touchUpInside])
self.deleteButtonImpl.addTarget(self, action: #selector(self.deletePressed), forControlEvents: [.touchUpInside])
self.sendButtonImpl.addTarget(self, action: #selector(self.sendPressed), forControlEvents: [.touchUpInside])
self.viewOnceButton.addTarget(self, action: #selector(self.viewOncePressed), forControlEvents: [.touchUpInside])
self.recordMoreButton.addTarget(self, action: #selector(self.recordMorePressed), forControlEvents: [.touchUpInside])
@ -304,7 +324,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
super.didLoad()
let gestureRecognizer = ContextGesture(target: nil, action: nil)
self.sendButton.view.addGestureRecognizer(gestureRecognizer)
self.sendButtonImpl.view.addGestureRecognizer(gestureRecognizer)
self.gestureRecognizer = gestureRecognizer
gestureRecognizer.shouldBegin = { [weak self] _ in
if let self, self.viewOnce {
@ -316,7 +336,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
guard let strongSelf = self else {
return
}
strongSelf.interfaceInteraction?.displaySendMessageOptions(strongSelf.sendButton, gesture)
strongSelf.interfaceInteraction?.displaySendMessageOptions(strongSelf.sendButtonImpl, gesture)
}
if let viewForOverlayContent = self.viewForOverlayContent {
@ -422,7 +442,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.textNode.isHidden = true
}
transition.updateFrame(node: self.sendButton, frame: CGRect(origin: CGPoint(x: width - rightInset - innerSize.width + 1.0 - UIScreenPixel, y: 1.0 + UIScreenPixel), size: innerSize))
transition.updateFrame(node: self.sendButtonImpl, frame: CGRect(origin: CGPoint(x: width - rightInset - innerSize.width + 1.0 - UIScreenPixel, y: 1.0 + UIScreenPixel), size: innerSize))
let backgroundSize = CGSize(width: innerSize.width - 11.0, height: 33.0)
let backgroundFrame = CGRect(origin: CGPoint(x: 5.0, y: floorToScreenPixels((innerSize.height - backgroundSize.height) / 2.0)), size: backgroundSize)
transition.updateFrame(node: self.sendBackgroundNode, frame: backgroundFrame)
@ -440,8 +460,8 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
updateWaveform = true
}
if self.presentationInterfaceState?.strings !== interfaceState.strings {
self.deleteButton.accessibilityLabel = interfaceState.strings.VoiceOver_MessageContextDelete
self.sendButton.accessibilityLabel = interfaceState.strings.VoiceOver_MessageContextSend
self.deleteButtonImpl.accessibilityLabel = interfaceState.strings.VoiceOver_MessageContextDelete
self.sendButtonImpl.accessibilityLabel = interfaceState.strings.VoiceOver_MessageContextSend
self.waveformButton.accessibilityLabel = interfaceState.strings.VoiceOver_Chat_RecordPreviewVoiceMessage
}
@ -451,10 +471,10 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
switch recordedMediaPreview {
case let .audio(audio):
self.waveformButton.isHidden = false
self.waveformBackgroundNode.isHidden = false
self.waveformBackgroundNodeImpl.isHidden = false
self.waveformForegroundNode.isHidden = false
self.waveformScrubberNode.isHidden = false
self.playButtonNode.isHidden = false
self.waveformScrubberNodeImpl.isHidden = false
self.playButtonNodeImpl.isHidden = false
if let view = self.scrubber.view, view.superview != nil {
view.removeFromSuperview()
@ -484,10 +504,10 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
}
}
self.mediaPlayer = mediaPlayer
self.playButtonNode.durationLabel.defaultDuration = Double(audio.duration)
self.playButtonNode.durationLabel.status = mediaPlayer.status
self.playButtonNode.durationLabel.trimRange = audio.trimRange
self.waveformScrubberNode.status = mediaPlayer.status
self.playButtonNodeImpl.durationLabel.defaultDuration = Double(audio.duration)
self.playButtonNodeImpl.durationLabel.status = mediaPlayer.status
self.playButtonNodeImpl.durationLabel.trimRange = audio.trimRange
self.waveformScrubberNodeImpl.status = mediaPlayer.status
self.statusDisposable.set((mediaPlayer.status
|> deliverOnMainQueue).startStrict(next: { [weak self] status in
@ -498,18 +518,18 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
if let recordedMediaPreview = self.presentationInterfaceState?.interfaceState.mediaDraftState, case let .audio(audio) = recordedMediaPreview, let _ = audio.trimRange {
self.ensureHasTimer()
}
self.playButtonNode.playPauseIconNode.enqueueState(.pause, animated: true)
self.playButtonNodeImpl.playPauseIconNode.enqueueState(.pause, animated: true)
default:
self.statusValue = nil
self.stopTimer()
self.playButtonNode.playPauseIconNode.enqueueState(.play, animated: true)
self.playButtonNodeImpl.playPauseIconNode.enqueueState(.play, animated: true)
}
}
}))
}
let minDuration = max(1.0, 56.0 * audio.duration / waveformBackgroundFrame.size.width)
let (leftHandleFrame, rightHandleFrame) = self.trimView.update(
let (leftHandleFrame, rightHandleFrame) = self.trimViewImpl.update(
style: .voiceMessage,
theme: interfaceState.theme,
visualInsets: .zero,
@ -522,7 +542,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
maxDuration: Double(audio.duration),
transition: .immediate
)
self.trimView.trimUpdated = { [weak self] start, end, updatedEnd, apply in
self.trimViewImpl.trimUpdated = { [weak self] start, end, updatedEnd, apply in
if let self {
self.mediaPlayer?.pause()
self.interfaceInteraction?.updateRecordingTrimRange(start, end, updatedEnd, apply)
@ -532,12 +552,12 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
} else {
self.mediaPlayer?.seek(timestamp: max(0.0, end - 1.0), play: true)
}
self.playButtonNode.durationLabel.isScrubbing = false
self.playButtonNodeImpl.durationLabel.isScrubbing = false
Queue.mainQueue().after(0.1) {
self.waveformForegroundNode.alpha = 1.0
}
} else {
self.playButtonNode.durationLabel.isScrubbing = true
self.playButtonNodeImpl.durationLabel.isScrubbing = true
self.waveformForegroundNode.alpha = 0.0
}
@ -546,18 +566,18 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.waveformForegroundNode.trimRange = startFraction ..< endFraction
}
}
self.trimView.frame = waveformBackgroundFrame
self.trimView.isHidden = audio.duration < 2.0
self.trimViewImpl.frame = waveformBackgroundFrame
self.trimViewImpl.isHidden = audio.duration < 2.0
let playButtonSize = CGSize(width: max(0.0, rightHandleFrame.minX - leftHandleFrame.maxX), height: waveformBackgroundFrame.height)
self.playButtonNode.update(size: playButtonSize, transition: transition)
transition.updateFrame(node: self.playButtonNode, frame: CGRect(origin: CGPoint(x: waveformBackgroundFrame.minX + leftHandleFrame.maxX, y: waveformBackgroundFrame.minY), size: playButtonSize))
self.playButtonNodeImpl.update(size: playButtonSize, transition: transition)
transition.updateFrame(node: self.playButtonNodeImpl, frame: CGRect(origin: CGPoint(x: waveformBackgroundFrame.minX + leftHandleFrame.maxX, y: waveformBackgroundFrame.minY), size: playButtonSize))
case let .video(video):
self.waveformButton.isHidden = true
self.waveformBackgroundNode.isHidden = true
self.waveformBackgroundNodeImpl.isHidden = true
self.waveformForegroundNode.isHidden = true
self.waveformScrubberNode.isHidden = true
self.playButtonNode.isHidden = true
self.waveformScrubberNodeImpl.isHidden = true
self.playButtonNodeImpl.isHidden = true
let scrubberSize = self.scrubber.update(
transition: .immediate,
@ -612,9 +632,9 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
}
let panelHeight = defaultHeight(metrics: metrics)
transition.updateFrame(node: self.deleteButton, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: 1), size: CGSize(width: 40.0, height: 40)))
transition.updateFrame(node: self.deleteButtonImpl, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: 1), size: CGSize(width: 40.0, height: 40)))
self.binNode.frame = self.deleteButton.bounds
self.binNode.frame = self.deleteButtonImpl.bounds
var viewOnceOffset: CGFloat = 0.0
if interfaceState.interfaceState.replyMessageSubject != nil {
@ -640,14 +660,14 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
sendButtonRadialStatusNode = current
} else {
sendButtonRadialStatusNode = ChatSendButtonRadialStatusNode(color: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
sendButtonRadialStatusNode.alpha = self.sendButton.alpha
sendButtonRadialStatusNode.alpha = self.sendButtonImpl.alpha
self.sendButtonRadialStatusNode = sendButtonRadialStatusNode
self.addSubnode(sendButtonRadialStatusNode)
}
transition.updateSublayerTransformScale(layer: self.sendButton.layer, scale: CGPoint(x: 0.7575, y: 0.7575))
transition.updateSublayerTransformScale(layer: self.sendButtonImpl.layer, scale: CGPoint(x: 0.7575, y: 0.7575))
sendButtonRadialStatusNode.frame = CGRect(origin: CGPoint(x: self.sendButton.frame.midX - 33.0 / 2.0, y: self.sendButton.frame.midY - 33.0 / 2.0), size: CGSize(width: 33.0, height: 33.0))
sendButtonRadialStatusNode.frame = CGRect(origin: CGPoint(x: self.sendButtonImpl.frame.midX - 33.0 / 2.0, y: self.sendButtonImpl.frame.midY - 33.0 / 2.0), size: CGSize(width: 33.0, height: 33.0))
sendButtonRadialStatusNode.slowmodeState = slowmodeState
} else {
if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode {
@ -657,7 +677,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
transition.updateSublayerTransformScale(layer: self.sendButton.layer, scale: CGPoint(x: 1.0, y: 1.0))
}
transition.updateFrame(node: self.waveformBackgroundNode, frame: waveformBackgroundFrame)
transition.updateFrame(node: self.waveformBackgroundNodeImpl, frame: waveformBackgroundFrame)
transition.updateFrame(node: self.waveformButton, frame: CGRect(origin: CGPoint(x: leftInset + 45.0, y: 0.0), size: CGSize(width: width - leftInset - rightInset - 45.0 - innerSize.width - 1.0, height: panelHeight)))
transition.updateFrame(node: self.waveformScrubberNode, frame: CGRect(origin: CGPoint(x: leftInset + 45.0 + 21.0, y: 7.0 + floor((33.0 - 13.0) / 2.0)), size: CGSize(width: width - leftInset - rightInset - 45.0 - innerSize.width - 41.0, height: 13.0)))
@ -710,20 +730,20 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
self.deleteButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15)
self.deleteButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.deleteButtonImpl.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15)
self.deleteButtonImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.playButtonNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3, delay: 0.1)
self.playButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1)
self.playButtonNodeImpl.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3, delay: 0.1)
self.playButtonNodeImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1)
self.trimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1)
self.trimViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1)
self.waveformScrubberNode.layer.animateScaleY(from: 0.1, to: 1.0, duration: 0.3, delay: 0.1)
self.waveformScrubberNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1)
self.waveformScrubberNodeImpl.layer.animateScaleY(from: 0.1, to: 1.0, duration: 0.3, delay: 0.1)
self.waveformScrubberNodeImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1)
self.waveformBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.waveformBackgroundNode.layer.animateFrame(
from: self.sendButton.frame.insetBy(dx: 5.5, dy: 5.5),
self.waveformBackgroundNodeImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.waveformBackgroundNodeImpl.layer.animateFrame(
from: self.sendButtonImpl.frame.insetBy(dx: 5.5, dy: 5.5),
to: waveformBackgroundFrame,
duration: 0.2,
delay: 0.12,
@ -853,7 +873,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
}
func frameForInputActionButton() -> CGRect? {
return self.sendButton.frame
return self.sendButtonImpl.frame
}
}
@ -906,112 +926,3 @@ private final class PlayPauseIconNode: ManagedAnimationNode {
}
}
}
final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode {
enum Icon {
case viewOnce
case recordMore
}
private let icon: Icon
private let backgroundNode: ASImageNode
private let iconNode: ASImageNode
private var theme: PresentationTheme?
init(icon: Icon) {
self.icon = icon
self.backgroundNode = ASImageNode()
self.backgroundNode.isUserInteractionEnabled = false
self.iconNode = ASImageNode()
self.iconNode.isUserInteractionEnabled = false
super.init(pointerStyle: .default)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.iconNode)
self.highligthedChanged = { [weak self] highlighted in
if let self, self.bounds.width > 0.0 {
let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
if highlighted {
self.layer.removeAnimation(forKey: "sublayerTransform")
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
transition.updateTransformScale(node: self, scale: topScale)
} else {
let transition = ContainedViewLayoutTransition.immediate
transition.updateTransformScale(node: self, scale: 1.0)
self.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
guard let self else {
return
}
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
})
}
}
}
}
private var innerIsSelected = false
func update(isSelected: Bool, animated: Bool = false) {
guard let theme = self.theme else {
return
}
let updated = self.iconNode.image == nil || self.innerIsSelected != isSelected
self.innerIsSelected = isSelected
if animated, updated && self.iconNode.image != nil, let snapshot = self.iconNode.view.snapshotContentTree() {
self.view.addSubview(snapshot)
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
snapshot.removeFromSuperview()
})
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
if updated {
if case .viewOnce = self.icon {
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor)
}
}
}
func update(theme: PresentationTheme) -> CGSize {
let size = CGSize(width: 44.0, height: 44.0)
let innerSize = CGSize(width: 40.0, height: 40.0)
if self.theme !== theme {
self.theme = theme
self.backgroundNode.image = generateFilledCircleImage(diameter: innerSize.width, color: theme.rootController.navigationBar.opaqueBackgroundColor, strokeColor: theme.chat.inputPanel.panelSeparatorColor, strokeWidth: 0.5, backgroundColor: nil)
switch self.icon {
case .viewOnce:
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor)
case .recordMore:
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: theme.chat.inputPanel.panelControlAccentColor)
}
}
if let backgroundImage = self.backgroundNode.image {
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - backgroundImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - backgroundImage.size.height / 2.0)), size: backgroundImage.size)
self.backgroundNode.frame = backgroundFrame
}
if let iconImage = self.iconNode.image {
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - iconImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - iconImage.size.height / 2.0)), size: iconImage.size)
self.iconNode.frame = iconFrame
}
return size
}
}