mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Refactor text input
This commit is contained in:
parent
980ced37a8
commit
871072ba95
28 changed files with 616 additions and 359 deletions
2
MODULE.bazel.lock
generated
2
MODULE.bazel.lock
generated
|
|
@ -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": {},
|
||||
|
|
|
|||
|
|
@ -1096,6 +1096,8 @@ public protocol ChatController: ViewController {
|
|||
func playShakeAnimation()
|
||||
|
||||
func removeAd(opaqueId: Data)
|
||||
|
||||
func restrictedSendingContentsText() -> String
|
||||
}
|
||||
|
||||
public protocol ChatMessagePreviewItemNode: AnyObject {
|
||||
|
|
|
|||
|
|
@ -1273,6 +1273,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, displayUndo: { _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ swift_library(
|
|||
"//submodules/ChatContextQuery",
|
||||
"//submodules/TooltipUI",
|
||||
"//submodules/AudioWaveform",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
|
|
@ -173,6 +173,8 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
|||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, displayUndo: { _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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) })
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -439,6 +439,8 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, displayUndo: { _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -826,6 +826,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, displayUndo: { _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue