diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index e6b2521f9d..84df9170dd 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -61,7 +61,7 @@ public final class PeerSelectionControllerParams { } } -public enum PeerSelectionControllerSendMode { +public enum AttachmentTextInputPanelSendMode { case generic case silent case schedule @@ -69,7 +69,7 @@ public enum PeerSelectionControllerSendMode { public protocol PeerSelectionController: ViewController { var peerSelected: ((Peer) -> Void)? { get set } - var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode, ChatInterfaceForwardOptionsState?) -> Void)? { get set } + var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? { get set } var inProgress: Bool { get set } var customDismiss: (() -> Void)? { get set } } diff --git a/submodules/AttachmentTextInputPanelNode/BUILD b/submodules/AttachmentTextInputPanelNode/BUILD new file mode 100644 index 0000000000..29d59ef3f8 --- /dev/null +++ b/submodules/AttachmentTextInputPanelNode/BUILD @@ -0,0 +1,35 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AttachmentTextInputPanelNode", + module_name = "AttachmentTextInputPanelNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TextFormat:TextFormat", + "//submodules/AccountContext:AccountContext", + "//submodules/TouchDownGesture:TouchDownGesture", + "//submodules/ActivityIndicator:ActivityIndicator", + "//submodules/Speak:Speak", + "//submodules/LegacyComponents:LegacyComponents", + "//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils", + "//submodules/InvisibleInkDustNode:InvisibleInkDustNode", + "//submodules/TextInputMenu:TextInputMenu", + "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + "//submodules/Pasteboard:Pasteboard", + "//submodules/ContextUI:ContextUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift new file mode 100644 index 0000000000..5c11c0ca6f --- /dev/null +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift @@ -0,0 +1,127 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import TelegramPresentationData +import ContextUI +import ChatPresentationInterfaceState + +final class AttachmentTextInputActionButtonsNode: ASDisplayNode { + private let strings: PresentationStrings + + let sendContainerNode: ASDisplayNode + let backgroundNode: ASDisplayNode + let sendButton: HighlightTrackingButtonNode + var sendButtonHasApplyIcon = false + var animatingSendButton = false + let expandMediaInputButton: HighlightableButtonNode + + var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)? + + private var gestureRecognizer: ContextGesture? + var sendButtonLongPressEnabled = false { + didSet { + self.gestureRecognizer?.isEnabled = self.sendButtonLongPressEnabled + } + } + + private var validLayout: CGSize? + + init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) { + let theme = presentationInterfaceState.theme + let strings = presentationInterfaceState.strings + self.strings = strings + + self.sendContainerNode = ASDisplayNode() + self.sendContainerNode.layer.allowsGroupOpacity = true + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor + self.backgroundNode.clipsToBounds = true + self.sendButton = HighlightTrackingButtonNode(pointerStyle: .lift) + + self.expandMediaInputButton = HighlightableButtonNode(pointerStyle: .default) + + super.init() + + self.isAccessibilityElement = true + self.accessibilityTraits = [.button, .notEnabled] + + self.sendButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if strongSelf.sendButtonHasApplyIcon || !strongSelf.sendButtonLongPressEnabled { + if highlighted { + strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity") + strongSelf.sendContainerNode.alpha = 0.4 + } else { + strongSelf.sendContainerNode.alpha = 1.0 + strongSelf.sendContainerNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } else { + if highlighted { + strongSelf.sendContainerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false) + } else if let presentationLayer = strongSelf.sendButton.layer.presentation() { + strongSelf.sendContainerNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false) + } + } + } + } + + self.addSubnode(self.sendContainerNode) + self.sendContainerNode.addSubnode(self.backgroundNode) + self.sendContainerNode.addSubnode(self.sendButton) + self.addSubnode(self.expandMediaInputButton) + } + + override func didLoad() { + super.didLoad() + + let gestureRecognizer = ContextGesture(target: nil, action: nil) + self.gestureRecognizer = gestureRecognizer + self.sendButton.view.addGestureRecognizer(gestureRecognizer) + gestureRecognizer.activated = { [weak self] recognizer, _ in + guard let strongSelf = self else { + return + } + if !strongSelf.sendButtonHasApplyIcon { + strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer) + } + } + } + + func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) { + self.expandMediaInputButton.setImage(PresentationResourcesChat.chatInputPanelExpandButtonImage(theme), for: []) + + self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor + } + + private var absoluteRect: (CGRect, CGSize)? + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { + self.absoluteRect = (rect, containerSize) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { + self.validLayout = size + + transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: size)) + + let backgroundSize = CGSize(width: 33.0, height: 33.0) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)) + self.backgroundNode.cornerRadius = backgroundSize.width / 2.0 + + transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size)) + var expanded = false + if case let .media(_, maybeExpanded, _) = interfaceState.inputMode, maybeExpanded != nil { + expanded = true + } + transition.updateSublayerTransformScale(node: self.expandMediaInputButton, scale: CGPoint(x: 1.0, y: expanded ? 1.0 : -1.0)) + } + + func updateAccessibility() { + self.accessibilityTraits = .button + self.accessibilityLabel = self.strings.MediaPicker_Send + self.accessibilityHint = nil + } +} diff --git a/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift similarity index 93% rename from submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift rename to submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 2ccbb781bf..a8400f3f1d 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -15,9 +15,12 @@ import Speak import ObjCRuntimeUtils import LegacyComponents import InvisibleInkDustNode +import TextInputMenu +import ChatPresentationInterfaceState +import Pasteboard private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) -private let minInputFontSize = chatTextInputMinFontSize +private let minInputFontSize: CGFloat = 5.0 private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) @@ -104,7 +107,7 @@ private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackground } } -class CaptionEditableTextNode: EditableTextNode { +private class CaptionEditableTextNode: EditableTextNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let previousAlpha = self.alpha self.alpha = 1.0 @@ -114,44 +117,47 @@ class CaptionEditableTextNode: EditableTextNode { } } -class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, ASEditableTextNodeDelegate { - private let isCaption: Bool +public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, ASEditableTextNodeDelegate { + private let context: AccountContext - var textPlaceholderNode: ImmediateTextNode - let textInputContainerBackgroundNode: ASImageNode - let textInputContainer: ASDisplayNode - var textInputNode: CaptionEditableTextNode? - var dustNode: InvisibleInkDustNode? + private let isCaption: Bool + private let isAttachment: Bool + + private var textPlaceholderNode: ImmediateTextNode + private let textInputContainerBackgroundNode: ASImageNode + private let textInputContainer: ASDisplayNode + public var textInputNode: EditableTextNode? + private var dustNode: InvisibleInkDustNode? private var oneLineNode: ImmediateTextNode private var oneLineDustNode: InvisibleInkDustNode? let textInputBackgroundNode: ASDisplayNode let textInputBackgroundImageNode: ASImageNode private var transparentTextInputBackgroundImage: UIImage? - let actionButtons: ChatTextInputActionButtonsNode + private let actionButtons: AttachmentTextInputActionButtonsNode private let counterTextNode: ImmediateTextNode private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)? - var sendMessage: (PeerSelectionControllerSendMode) -> Void = { _ in } - var updateHeight: (Bool) -> Void = { _ in } + public var sendMessage: (AttachmentTextInputPanelSendMode) -> Void = { _ in } + public var updateHeight: (Bool) -> Void = { _ in } private var updatingInputState = false private var currentPlaceholder: String? - var effectivePresentationInterfaceState: (() -> ChatPresentationInterfaceState?)? + public var effectivePresentationInterfaceState: (() -> ChatPresentationInterfaceState?)? private var presentationInterfaceState: ChatPresentationInterfaceState? private var initializedPlaceholder = false - private let inputMenu: ChatTextInputMenu + private let inputMenu: TextInputMenu private var theme: PresentationTheme? private var strings: PresentationStrings? private let hapticFeedback = HapticFeedback() - var inputTextState: ChatTextInputState { + public var inputTextState: ChatTextInputState { if let textInputNode = self.textInputNode { let selectionRange: Range = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length) return ChatTextInputState(inputText: stateAttributedStringForText(textInputNode.attributedText ?? NSAttributedString()), selectionRange: selectionRange) @@ -177,24 +183,16 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - override var context: AccountContext? { - didSet { - self.actionButtons.micButton.account = self.context?.account - } - } - - var micButton: ChatTextInputMediaRecordingButton? { - return self.actionButtons.micButton - } + public var interfaceInteraction: ChatPanelInterfaceInteraction? - func updateSendButtonEnabled(_ enabled: Bool, animated: Bool) { + public func updateSendButtonEnabled(_ enabled: Bool, animated: Bool) { self.actionButtons.isUserInteractionEnabled = enabled let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate transition.updateAlpha(node: self.actionButtons, alpha: enabled ? 1.0 : 0.3) } - func updateInputTextState(_ state: ChatTextInputState, animated: Bool) { + public func updateInputTextState(_ state: ChatTextInputState, animated: Bool) { if state.inputText.length != 0 && self.textInputNode == nil { self.loadTextInputNode() } @@ -218,7 +216,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - var text: String { + public var text: String { get { return self.textInputNode?.attributedText?.string ?? "" } set(value) { @@ -235,7 +233,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - func caption() -> NSAttributedString { + public func caption() -> NSAttributedString { return self.textInputNode?.attributedText ?? NSAttributedString() } @@ -243,15 +241,17 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A private var spoilersRevealed = false - init(presentationInterfaceState: ChatPresentationInterfaceState, isCaption: Bool = false, presentController: @escaping (ViewController) -> Void) { + public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, isCaption: Bool = false, isAttachment: Bool = false, presentController: @escaping (ViewController) -> Void) { + self.context = context self.presentationInterfaceState = presentationInterfaceState self.isCaption = isCaption + self.isAttachment = isAttachment var hasSpoilers = true if presentationInterfaceState.chatLocation.peerId.namespace == Namespaces.Peer.SecretChat { hasSpoilers = false } - self.inputMenu = ChatTextInputMenu(hasSpoilers: hasSpoilers) + self.inputMenu = TextInputMenu(hasSpoilers: hasSpoilers) self.textInputContainerBackgroundNode = ASImageNode() self.textInputContainerBackgroundNode.isUserInteractionEnabled = false @@ -275,7 +275,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A self.oneLineNode.maximumNumberOfLines = 1 self.oneLineNode.isUserInteractionEnabled = false - self.actionButtons = ChatTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, presentationContext: nil, presentController: presentController) + self.actionButtons = AttachmentTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, presentController: presentController) self.counterTextNode = ImmediateTextNode() self.counterTextNode.textAlignment = .center @@ -287,7 +287,6 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A self.actionButtons.sendButton.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside) self.actionButtons.sendButton.alpha = 1.0 - self.actionButtons.micButton.alpha = 0.0 self.actionButtons.expandMediaInputButton.alpha = 0.0 self.actionButtons.updateAccessibility() @@ -313,12 +312,12 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } self.textInputBackgroundNode.view.addGestureRecognizer(recognizer) - self.updateSendButtonEnabled(isCaption, animated: false) + self.updateSendButtonEnabled(isCaption || isAttachment, animated: false) } - var sendPressed: ((NSAttributedString?) -> Void)? - var focusUpdated: ((Bool) -> Void)? - var heightUpdated: ((Bool) -> Void)? + public var sendPressed: ((NSAttributedString?) -> Void)? + public var focusUpdated: ((Bool) -> Void)? + public var heightUpdated: ((Bool) -> Void)? public func updateLayoutSize(_ size: CGSize, sideInset: CGFloat) -> CGFloat { guard let presentationInterfaceState = self.presentationInterfaceState else { @@ -345,7 +344,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A fatalError("init(coder:) has not been implemented") } - func loadTextInputNodeIfNeeded() { + public func loadTextInputNodeIfNeeded() { if self.textInputNode == nil { self.loadTextInputNode() } @@ -459,7 +458,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A return result } - override func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { + 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 { @@ -468,7 +467,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A return minimalHeight } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { + public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { let hadLayout = self.validLayout != nil let previousAdditionalSideInsets = self.validLayout?.3 self.validLayout = (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) @@ -550,7 +549,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A if themeUpdated || !self.initializedPlaceholder { self.initializedPlaceholder = true - let placeholder = self.isCaption ? interfaceState.strings.MediaPicker_AddCaption : interfaceState.strings.Conversation_InputTextPlaceholder + let placeholder = self.isCaption || self.isAttachment ? interfaceState.strings.MediaPicker_AddCaption : interfaceState.strings.Conversation_InputTextPlaceholder if self.currentPlaceholder != placeholder || themeUpdated { self.currentPlaceholder = placeholder @@ -704,19 +703,11 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A self.actionButtons.updateAccessibility() - if let prevInputPanelNode = self.prevInputPanelNode { - prevInputPanelNode.frame = CGRect(origin: .zero, size: prevInputPanelNode.frame.size) - } - return panelHeight } - - override func canHandleTransition(from prevInputPanelNode: ChatInputPanelNode?) -> Bool { - return false - } - + private var skipUpdate = false - @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { + @objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed) @@ -898,8 +889,8 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A private func updateCounterTextNode(transition: ContainedViewLayoutTransition) { let inputTextMaxLength: Int32? - if self.isCaption { - inputTextMaxLength = self.context?.currentLimitsConfiguration.with { $0 }.maxMediaCaptionLength + if self.isCaption || self.isAttachment { + inputTextMaxLength = self.context.currentLimitsConfiguration.with { $0 }.maxMediaCaptionLength } else { inputTextMaxLength = nil } @@ -938,11 +929,9 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A inputHasText = true } - if let _ = self.presentationInterfaceState { - self.textPlaceholderNode.isHidden = inputHasText - } - if let presentationInterfaceState = self.presentationInterfaceState { + self.textPlaceholderNode.isHidden = inputHasText + let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) let textFont = Font.regular(baseFontSize) @@ -999,7 +988,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - @objc func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool { + @objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool { if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendButton.alpha.isZero { self.sendButtonPressed() } @@ -1025,7 +1014,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - @objc func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) { + @objc public func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) { if !dueToEditing && !self.updatingInputState { let inputTextState = self.inputTextState self.skipUpdate = true @@ -1045,7 +1034,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - @objc func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) { + @objc public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) { self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in return (.text, state.keyboardButtonsMessage?.id) }) @@ -1058,7 +1047,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) { + public func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) { self.storedInputLanguage = editableTextNode.textInputMode.primaryLanguage self.inputMenu.deactivate() @@ -1069,7 +1058,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - 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) @@ -1196,7 +1185,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A self.updateSpoilersRevealed(animated: animated) } - @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 { var cleanText = text let removeSequences: [String] = ["\u{202d}", "\u{202c}"] for sequence in removeSequences { @@ -1229,7 +1218,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A return true } - @objc func editableTextNodeShouldCopy(_ editableTextNode: ASEditableTextNode) -> Bool { + @objc public func editableTextNodeShouldCopy(_ editableTextNode: ASEditableTextNode) -> Bool { self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in storeInputTextInPasteboard(current.inputText.attributedSubstring(from: NSMakeRange(current.selectionRange.lowerBound, current.selectionRange.count))) return (current, inputMode) @@ -1237,7 +1226,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A return false } - @objc func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool { + @objc public func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool { let pasteboard = UIPasteboard.general var attributedString: NSAttributedString? @@ -1264,8 +1253,8 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A @objc func sendButtonPressed() { let inputTextMaxLength: Int32? - if self.isCaption { - inputTextMaxLength = self.context?.currentLimitsConfiguration.with { $0 }.maxMediaCaptionLength + if self.isCaption || self.isAttachment { + inputTextMaxLength = self.context.currentLimitsConfiguration.with { $0 }.maxMediaCaptionLength } else { inputTextMaxLength = nil } @@ -1294,18 +1283,18 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A } } - var isFocused: Bool { + public var isFocused: Bool { if self.imitateFocus { return true } return self.textInputNode?.isFirstResponder() ?? false } - func ensureUnfocused() { + public func ensureUnfocused() { self.textInputNode?.resignFirstResponder() } - func ensureFocused() { + public func ensureFocused() { self.imitateFocus = false if self.textInputNode == nil { @@ -1315,13 +1304,9 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A self.textInputNode?.becomeFirstResponder() } - 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) - } else { - return self.actionButtons.frame.insetBy(dx: 0.0, dy: 6.0).offsetBy(dx: 2.0, dy: 0.0) - } + return self.actionButtons.frame.insetBy(dx: 0.0, dy: 6.0).offsetBy(dx: 4.0, dy: 0.0) } return nil } diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index 500fea157c..38ab706a33 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -24,6 +24,9 @@ swift_library( "//submodules/ComponentFlow:ComponentFlow", "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", "//submodules/DirectionalPanGesture:DirectionalPanGesture", + "//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode", + "//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI", + "//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 081151df44..9e63e249f1 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -5,9 +5,11 @@ import SwiftSignalKit import UIKitRuntimeUtils import Display import DirectionalPanGesture +import TelegramPresentationData final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { - let scrollNode: ASDisplayNode + let wrappingNode: ASDisplayNode + let clipNode: ASDisplayNode let container: NavigationContainer private(set) var isReady: Bool = false @@ -22,7 +24,9 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { private var isDismissed = false private var isInteractiveDimissEnabled = true - private var validLayout: (layout: ContainerViewLayout, controllers: [ViewController], coveredByModalTransition: CGFloat)? + public private(set) var isExpanded = false + + private var validLayout: (layout: ContainerViewLayout, controllers: [AttachmentContainable], coveredByModalTransition: CGFloat)? var keyboardViewManager: KeyboardViewManager? { didSet { @@ -40,16 +44,19 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { private var panGestureRecognizer: UIPanGestureRecognizer? - init(controllerRemoved: @escaping (ViewController) -> Void) { - self.scrollNode = ASDisplayNode() + init(presentationData: PresentationData) { + self.wrappingNode = ASDisplayNode() + self.clipNode = ASDisplayNode() + self.clipNode.backgroundColor = presentationData.theme.list.plainBackgroundColor - self.container = NavigationContainer(controllerRemoved: controllerRemoved) + self.container = NavigationContainer(controllerRemoved: { _ in }) self.container.clipsToBounds = true super.init() - self.addSubnode(self.scrollNode) - self.scrollNode.addSubnode(self.container) + self.addSubnode(self.wrappingNode) + self.wrappingNode.addSubnode(self.clipNode) + self.clipNode.addSubnode(self.container) self.isReady = self.container.isReady self.container.isReadyUpdated = { [weak self] in @@ -75,7 +82,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { panRecognizer.delaysTouchesBegan = false panRecognizer.cancelsTouchesInView = true self.panGestureRecognizer = panRecognizer - self.scrollNode.view.addGestureRecognizer(panRecognizer) + self.wrappingNode.view.addGestureRecognizer(panRecognizer) } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { @@ -98,6 +105,12 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { switch recognizer.state { case .began: + let point = recognizer.location(in: self.view) + let currentHitView = self.hitTest(point, with: nil) + let scrollViewAndListNode = self.findScrollView(view: currentHitView) + let scrollView = scrollViewAndListNode?.0 + let listNode = scrollViewAndListNode?.1 + let topInset: CGFloat if self.isExpanded { topInset = 0.0 @@ -105,12 +118,6 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { topInset = edgeTopInset } - let point = recognizer.location(in: self.view) - let currentHitView = self.hitTest(point, with: nil) - let scrollViewAndListNode = self.findScrollView(view: currentHitView) - let scrollView = scrollViewAndListNode?.0 - let listNode = scrollViewAndListNode?.1 - self.panGestureArguments = (topInset, 0.0, scrollView, listNode) case .changed: guard let (topInset, panOffset, scrollView, listNode) = self.panGestureArguments else { @@ -121,18 +128,30 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { var translation = recognizer.translation(in: self.view).y - if case let .known(value) = visibleContentOffset, value <= 0.5 { - } else if contentOffset <= 0.5 { - } else { + var currentOffset = topInset + translation + + let epsilon = 1.0 + if case let .known(value) = visibleContentOffset, value <= epsilon { + if let scrollView = scrollView { + scrollView.bounces = false + scrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: false) + } + } else if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon { + scrollView.bounces = false + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } else if let scrollView = scrollView { translation = panOffset + currentOffset = topInset + translation if self.isExpanded { recognizer.setTranslation(CGPoint(), in: self.view) + } else if currentOffset > 0.0 { + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } } self.panGestureArguments = (topInset, translation, scrollView, listNode) - let currentOffset = topInset + translation + if !self.isExpanded { if currentOffset > 0.0, let scrollView = scrollView { scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) @@ -166,7 +185,9 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { var bounds = self.bounds bounds.origin.y = -translation bounds.origin.y = min(0.0, bounds.origin.y) - + + scrollView?.bounces = true + let offset = currentTopInset + panOffset let topInset: CGFloat = edgeTopInset if self.isExpanded { @@ -243,9 +264,20 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { } return true } + + func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) { + guard isExpanded != self.isExpanded else { + return + } + self.isExpanded = isExpanded + + guard let (layout, controllers, coveredByModalTransition) = self.validLayout else { + return + } + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: transition) + } - var isExpanded = false - func update(layout: ContainerViewLayout, controllers: [ViewController], coveredByModalTransition: CGFloat, transition: ContainedViewLayoutTransition) { + func update(layout: ContainerViewLayout, controllers: [AttachmentContainable], coveredByModalTransition: CGFloat, transition: ContainedViewLayoutTransition) { if self.isDismissed { return } @@ -269,28 +301,29 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { } else { topInset = self.isExpanded ? 0.0 : edgeTopInset } - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size)) + transition.updateFrame(node: self.wrappingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size)) let modalProgress = isLandscape ? 0.0 : (1.0 - topInset / defaultTopInset) self.updateModalProgress?(modalProgress, transition) let containerLayout: ContainerViewLayout let containerFrame: CGRect + let clipFrame: CGRect let containerScale: CGFloat if layout.metrics.widthClass == .compact { - self.container.clipsToBounds = true + self.clipNode.clipsToBounds = true if isLandscape { - self.container.cornerRadius = 0.0 + self.clipNode.cornerRadius = 0.0 } else { - self.container.cornerRadius = 10.0 + self.clipNode.cornerRadius = 10.0 } if #available(iOS 11.0, *) { if layout.safeInsets.bottom.isZero { - self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.wrappingNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] } else { - self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] + self.wrappingNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] } } @@ -302,6 +335,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let unscaledFrame = CGRect(origin: CGPoint(), size: containerLayout.size) containerScale = 1.0 containerFrame = unscaledFrame + clipFrame = unscaledFrame } else { containerTopInset = 10.0 if let statusBarHeight = layout.statusBarHeight { @@ -310,19 +344,30 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let effectiveStatusBarHeight: CGFloat? = nil - containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: layout.intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.safeInsets.bottom, right: layout.safeInsets.right), additionalInsets: layout.additionalInsets, statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) + let inset: CGFloat = 70.0 + var safeInsets = layout.safeInsets + safeInsets.left += inset + safeInsets.right += inset + + var intrinsicInsets = layout.intrinsicInsets + intrinsicInsets.left += inset + intrinsicInsets.right += inset + + containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width + inset * 2.0, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom + 49.0, right: intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: safeInsets.left, bottom: safeInsets.bottom, right: safeInsets.right), additionalInsets: layout.additionalInsets, statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: containerLayout.size) let maxScale: CGFloat = (containerLayout.size.width - 16.0 * 2.0) / containerLayout.size.width containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition let maxScaledTopInset: CGFloat = containerTopInset - 10.0 let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition - containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) + containerFrame = unscaledFrame.offsetBy(dx: -inset, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) + + clipFrame = CGRect(x: containerFrame.minX + inset, y: containerFrame.minY, width: containerFrame.width - inset * 2.0, height: containerFrame.height) } } else { - self.container.clipsToBounds = true - self.container.cornerRadius = 10.0 + self.clipNode.clipsToBounds = true + self.clipNode.cornerRadius = 10.0 if #available(iOS 11.0, *) { - self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] + self.clipNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] } let verticalInset: CGFloat = 44.0 @@ -332,6 +377,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) containerScale = 1.0 + clipFrame = containerFrame var inputHeight: CGFloat? if let inputHeightValue = layout.inputHeight { @@ -342,7 +388,8 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { containerLayout = ContainerViewLayout(size: containerSize, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: effectiveStatusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) } - transition.updateFrameAsPositionAndBounds(node: self.container, frame: containerFrame) + transition.updateFrameAsPositionAndBounds(node: self.clipNode, frame: clipFrame) + transition.updateFrameAsPositionAndBounds(node: self.container, frame: CGRect(origin: CGPoint(x: containerFrame.minX, y: 0.0), size: containerFrame.size)) transition.updateTransformScale(node: self.container, scale: containerScale) self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition) @@ -404,7 +451,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - if !self.scrollNode.frame.contains(point) { + if !self.wrappingNode.frame.contains(point) { return false } return super.point(inside: point, with: event) diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index cf0fc64b2d..6ae9e738d6 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -21,21 +21,60 @@ public enum AttachmentButtonType: Equatable { case app(String) } +public protocol AttachmentContainable: ViewController { + var requestAttachmentMenuExpansion: () -> Void { get set } +} + +public protocol AttachmentMediaPickerContext { + var selectionCount: Signal { get } + var caption: Signal { get } + + func setCaption(_ caption: NSAttributedString) + func send(silently: Bool) + func schedule() +} + public class AttachmentController: ViewController { private let context: AccountContext - + private let buttons: [AttachmentButtonType] + private final class Node: ASDisplayNode { private weak var controller: AttachmentController? private let dim: ASDisplayNode private let container: AttachmentContainer - private let panel: AttachmentPanel + let panel: AttachmentPanel private var validLayout: ContainerViewLayout? private var modalProgress: CGFloat = 0.0 private var currentType: AttachmentButtonType? - private var currentController: ViewController? - + private var currentController: AttachmentContainable? + + private let captionDisposable = MetaDisposable() + + private let mediaSelectionCountDisposable = MetaDisposable() + private var mediaPickerContext: AttachmentMediaPickerContext? { + didSet { + if let mediaPickerContext = self.mediaPickerContext { + self.captionDisposable.set((mediaPickerContext.caption + |> deliverOnMainQueue).start(next: { [weak self] caption in + if let strongSelf = self { + strongSelf.panel.updateCaption(caption ?? NSAttributedString()) + } + })) + self.mediaSelectionCountDisposable.set((mediaPickerContext.selectionCount + |> deliverOnMainQueue).start(next: { [weak self] count in + if let strongSelf = self { + strongSelf.updateSelectionCount(count) + } + })) + } else { + self.updateSelectionCount(0) + self.mediaSelectionCountDisposable.set(nil) + } + } + } + init(controller: AttachmentController) { self.controller = controller @@ -43,14 +82,15 @@ public class AttachmentController: ViewController { self.dim.alpha = 0.0 self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) - self.container = AttachmentContainer(controllerRemoved: { _ in - }) + let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } + self.container = AttachmentContainer(presentationData: presentationData) + self.container.canHaveKeyboardFocus = true self.panel = AttachmentPanel(context: controller.context) super.init() self.addSubnode(self.dim) - + self.container.updateModalProgress = { [weak self] progress, transition in if let strongSelf = self, let layout = strongSelf.validLayout { strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition) @@ -65,17 +105,65 @@ public class AttachmentController: ViewController { } } + self.container.interactivelyDismissed = { [weak self] in + if let strongSelf = self { + strongSelf.controller?.dismiss(animated: true) + } + } + self.panel.selectionChanged = { [weak self] type, ascending in if let strongSelf = self { strongSelf.switchToController(type, ascending) } } - self.container.interactivelyDismissed = { [weak self] in + self.panel.beganTextEditing = { [weak self] in if let strongSelf = self { - strongSelf.controller?.dismiss(animated: true) + strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) } } + + self.panel.textUpdated = { [weak self] text in + if let strongSelf = self { + strongSelf.mediaPickerContext?.setCaption(text) + } + } + + self.panel.sendMessagePressed = { [weak self] mode in + if let strongSelf = self { + switch mode { + case .generic: + strongSelf.mediaPickerContext?.send(silently: false) + case .silent: + strongSelf.mediaPickerContext?.send(silently: true) + case .schedule: + strongSelf.mediaPickerContext?.schedule() + } + } + } + + self.panel.requestLayout = { [weak self] in + if let strongSelf = self, let layout = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + self.panel.present = { [weak self] c in + if let strongSelf = self { + strongSelf.controller?.present(c, in: .window(.root)) + } + } + + self.panel.presentInGlobalOverlay = { [weak self] c in + if let strongSelf = self { + strongSelf.controller?.presentInGlobalOverlay(c, with: nil) + } + } + } + + deinit { + self.captionDisposable.dispose() + self.mediaSelectionCountDisposable.dispose() } override func didLoad() { @@ -86,6 +174,14 @@ public class AttachmentController: ViewController { self.switchToController(.gallery, false) } + private var selectionCount: Int = 0 + private func updateSelectionCount(_ count: Int) { + self.selectionCount = count + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + } + } + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.controller?.dismiss(animated: true) @@ -108,6 +204,8 @@ public class AttachmentController: ViewController { }) let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) + + self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) } else { self.controller?.dismiss(animated: false, completion: nil) } @@ -119,36 +217,64 @@ public class AttachmentController: ViewController { } let previousType = self.currentType self.currentType = type - self.controller?.requestController(type, { [weak self] controller in - if let strongSelf = self, let controller = controller { - controller._presentedInModal = true - controller.navigation_setPresenting(strongSelf.controller) - - let animateTransition = previousType != nil - strongSelf.currentController = controller - - if animateTransition, let snapshotView = strongSelf.container.scrollNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = strongSelf.container.scrollNode.frame - strongSelf.container.view.insertSubview(snapshotView, belowSubview: strongSelf.panel.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - - if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in + if let strongSelf = self { + strongSelf.mediaPickerContext = mediaPickerContext + if let controller = controller { + controller._presentedInModal = true + controller.navigation_setPresenting(strongSelf.controller) + controller.requestAttachmentMenuExpansion = { [weak self] in + self?.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) + } + + let animateTransition = previousType != nil + strongSelf.currentController = controller + + if animateTransition, let snapshotView = strongSelf.container.container.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = strongSelf.container.container.frame + strongSelf.container.clipNode.view.addSubview(snapshotView) + + let _ = (controller.ready.get() + |> filter { + $0 + } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self, weak snapshotView] _ in + guard let strongSelf = self else { + return + } + + if ascending { + strongSelf.container.container.view.layer.animatePosition(from: CGPoint(x: 70.0, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } else { + strongSelf.container.container.view.layer.animatePosition(from: CGPoint(x: -70.0, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + + snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + }) + } + + if let layout = strongSelf.validLayout { + strongSelf.switchingController = true + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.switchingController = false + } } } }) } func animateIn(transition: ContainedViewLayoutTransition) { - transition.updateAlpha(node: self.dim, alpha: 1.0) + ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) + transition.animatePositionAdditive(node: self.container, offset: CGPoint(x: 0.0, y: self.bounds.height + self.container.bounds.height / 2.0 - (self.container.position.y - self.bounds.height))) } private var isCollapsed: Bool = false private var isUpdatingContainer = false + private var switchingController = false func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout @@ -166,7 +292,7 @@ public class AttachmentController: ViewController { let controllers = self.currentController.flatMap { [$0] } ?? [] containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: layout.size)) - self.container.update(layout: layout, controllers: controllers, coveredByModalTransition: 0.0, transition: .immediate) + self.container.update(layout: layout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady { self.addSubnode(self.container) @@ -177,40 +303,36 @@ public class AttachmentController: ViewController { self.isUpdatingContainer = false } - - let buttons: [AttachmentButtonType] = [.camera, .gallery, .file, .location, .contact, .poll, .app("App")] - - let sideInset: CGFloat = 16.0 - let bottomInset: CGFloat - if layout.intrinsicInsets.bottom > 0.0 { - bottomInset = layout.intrinsicInsets.bottom - 4.0 - } else { - bottomInset = 4.0 - } - - if self.modalProgress < 0.75 { + + if self.modalProgress < 0.5 { self.isCollapsed = false } else if self.modalProgress == 1.0 { self.isCollapsed = true } - let panelSize = CGSize(width: layout.size.width - sideInset * 2.0, height: panelButtonSize.height) - transition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - panelSize.height - bottomInset), size: panelSize)) - self.panel.update(buttons: buttons, isCollapsed: self.isCollapsed, size: panelSize, transition: transition) + let isEffecitvelyCollapsedUpdated = (self.isCollapsed || self.selectionCount > 0) != (self.panel.isCollapsed || self.panel.isSelecting) + let panelHeight = self.panel.update(layout: layout, buttons: self.controller?.buttons ?? [], isCollapsed: self.isCollapsed, isSelecting: self.selectionCount > 0, transition: transition) + var panelTransition = transition + if isEffecitvelyCollapsedUpdated { + panelTransition = .animated(duration: 0.25, curve: .easeInOut) + } + panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) } } - public var requestController: (AttachmentButtonType, @escaping (ViewController?) -> Void) -> Void = { _, completion in - completion(nil) + public var requestController: (AttachmentButtonType, @escaping (AttachmentContainable?, AttachmentMediaPickerContext?) -> Void) -> Void = { _, completion in + completion(nil, nil) } - public init(context: AccountContext) { + public init(context: AccountContext, buttons: [AttachmentButtonType]) { self.context = context + self.buttons = buttons super.init(navigationBarPresentationData: nil) self.statusBar.statusBarStyle = .Ignore self.blocksBackgroundWhenInOverlay = true + self.acceptsFocusWhenInOverlay = true } public required init(coder aDecoder: NSCoder) { diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 1671ce87d0..f3809c60ef 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -3,14 +3,20 @@ import UIKit import AsyncDisplayKit import Display import ComponentFlow +import Postbox +import TelegramCore import TelegramPresentationData import AccountContext +import AttachmentTextInputPanelNode +import ChatPresentationInterfaceState +import ChatSendMessageActionUI +import ChatTextLinkEditUI let panelButtonSize = CGSize(width: 80.0, height: 72.0) -let smallPanelButtonSize = CGSize(width: 54.0, height: 46.0) +let smallPanelButtonSize = CGSize(width: 60.0, height: 49.0) private let iconSize = CGSize(width: 54.0, height: 42.0) -private let sideInset: CGFloat = 3.0 +private let normalSideInset: CGFloat = 3.0 private let smallSideInset: CGFloat = 0.0 private enum AttachmentButtonTransition { @@ -18,34 +24,6 @@ private enum AttachmentButtonTransition { case selection } -private func generateShadowImage() -> UIImage? { - return generateImage(CGSize(width: 90.0, height: 90.0), rotatedContext: { size, context in - let bounds = CGRect(origin: CGPoint(), size: size) - context.clear(bounds) - -// let rect = bounds.insetBy(dx: 12.0, dy: 24.0) -// let path = UIBezierPath(roundedRect: CGRect(origin: rect.origin, size: CGSize(width: rect.width, height: rect.height + 33.0)), cornerRadius: 24.0).cgPath -// context.addRect(bounds) -// context.addPath(path) -// context.clip(using: .evenOdd) -// -// context.addPath(path) -// context.setShadow(offset: CGSize(width: 0.0, height: 0.0), blur: 40.0, color: UIColor(rgb: 0x000000, alpha: 0.06).cgColor) -// context.setFillColor(UIColor.white.cgColor) -// context.setBlendMode(.multiply) -// context.fillPath() -// -// context.resetClip() -// context.setBlendMode(.normal) - - var locations: [CGFloat] = [0.0, 1.0] - let colors: [CGColor] = [UIColor(rgb: 0x000000, alpha: 0.0).cgColor, UIColor(rgb: 0x000000, alpha: 0.2).cgColor] - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: []) - })?.stretchableImage(withLeftCapWidth: 45, topCapHeight: 70) -} - private func generateBackgroundImage(colors: [UIColor]) -> UIImage? { return generateImage(iconSize, rotatedContext: { size, context in var locations: [CGFloat] @@ -100,10 +78,6 @@ private let buttonSelectionMaskImage: UIImage? = { })?.withRenderingMode(.alwaysTemplate) }() -private let ringImage: UIImage? = { - return generateFilledCircleImage(diameter: iconSize.width, color: nil, strokeColor: .white, strokeWidth: 2.0, backgroundColor: nil) -}() - private final class AttachButtonComponent: CombinedComponent { let context: AccountContext let type: AttachmentButtonType @@ -177,7 +151,7 @@ private final class AttachButtonComponent: CombinedComponent { name = context.component.strings.Attachment_Camera animationName = "anim_camera" imageName = "Chat/Attach Menu/Camera" - backgroundColors = [UIColor(rgb: 0xba4aae), UIColor(rgb: 0xdd4e6f), UIColor(rgb: 0xf3b76c)] + backgroundColors = [UIColor(rgb: 0xba4aae), UIColor(rgb: 0xdd4e6f), UIColor(rgb: 0xf4b76c)] case .gallery: name = context.component.strings.Attachment_Gallery animationName = "anim_gallery" @@ -243,10 +217,10 @@ private final class AttachButtonComponent: CombinedComponent { let iconScale = normalIconScale - (normalIconScale - smallIconScale) * abs(context.component.transitionFraction) let iconOffset: CGFloat = (isCollapsed ? 10.0 : 20.0) * context.component.transitionFraction - let iconFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - icon.size.width) / 2.0) + iconOffset, y: isCollapsed ? 3.0 : topInset), size: icon.size) - var titleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - title.size.width) / 2.0) + iconOffset, y: iconFrame.midY + (iconFrame.height * 0.5 * iconScale) + spacing), size: title.size) + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - icon.size.width) / 2.0) + iconOffset, y: isCollapsed ? 3.0 : topInset), size: icon.size) + var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - title.size.width) / 2.0) + iconOffset, y: iconFrame.midY + (iconFrame.height * 0.5 * iconScale) + spacing), size: title.size) if isCollapsed { - titleFrame.origin.y = floor(iconFrame.midY - title.size.height / 2.0) + titleFrame.origin.y = floorToScreenPixels(iconFrame.midY - title.size.height / 2.0) } context.add(title @@ -321,18 +295,25 @@ private final class AttachButtonIconComponent: Component { } final class View: HighlightTrackingButton { + private let containerView: UIView private let glowView: UIImageView private let selectionView: UIImageView private let backgroundView: UIView private let iconView: UIImageView + private let highlightView: UIView private var action: (() -> Void)? private var currentColors: [UIColor] = [] private var currentImageName: String? private var currentIsSelected: Bool? + + private let hapticFeedback = HapticFeedback() init() { + self.containerView = UIView() + self.containerView.isUserInteractionEnabled = false + self.glowView = UIImageView() self.glowView.image = buttonGlowImage self.glowView.isUserInteractionEnabled = false @@ -348,16 +329,41 @@ private final class AttachButtonIconComponent: Component { self.iconView = UIImageView() + self.highlightView = UIView() + self.highlightView.alpha = 0.0 + self.highlightView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.1) + self.highlightView.isUserInteractionEnabled = false + super.init(frame: CGRect()) - self.addSubview(self.glowView) - self.addSubview(self.selectionView) - self.addSubview(self.backgroundView) - self.addSubview(self.iconView) + self.addSubview(self.containerView) + self.containerView.addSubview(self.glowView) + self.containerView.addSubview(self.selectionView) + self.containerView.addSubview(self.backgroundView) + self.backgroundView.addSubview(self.iconView) + self.backgroundView.addSubview(self.highlightView) self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - self.highligthedChanged = { _ in - + self.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.containerView.layer.animateScale(from: 1.0, to: 0.9, duration: 0.3, removeOnCompletion: false) + + strongSelf.highlightView.layer.removeAnimation(forKey: "opacity") + strongSelf.highlightView.alpha = 1.0 + + strongSelf.hapticFeedback.impact(.click05) + } else { + if let presentationLayer = strongSelf.containerView.layer.presentation() { + strongSelf.containerView.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.2, removeOnCompletion: false) + } + + strongSelf.highlightView.alpha = 0.0 + strongSelf.highlightView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + + strongSelf.hapticFeedback.impact(.click06) + } + } } } @@ -400,7 +406,9 @@ private final class AttachButtonIconComponent: Component { } let contentFrame = CGRect(origin: CGPoint(), size: availableSize) + self.containerView.frame = contentFrame self.backgroundView.frame = contentFrame + self.highlightView.frame = contentFrame self.glowView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: contentFrame.width + 12.0, height: contentFrame.height + 12.0)) self.glowView.center = CGPoint(x: contentFrame.midX, y: contentFrame.midY) @@ -425,30 +433,39 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private let context: AccountContext private var presentationData: PresentationData - private let shadowNode: ASImageNode + private var presentationInterfaceState: ChatPresentationInterfaceState + private var interfaceInteraction: ChatPanelInterfaceInteraction? + private let containerNode: ASDisplayNode private var effectView: UIVisualEffectView? private let scrollNode: ASScrollNode private let backgroundNode: ASDisplayNode + private let separatorNode: ASDisplayNode private var buttonViews: [Int: ComponentHostView] = [:] + private var textInputPanelNode: AttachmentTextInputPanelNode? + private var buttons: [AttachmentButtonType] = [] private var selectedIndex: Int = 1 - private var isCollapsed: Bool = false + private(set) var isCollapsed: Bool = false + private(set) var isSelecting: Bool = false - private var validLayout: CGSize? + private var validLayout: ContainerViewLayout? private var scrollLayout: (width: CGFloat, contentSize: CGSize)? var selectionChanged: (AttachmentButtonType, Bool) -> Void = { _, _ in } + var beganTextEditing: () -> Void = {} + var textUpdated: (NSAttributedString) -> Void = { _ in } + var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in } + var requestLayout: () -> Void = {} + var present: (ViewController) -> Void = { _ in } + var presentInGlobalOverlay: (ViewController) -> Void = { _ in } init(context: AccountContext) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - self.shadowNode = ASImageNode() - self.shadowNode.contentMode = .scaleToFill - self.shadowNode.displaysAsynchronously = false - self.shadowNode.image = generateShadowImage() + + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil) self.containerNode = ASDisplayNode() self.containerNode.clipsToBounds = true @@ -457,12 +474,182 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor + super.init() - self.addSubnode(self.shadowNode) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.backgroundNode) + self.containerNode.addSubnode(self.separatorNode) self.containerNode.addSubnode(self.scrollNode) + + self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in + }, setupEditMessage: { _, _ in + }, beginMessageSelection: { _, _ in + }, deleteSelectedMessages: { + }, reportSelectedMessages: { + }, reportMessages: { _, _ in + }, blockMessageAuthor: { _, _ in + }, deleteMessages: { _, _, f in + f(.default) + }, forwardSelectedMessages: { + }, forwardCurrentForwardMessages: { + }, forwardMessages: { _ in + }, updateForwardOptionsState: { [weak self] value in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardOptionsState($0.forwardOptionsState) }) }) + } + }, presentForwardOptions: { _ in + }, shareSelectedMessages: { + }, updateTextInputStateAndMode: { [weak self] f in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, { state in + let (updatedState, updatedMode) = f(state.interfaceState.effectiveInputState, state.inputMode) + return state.updatedInterfaceState { interfaceState in + return interfaceState.withUpdatedEffectiveInputState(updatedState) + }.updatedInputMode({ _ in updatedMode }) + }) + } + }, updateInputModeAndDismissedButtonKeyboardMessageId: { [weak self] f in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, { + let (updatedInputMode, updatedClosedButtonKeyboardMessageId) = f($0) + return $0.updatedInputMode({ _ in return updatedInputMode }).updatedInterfaceState({ + $0.withUpdatedMessageActionsState({ value in + var value = value + value.closedButtonKeyboardMessageId = updatedClosedButtonKeyboardMessageId + return value + }) + }) + }) + } + }, openStickers: { + }, editMessage: { + }, beginMessageSearch: { _, _ in + }, dismissMessageSearch: { + }, updateMessageSearch: { _ in + }, openSearchResults: { + }, navigateMessageSearch: { _ in + }, openCalendarSearch: { + }, toggleMembersSearch: { _ in + }, navigateToMessage: { _, _, _, _ in + }, navigateToChat: { _ in + }, navigateToProfile: { _ in + }, openPeerInfo: { + }, togglePeerNotifications: { + }, sendContextResult: { _, _, _, _ in + return false + }, sendBotCommand: { _, _ in + }, sendBotStart: { _ in + }, botSwitchChatWithPayload: { _, _ in + }, beginMediaRecording: { _ in + }, finishMediaRecording: { _ in + }, stopMediaRecording: { + }, lockMediaRecording: { + }, deleteRecordedMedia: { + }, sendRecordedMedia: { _ in + }, displayRestrictedInfo: { _, _ in + }, displayVideoUnmuteTip: { _ in + }, switchMediaRecordingMode: { + }, setupMessageAutoremoveTimeout: { + }, sendSticker: { _, _, _, _ in + return false + }, unblockPeer: { + }, pinMessage: { _, _ in + }, unpinMessage: { _, _, _ in + }, unpinAllMessages: { + }, openPinnedList: { _ in + }, shareAccountContact: { + }, reportPeer: { + }, presentPeerContact: { + }, dismissReportPeer: { + }, deleteChat: { + }, beginCall: { _ in + }, toggleMessageStickerStarred: { _ in + }, presentController: { _, _ in + }, getNavigationController: { + return nil + }, presentGlobalOverlayController: { _, _ in + }, navigateFeed: { + }, openGrouping: { + }, toggleSilentPost: { + }, requestUnvoteInMessage: { _ in + }, requestStopPollInMessage: { _ in + }, updateInputLanguage: { _ in + }, unarchiveChat: { + }, openLinkEditing: { [weak self] in + if let strongSelf = self { + var selectionRange: Range? + var text: String? + var inputMode: ChatInputMode? + + strongSelf.updateChatPresentationInterfaceState(animated: true, { state in + selectionRange = state.interfaceState.effectiveInputState.selectionRange + if let selectionRange = selectionRange { + text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count)).string + } + inputMode = state.inputMode + return state + }) + + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: (presentationData, .never()), account: strongSelf.context.account, text: text ?? "", link: nil, apply: { [weak self] link in + if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange { + if let link = link { + strongSelf.updateChatPresentationInterfaceState(animated: true, { state in + return state.updatedInterfaceState({ + $0.withUpdatedEffectiveInputState(chatTextInputAddLinkAttribute($0.effectiveInputState, selectionRange: selectionRange, url: link)) + }) + }) + } + if let textInputPanelNode = strongSelf.textInputPanelNode { + textInputPanelNode.ensureFocused() + } + strongSelf.updateChatPresentationInterfaceState(animated: true, { state in + return state.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({ + $0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex)) + }) + }) + } + }) + strongSelf.present(controller) + } + }, reportPeerIrrelevantGeoLocation: { + }, displaySlowmodeTooltip: { _, _ in + }, displaySendMessageOptions: { [weak self] node, gesture in + guard let strongSelf = self, let textInputPanelNode = strongSelf.textInputPanelNode else { + return + } + textInputPanelNode.loadTextInputNodeIfNeeded() + guard let textInputNode = textInputPanelNode.textInputNode else { + return + } + let controller = ChatSendMessageActionSheetController(context: strongSelf.context, interfaceState: strongSelf.presentationInterfaceState, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, completion: { + }, sendMessage: { [weak textInputPanelNode] silently in + textInputPanelNode?.sendMessage(silently ? .silent : .generic) + }, schedule: { [weak textInputPanelNode] in + textInputPanelNode?.sendMessage(.schedule) + }) + strongSelf.presentInGlobalOverlay(controller) + }, openScheduledMessages: { + }, openPeersNearby: { + }, displaySearchResultsTooltip: { _, _ in + }, unarchivePeer: { + }, scrollToTop: { + }, viewReplies: { _, _ in + }, activatePinnedListPreview: { _, _ in + }, joinGroupCall: { _ in + }, presentInviteMembers: { + }, presentGigagroupHelp: { + }, editMessageMedia: { _, _ in + }, updateShowCommands: { _ in + }, updateShowSendAsPeers: { _ in + }, openInviteRequests: { + }, openSendAsPeer: { _, _ in + }, presentChatRequestAdminInfo: { + }, displayCopyProtectionTip: { _, _ in + }, statuses: nil) } override func didLoad() { @@ -470,8 +657,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { if #available(iOS 13.0, *) { self.containerNode.layer.cornerCurve = .continuous } - self.containerNode.layer.cornerRadius = 23.0 - + self.scrollNode.view.delegate = self self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false @@ -488,8 +674,32 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.containerNode.view.insertSubview(effectView, at: 0) } + func updateCaption(_ caption: NSAttributedString) { + if !caption.string.isEmpty { + self.loadTextNodeIfNeeded() + } + self.updateChatPresentationInterfaceState(animated: false, { $0.updatedInterfaceState { $0.withUpdatedComposeInputState(ChatTextInputState(inputText: caption))} }) + } + + private func updateChatPresentationInterfaceState(animated: Bool = true, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { + self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, f, completion: completion) + } + + private func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { + let presentationInterfaceState = f(self.presentationInterfaceState) + let updateInputTextState = self.presentationInterfaceState.interfaceState.effectiveInputState != presentationInterfaceState.interfaceState.effectiveInputState + + self.presentationInterfaceState = presentationInterfaceState + + if let textInputPanelNode = self.textInputPanelNode, updateInputTextState { + textInputPanelNode.updateInputTextState(presentationInterfaceState.interfaceState.effectiveInputState, animated: transition.isAnimated) + + self.textUpdated(presentationInterfaceState.interfaceState.effectiveInputState.inputText) + } + } + func updateViews(transition: Transition) { - guard let _ = self.validLayout else { + guard let layout = self.validLayout else { return } @@ -498,9 +708,15 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { var validButtons = Set() let buttonSize = self.isCollapsed ? smallPanelButtonSize : panelButtonSize + var sideInset = self.isCollapsed ? smallSideInset : normalSideInset + + let buttonsWidth = sideInset * 2.0 + buttonSize.width * CGFloat(self.buttons.count) + if buttonsWidth < layout.size.width { + sideInset = floorToScreenPixels((layout.size.width - buttonsWidth) / 2.0) + } for i in 0 ..< self.buttons.count { - let buttonFrame = CGRect(origin: CGPoint(x: (self.isCollapsed ? smallSideInset : sideInset) + buttonSize.width * CGFloat(i), y: 0.0), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset + buttonSize.width * CGFloat(i), y: 0.0), size: buttonSize) if !visibleRect.intersects(buttonFrame) { continue } @@ -556,64 +772,152 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } private func updateScrollLayoutIfNeeded(force: Bool, transition: ContainedViewLayoutTransition) -> Bool { - guard let size = self.validLayout else { + guard let layout = self.validLayout else { return false } - if self.scrollLayout?.width == size.width && !force { + if self.scrollLayout?.width == layout.size.width && !force { return false } let buttonSize = self.isCollapsed ? smallPanelButtonSize : panelButtonSize - let contentSize = CGSize(width: (self.isCollapsed ? smallSideInset : sideInset) * 2.0 + CGFloat(self.buttons.count) * buttonSize.width, height: buttonSize.height) - self.scrollLayout = (size.width, contentSize) + let contentSize = CGSize(width: (self.isCollapsed ? smallSideInset : normalSideInset) * 2.0 + CGFloat(self.buttons.count) * buttonSize.width, height: buttonSize.height) + self.scrollLayout = (layout.size.width, contentSize) - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSelecting ? -panelButtonSize.height : 0.0), size: CGSize(width: layout.size.width, height: panelButtonSize.height))) self.scrollNode.view.contentSize = contentSize return true } - func update(buttons: [AttachmentButtonType], isCollapsed: Bool, size: CGSize, transition: ContainedViewLayoutTransition) { - self.validLayout = size + private func loadTextNodeIfNeeded() { + if let _ = self.textInputPanelNode { + } else { + let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, isAttachment: true, presentController: { [weak self] c in + if let strongSelf = self { + strongSelf.present(c) + } + }) + textInputPanelNode.interfaceInteraction = self.interfaceInteraction + textInputPanelNode.sendMessage = { [weak self] mode in + if let strongSelf = self { + strongSelf.sendMessagePressed(mode) + } + } + textInputPanelNode.focusUpdated = { [weak self] focus in + if let strongSelf = self, focus { + strongSelf.beganTextEditing() + } + } + textInputPanelNode.updateHeight = { [weak self] _ in + if let strongSelf = self { + strongSelf.requestLayout() + } + } + self.addSubnode(textInputPanelNode) + self.textInputPanelNode = textInputPanelNode + + textInputPanelNode.alpha = self.isSelecting ? 1.0 : 0.0 + textInputPanelNode.isUserInteractionEnabled = self.isSelecting + } + } + + func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isCollapsed: Bool, isSelecting: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { + self.validLayout = layout self.buttons = buttons let isCollapsedUpdated = self.isCollapsed != isCollapsed self.isCollapsed = isCollapsed + + let isSelectingUpdated = self.isSelecting != isSelecting + self.isSelecting = isSelecting - let bounds = CGRect(origin: CGPoint(), size: size) + self.scrollNode.isUserInteractionEnabled = !isSelecting + var insets = layout.insets(options: []) + if let inputHeight = layout.inputHeight, inputHeight > 0.0 && isSelecting { + insets.bottom = inputHeight + } else if layout.intrinsicInsets.bottom > 0.0 { + insets.bottom = layout.intrinsicInsets.bottom + } + + if isSelecting { + self.loadTextNodeIfNeeded() + } else { + self.textInputPanelNode?.ensureUnfocused() + } + var textPanelHeight: CGFloat = 0.0 + if let textInputPanelNode = self.textInputPanelNode { + textInputPanelNode.isUserInteractionEnabled = isSelecting + + var panelTransition = transition + if textInputPanelNode.frame.width.isZero { + panelTransition = .immediate + } + let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left, rightInset: insets.right, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics) + let panelFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: panelHeight) + if textInputPanelNode.frame.width.isZero { + textInputPanelNode.frame = panelFrame + } + transition.updateFrame(node: textInputPanelNode, frame: panelFrame) + if panelFrame.height > 0.0 { + textPanelHeight = panelFrame.height + } else { + textPanelHeight = 45.0 + } + } + + let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelButtonSize.height + insets.bottom)) let containerTransition: ContainedViewLayoutTransition let containerFrame: CGRect - if isCollapsed { - containerFrame = CGRect(origin: CGPoint(x: 0.0, y: bounds.height - smallPanelButtonSize.height), size: CGSize(width: bounds.width, height: smallPanelButtonSize.height)) + if isSelecting { + containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: textPanelHeight + insets.bottom)) + } else if isCollapsed { + containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: smallPanelButtonSize.height + insets.bottom)) } else { containerFrame = bounds } let containerBounds = CGRect(origin: CGPoint(), size: containerFrame.size) - if isCollapsedUpdated { + if isCollapsedUpdated || isSelectingUpdated { containerTransition = .animated(duration: 0.25, curve: .easeInOut) } else { containerTransition = transition } + containerTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting ? 0.0 : 1.0) + + if isSelectingUpdated { + if isSelecting { + self.loadTextNodeIfNeeded() + if let textInputPanelNode = self.textInputPanelNode { + textInputPanelNode.alpha = 1.0 + textInputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + textInputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: CGPoint(), duration: 0.25, additive: true) + } + } else { + if let textInputPanelNode = self.textInputPanelNode { + textInputPanelNode.alpha = 0.0 + textInputPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) + textInputPanelNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 44.0), duration: 0.25, additive: true) + } + } + } + containerTransition.updateFrame(node: self.containerNode, frame: containerFrame) + containerTransition.updateFrame(node: self.backgroundNode, frame: containerBounds) + containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) if let effectView = self.effectView { containerTransition.updateFrame(view: effectView, frame: bounds) } - containerTransition.updateFrame(node: self.backgroundNode, frame: containerBounds) - - var shadowFrame = bounds.insetBy(dx: -16.0, dy: -24.0) - shadowFrame.size.height += 44.0 - transition.updateFrame(node: self.shadowNode, frame: shadowFrame) - - let _ = self.updateScrollLayoutIfNeeded(force: isCollapsedUpdated, transition: containerTransition) - + + let _ = self.updateScrollLayoutIfNeeded(force: isCollapsedUpdated || isSelectingUpdated, transition: containerTransition) + var buttonTransition: Transition = .immediate if isCollapsedUpdated { buttonTransition = .easeInOut(duration: 0.25) } - self.updateViews(transition: buttonTransition) + + return containerFrame.height } func scrollViewDidScroll(_ scrollView: UIScrollView) { diff --git a/submodules/ChatPresentationInterfaceState/BUILD b/submodules/ChatPresentationInterfaceState/BUILD new file mode 100644 index 0000000000..a6c4d04ec5 --- /dev/null +++ b/submodules/ChatPresentationInterfaceState/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatPresentationInterfaceState", + module_name = "ChatPresentationInterfaceState", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/ContextUI:ContextUI", + "//submodules/ChatInterfaceState:ChatInterfaceState", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/AudioWaveform.swift b/submodules/ChatPresentationInterfaceState/Sources/AudioWaveForm.swift similarity index 89% rename from submodules/TelegramUI/Sources/AudioWaveform.swift rename to submodules/ChatPresentationInterfaceState/Sources/AudioWaveForm.swift index 954f810ae4..2fd0059971 100644 --- a/submodules/TelegramUI/Sources/AudioWaveform.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/AudioWaveForm.swift @@ -25,16 +25,16 @@ private func setBits(data: UnsafeMutableRawPointer, bitOffset: Int, numBits: Int normalizedData.assumingMemoryBound(to: Int32.self).pointee |= value << Int32(normalizedBitOffset) } -final class AudioWaveform: Equatable { - let samples: Data - let peak: Int32 +public final class AudioWaveform: Equatable { + public let samples: Data + public let peak: Int32 - init(samples: Data, peak: Int32) { + public init(samples: Data, peak: Int32) { self.samples = samples self.peak = peak } - convenience init(bitstream: Data, bitsPerSample: Int) { + public convenience init(bitstream: Data, bitsPerSample: Int) { let numSamples = Int(Float(bitstream.count * 8) / Float(bitsPerSample)) var result = Data() result.count = numSamples * 2 @@ -51,7 +51,7 @@ final class AudioWaveform: Equatable { self.init(samples: result, peak: 31) } - func makeBitstream() -> Data { + public func makeBitstream() -> Data { let numSamples = self.samples.count / 2 let bitstreamLength = (numSamples * 5) / 8 + (((numSamples * 5) % 8) == 0 ? 0 : 1) var result = Data() @@ -80,7 +80,7 @@ final class AudioWaveform: Equatable { return result } - static func ==(lhs: AudioWaveform, rhs: AudioWaveform) -> Bool { + public static func ==(lhs: AudioWaveform, rhs: AudioWaveform) -> Bool { return lhs.peak == rhs.peak && lhs.samples == rhs.samples } } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatEditInterfaceMessageState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatEditInterfaceMessageState.swift new file mode 100644 index 0000000000..476eeb5200 --- /dev/null +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatEditInterfaceMessageState.swift @@ -0,0 +1,44 @@ +import Foundation +import UIKit +import Postbox +import TelegramCore + +public struct MessageMediaEditingOptions: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let imageOrVideo = MessageMediaEditingOptions(rawValue: 1 << 0) + public static let file = MessageMediaEditingOptions(rawValue: 1 << 1) +} + +public enum ChatEditInterfaceMessageStateContent: Equatable { + case plaintext + case media(mediaOptions: MessageMediaEditingOptions) +} + +public final class ChatEditInterfaceMessageState: Equatable { + public let content: ChatEditInterfaceMessageStateContent + public let mediaReference: AnyMediaReference? + + public init(content: ChatEditInterfaceMessageStateContent, mediaReference: AnyMediaReference?) { + self.content = content + self.mediaReference = mediaReference + } + + public static func ==(lhs: ChatEditInterfaceMessageState, rhs: ChatEditInterfaceMessageState) -> Bool { + if lhs.content != rhs.content { + return false + } + if let lhsMedia = lhs.mediaReference, let rhsMedia = rhs.mediaReference { + if !lhsMedia.media.isEqual(to: rhsMedia.media) { + return false + } + } else if (lhs.mediaReference != nil) != (rhs.mediaReference != nil) { + return false + } + return true + } +} diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift similarity index 70% rename from submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift rename to submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 74d282cc2a..dcca2efc29 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -8,21 +8,26 @@ import Display import AccountContext import ContextUI +public enum ChatLoadingMessageSubject { + case generic + case pinnedMessage +} + public enum ChatFinishMediaRecordingAction { case dismiss case preview case send } -final class ChatPanelInterfaceInteractionStatuses { - let editingMessage: Signal - let startingBot: Signal - let unblockingPeer: Signal - let searching: Signal - let loadingMessage: Signal - let inlineSearch: Signal +public final class ChatPanelInterfaceInteractionStatuses { + public let editingMessage: Signal + public let startingBot: Signal + public let unblockingPeer: Signal + public let searching: Signal + public let loadingMessage: Signal + public let inlineSearch: Signal - init(editingMessage: Signal, startingBot: Signal, unblockingPeer: Signal, searching: Signal, loadingMessage: Signal, inlineSearch: Signal) { + public init(editingMessage: Signal, startingBot: Signal, unblockingPeer: Signal, searching: Signal, loadingMessage: Signal, inlineSearch: Signal) { self.editingMessage = editingMessage self.startingBot = startingBot self.unblockingPeer = unblockingPeer @@ -32,114 +37,114 @@ final class ChatPanelInterfaceInteractionStatuses { } } -enum ChatPanelSearchNavigationAction { +public enum ChatPanelSearchNavigationAction { case earlier case later case index(Int) } -enum ChatPanelRestrictionInfoSubject { +public enum ChatPanelRestrictionInfoSubject { case mediaRecording case stickers } -enum ChatPanelRestrictionInfoDisplayType { +public enum ChatPanelRestrictionInfoDisplayType { case tooltip case alert } -final class ChatPanelInterfaceInteraction { - let setupReplyMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void - let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void - let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void - let deleteSelectedMessages: () -> Void - let reportSelectedMessages: () -> Void - let reportMessages: ([Message], ContextControllerProtocol?) -> Void - let blockMessageAuthor: (Message, ContextControllerProtocol?) -> Void - let deleteMessages: ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void - let forwardSelectedMessages: () -> Void - let forwardCurrentForwardMessages: () -> Void - let forwardMessages: ([Message]) -> Void - let updateForwardOptionsState: ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void - let presentForwardOptions: (ASDisplayNode) -> Void - let shareSelectedMessages: () -> Void - let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void - let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void - let openStickers: () -> Void - let editMessage: () -> Void - let beginMessageSearch: (ChatSearchDomain, String) -> Void - let dismissMessageSearch: () -> Void - let updateMessageSearch: (String) -> Void - let navigateMessageSearch: (ChatPanelSearchNavigationAction) -> Void - let openSearchResults: () -> Void - let openCalendarSearch: () -> Void - let toggleMembersSearch: (Bool) -> Void - let navigateToMessage: (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void - let navigateToChat: (PeerId) -> Void - let navigateToProfile: (PeerId) -> Void - let openPeerInfo: () -> Void - let togglePeerNotifications: () -> Void - let sendContextResult: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool - let sendBotCommand: (Peer, String) -> Void - let sendBotStart: (String?) -> Void - let botSwitchChatWithPayload: (PeerId, String) -> Void - let beginMediaRecording: (Bool) -> Void - let finishMediaRecording: (ChatFinishMediaRecordingAction) -> Void - let stopMediaRecording: () -> Void - let lockMediaRecording: () -> Void - let deleteRecordedMedia: () -> Void - let sendRecordedMedia: (Bool) -> Void - let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void - let displayVideoUnmuteTip: (CGPoint?) -> Void - let switchMediaRecordingMode: () -> Void - let setupMessageAutoremoveTimeout: () -> Void - let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool - let unblockPeer: () -> Void - let pinMessage: (MessageId, ContextControllerProtocol?) -> Void - let unpinMessage: (MessageId, Bool, ContextControllerProtocol?) -> Void - let unpinAllMessages: () -> Void - let openPinnedList: (MessageId) -> Void - let shareAccountContact: () -> Void - let reportPeer: () -> Void - let presentPeerContact: () -> Void - let dismissReportPeer: () -> Void - let deleteChat: () -> Void - let beginCall: (Bool) -> Void - let toggleMessageStickerStarred: (MessageId) -> Void - let presentController: (ViewController, Any?) -> Void - let getNavigationController: () -> NavigationController? - let presentGlobalOverlayController: (ViewController, Any?) -> Void - let navigateFeed: () -> Void - let openGrouping: () -> Void - let toggleSilentPost: () -> Void - let requestUnvoteInMessage: (MessageId) -> Void - let requestStopPollInMessage: (MessageId) -> Void - let updateInputLanguage: (@escaping (String?) -> String?) -> Void - let unarchiveChat: () -> Void - let openLinkEditing: () -> Void - let reportPeerIrrelevantGeoLocation: () -> Void - let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void - let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void - let openScheduledMessages: () -> Void - let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void - let openPeersNearby: () -> Void - let unarchivePeer: () -> Void - let scrollToTop: () -> Void - let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void - let activatePinnedListPreview: (ASDisplayNode, ContextGesture) -> Void - let editMessageMedia: (MessageId, Bool) -> Void - let joinGroupCall: (CachedChannelData.ActiveCall) -> Void - let presentInviteMembers: () -> Void - let presentGigagroupHelp: () -> Void - let updateShowCommands: ((Bool) -> Bool) -> Void - let updateShowSendAsPeers: ((Bool) -> Bool) -> Void - let openInviteRequests: () -> Void - let openSendAsPeer: (ASDisplayNode, ContextGesture?) -> Void - let presentChatRequestAdminInfo: () -> Void - let displayCopyProtectionTip: (ASDisplayNode, Bool) -> Void - let statuses: ChatPanelInterfaceInteractionStatuses? +public final class ChatPanelInterfaceInteraction { + public let setupReplyMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void + public let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void + public let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void + public let deleteSelectedMessages: () -> Void + public let reportSelectedMessages: () -> Void + public let reportMessages: ([Message], ContextControllerProtocol?) -> Void + public let blockMessageAuthor: (Message, ContextControllerProtocol?) -> Void + public let deleteMessages: ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void + public let forwardSelectedMessages: () -> Void + public let forwardCurrentForwardMessages: () -> Void + public let forwardMessages: ([Message]) -> Void + public let updateForwardOptionsState: ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void + public let presentForwardOptions: (ASDisplayNode) -> Void + public let shareSelectedMessages: () -> Void + public let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void + public let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void + public let openStickers: () -> Void + public let editMessage: () -> Void + public let beginMessageSearch: (ChatSearchDomain, String) -> Void + public let dismissMessageSearch: () -> Void + public let updateMessageSearch: (String) -> Void + public let navigateMessageSearch: (ChatPanelSearchNavigationAction) -> Void + public let openSearchResults: () -> Void + public let openCalendarSearch: () -> Void + public let toggleMembersSearch: (Bool) -> Void + public let navigateToMessage: (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void + public let navigateToChat: (PeerId) -> Void + public let navigateToProfile: (PeerId) -> Void + public let openPeerInfo: () -> Void + public let togglePeerNotifications: () -> Void + public let sendContextResult: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool + public let sendBotCommand: (Peer, String) -> Void + public let sendBotStart: (String?) -> Void + public let botSwitchChatWithPayload: (PeerId, String) -> Void + public let beginMediaRecording: (Bool) -> Void + public let finishMediaRecording: (ChatFinishMediaRecordingAction) -> Void + public let stopMediaRecording: () -> Void + public let lockMediaRecording: () -> Void + public let deleteRecordedMedia: () -> Void + public let sendRecordedMedia: (Bool) -> Void + public let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void + public let displayVideoUnmuteTip: (CGPoint?) -> Void + public let switchMediaRecordingMode: () -> Void + public let setupMessageAutoremoveTimeout: () -> Void + public let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool + public let unblockPeer: () -> Void + public let pinMessage: (MessageId, ContextControllerProtocol?) -> Void + public let unpinMessage: (MessageId, Bool, ContextControllerProtocol?) -> Void + public let unpinAllMessages: () -> Void + public let openPinnedList: (MessageId) -> Void + public let shareAccountContact: () -> Void + public let reportPeer: () -> Void + public let presentPeerContact: () -> Void + public let dismissReportPeer: () -> Void + public let deleteChat: () -> Void + public let beginCall: (Bool) -> Void + public let toggleMessageStickerStarred: (MessageId) -> Void + public let presentController: (ViewController, Any?) -> Void + public let getNavigationController: () -> NavigationController? + public let presentGlobalOverlayController: (ViewController, Any?) -> Void + public let navigateFeed: () -> Void + public let openGrouping: () -> Void + public let toggleSilentPost: () -> Void + public let requestUnvoteInMessage: (MessageId) -> Void + public let requestStopPollInMessage: (MessageId) -> Void + public let updateInputLanguage: (@escaping (String?) -> String?) -> Void + public let unarchiveChat: () -> Void + public let openLinkEditing: () -> Void + public let reportPeerIrrelevantGeoLocation: () -> Void + public let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void + public let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void + public let openScheduledMessages: () -> Void + public let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void + public let openPeersNearby: () -> Void + public let unarchivePeer: () -> Void + public let scrollToTop: () -> Void + public let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void + public let activatePinnedListPreview: (ASDisplayNode, ContextGesture) -> Void + public let editMessageMedia: (MessageId, Bool) -> Void + public let joinGroupCall: (CachedChannelData.ActiveCall) -> Void + public let presentInviteMembers: () -> Void + public let presentGigagroupHelp: () -> Void + public let updateShowCommands: ((Bool) -> Bool) -> Void + public let updateShowSendAsPeers: ((Bool) -> Bool) -> Void + public let openInviteRequests: () -> Void + public let openSendAsPeer: (ASDisplayNode, ContextGesture?) -> Void + public let presentChatRequestAdminInfo: () -> Void + public let displayCopyProtectionTip: (ASDisplayNode, Bool) -> Void + public let statuses: ChatPanelInterfaceInteractionStatuses? - init( + public init( setupReplyMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, @@ -321,7 +326,7 @@ final class ChatPanelInterfaceInteraction { self.statuses = statuses } - convenience init( + public convenience init( updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openLinkEditing: @escaping () -> Void diff --git a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift similarity index 88% rename from submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift rename to submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index a83038a8df..b6b7d5e33f 100644 --- a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -7,7 +7,18 @@ import TelegramUIPreferences import AccountContext import ChatInterfaceState -enum ChatPresentationInputQueryKind: Int32 { +public extension ChatLocation { + var peerId: PeerId { + switch self { + case let .peer(peerId): + return peerId + case let .replyThread(replyThreadMessage): + return replyThreadMessage.messageId.peerId + } + } +} + +public enum ChatPresentationInputQueryKind: Int32 { case emoji case hashtag case mention @@ -16,19 +27,19 @@ enum ChatPresentationInputQueryKind: Int32 { case emojiSearch } -struct ChatInputQueryMentionTypes: OptionSet, Hashable { - var rawValue: Int32 +public struct ChatInputQueryMentionTypes: OptionSet, Hashable { + public var rawValue: Int32 - init(rawValue: Int32) { + public init(rawValue: Int32) { self.rawValue = rawValue } - static let contextBots = ChatInputQueryMentionTypes(rawValue: 1 << 0) - static let members = ChatInputQueryMentionTypes(rawValue: 1 << 1) - static let accountPeer = ChatInputQueryMentionTypes(rawValue: 1 << 2) + public static let contextBots = ChatInputQueryMentionTypes(rawValue: 1 << 0) + public static let members = ChatInputQueryMentionTypes(rawValue: 1 << 1) + public static let accountPeer = ChatInputQueryMentionTypes(rawValue: 1 << 2) } -enum ChatPresentationInputQuery: Hashable, Equatable { +public enum ChatPresentationInputQuery: Hashable, Equatable { case emoji(String) case hashtag(String) case mention(query: String, types: ChatInputQueryMentionTypes) @@ -36,7 +47,7 @@ enum ChatPresentationInputQuery: Hashable, Equatable { case emojiSearch(query: String, languageCode: String, range: NSRange) case contextRequest(addressName: String, query: String) - var kind: ChatPresentationInputQueryKind { + public var kind: ChatPresentationInputQueryKind { switch self { case .emoji: return .emoji @@ -54,30 +65,30 @@ enum ChatPresentationInputQuery: Hashable, Equatable { } } -enum ChatMediaInputMode { +public enum ChatMediaInputMode { case gif case other } -enum ChatMediaInputSearchMode { +public enum ChatMediaInputSearchMode { case gif case sticker case trending } -enum ChatMediaInputExpanded: Equatable { +public enum ChatMediaInputExpanded: Equatable { case content case search(ChatMediaInputSearchMode) } -enum ChatInputMode: Equatable { +public enum ChatInputMode: Equatable { case none case text case media(mode: ChatMediaInputMode, expanded: ChatMediaInputExpanded?, focused: Bool) case inputButtons } -enum ChatTitlePanelContext: Equatable, Comparable { +public enum ChatTitlePanelContext: Equatable, Comparable { case pinnedMessage case chatInfo case requestInProgress @@ -99,38 +110,46 @@ enum ChatTitlePanelContext: Equatable, Comparable { } } - static func <(lhs: ChatTitlePanelContext, rhs: ChatTitlePanelContext) -> Bool { + public static func <(lhs: ChatTitlePanelContext, rhs: ChatTitlePanelContext) -> Bool { return lhs.index < rhs.index } } -struct ChatSearchResultsState: Equatable { - let messageIndices: [MessageIndex] - let currentId: MessageId? - let state: SearchMessagesState - let totalCount: Int32 - let completed: Bool +public struct ChatSearchResultsState: Equatable { + public let messageIndices: [MessageIndex] + public let currentId: MessageId? + public let state: SearchMessagesState + public let totalCount: Int32 + public let completed: Bool + + public init(messageIndices: [MessageIndex], currentId: MessageId?, state: SearchMessagesState, totalCount: Int32, completed: Bool) { + self.messageIndices = messageIndices + self.currentId = currentId + self.state = state + self.totalCount = totalCount + self.completed = completed + } } -enum ChatSearchDomainSuggestionContext: Equatable { +public enum ChatSearchDomainSuggestionContext: Equatable { case none case members(String) } -struct ChatSearchData: Equatable { - let query: String - let domain: ChatSearchDomain - let domainSuggestionContext: ChatSearchDomainSuggestionContext - let resultsState: ChatSearchResultsState? +public struct ChatSearchData: Equatable { + public let query: String + public let domain: ChatSearchDomain + public let domainSuggestionContext: ChatSearchDomainSuggestionContext + public let resultsState: ChatSearchResultsState? - init(query: String = "", domain: ChatSearchDomain = .everything, domainSuggestionContext: ChatSearchDomainSuggestionContext = .none, resultsState: ChatSearchResultsState? = nil) { + public init(query: String = "", domain: ChatSearchDomain = .everything, domainSuggestionContext: ChatSearchDomainSuggestionContext = .none, resultsState: ChatSearchResultsState? = nil) { self.query = query self.domain = domain self.domainSuggestionContext = domainSuggestionContext self.resultsState = resultsState } - static func ==(lhs: ChatSearchData, rhs: ChatSearchData) -> Bool { + public static func ==(lhs: ChatSearchData, rhs: ChatSearchData) -> Bool { if lhs.query != rhs.query { return false } @@ -146,37 +165,37 @@ struct ChatSearchData: Equatable { return true } - func withUpdatedQuery(_ query: String) -> ChatSearchData { + public func withUpdatedQuery(_ query: String) -> ChatSearchData { return ChatSearchData(query: query, domain: self.domain, domainSuggestionContext: self.domainSuggestionContext, resultsState: self.resultsState) } - func withUpdatedDomain(_ domain: ChatSearchDomain) -> ChatSearchData { + public func withUpdatedDomain(_ domain: ChatSearchDomain) -> ChatSearchData { return ChatSearchData(query: self.query, domain: domain, domainSuggestionContext: self.domainSuggestionContext, resultsState: self.resultsState) } - func withUpdatedDomainSuggestionContext(_ domain: ChatSearchDomainSuggestionContext) -> ChatSearchData { + public func withUpdatedDomainSuggestionContext(_ domain: ChatSearchDomainSuggestionContext) -> ChatSearchData { return ChatSearchData(query: self.query, domain: self.domain, domainSuggestionContext: domainSuggestionContext, resultsState: self.resultsState) } - func withUpdatedResultsState(_ resultsState: ChatSearchResultsState?) -> ChatSearchData { + public func withUpdatedResultsState(_ resultsState: ChatSearchResultsState?) -> ChatSearchData { return ChatSearchData(query: self.query, domain: self.domain, domainSuggestionContext: self.domainSuggestionContext, resultsState: resultsState) } } -final class ChatRecordedMediaPreview: Equatable { - let resource: TelegramMediaResource - let fileSize: Int32 - let duration: Int32 - let waveform: AudioWaveform +public final class ChatRecordedMediaPreview: Equatable { + public let resource: TelegramMediaResource + public let fileSize: Int32 + public let duration: Int32 + public let waveform: AudioWaveform - init(resource: TelegramMediaResource, duration: Int32, fileSize: Int32, waveform: AudioWaveform) { + public init(resource: TelegramMediaResource, duration: Int32, fileSize: Int32, waveform: AudioWaveform) { self.resource = resource self.duration = duration self.fileSize = fileSize self.waveform = waveform } - static func ==(lhs: ChatRecordedMediaPreview, rhs: ChatRecordedMediaPreview) -> Bool { + public static func ==(lhs: ChatRecordedMediaPreview, rhs: ChatRecordedMediaPreview) -> Bool { if !lhs.resource.isEqual(to: rhs.resource) { return false } @@ -193,13 +212,20 @@ final class ChatRecordedMediaPreview: Equatable { } } -struct ChatContactStatus: Equatable { - var canAddContact: Bool - var canReportIrrelevantLocation: Bool - var peerStatusSettings: PeerStatusSettings? - var invitedBy: Peer? +public struct ChatContactStatus: Equatable { + public var canAddContact: Bool + public var canReportIrrelevantLocation: Bool + public var peerStatusSettings: PeerStatusSettings? + public var invitedBy: Peer? - var isEmpty: Bool { + public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?) { + self.canAddContact = canAddContact + self.canReportIrrelevantLocation = canReportIrrelevantLocation + self.peerStatusSettings = peerStatusSettings + self.invitedBy = invitedBy + } + + public var isEmpty: Bool { guard var peerStatusSettings = self.peerStatusSettings else { return false } @@ -212,7 +238,7 @@ struct ChatContactStatus: Equatable { return peerStatusSettings.flags.isEmpty } - static func ==(lhs: ChatContactStatus, rhs: ChatContactStatus) -> Bool { + public static func ==(lhs: ChatContactStatus, rhs: ChatContactStatus) -> Bool { if lhs.canAddContact != rhs.canAddContact { return false } @@ -229,30 +255,35 @@ struct ChatContactStatus: Equatable { } } -enum ChatSlowmodeVariant: Equatable { +public enum ChatSlowmodeVariant: Equatable { case timestamp(Int32) case pendingMessages } -struct ChatSlowmodeState: Equatable { - var timeout: Int32 - var variant: ChatSlowmodeVariant +public struct ChatSlowmodeState: Equatable { + public var timeout: Int32 + public var variant: ChatSlowmodeVariant + + public init(timeout: Int32, variant: ChatSlowmodeVariant) { + self.timeout = timeout + self.variant = variant + } } -final class ChatPinnedMessage: Equatable { - let message: Message - let index: Int - let totalCount: Int - let topMessageId: MessageId +public final class ChatPinnedMessage: Equatable { + public let message: Message + public let index: Int + public let totalCount: Int + public let topMessageId: MessageId - init(message: Message, index: Int, totalCount: Int, topMessageId: MessageId) { + public init(message: Message, index: Int, totalCount: Int, topMessageId: MessageId) { self.message = message self.index = index self.totalCount = totalCount self.topMessageId = topMessageId } - static func ==(lhs: ChatPinnedMessage, rhs: ChatPinnedMessage) -> Bool { + public static func ==(lhs: ChatPinnedMessage, rhs: ChatPinnedMessage) -> Bool { if lhs === rhs { return true } @@ -275,74 +306,87 @@ final class ChatPinnedMessage: Equatable { } } -struct ChatActiveGroupCallInfo: Equatable { - var activeCall: CachedChannelData.ActiveCall -} - -struct ChatPresentationImportState: Equatable { - var progress: Float -} - -final class ChatPresentationInterfaceState: Equatable { - let interfaceState: ChatInterfaceState - let chatLocation: ChatLocation - let renderedPeer: RenderedPeer? - let isNotAccessible: Bool - let explicitelyCanPinMessages: Bool - let contactStatus: ChatContactStatus? - let hasBots: Bool - let isArchived: Bool - let inputTextPanelState: ChatTextInputPanelState - let editMessageState: ChatEditInterfaceMessageState? - let recordedMediaPreview: ChatRecordedMediaPreview? - let inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult] - let inputMode: ChatInputMode - let titlePanelContexts: [ChatTitlePanelContext] - let keyboardButtonsMessage: Message? - let pinnedMessageId: MessageId? - let pinnedMessage: ChatPinnedMessage? - let peerIsBlocked: Bool - let peerIsMuted: Bool - let peerDiscussionId: PeerId? - let peerGeoLocation: PeerGeoLocation? - let callsAvailable: Bool - let callsPrivate: Bool - let slowmodeState: ChatSlowmodeState? - let chatHistoryState: ChatHistoryNodeHistoryState? - let botStartPayload: String? - let urlPreview: (String, TelegramMediaWebpage)? - let editingUrlPreview: (String, TelegramMediaWebpage)? - let search: ChatSearchData? - let searchQuerySuggestionResult: ChatPresentationInputQueryResult? - let presentationReady: Bool - let chatWallpaper: TelegramWallpaper - let theme: PresentationTheme - let strings: PresentationStrings - let dateTimeFormat: PresentationDateTimeFormat - let nameDisplayOrder: PresentationPersonNameOrder - let limitsConfiguration: LimitsConfiguration - let fontSize: PresentationFontSize - let bubbleCorners: PresentationChatBubbleCorners - let accountPeerId: PeerId - let mode: ChatControllerPresentationMode - let hasScheduledMessages: Bool - let autoremoveTimeout: Int32? - let subject: ChatControllerSubject? - let peerNearbyData: ChatPeerNearbyData? - let greetingData: ChatGreetingData? - let pendingUnpinnedAllMessages: Bool - let activeGroupCallInfo: ChatActiveGroupCallInfo? - let hasActiveGroupCall: Bool - let importState: ChatPresentationImportState? - let reportReason: ReportReason? - let showCommands: Bool - let hasBotCommands: Bool - let showSendAsPeers: Bool - let sendAsPeers: [FoundPeer]? - let currentSendAsPeerId: PeerId? - let copyProtectionEnabled: Bool +public struct ChatActiveGroupCallInfo: Equatable { + public var activeCall: CachedChannelData.ActiveCall - init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?) { + public init(activeCall: CachedChannelData.ActiveCall) { + self.activeCall = activeCall + } +} + +public struct ChatPresentationImportState: Equatable { + public var progress: Float + + public init(progress: Float) { + self.progress = progress + } +} + +public enum ChatHistoryNodeHistoryState: Equatable { + case loading + case loaded(isEmpty: Bool) +} + +public final class ChatPresentationInterfaceState: Equatable { + public let interfaceState: ChatInterfaceState + public let chatLocation: ChatLocation + public let renderedPeer: RenderedPeer? + public let isNotAccessible: Bool + public let explicitelyCanPinMessages: Bool + public let contactStatus: ChatContactStatus? + public let hasBots: Bool + public let isArchived: Bool + public let inputTextPanelState: ChatTextInputPanelState + public let editMessageState: ChatEditInterfaceMessageState? + public let recordedMediaPreview: ChatRecordedMediaPreview? + public let inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult] + public let inputMode: ChatInputMode + public let titlePanelContexts: [ChatTitlePanelContext] + public let keyboardButtonsMessage: Message? + public let pinnedMessageId: MessageId? + public let pinnedMessage: ChatPinnedMessage? + public let peerIsBlocked: Bool + public let peerIsMuted: Bool + public let peerDiscussionId: PeerId? + public let peerGeoLocation: PeerGeoLocation? + public let callsAvailable: Bool + public let callsPrivate: Bool + public let slowmodeState: ChatSlowmodeState? + public let chatHistoryState: ChatHistoryNodeHistoryState? + public let botStartPayload: String? + public let urlPreview: (String, TelegramMediaWebpage)? + public let editingUrlPreview: (String, TelegramMediaWebpage)? + public let search: ChatSearchData? + public let searchQuerySuggestionResult: ChatPresentationInputQueryResult? + public let presentationReady: Bool + public let chatWallpaper: TelegramWallpaper + public let theme: PresentationTheme + public let strings: PresentationStrings + public let dateTimeFormat: PresentationDateTimeFormat + public let nameDisplayOrder: PresentationPersonNameOrder + public let limitsConfiguration: LimitsConfiguration + public let fontSize: PresentationFontSize + public let bubbleCorners: PresentationChatBubbleCorners + public let accountPeerId: PeerId + public let mode: ChatControllerPresentationMode + public let hasScheduledMessages: Bool + public let autoremoveTimeout: Int32? + public let subject: ChatControllerSubject? + public let peerNearbyData: ChatPeerNearbyData? + public let greetingData: ChatGreetingData? + public let pendingUnpinnedAllMessages: Bool + public let activeGroupCallInfo: ChatActiveGroupCallInfo? + public let hasActiveGroupCall: Bool + public let importState: ChatPresentationImportState? + public let reportReason: ReportReason? + public let showCommands: Bool + public let hasBotCommands: Bool + public let showSendAsPeers: Bool + public let sendAsPeers: [FoundPeer]? + public let currentSendAsPeerId: PeerId? + public let copyProtectionEnabled: Bool + + public init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?) { self.interfaceState = ChatInterfaceState() self.inputTextPanelState = ChatTextInputPanelState() self.editMessageState = nil @@ -402,7 +446,7 @@ final class ChatPresentationInterfaceState: Equatable { self.copyProtectionEnabled = false } - init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [FoundPeer]?, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool) { + public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [FoundPeer]?, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -462,7 +506,7 @@ final class ChatPresentationInterfaceState: Equatable { self.copyProtectionEnabled = copyProtectionEnabled } - static func ==(lhs: ChatPresentationInterfaceState, rhs: ChatPresentationInterfaceState) -> Bool { + public static func ==(lhs: ChatPresentationInterfaceState, rhs: ChatPresentationInterfaceState) -> Bool { if lhs.interfaceState != rhs.interfaceState { return false } @@ -649,35 +693,35 @@ final class ChatPresentationInterfaceState: Equatable { return true } - func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { + public func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { + public func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedIsNotAccessible(_ isNotAccessible: Bool) -> ChatPresentationInterfaceState { + public func updatedIsNotAccessible(_ isNotAccessible: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedExplicitelyCanPinMessages(_ explicitelyCanPinMessages: Bool) -> ChatPresentationInterfaceState { + public func updatedExplicitelyCanPinMessages(_ explicitelyCanPinMessages: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedContactStatus(_ contactStatus: ChatContactStatus?) -> ChatPresentationInterfaceState { + public func updatedContactStatus(_ contactStatus: ChatContactStatus?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedHasBots(_ hasBots: Bool) -> ChatPresentationInterfaceState { + public func updatedHasBots(_ hasBots: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedIsArchived(_ isArchived: Bool) -> ChatPresentationInterfaceState { + public func updatedIsArchived(_ isArchived: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { + public func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { var inputQueryResults = self.inputQueryResults let updated = f(inputQueryResults[queryKind]) if let updated = updated { @@ -689,172 +733,172 @@ final class ChatPresentationInterfaceState: Equatable { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { + public func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { + public func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedRecordedMediaPreview(_ recordedMediaPreview: ChatRecordedMediaPreview?) -> ChatPresentationInterfaceState { + public func updatedRecordedMediaPreview(_ recordedMediaPreview: ChatRecordedMediaPreview?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { + public func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { + public func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { + public func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { + public func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPinnedMessage(_ pinnedMessage: ChatPinnedMessage?) -> ChatPresentationInterfaceState { + public func updatedPinnedMessage(_ pinnedMessage: ChatPinnedMessage?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { + public func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { + public func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPeerDiscussionId(_ peerDiscussionId: PeerId?) -> ChatPresentationInterfaceState { + public func updatedPeerDiscussionId(_ peerDiscussionId: PeerId?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> ChatPresentationInterfaceState { + public func updatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { + public func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { + public func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedSlowmodeState(_ slowmodeState: ChatSlowmodeState?) -> ChatPresentationInterfaceState { + public func updatedSlowmodeState(_ slowmodeState: ChatSlowmodeState?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { + public func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { + public func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { + public func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedEditingUrlPreview(_ editingUrlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { + public func updatedEditingUrlPreview(_ editingUrlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { + public func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { + public func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { + public func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPresentationReady(_ presentationReady: Bool) -> ChatPresentationInterfaceState { + public func updatedPresentationReady(_ presentationReady: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { + public func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { + public func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedDateTimeFormat(_ dateTimeFormat: PresentationDateTimeFormat) -> ChatPresentationInterfaceState { + public func updatedDateTimeFormat(_ dateTimeFormat: PresentationDateTimeFormat) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { + public func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedBubbleCorners(_ bubbleCorners: PresentationChatBubbleCorners) -> ChatPresentationInterfaceState { + public func updatedBubbleCorners(_ bubbleCorners: PresentationChatBubbleCorners) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> ChatPresentationInterfaceState { + public func updatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedAutoremoveTimeout(_ autoremoveTimeout: Int32?) -> ChatPresentationInterfaceState { + public func updatedAutoremoveTimeout(_ autoremoveTimeout: Int32?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedPendingUnpinnedAllMessages(_ pendingUnpinnedAllMessages: Bool) -> ChatPresentationInterfaceState { + public func updatedPendingUnpinnedAllMessages(_ pendingUnpinnedAllMessages: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedActiveGroupCallInfo(_ activeGroupCallInfo: ChatActiveGroupCallInfo?) -> ChatPresentationInterfaceState { + public func updatedActiveGroupCallInfo(_ activeGroupCallInfo: ChatActiveGroupCallInfo?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedHasActiveGroupCall(_ hasActiveGroupCall: Bool) -> ChatPresentationInterfaceState { + public func updatedHasActiveGroupCall(_ hasActiveGroupCall: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedImportState(_ importState: ChatPresentationImportState?) -> ChatPresentationInterfaceState { + public func updatedImportState(_ importState: ChatPresentationImportState?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedReportReason(_ reportReason: ReportReason?) -> ChatPresentationInterfaceState { + public func updatedReportReason(_ reportReason: ReportReason?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedShowCommands(_ showCommands: Bool) -> ChatPresentationInterfaceState { + public func updatedShowCommands(_ showCommands: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedHasBotCommands(_ hasBotCommands: Bool) -> ChatPresentationInterfaceState { + public func updatedHasBotCommands(_ hasBotCommands: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedShowSendAsPeers(_ showSendAsPeers: Bool) -> ChatPresentationInterfaceState { + public func updatedShowSendAsPeers(_ showSendAsPeers: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedSendAsPeers(_ sendAsPeers: [FoundPeer]?) -> ChatPresentationInterfaceState { + public func updatedSendAsPeers(_ sendAsPeers: [FoundPeer]?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedCurrentSendAsPeerId(_ currentSendAsPeerId: PeerId?) -> ChatPresentationInterfaceState { + public func updatedCurrentSendAsPeerId(_ currentSendAsPeerId: PeerId?) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled) } - func updatedCopyProtectionEnabled(_ copyProtectionEnabled: Bool) -> ChatPresentationInterfaceState { + public func updatedCopyProtectionEnabled(_ copyProtectionEnabled: Bool) -> ChatPresentationInterfaceState { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: copyProtectionEnabled) } } -func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bool { +public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bool { if let peer = state.renderedPeer?.peer { if canSendMessagesToPeer(peer) { return true diff --git a/submodules/TelegramUI/Sources/ChatTextFormat.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift similarity index 90% rename from submodules/TelegramUI/Sources/ChatTextFormat.swift rename to submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift index 363af38582..d9e59bd7e2 100644 --- a/submodules/TelegramUI/Sources/ChatTextFormat.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift @@ -4,7 +4,7 @@ import Postbox import TelegramCore import AccountContext -func chatTextInputAddFormattingAttribute(_ state: ChatTextInputState, attribute: NSAttributedString.Key) -> ChatTextInputState { +public func chatTextInputAddFormattingAttribute(_ state: ChatTextInputState, attribute: NSAttributedString.Key) -> ChatTextInputState { if !state.selectionRange.isEmpty { let nsRange = NSRange(location: state.selectionRange.lowerBound, length: state.selectionRange.count) var addAttribute = true @@ -31,7 +31,7 @@ func chatTextInputAddFormattingAttribute(_ state: ChatTextInputState, attribute: } } -func chatTextInputClearFormattingAttributes(_ state: ChatTextInputState) -> ChatTextInputState { +public func chatTextInputClearFormattingAttributes(_ state: ChatTextInputState) -> ChatTextInputState { if !state.selectionRange.isEmpty { let nsRange = NSRange(location: state.selectionRange.lowerBound, length: state.selectionRange.count) var attributesToRemove: [NSAttributedString.Key] = [] @@ -51,7 +51,7 @@ func chatTextInputClearFormattingAttributes(_ state: ChatTextInputState) -> Chat } } -func chatTextInputAddLinkAttribute(_ state: ChatTextInputState, selectionRange: Range, url: String) -> ChatTextInputState { +public func chatTextInputAddLinkAttribute(_ state: ChatTextInputState, selectionRange: Range, url: String) -> ChatTextInputState { if !selectionRange.isEmpty { let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count) var linkRange = nsRange @@ -78,7 +78,7 @@ func chatTextInputAddLinkAttribute(_ state: ChatTextInputState, selectionRange: } } -func chatTextInputAddMentionAttribute(_ state: ChatTextInputState, peer: Peer) -> ChatTextInputState { +public func chatTextInputAddMentionAttribute(_ state: ChatTextInputState, peer: Peer) -> ChatTextInputState { let inputText = NSMutableAttributedString(attributedString: state.inputText) let range = NSMakeRange(state.selectionRange.startIndex, state.selectionRange.endIndex - state.selectionRange.startIndex) diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatTextInputPanelState.swift similarity index 63% rename from submodules/TelegramUI/Sources/ChatTextInputPanelState.swift rename to submodules/ChatPresentationInterfaceState/Sources/ChatTextInputPanelState.swift index 93105cc70d..2e50a789fa 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatTextInputPanelState.swift @@ -1,28 +1,49 @@ import Foundation import AccountContext +import SwiftSignalKit -struct ChatTextInputPanelState: Equatable { - let accessoryItems: [ChatTextInputAccessoryItem] - let contextPlaceholder: NSAttributedString? - let mediaRecordingState: ChatTextInputPanelMediaRecordingState? +public enum ChatTextInputAccessoryItem: Equatable { + case keyboard + case stickers(Bool) + case inputButtons + case commands + case silentPost(Bool) + case messageAutoremoveTimeout(Int32?) + case scheduledMessages +} + +public final class InstantVideoControllerRecordingStatus { + public let micLevel: Signal + public let duration: Signal - init(accessoryItems: [ChatTextInputAccessoryItem], contextPlaceholder: NSAttributedString?, mediaRecordingState: ChatTextInputPanelMediaRecordingState?) { + public init(micLevel: Signal, duration: Signal) { + self.micLevel = micLevel + self.duration = duration + } +} + +public struct ChatTextInputPanelState: Equatable { + public let accessoryItems: [ChatTextInputAccessoryItem] + public let contextPlaceholder: NSAttributedString? + public let mediaRecordingState: ChatTextInputPanelMediaRecordingState? + + public init(accessoryItems: [ChatTextInputAccessoryItem], contextPlaceholder: NSAttributedString?, mediaRecordingState: ChatTextInputPanelMediaRecordingState?) { self.accessoryItems = accessoryItems self.contextPlaceholder = contextPlaceholder self.mediaRecordingState = mediaRecordingState } - init() { + public init() { self.accessoryItems = [] self.contextPlaceholder = nil self.mediaRecordingState = nil } - func withUpdatedMediaRecordingState(_ mediaRecordingState: ChatTextInputPanelMediaRecordingState?) -> ChatTextInputPanelState { + public func withUpdatedMediaRecordingState(_ mediaRecordingState: ChatTextInputPanelMediaRecordingState?) -> ChatTextInputPanelState { return ChatTextInputPanelState(accessoryItems: self.accessoryItems, contextPlaceholder: self.contextPlaceholder, mediaRecordingState: mediaRecordingState) } - static func ==(lhs: ChatTextInputPanelState, rhs: ChatTextInputPanelState) -> Bool { + public static func ==(lhs: ChatTextInputPanelState, rhs: ChatTextInputPanelState) -> Bool { if lhs.accessoryItems != rhs.accessoryItems { return false } @@ -38,11 +59,11 @@ struct ChatTextInputPanelState: Equatable { } } -enum ChatVideoRecordingStatus: Equatable { +public enum ChatVideoRecordingStatus: Equatable { case recording(InstantVideoControllerRecordingStatus) case editing - static func ==(lhs: ChatVideoRecordingStatus, rhs: ChatVideoRecordingStatus) -> Bool { + public static func ==(lhs: ChatVideoRecordingStatus, rhs: ChatVideoRecordingStatus) -> Bool { switch lhs { case let .recording(lhsStatus): if case let .recording(rhsStatus) = rhs, lhsStatus === rhsStatus { @@ -60,12 +81,12 @@ enum ChatVideoRecordingStatus: Equatable { } } -enum ChatTextInputPanelMediaRecordingState: Equatable { +public enum ChatTextInputPanelMediaRecordingState: Equatable { case audio(recorder: ManagedAudioRecorder, isLocked: Bool) case video(status: ChatVideoRecordingStatus, isLocked: Bool) case waitingForPreview - var isLocked: Bool { + public var isLocked: Bool { switch self { case let .audio(_, isLocked): return isLocked @@ -76,7 +97,7 @@ enum ChatTextInputPanelMediaRecordingState: Equatable { } } - func withLocked(_ isLocked: Bool) -> ChatTextInputPanelMediaRecordingState { + public func withLocked(_ isLocked: Bool) -> ChatTextInputPanelMediaRecordingState { switch self { case let .audio(recorder, _): return .audio(recorder: recorder, isLocked: isLocked) @@ -87,7 +108,7 @@ enum ChatTextInputPanelMediaRecordingState: Equatable { } } - static func ==(lhs: ChatTextInputPanelMediaRecordingState, rhs: ChatTextInputPanelMediaRecordingState) -> Bool { + public static func ==(lhs: ChatTextInputPanelMediaRecordingState, rhs: ChatTextInputPanelMediaRecordingState) -> Bool { switch lhs { case let .audio(lhsRecorder, lhsIsLocked): if case let .audio(rhsRecorder, rhsIsLocked) = rhs, lhsRecorder === rhsRecorder, lhsIsLocked == rhsIsLocked { diff --git a/submodules/ChatSendMessageActionUI/BUILD b/submodules/ChatSendMessageActionUI/BUILD new file mode 100644 index 0000000000..f78a1da76a --- /dev/null +++ b/submodules/ChatSendMessageActionUI/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatSendMessageActionUI", + module_name = "ChatSendMessageActionUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + "//submodules/ContextUI:ContextUI", + "//submodules/AppBundle:AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift similarity index 88% rename from submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift rename to submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index 6c18bb09a9..a86b71509e 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -7,9 +7,10 @@ import TelegramPresentationData import AccountContext import ContextUI import TelegramCore +import ChatPresentationInterfaceState -final class ChatSendMessageActionSheetController: ViewController { - var controllerNode: ChatSendMessageActionSheetControllerNode { +public final class ChatSendMessageActionSheetController: ViewController { + private var controllerNode: ChatSendMessageActionSheetControllerNode { return self.displayNode as! ChatSendMessageActionSheetControllerNode } @@ -31,7 +32,7 @@ final class ChatSendMessageActionSheetController: ViewController { private let hapticFeedback = HapticFeedback() - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, interfaceState: ChatPresentationInterfaceState, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputNode: EditableTextNode, completion: @escaping () -> Void, sendMessage: @escaping (Bool) -> Void, schedule: @escaping () -> Void) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, interfaceState: ChatPresentationInterfaceState, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputNode: EditableTextNode, completion: @escaping () -> Void, sendMessage: @escaping (Bool) -> Void, schedule: @escaping () -> Void) { self.context = context self.interfaceState = interfaceState self.gesture = gesture @@ -67,7 +68,7 @@ final class ChatSendMessageActionSheetController: ViewController { self.presentationDataDisposable?.dispose() } - override func loadDisplayNode() { + override public func loadDisplayNode() { var forwardedCount = 0 if let forwardMessageIds = self.interfaceState.interfaceState.forwardMessageIds { forwardedCount = forwardMessageIds.count diff --git a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift similarity index 98% rename from submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift rename to submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift index a9f9efa9f4..87cdde4c45 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift @@ -156,7 +156,6 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, private let sourceSendButton: ASDisplayNode private let textFieldFrame: CGRect private let textInputNode: EditableTextNode - private let accessoryPanelNode: AccessoryPanelNode? private let forwardedCount: Int? private let send: (() -> Void)? @@ -187,7 +186,6 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, self.sourceSendButton = sourceSendButton self.textFieldFrame = textInputNode.convert(textInputNode.bounds, to: nil) self.textInputNode = textInputNode - self.accessoryPanelNode = nil self.forwardedCount = forwardedCount self.send = send @@ -280,10 +278,6 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, self.messageClipNode.addSubnode(self.messageBackgroundNode) self.messageClipNode.addSubnode(self.fromMessageTextNode) self.messageClipNode.addSubnode(self.toMessageTextNode) - - if let accessoryPanelNode = self.accessoryPanelNode { - self.addSubnode(accessoryPanelNode) - } self.contentNodes.forEach(self.contentContainerNode.addSubnode) @@ -652,11 +646,6 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, self.fromMessageTextNode.frame = textFrame self.toMessageTextNode.frame = textFrame - - if let accessoryPanelNode = self.accessoryPanelNode { - let size = accessoryPanelNode.calculateSizeThatFits(CGSize(width: messageFrame.width, height: 45.0)) - accessoryPanelNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.textFieldFrame.minY - size.height - 7.0), size: size) - } } @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { diff --git a/submodules/ChatTextLinkEditUI/BUILD b/submodules/ChatTextLinkEditUI/BUILD new file mode 100644 index 0000000000..e48e567605 --- /dev/null +++ b/submodules/ChatTextLinkEditUI/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatTextLinkEditUI", + module_name = "ChatTextLinkEditUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/UrlEscaping:UrlEscaping", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatTextLinkEditController.swift b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift similarity index 98% rename from submodules/TelegramUI/Sources/ChatTextLinkEditController.swift rename to submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift index dfa75bf38a..e5bd678db3 100644 --- a/submodules/TelegramUI/Sources/ChatTextLinkEditController.swift +++ b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift @@ -385,7 +385,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode { } } -func chatTextLinkEditController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, account: Account, text: String, link: String?, apply: @escaping (String?) -> Void) -> AlertController { +public func chatTextLinkEditController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, account: Account, text: String, link: String?, apply: @escaping (String?) -> Void) -> AlertController { let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } var dismissImpl: ((Bool) -> Void)? diff --git a/submodules/ComposePollUI/BUILD b/submodules/ComposePollUI/BUILD index ca1caad8ed..4077c4e2cc 100644 --- a/submodules/ComposePollUI/BUILD +++ b/submodules/ComposePollUI/BUILD @@ -20,6 +20,8 @@ swift_library( "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/TextFormat:TextFormat", "//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils", + "//submodules/AttachmentUI:AttachmentUI", + "//submodules/TextInputMenu:TextInputMenu", ], visibility = [ "//visibility:public", diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index d46d52bc90..383ca9e685 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -10,6 +10,7 @@ import AccountContext import AlertUI import PresentationDataUtils import TextFormat +import AttachmentUI private struct OrderedLinkedListItemOrderingId: RawRepresentable, Hashable { var rawValue: Int @@ -160,8 +161,9 @@ private final class CreatePollControllerArguments { let updateQuiz: (Bool) -> Void let updateSolutionText: (NSAttributedString) -> Void let solutionTextFocused: (Bool) -> Void + let questionTextFocused: (Bool) -> Void - init(updatePollText: @escaping (String) -> Void, updateOptionText: @escaping (Int, String, Bool) -> Void, moveToNextOption: @escaping (Int) -> Void, moveToPreviousOption: @escaping (Int) -> Void, removeOption: @escaping (Int, Bool) -> Void, optionFocused: @escaping (Int, Bool) -> Void, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, toggleOptionSelected: @escaping (Int) -> Void, updateAnonymous: @escaping (Bool) -> Void, updateMultipleChoice: @escaping (Bool) -> Void, displayMultipleChoiceDisabled: @escaping () -> Void, updateQuiz: @escaping (Bool) -> Void, updateSolutionText: @escaping (NSAttributedString) -> Void, solutionTextFocused: @escaping (Bool) -> Void) { + init(updatePollText: @escaping (String) -> Void, updateOptionText: @escaping (Int, String, Bool) -> Void, moveToNextOption: @escaping (Int) -> Void, moveToPreviousOption: @escaping (Int) -> Void, removeOption: @escaping (Int, Bool) -> Void, optionFocused: @escaping (Int, Bool) -> Void, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, toggleOptionSelected: @escaping (Int) -> Void, updateAnonymous: @escaping (Bool) -> Void, updateMultipleChoice: @escaping (Bool) -> Void, displayMultipleChoiceDisabled: @escaping () -> Void, updateQuiz: @escaping (Bool) -> Void, updateSolutionText: @escaping (NSAttributedString) -> Void, solutionTextFocused: @escaping (Bool) -> Void, questionTextFocused: @escaping (Bool) -> Void) { self.updatePollText = updatePollText self.updateOptionText = updateOptionText self.moveToNextOption = moveToNextOption @@ -176,6 +178,7 @@ private final class CreatePollControllerArguments { self.updateQuiz = updateQuiz self.updateSolutionText = updateSolutionText self.solutionTextFocused = solutionTextFocused + self.questionTextFocused = questionTextFocused } } @@ -348,6 +351,8 @@ private enum CreatePollEntry: ItemListNodeEntry { case let .text(placeholder, text, maxLength): return ItemListMultilineInputItem(presentationData: presentationData, text: text, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: maxLength, display: false), sectionId: self.section, style: .blocks, textUpdated: { value in arguments.updatePollText(value) + }, updatedFocus: { value in + arguments.questionTextFocused(value) }, tag: CreatePollEntryTag.text) case let .optionsHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) @@ -511,7 +516,11 @@ public final class ComposedPoll { } } -public func createPollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (ComposedPoll) -> Void) -> ViewController { +private class CreatePollControllerImpl: ItemListController, AttachmentContainable { + public var requestAttachmentMenuExpansion: () -> Void = {} +} + +public func createPollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (ComposedPoll) -> Void) -> AttachmentContainable { var initialState = CreatePollControllerState() if let isQuiz = isQuiz { initialState.isQuiz = isQuiz @@ -528,6 +537,7 @@ public func createPollController(context: AccountContext, updatedPresentationDat var ensureTextVisibleImpl: (() -> Void)? var ensureOptionVisibleImpl: ((Int) -> Void)? var ensureSolutionVisibleImpl: (() -> Void)? + var ensureQuestionVisibleImpl: (() -> Void)? var displayQuizTooltipImpl: ((Bool) -> Void)? var attemptNavigationImpl: (() -> Bool)? @@ -767,6 +777,10 @@ public func createPollController(context: AccountContext, updatedPresentationDat if isFocused { ensureSolutionVisibleImpl?() } + }, questionTextFocused: { isFocused in + if isFocused { + ensureQuestionVisibleImpl?() + } }) let previousOptionIds = Atomic<[Int]?>(value: nil) @@ -905,7 +919,7 @@ public func createPollController(context: AccountContext, updatedPresentationDat } weak var currentTooltipController: TooltipController? - let controller = ItemListController(context: context, state: signal) + let controller = CreatePollControllerImpl(context: context, state: signal) controller.navigationPresentation = .modal presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) @@ -940,6 +954,8 @@ public func createPollController(context: AccountContext, updatedPresentationDat return } + controller.requestAttachmentMenuExpansion() + var resultItemNode: ListViewItemNode? let _ = controller.frameForItemNode({ itemNode in if let itemNode = itemNode as? ItemListItemNode { @@ -955,12 +971,22 @@ public func createPollController(context: AccountContext, updatedPresentationDat } }) } + ensureQuestionVisibleImpl = { [weak controller] in + controller?.afterLayout({ + guard let controller = controller else { + return + } + controller.requestAttachmentMenuExpansion() + }) + } ensureOptionVisibleImpl = { [weak controller] id in controller?.afterLayout({ guard let controller = controller else { return } + controller.requestAttachmentMenuExpansion() + var resultItemNode: ListViewItemNode? let state = stateValue.with({ $0 }) var isLast = false diff --git a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift index 37a4044aff..c2f290a428 100644 --- a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift +++ b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift @@ -7,6 +7,7 @@ import TelegramPresentationData import ItemListUI import TextFormat import ObjCRuntimeUtils +import TextInputMenu public enum CreatePollTextInputItemTextLimitMode { case characters @@ -108,95 +109,6 @@ public class CreatePollTextInputItem: ListViewItem, ItemListItem { } } -private enum ChatTextInputMenuState { - case inactive - case general - case format -} - -private final class ChatTextInputMenu { - private var stringBold: String = "Bold" - private var stringItalic: String = "Italic" - private var stringMonospace: String = "Monospace" - private var stringLink: String = "Link" - private var stringStrikethrough: String = "Strikethrough" - private var stringUnderline: String = "Underline" - - private(set) var state: ChatTextInputMenuState = .inactive { - didSet { - if self.state != oldValue { - switch self.state { - case .inactive: - UIMenuController.shared.menuItems = [] - case .general: - UIMenuController.shared.menuItems = [] - case .format: - UIMenuController.shared.menuItems = [ - UIMenuItem(title: self.stringBold, action: Selector(("formatAttributesBold:"))), - UIMenuItem(title: self.stringItalic, action: Selector(("formatAttributesItalic:"))), - UIMenuItem(title: self.stringMonospace, action: Selector(("formatAttributesMonospace:"))), - UIMenuItem(title: self.stringStrikethrough, action: Selector(("formatAttributesStrikethrough:"))), - UIMenuItem(title: self.stringUnderline, action: Selector(("formatAttributesUnderline:"))) - ] - } - - } - } - } - - private var observer: NSObjectProtocol? - - init() { - self.observer = NotificationCenter.default.addObserver(forName: UIMenuController.didHideMenuNotification, object: nil, queue: nil, using: { [weak self] _ in - self?.back() - }) - } - - deinit { - if let observer = self.observer { - NotificationCenter.default.removeObserver(observer) - } - } - - func updateStrings(_ strings: PresentationStrings) { - self.stringBold = strings.TextFormat_Bold - self.stringItalic = strings.TextFormat_Italic - self.stringMonospace = strings.TextFormat_Monospace - self.stringLink = strings.TextFormat_Link - self.stringStrikethrough = strings.TextFormat_Strikethrough - self.stringUnderline = strings.TextFormat_Underline - } - - func activate() { - if self.state == .inactive { - self.state = .general - } - } - - func deactivate() { - self.state = .inactive - } - - func format(view: UIView, rect: CGRect) { - if self.state == .general { - self.state = .format - if #available(iOS 13.0, *) { - UIMenuController.shared.showMenu(from: view, rect: rect) - } else { - UIMenuController.shared.isMenuVisible = true - UIMenuController.shared.update() - } - } - } - - func back() { - if self.state == .format { - self.state = .general - } - } -} - - public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDelegate, ItemListItemNode, ItemListItemFocusableNode { private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode @@ -213,7 +125,7 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe private var item: CreatePollTextInputItem? private var layoutParams: ListViewItemLayoutParams? - private let inputMenu = ChatTextInputMenu() + private let inputMenu = TextInputMenu() public var tag: ItemListItemTag? { return self.item?.tag diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index bd4d185073..f4373d5fa2 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -980,8 +980,8 @@ public final class ContactListNode: ASDisplayNode { } var insets = layout.0.insets(options: [.input]) - insets.left += layout.0.safeInsets.left - insets.right += layout.0.safeInsets.right + insets.left = layout.0.safeInsets.left + insets.right = layout.0.safeInsets.right var headerInsets = layout.1 if headerInsets.top == insets.top { @@ -1469,8 +1469,8 @@ public final class ContactListNode: ASDisplayNode { self.validLayout = (layout, headerInsets) var insets = layout.insets(options: [.input]) - insets.left += layout.safeInsets.left - insets.right += layout.safeInsets.right + insets.left = layout.safeInsets.left + insets.right = layout.safeInsets.right var headerInsets = headerInsets if !hadValidLayout { @@ -1488,8 +1488,8 @@ public final class ContactListNode: ASDisplayNode { if let inputHeight = layout.inputHeight { insets.bottom -= inputHeight } - insets.left += layout.safeInsets.left - insets.right += layout.safeInsets.right + insets.left = layout.safeInsets.left + insets.right = layout.safeInsets.right let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) transition.updateFrame(node: indexNode, frame: indexNodeFrame) @@ -1534,8 +1534,8 @@ public final class ContactListNode: ASDisplayNode { self.indexSections = transition.indexSections var insets = layout.insets(options: [.input]) - insets.left += layout.safeInsets.left - insets.right += layout.safeInsets.right + insets.left = layout.safeInsets.left + insets.right = layout.safeInsets.right if let inputHeight = layout.inputHeight { insets.bottom -= inputHeight diff --git a/submodules/Display/Source/HapticFeedback.swift b/submodules/Display/Source/HapticFeedback.swift index 84751602b2..cb58460fa6 100644 --- a/submodules/Display/Source/HapticFeedback.swift +++ b/submodules/Display/Source/HapticFeedback.swift @@ -10,6 +10,8 @@ public enum ImpactHapticFeedbackStyle: Hashable { case soft case rigid case veryLight + case click05 + case click06 } @available(iOSApplicationExtension 10.0, iOS 10.0, *) @@ -21,7 +23,9 @@ private final class HapticFeedbackImpl { .heavy: UIImpactFeedbackGenerator(style: .heavy), .soft: UIImpactFeedbackGenerator(style: .soft), .rigid: UIImpactFeedbackGenerator(style: .rigid), - .veryLight: UIImpactFeedbackGenerator()] + .veryLight: UIImpactFeedbackGenerator(), + .click05: UIImpactFeedbackGenerator(), + .click06: UIImpactFeedbackGenerator()] } else { return [.light: UIImpactFeedbackGenerator(style: .light), .medium: UIImpactFeedbackGenerator(style: .medium), @@ -78,8 +82,17 @@ private final class HapticFeedbackImpl { func impact(_ style: ImpactHapticFeedbackStyle) { if let impactGenerator = self.impactGenerator[style] { - if #available(iOSApplicationExtension 13.0, iOS 13.0, *), case .veryLight = style { - impactGenerator.impactOccurred(intensity: 0.3) + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + switch style { + case .click05: + impactGenerator.impactOccurred(intensity: 0.5) + case .click06: + impactGenerator.impactOccurred(intensity: 0.6) + case .veryLight: + impactGenerator.impactOccurred(intensity: 0.3) + default: + impactGenerator.impactOccurred() + } } else { impactGenerator.impactOccurred() } diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 3af6ff47d0..27dec081c0 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -533,7 +533,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { } navigationController.filterController(self, animated: animated) } else { - self.presentingViewController?.dismiss(animated: false, completion: nil) + self.presentingViewController?.dismiss(animated: flag, completion: nil) } } diff --git a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift index ebcc0ab693..4182ec33f2 100644 --- a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift +++ b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift @@ -301,7 +301,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { strongSelf.topStripeNode.removeFromSupernode() } if strongSelf.bottomStripeNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1) } if strongSelf.maskNode.supernode != nil { strongSelf.maskNode.removeFromSupernode() diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPGrowingTextView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPGrowingTextView.h deleted file mode 100644 index 1916a3f9fe..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPGrowingTextView.h +++ /dev/null @@ -1,116 +0,0 @@ - - -// -// HPTextView.h -// -// Created by Hans Pinckaers on 29-06-10. -// -// MIT License -// -// Copyright (c) 2011 Hans Pinckaers -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import - -@class HPGrowingTextView; -@class HPTextViewInternal; - -extern NSString *TGMentionUidAttributeName; -extern NSString *TGMentionBoldAttributeName; -@class TGMessageEntity; - -@class TGKeyCommandController; - -@protocol HPGrowingTextViewDelegate - -@optional - -- (BOOL)growingTextViewShouldBeginEditing:(HPGrowingTextView *)growingTextView; -- (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)growingTextView; -- (void)growingTextViewDidEndEditing:(HPGrowingTextView *)growingTextView; -- (BOOL)growingTextViewEnabled:(HPGrowingTextView *)growingTextView; - -- (BOOL)growingTextView:(HPGrowingTextView *)growingTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; -- (void)growingTextViewDidChange:(HPGrowingTextView *)growingTextView afterSetText:(bool)afterSetText afterPastingText:(bool)afterPastingText; - -- (void)growingTextView:(HPGrowingTextView *)growingTextView willChangeHeight:(CGFloat)height duration:(NSTimeInterval)duration animationCurve:(int)animationCurve; - -- (void)growingTextViewDidChangeSelection:(HPGrowingTextView *)growingTextView; -- (BOOL)growingTextViewShouldReturn:(HPGrowingTextView *)growingTextView; - -- (void)growingTextView:(HPGrowingTextView *)growingTextView didPasteImages:(NSArray *)images andText:(NSString *)text; -- (void)growingTextView:(HPGrowingTextView *)growingTextView didPasteData:(NSData *)data; - -- (void)growingTextView:(HPGrowingTextView *)growingTextView receivedReturnKeyCommandWithModifierFlags:(UIKeyModifierFlags)flags; - -@end - -@interface TGAttributedTextRange : NSObject - -@property (nonatomic, strong, readonly) id attachment; - -- (instancetype)initWithAttachment:(id)attachment; - -@end - -@interface HPGrowingTextView : UIView - -@property (nonatomic, strong) UIView *placeholderView; -@property (nonatomic, assign) bool showPlaceholderWhenFocussed; - -@property (nonatomic) int minNumberOfLines; -@property (nonatomic) int maxNumberOfLines; -@property (nonatomic) CGFloat maxHeight; -@property (nonatomic) CGFloat minHeight; -@property (nonatomic) BOOL animateHeightChange; -@property (nonatomic) NSTimeInterval animationDuration; -@property (nonatomic, strong) HPTextViewInternal *internalTextView; -@property (nonatomic, assign) bool disableFormatting; - -@property (nonatomic) bool oneTimeLongAnimation; - -@property (nonatomic, weak) id delegate; -@property (nonatomic, strong) NSString *text; -@property (nonatomic, strong) NSAttributedString *attributedText; -@property (nonatomic, strong) UIFont *font; -@property (nonatomic, strong) UIColor *textColor; -@property (nonatomic, strong) UIColor *accentColor; -@property (nonatomic) NSTextAlignment textAlignment; - -@property (nonatomic, readonly) bool ignoreChangeNotification; - -@property (nonatomic, assign) bool receiveKeyCommands; - -- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController; - -- (void)refreshHeight:(bool)textChanged; -- (void)notifyHeight; - -- (void)setText:(NSString *)newText animated:(bool)animated; -- (void)setAttributedText:(NSAttributedString *)newText animated:(bool)animated; -- (void)setAttributedText:(NSAttributedString *)newText keepFormatting:(bool)keepFormatting animated:(bool)animated; -- (void)selectRange:(NSRange)range force:(bool)force; - -- (NSString *)textWithEntities:(__autoreleasing NSArray **)entities; - -+ (void)replaceMention:(NSString *)mention inputField:(HPGrowingTextView *)inputField username:(bool)username userId:(int64_t)userId; -+ (void)replaceHashtag:(NSString *)hashtag inputField:(HPGrowingTextView *)inputField; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPTextViewInternal.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPTextViewInternal.h deleted file mode 100644 index 0ad1e8080e..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPTextViewInternal.h +++ /dev/null @@ -1,59 +0,0 @@ - - -// -// HPTextViewInternal.h -// -// Created by Hans Pinckaers on 29-06-10. -// -// MIT License -// -// Copyright (c) 2011 Hans Pinckaers -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import - -#import - -@class TGKeyCommandController; - -@interface HPTextViewInternal : UITextView - -@property (nonatomic) bool isPasting; - -@property (nonatomic) bool freezeContentOffset; -@property (nonatomic) bool disableContentOffsetAnimation; - -@property (nonatomic, strong) TGWeakDelegate *responderStateDelegate; - -@property (nonatomic) bool enableFirstResponder; - -- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController; - -- (void)textViewEnsureSelectionVisible; - -@end - -@protocol HPTextViewInternalDelegate - -@required - -- (void)hpTextViewChangedResponderState:(bool)firstResponder; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h index df30d859e9..40d5d44f8d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h @@ -9,8 +9,6 @@ #import #import #import -#import -#import #import #import #import @@ -187,11 +185,7 @@ #import #import #import -#import -#import -#import #import -#import #import #import #import diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index 82c3375396..be28307f0a 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -90,8 +90,12 @@ typedef enum @property (nonatomic, copy) void (^selectionLimitExceeded)(void); +- (UIBarButtonItem *)leftBarButtonItem; - (UIBarButtonItem *)rightBarButtonItem; +- (void)send:(bool)silently; +- (void)schedule; + - (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator; - (void)completeWithAvatarImage:(UIImage *)image; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h index a23046d7a2..56cb2a5c4c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h @@ -68,7 +68,9 @@ - (void)setCaption:(NSAttributedString *)caption forItem:(NSObject *)item; - (bool)isForcedCaption; +- (SSignal *)forcedCaption; - (void)setForcedCaption:(NSAttributedString *)caption; +- (void)setForcedCaption:(NSAttributedString *)caption skipUpdate:(bool)skipUpdate; - (NSObject *)adjustmentsForItem:(NSObject *)item; - (SSignal *)adjustmentsSignalForItem:(NSObject *)item; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerCaptionInputPanel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerCaptionInputPanel.h deleted file mode 100644 index d58441d07a..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerCaptionInputPanel.h +++ /dev/null @@ -1,57 +0,0 @@ -#import - -@class TGModernConversationAssociatedInputPanel; -@class TGKeyCommandController; - -@protocol TGMediaPickerCaptionInputPanelDelegate; - -@interface TGMediaPickerCaptionInputPanel : UIView - -- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController frame:(CGRect)frame; - -@property (nonatomic, weak) id delegate; - -@property (nonatomic, strong) NSString *caption; -- (void)setCaption:(NSString *)caption entities:(NSArray *)entities animated:(bool)animated; - -@property (nonatomic, readonly) HPGrowingTextView *inputField; -@property (nonatomic, assign) bool allowEntities; - -@property (nonatomic, assign) CGFloat bottomMargin; -@property (nonatomic, assign, getter=isCollapsed) bool collapsed; -- (void)setCollapsed:(bool)collapsed animated:(bool)animated; - -- (void)replaceMention:(NSString *)mention; -- (void)replaceMention:(NSString *)mention username:(bool)username userId:(int64_t)userId; -- (void)replaceHashtag:(NSString *)hashtag; - -- (void)adjustForOrientation:(UIInterfaceOrientation)orientation keyboardHeight:(CGFloat)keyboardHeight duration:(NSTimeInterval)duration animationCurve:(NSInteger)animationCurve; - -- (void)dismiss; - -- (CGFloat)heightForInputFieldHeight:(CGFloat)inputFieldHeight; -- (CGFloat)baseHeight; - -- (void)setAssociatedPanel:(TGModernConversationAssociatedInputPanel *)associatedPanel animated:(bool)animated; -- (TGModernConversationAssociatedInputPanel *)associatedPanel; - -- (void)setContentAreaHeight:(CGFloat)contentAreaHeight; - -- (NSInteger)textCaretPosition; - -@end - -@protocol TGMediaPickerCaptionInputPanelDelegate - -- (bool)inputPanelShouldBecomeFirstResponder:(TGMediaPickerCaptionInputPanel *)inputPanel; -- (void)inputPanelFocused:(TGMediaPickerCaptionInputPanel *)inputPanel; -- (void)inputPanelRequestedSetCaption:(TGMediaPickerCaptionInputPanel *)inputPanel text:(NSString *)text entities:(NSArray *)entities; -- (void)inputPanelMentionEntered:(TGMediaPickerCaptionInputPanel *)inputTextPanel mention:(NSString *)mention startOfLine:(bool)startOfLine; -- (void)inputPanelHashtagEntered:(TGMediaPickerCaptionInputPanel *)inputTextPanel hashtag:(NSString *)hashtag; -- (void)inputPanelAlphacodeEntered:(TGMediaPickerCaptionInputPanel *)inputTextPanel alphacode:(NSString *)alphacode; -- (void)inputPanelWillChangeHeight:(TGMediaPickerCaptionInputPanel *)inputPanel height:(CGFloat)height duration:(NSTimeInterval)duration animationCurve:(int)animationCurve; - -@optional -- (void)inputPanelTextChanged:(TGMediaPickerCaptionInputPanel *)inputTextPanel text:(NSString *)text; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationAlphacodeAssociatedPanel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationAlphacodeAssociatedPanel.h deleted file mode 100644 index 31b15fa9d9..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationAlphacodeAssociatedPanel.h +++ /dev/null @@ -1,13 +0,0 @@ -#import "TGModernConversationAssociatedInputPanel.h" - -#import - -@class TGAlphacodeEntry; - -@interface TGModernConversationAlphacodeAssociatedPanel : TGModernConversationAssociatedInputPanel - -@property (nonatomic, copy) void (^alphacodeSelected)(TGAlphacodeEntry *); - -- (void)setAlphacodeListSignal:(SSignal *)alphacodeListSignal; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationAssociatedInputPanel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationAssociatedInputPanel.h deleted file mode 100644 index 37c6d9b35f..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationAssociatedInputPanel.h +++ /dev/null @@ -1,68 +0,0 @@ -#import - -typedef enum -{ - TGModernConversationAssociatedInputPanelDefaultStyle, - TGModernConversationAssociatedInputPanelDarkStyle, - TGModernConversationAssociatedInputPanelDarkBlurredStyle -} TGModernConversationAssociatedInputPanelStyle; - -@interface TGConversationAssociatedInputPanelPallete : NSObject - -@property (nonatomic, readonly) bool isDark; -@property (nonatomic, readonly) UIColor *backgroundColor; -@property (nonatomic, readonly) UIColor *separatorColor; -@property (nonatomic, readonly) UIColor *selectionColor; -@property (nonatomic, readonly) UIColor *barBackgroundColor; -@property (nonatomic, readonly) UIColor *barSeparatorColor; -@property (nonatomic, readonly) UIColor *textColor; -@property (nonatomic, readonly) UIColor *secondaryTextColor; -@property (nonatomic, readonly) UIColor *accentColor; -@property (nonatomic, readonly) UIColor *placeholderBackgroundColor; -@property (nonatomic, readonly) UIColor *placeholderIconColor; -@property (nonatomic, readonly) UIImage *avatarPlaceholder; -@property (nonatomic, readonly) UIImage *closeIcon; -@property (nonatomic, readonly) UIImage *largeCloseIcon; - -+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor separatorColor:(UIColor *)separatorColor selectionColor:(UIColor *)selectionColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor placeholderBackgroundColor:(UIColor *)placeholderBackgroundColor placeholderIconColor:(UIColor *)placeholderIconColor avatarPlaceholder:(UIImage *)avatarPlaceholder closeIcon:(UIImage *)closeIcon largeCloseIcon:(UIImage *)largeCloseIcon; - -@end - -@interface TGModernConversationAssociatedInputPanel : UIView -{ - UIEdgeInsets _safeAreaInset; -} - -@property (nonatomic, readonly) TGModernConversationAssociatedInputPanelStyle style; -@property (nonatomic, copy) void (^preferredHeightUpdated)(); - -@property (nonatomic, copy) void (^resultPreviewAppeared)(void); -@property (nonatomic, copy) void (^resultPreviewDisappeared)(bool restoreFocus); - -@property (nonatomic, strong) TGConversationAssociatedInputPanelPallete *pallete; -@property (nonatomic) UIEdgeInsets safeAreaInset; -@property (nonatomic) CGFloat overlayBarOffset; -@property (nonatomic) CGFloat barInset; -@property (nonatomic, copy) void (^updateOverlayBarOffset)(CGFloat); - -- (CGFloat)preferredHeight; -- (bool)displayForTextEntryOnly; -- (bool)fillsAvailableSpace; -- (void)setNeedsPreferredHeightUpdate; - -- (void)setSendAreaWidth:(CGFloat)sendAreaWidth attachmentAreaWidth:(CGFloat)attachmentAreaWidth; -- (void)setContentAreaHeight:(CGFloat)contentAreaHeight; - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style; - -- (bool)hasSelectedItem; -- (void)selectPreviousItem; -- (void)selectNextItem; -- (void)commitSelectedItem; - -- (void)animateIn; -- (void)animateOut:(void (^)())completion; - -- (void)setBarInset:(CGFloat)barInset animated:(bool)animated; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationHashtagsAssociatedPanel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationHashtagsAssociatedPanel.h deleted file mode 100644 index 5f491fbb98..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationHashtagsAssociatedPanel.h +++ /dev/null @@ -1,11 +0,0 @@ -#import - -#import - -@interface TGModernConversationHashtagsAssociatedPanel : TGModernConversationAssociatedInputPanel - -@property (nonatomic, copy) void (^hashtagSelected)(NSString *); - -- (void)setHashtagListSignal:(SSignal *)hashtagListSignal; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationMentionsAssociatedPanel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationMentionsAssociatedPanel.h deleted file mode 100644 index 69a77468d6..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationMentionsAssociatedPanel.h +++ /dev/null @@ -1,15 +0,0 @@ -#import - -#import - -@class TGUser; - -@interface TGModernConversationMentionsAssociatedPanel : TGModernConversationAssociatedInputPanel - -@property (nonatomic) bool inverted; - -@property (nonatomic, copy) void (^userSelected)(TGUser *); - -- (void)setUserListSignal:(SSignal *)userListSignal; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h index 453f08c8f9..7307c4eddb 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h @@ -1,5 +1,5 @@ #import -#import +#import @class TGSuggestionContext; @protocol TGPhotoPaintStickersContext; diff --git a/submodules/LegacyComponents/Sources/HPGrowingTextView.m b/submodules/LegacyComponents/Sources/HPGrowingTextView.m deleted file mode 100644 index bb93dba309..0000000000 --- a/submodules/LegacyComponents/Sources/HPGrowingTextView.m +++ /dev/null @@ -1,912 +0,0 @@ -#import "HPGrowingTextView.h" - -#import "LegacyComponentsInternal.h" -#import "TGFont.h" - -#import "HPTextViewInternal.h" - -#import "TGInputTextTag.h" - -#import "TGMessage.h" - -#import "TGColor.h" - -NSString *TGMentionUidAttributeName = @"TGMentionUidAttributeName"; -NSString *TGMentionBoldAttributeName = @"TGMentionBoldAttributeName"; - -@implementation TGAttributedTextRange - -- (instancetype)initWithAttachment:(id)attachment { - self = [super init]; - if (self != nil) { - _attachment = attachment; - } - return self; -} - -@end - -@interface HPGrowingTextView () -{ - UIColor *_intrinsicTextColor; - UIFont *_intrinsicTextFont; - - __weak TGKeyCommandController *_keyCommandController; -} - -@end - -@implementation HPGrowingTextView - -- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController -{ - self = [super initWithFrame:CGRectZero]; - if (self != nil) - { - _keyCommandController = keyCommandController; - - [self commonInitialiser]; - } - return self; -} - -- (NSDictionary *)defaultAttributes { - if (_intrinsicTextFont == nil) { - return @{NSFontAttributeName: TGSystemFontOfSize(17)}; - } else { - if (_intrinsicTextColor) - return @{NSFontAttributeName: _intrinsicTextFont, NSForegroundColorAttributeName: _intrinsicTextColor}; - else - return @{NSFontAttributeName: _intrinsicTextFont}; - } -} - -- (void)commonInitialiser -{ - CGRect frame = self.frame; - frame.origin = CGPointZero; - _internalTextView = [[HPTextViewInternal alloc] initWithKeyCommandController:_keyCommandController]; - _internalTextView.frame = frame; - _internalTextView.delegate = self; - _internalTextView.contentInset = UIEdgeInsetsZero; - _internalTextView.showsHorizontalScrollIndicator = NO; - _internalTextView.attributedText = [[NSAttributedString alloc] initWithString:@"-" attributes:[self defaultAttributes]]; - _internalTextView.scrollsToTop = false; - if (iosMajorVersion() >= 7) { - _internalTextView.textContainer.layoutManager.allowsNonContiguousLayout = true; - _internalTextView.allowsEditingTextAttributes = true; - } - [self addSubview:_internalTextView]; - - _minHeight = _internalTextView.frame.size.height; - _minNumberOfLines = 1; - - _animateHeightChange = true; - _animationDuration = 0.1f; - - _internalTextView.attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:[self defaultAttributes]]; -} - -- (void)setDisableFormatting:(bool)disableFormatting -{ - _disableFormatting = disableFormatting; - if (iosMajorVersion() >= 7) - _internalTextView.allowsEditingTextAttributes = !disableFormatting; -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - - frame.origin = CGPointZero; - _internalTextView.frame = frame; -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - if (self.attributedText.length == 0) - size.height = _minHeight; - - return size; -} - -- (void)setMaxNumberOfLines:(int)maxNumberOfLines -{ - if (maxNumberOfLines == 0 && _maxHeight > 0) // the user specified a maxHeight themselves. - return; - - // Use internalTextView for height calculations, thanks to Gwynne - NSAttributedString *saveText = _internalTextView.attributedText; - NSMutableAttributedString *newText = [[NSMutableAttributedString alloc] initWithString:@"-" attributes:[self defaultAttributes]]; - - _internalTextView.delegate = nil; - _internalTextView.hidden = YES; - - for (int i = 1; i < maxNumberOfLines; ++i) { - [newText appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n|W|"]]; - } - - _internalTextView.attributedText = newText; - - _maxHeight = [self measureHeight]; - - _internalTextView.attributedText = saveText; - _internalTextView.hidden = NO; - _internalTextView.delegate = self; - - [self sizeToFit]; - - _maxNumberOfLines = maxNumberOfLines; -} - -- (void)setMaxHeight:(CGFloat)maxHeight -{ - _maxHeight = maxHeight; - _maxNumberOfLines = 0; -} - -- (void)setMinNumberOfLines:(int)minNumberOfLines -{ - if (minNumberOfLines == 0 && _minHeight > 0) // the user specified a minHeight themselves. - return; - - // Use internalTextView for height calculations, thanks to Gwynne - NSAttributedString *saveText = _internalTextView.attributedText; - NSMutableAttributedString *newText = [[NSMutableAttributedString alloc] initWithString:@"-" attributes:[self defaultAttributes]]; - - _internalTextView.delegate = nil; - _internalTextView.hidden = YES; - - for (int i = 1; i < minNumberOfLines; ++i) { - [newText appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n|W|"]]; - } - - _internalTextView.attributedText = newText; - - _minHeight = [self measureHeight]; - - _internalTextView.attributedText = saveText; - _internalTextView.hidden = NO; - _internalTextView.delegate = self; - - [self sizeToFit]; - - _minNumberOfLines = minNumberOfLines; -} - -- (void)setMinHeight:(CGFloat)minHeight -{ - _minHeight = minHeight; - _minNumberOfLines = 0; -} - -- (void)textViewDidChange:(UITextView *)__unused textView -{ - [self refreshAttributes]; - - [self refreshHeight:true]; - if (self.showPlaceholderWhenFocussed) - _placeholderView.hidden = [_internalTextView hasText]; -} - -- (void)textViewDidChangeSelection:(UITextView *)__unused textView -{ - id delegate = _delegate; - - if ([delegate respondsToSelector:@selector(growingTextViewDidChangeSelection:)]) - [delegate growingTextViewDidChangeSelection:self]; - - //_internalTextView.typingAttributes = [self defaultAttributes]; - - if ([_internalTextView selectedRange].length == 0) { - //[self refreshAttributes]; - } -} - -- (void)refreshHeight:(bool)textChanged -{ - CGFloat newSizeH = [self measureHeight]; //size of content, so we can set the frame of self - - if(newSizeH < _minHeight || !_internalTextView.hasText) - newSizeH = _minHeight; //not smalles than minHeight - - if (_internalTextView.frame.size.height > _maxHeight) - newSizeH = _maxHeight; // not taller than maxHeight - - id delegate = _delegate; - - if (ABS(_internalTextView.frame.size.height - newSizeH) > FLT_EPSILON || _oneTimeLongAnimation) - { - // [fixed] Pasting too much text into the view failed to fire the height change, - // thanks to Gwynne - - if (newSizeH > _maxHeight && _internalTextView.frame.size.height <= _maxHeight) - newSizeH = _maxHeight; - - if (newSizeH <= _maxHeight) - { - if (_animateHeightChange && !_internalTextView.isPasting) - { - NSTimeInterval currentAnimationDuration = 0.12; - if (_oneTimeLongAnimation) - { - _oneTimeLongAnimation = false; - currentAnimationDuration = 0.3; - if (iosMajorVersion() < 7) - currentAnimationDuration *= 0.7; - } - - [UIView animateWithDuration:currentAnimationDuration delay:0 options:(UIViewAnimationOptionAllowUserInteraction| UIViewAnimationOptionBeginFromCurrentState) animations:^ - { - [self resizeTextView:newSizeH]; - } completion:nil]; - - if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:duration:animationCurve:)]) - [delegate growingTextView:self willChangeHeight:newSizeH duration:currentAnimationDuration animationCurve:0]; - } - else - { - [self resizeTextView:newSizeH]; - - if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:duration:animationCurve:)]) - [delegate growingTextView:self willChangeHeight:newSizeH duration:0.0 animationCurve:0]; - } - } - - // scroll to caret (needed on iOS7) - if (iosMajorVersion() >= 7) - { - /*NSRange range = _internalTextView.selectedRange; - [_internalTextView _scrollRangeToVisible:range animated:false]; - - CGRect r = [_internalTextView caretRectForPosition:_internalTextView.selectedTextRange.end]; - CGFloat frameHeight = _internalTextView.frame.size.height; - CGFloat caretY = MAX(r.origin.y - frameHeight + r.size.height + 8, 0); - if (r.origin.y != INFINITY) - { - CGPoint contentOffset = _internalTextView.contentOffset; - contentOffset.y = caretY; - _internalTextView.contentOffset = contentOffset; - }*/ - } - } - - if (textChanged && [delegate respondsToSelector:@selector(growingTextViewDidChange:afterSetText:afterPastingText:)]) { - [delegate growingTextViewDidChange:self afterSetText:_ignoreChangeNotification afterPastingText:_internalTextView.isPasting]; - } - - _oneTimeLongAnimation = false; -} - -- (void)notifyHeight { - id delegate = _delegate; - if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:duration:animationCurve:)]) - [delegate growingTextView:self willChangeHeight:_internalTextView.frame.size.height duration:0.0 animationCurve:0]; -} - -// Code from apple developer forum - @Steve Krulewitz, @Mark Marszal, @Eric Silverberg -- (CGFloat)measureHeight -{ - if (iosMajorVersion() >= 7) - { - CGRect frame = _internalTextView.bounds; - CGSize fudgeFactor = CGSizeMake(10.0, 17.0); - - frame.size.height -= fudgeFactor.height; - frame.size.width -= fudgeFactor.width; - - frame.size.width -= _internalTextView.textContainerInset.right; - - NSMutableAttributedString *textToMeasure = [[NSMutableAttributedString alloc] initWithAttributedString:_internalTextView.attributedText]; - if ([textToMeasure.string hasSuffix:@"\n"]) - { - [textToMeasure appendAttributedString:[[NSAttributedString alloc] initWithString:@"-"]]; - } - [textToMeasure removeAttribute:NSFontAttributeName range:NSMakeRange(0, textToMeasure.length)]; - if (_intrinsicTextFont != nil) { - [textToMeasure addAttribute:NSFontAttributeName value:_intrinsicTextFont range:NSMakeRange(0, textToMeasure.length)]; - } - - // NSString class method: boundingRectWithSize:options:attributes:context is - // available only on ios7.0 sdk. - CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT) - options:NSStringDrawingUsesLineFragmentOrigin - //attributes:attributes - context:nil]; - - return CGFloor(CGRectGetHeight(size) + fudgeFactor.height); - } - else - { - return CGFloor(self.internalTextView.contentSize.height); - } -} - -- (void)resizeTextView:(CGFloat)newSizeH -{ - CGRect internalTextViewFrame = self.frame; - internalTextViewFrame.size.height = CGFloor(newSizeH); - self.frame = internalTextViewFrame; - - internalTextViewFrame.origin = CGPointZero; - if(!CGRectEqualToRect(_internalTextView.frame, internalTextViewFrame)) - _internalTextView.frame = internalTextViewFrame; - - //[_internalTextView textViewEnsureSelectionVisible]; -} - -- (BOOL)becomeFirstResponder -{ - return [_internalTextView becomeFirstResponder]; -} - -- (BOOL)resignFirstResponder -{ - return [_internalTextView resignFirstResponder]; -} - -- (BOOL)isFirstResponder -{ - return [_internalTextView isFirstResponder]; -} - -- (BOOL)canBecomeFirstResponder -{ - return [_internalTextView canBecomeFirstResponder]; -} - -- (void)setText:(NSString *)newText -{ - [self setText:newText animated:true]; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText { - [self setAttributedText:attributedText animated:true]; -} - -- (NSAttributedString *)attributedText { - return _internalTextView.attributedText; -} - -- (void)setText:(NSString *)newText animated:(bool)animated { - [self setAttributedText:[[NSAttributedString alloc] initWithString:newText == nil ? @"" : newText attributes:[self defaultAttributes]] animated:animated]; -} - -- (void)setAttributedText:(NSAttributedString *)newText animated:(bool)animated { - [self setAttributedText:newText keepFormatting:false animated:animated]; -} - -- (void)setAttributedText:(NSAttributedString *)newText keepFormatting:(bool)keepFormatting animated:(bool)animated { - NSMutableAttributedString *fixedFontString = [[NSMutableAttributedString alloc] initWithAttributedString:newText]; - if (!keepFormatting) - { - [fixedFontString removeAttribute:NSFontAttributeName range:NSMakeRange(0, fixedFontString.length)]; - [fixedFontString addAttribute:NSFontAttributeName value:_intrinsicTextFont == nil ? [UIFont systemFontOfSize:17.0f] : _intrinsicTextFont range:NSMakeRange(0, fixedFontString.length)]; - } - - _internalTextView.attributedText = fixedFontString; - _internalTextView.typingAttributes = [self defaultAttributes]; - [self refreshAttributes]; - - _placeholderView.hidden = fixedFontString.length != 0 || [_internalTextView isFirstResponder]; - - // include this line to analyze the height of the textview. - // fix from Ankit Thakur - - bool previousAnimateHeightChange = _animateHeightChange; - _animateHeightChange = animated; - _ignoreChangeNotification = true; - [self performSelector:@selector(textViewDidChange:) withObject:_internalTextView]; - _ignoreChangeNotification = false; - _animateHeightChange = previousAnimateHeightChange; -} - -- (void)selectRange:(NSRange)range force:(bool)force { - if (range.length != 0 || force) { - UITextPosition *startPosition = [_internalTextView positionFromPosition:_internalTextView.beginningOfDocument offset:range.location]; - UITextPosition *endPosition = [_internalTextView positionFromPosition:_internalTextView.beginningOfDocument offset:range.location + range.length]; - UITextRange *selection = [_internalTextView textRangeFromPosition:startPosition toPosition:endPosition]; - _internalTextView.selectedTextRange = selection; - } -} - --(NSString *)text -{ - return _internalTextView.text; -} - -- (void)setFont:(UIFont *)afont -{ - _internalTextView.font = afont; - _intrinsicTextFont = afont; - - [self setMaxNumberOfLines:_maxNumberOfLines]; - [self setMinNumberOfLines:_minNumberOfLines]; -} - -- (UIFont *)font -{ - return _intrinsicTextFont; -} - -- (void)setTextColor:(UIColor *)color -{ - _internalTextView.textColor = color; - _intrinsicTextColor = color; -} - -- (UIColor *)textColor -{ - return _internalTextView.textColor; -} - -- (UIColor *)accentColor -{ - if (_accentColor != nil) - return _accentColor; - - return TGAccentColor(); -} - -- (void)setTextAlignment:(NSTextAlignment)aligment -{ - _internalTextView.textAlignment = aligment; -} - -- (NSTextAlignment)textAlignment -{ - return _internalTextView.textAlignment; -} - -#pragma mark - - -- (BOOL)textViewShouldBeginEditing:(UITextView *)__unused textView -{ - id delegate = _delegate; - - if ([delegate respondsToSelector:@selector(growingTextViewShouldBeginEditing:)]) - return [delegate growingTextViewShouldBeginEditing:self]; - - return true; -} - -- (void)textViewDidBeginEditing:(UITextView *)__unused textView -{ - id delegate = _delegate; - - if ([delegate respondsToSelector:@selector(growingTextViewDidBeginEditing:)]) - [delegate growingTextViewDidBeginEditing:self]; - - if (!self.showPlaceholderWhenFocussed && ![_internalTextView hasText]) - _placeholderView.hidden = true; -} - -- (void)textViewDidEndEditing:(UITextView *)__unused textView -{ - id delegate = _delegate; - - if ([delegate respondsToSelector:@selector(growingTextViewDidEndEditing:)]) - [delegate growingTextViewDidEndEditing:self]; - - if (!self.showPlaceholderWhenFocussed) - _placeholderView.hidden = [_internalTextView hasText]; -} - -- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)atext -{ - id delegate = _delegate; - - if ([delegate respondsToSelector:@selector(growingTextViewEnabled:)]) { - if (![delegate growingTextViewEnabled:self]) { - return false; - } - } - - if (![textView hasText] && [atext isEqualToString:@""]) - return NO; - - if ([atext isEqualToString:@"\n"]) - { - id delegate = _delegate; - - if ([delegate respondsToSelector:@selector(growingTextViewShouldReturn:)]) - return (BOOL)[delegate performSelector:@selector(growingTextViewShouldReturn:) withObject:self]; - } - - if (atext.length == 0) { - if (range.location != 0 && range.length == 1) { - NSAttributedString *string = self.attributedText; - if ([string attributesAtIndex:range.location effectiveRange:nil][NSAttachmentAttributeName] != nil) { - NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string]; - [mutableString replaceCharactersInRange:NSMakeRange(range.location - 1, 1) withString:@""]; - NSRange badRange = [mutableString.string rangeOfString:@"\uFFFC"]; - if (badRange.location != NSNotFound) - [mutableString replaceCharactersInRange:badRange withString:@""]; - self.attributedText = mutableString; - - return false; - } - } - } else if (range.length == 0) { - NSAttributedString *string = self.attributedText; - if (range.location != 0 && [string attributesAtIndex:range.location - 1 effectiveRange:nil][NSAttachmentAttributeName] != nil) { - NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string]; - [mutableString replaceCharactersInRange:NSMakeRange(range.location - 1, 0) withString:atext]; - NSRange badRange = [mutableString.string rangeOfString:@"\uFFFC"]; - if (badRange.location != NSNotFound) - [mutableString replaceCharactersInRange:badRange withString:@""]; - self.attributedText = mutableString; - - return false; - } - } - - return true; -} - -- (void)keyCommandPressed:(UIKeyCommand *)keyCommand -{ - id delegate = _delegate; - - if ([delegate respondsToSelector:@selector(growingTextView:receivedReturnKeyCommandWithModifierFlags:)]) - [delegate growingTextView:self receivedReturnKeyCommandWithModifierFlags:keyCommand.modifierFlags]; -} - -- (NSArray *)keyCommands -{ - if (!self.receiveKeyCommands) - return nil; - - return @ - [ - [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(keyCommandPressed:)], - [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierAlternate action:@selector(keyCommandPressed:)] - ]; -} - -- (void)refreshAttributes { - if (iosMajorVersion() < 7) { - return; - } - - self.internalTextView.typingAttributes = [self defaultAttributes]; - - NSAttributedString *string = self.attributedText; - if (string.length == 0) { - return; - } - - CGPoint contentOffset = _internalTextView.contentOffset; - [_internalTextView setScrollEnabled:false]; - _internalTextView.disableContentOffsetAnimation = true; - _internalTextView.freezeContentOffset = true; - - //NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string]; - //[mutableString removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, string.length)]; - [_internalTextView.textStorage removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, string.length)]; - - if (_intrinsicTextColor != nil) { - //[mutableString addAttribute:NSForegroundColorAttributeName value:_intrinsicTextColor == nil ? [UIColor blackColor] : _intrinsicTextColor range:NSMakeRange(0, string.length)]; - [_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:_intrinsicTextColor == nil ? [UIColor blackColor] : _intrinsicTextColor range:NSMakeRange(0, string.length)]; - } - - __block NSMutableArray *inputTextTags = [[NSMutableArray alloc] init]; - [string enumerateAttribute:TGMentionUidAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) { - if ([value isKindOfClass:[TGInputTextTag class]]) { - [inputTextTags addObject:[[TGInputTextTagAndRange alloc] initWithTag:value range:range]]; - } - }]; - - [string enumerateAttribute:TGMentionBoldAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) { - if ([value isKindOfClass:[TGInputTextTag class]]) { - [inputTextTags addObject:[[TGInputTextTagAndRange alloc] initWithTag:value range:range]]; - } - }]; - - if (inputTextTags != nil) { - /*if (mutableString == nil) { - mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string]; - }*/ - - static NSCharacterSet *alphanumericSet = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; - }); - - NSMutableSet *removeTags = [[NSMutableSet alloc] init]; - for (NSInteger i = 0; i < ((NSInteger)inputTextTags.count); i++) { - TGInputTextTagAndRange *tagAndRange = inputTextTags[i]; - if ([removeTags containsObject:@(tagAndRange.tag.uniqueId)]) { - [inputTextTags removeObjectAtIndex:i]; - //[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - [_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - - i--; - } else { - NSInteger j = tagAndRange.range.location; - while (j < (NSInteger)(tagAndRange.range.location + tagAndRange.range.length)) { - unichar c = [string.string characterAtIndex:j]; - if (c != ' ') { - break; - } - j++; - } - - if (j != (NSInteger)tagAndRange.range.location) { - NSRange updatedRange = NSMakeRange(j, tagAndRange.range.location + tagAndRange.range.length - j); - //[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - [_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - - //[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - [_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - - inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; - - i--; - } else { - NSInteger j = tagAndRange.range.location; - while (j >= 0) { - unichar c = [string.string characterAtIndex:j]; - if (![alphanumericSet characterIsMember:c]) { - break; - } - j--; - } - j++; - - if (j < ((NSInteger)tagAndRange.range.location)) { - NSRange updatedRange = NSMakeRange(j, tagAndRange.range.location + tagAndRange.range.length - j); - //[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - [_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - - //[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - [_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - - inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; - - i--; - } else { - TGInputTextTagAndRange *nextTagAndRange = nil; - if (i != ((NSInteger)inputTextTags.count) - 1) { - nextTagAndRange = inputTextTags[i + 1]; - } - - if (nextTagAndRange == nil || nextTagAndRange.tag.uniqueId != tagAndRange.tag.uniqueId) { - NSInteger candidateStart = tagAndRange.range.location + tagAndRange.range.length; - NSInteger candidateEnd = nextTagAndRange == nil ? string.length : nextTagAndRange.range.location; - NSInteger j = candidateStart; - while (j < candidateEnd) { - unichar c = [string.string characterAtIndex:j]; - static NSCharacterSet *alphanumericSet = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; - }); - if (![alphanumericSet characterIsMember:c]) { - break; - } - j++; - } - - if (j == candidateStart) { - [removeTags addObject:@(tagAndRange.tag.uniqueId)]; - //[mutableString addAttribute:NSForegroundColorAttributeName value:TGAccentColor() range:tagAndRange.range]; - [_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:self.accentColor range:tagAndRange.range]; - } else { - //[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - [_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - - NSRange updatedRange = NSMakeRange(tagAndRange.range.location, j - tagAndRange.range.location); - //[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - [_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; - - i--; - } - } else { - NSInteger candidateStart = tagAndRange.range.location + tagAndRange.range.length; - NSInteger candidateEnd = nextTagAndRange.range.location; - NSInteger j = candidateStart; - while (j < candidateEnd) { - unichar c = [string.string characterAtIndex:j]; - if (![alphanumericSet characterIsMember:c] && c != ' ') { - break; - } - j++; - } - - if (j == candidateEnd) { - //[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - [_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - - //[mutableString removeAttribute:TGMentionUidAttributeName range:nextTagAndRange.range]; - [_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:nextTagAndRange.range]; - - NSRange updatedRange = NSMakeRange(tagAndRange.range.location, nextTagAndRange.range.location + nextTagAndRange.range.length - tagAndRange.range.location); - - //[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - [_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - - inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; - [inputTextTags removeObjectAtIndex:i + 1]; - - i--; - } else if (j != candidateStart) { - //[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - [_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; - - NSRange updatedRange = NSMakeRange(tagAndRange.range.location, j - tagAndRange.range.location); - //[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - [_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; - - inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; - - i--; - } else { - [removeTags addObject:@(tagAndRange.tag.uniqueId)]; - //[mutableString addAttribute:NSForegroundColorAttributeName value:TGAccentColor() range:tagAndRange.range]; - [_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:self.accentColor range:tagAndRange.range]; - } - } - } - } - } - } - } - - _internalTextView.freezeContentOffset = false; - [_internalTextView setContentOffset:contentOffset]; - _internalTextView.disableContentOffsetAnimation = false; - [_internalTextView setScrollEnabled:true]; - - /*if (mutableString != nil && ![mutableString isEqualToAttributedString:_internalTextView.attributedText])*/ { - /*[_internalTextView.textStorage removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, string.length)]; - [_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:TGAccentColor() range:NSMakeRange(string.length - 1, 1)]; - - return; - - UITextRange *previousRange = [_internalTextView selectedTextRange]; - UITextPosition *selStartPos = previousRange.start; - NSInteger previousIdx = [_internalTextView offsetFromPosition:_internalTextView.beginningOfDocument toPosition:selStartPos]; - - _internalTextView.attributedText = mutableString; - - UITextPosition *textPosition = [_internalTextView positionFromPosition:_internalTextView.beginningOfDocument offset:MIN(previousIdx, (NSInteger)mutableString.length)]; - [_internalTextView setSelectedTextRange:[_internalTextView textRangeFromPosition:textPosition toPosition:textPosition]];*/ - } -} - -- (NSString *)textWithEntities:(__autoreleasing NSArray ** _Nullable)entities { - NSAttributedString *string = self.attributedText; - - NSMutableArray *result = [[NSMutableArray alloc] init]; - [string enumerateAttribute:TGMentionUidAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) { - if ([value isKindOfClass:[TGInputTextTag class]]) { - [result addObject:[[TGMessageEntityMentionName alloc] initWithRange:range userId:[((TGInputTextTag *)value).attachment intValue]]]; - } - }]; - - if (iosMajorVersion() >= 7) { - [string enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { - NSString *fontDescription = font.description; - if ([fontDescription rangeOfString:@"font-weight: bold"].location != NSNotFound) { - [result addObject:[[TGMessageEntityBold alloc] initWithRange:range]]; - } else if ([fontDescription rangeOfString:@"font-style: italic"].location != NSNotFound) { - [result addObject:[[TGMessageEntityItalic alloc] initWithRange:range]]; - } - }]; - } - - if (entities) { - *entities = result; - } - - return string.string; -} - -+ (void)replaceMention:(NSString *)mention inputField:(HPGrowingTextView *)inputField username:(bool)username userId:(int64_t)userId -{ - NSString *replacementText = [mention stringByAppendingString:@" "]; - - NSMutableAttributedString *text = inputField.internalTextView.attributedText == nil ? [[NSMutableAttributedString alloc] init] : [[NSMutableAttributedString alloc] initWithAttributedString:inputField.internalTextView.attributedText]; - - UITextRange *selRange = inputField.internalTextView.selectedTextRange; - UITextPosition *selStartPos = selRange.start; - NSInteger idx = [inputField.internalTextView offsetFromPosition:inputField.internalTextView.beginningOfDocument toPosition:selStartPos]; - idx--; - NSRange candidateMentionRange = NSMakeRange(NSNotFound, 0); - - if (idx >= 0 && idx < (int)text.length) - { - for (NSInteger i = idx; i >= 0; i--) - { - unichar c = [text.string characterAtIndex:i]; - if (c == '@') - { - if (i == idx) - candidateMentionRange = NSMakeRange(i + 1, 0); - else - candidateMentionRange = NSMakeRange(i + 1, idx - i); - break; - } - - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) - break; - } - } - - if (candidateMentionRange.location != NSNotFound) - { - if (!username) { - candidateMentionRange.location -= 1; - candidateMentionRange.length += 1; - - [text replaceCharactersInRange:candidateMentionRange withString:replacementText]; - - static int64_t nextId = 0; - nextId++; - [text addAttributes:@{TGMentionUidAttributeName: [[TGInputTextTag alloc] initWithUniqueId:nextId left:true attachment:@(userId)]} range:NSMakeRange(candidateMentionRange.location, replacementText.length - 1)]; - } else { - [text replaceCharactersInRange:candidateMentionRange withString:replacementText]; - } - - [inputField setAttributedText:text]; - UITextPosition *textPosition = [inputField.internalTextView positionFromPosition:inputField.internalTextView.beginningOfDocument offset:candidateMentionRange.location + replacementText.length]; - [inputField.internalTextView setSelectedTextRange:[inputField.internalTextView textRangeFromPosition:textPosition toPosition:textPosition]]; - } -} - -+ (void)replaceHashtag:(NSString *)hashtag inputField:(HPGrowingTextView *)inputField -{ - if (inputField.attributedText == nil) { - return; - } - - static NSCharacterSet *characterSet = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - characterSet = [NSCharacterSet alphanumericCharacterSet]; - }); - - NSString *replacementText = [hashtag stringByAppendingString:@" "]; - - NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithAttributedString:inputField.attributedText]; - - UITextRange *selRange = inputField.internalTextView.selectedTextRange; - UITextPosition *selStartPos = selRange.start; - NSInteger idx = [inputField.internalTextView offsetFromPosition:inputField.internalTextView.beginningOfDocument toPosition:selStartPos]; - idx--; - NSRange candidateHashtagRange = NSMakeRange(NSNotFound, 0); - NSString *string = text.string; - - if (idx >= 0 && idx < (int)text.length) - { - for (NSInteger i = idx; i >= 0; i--) - { - unichar c = [string characterAtIndex:i]; - if (c == '#') - { - if (i == idx) - candidateHashtagRange = NSMakeRange(i + 1, 0); - else - candidateHashtagRange = NSMakeRange(i + 1, idx - i); - break; - } - - if (c == ' ' || (![characterSet characterIsMember:c] && c != '_')) - break; - } - } - - if (candidateHashtagRange.location != NSNotFound) - { - [text replaceCharactersInRange:candidateHashtagRange withString:replacementText]; - [inputField setAttributedText:text]; - UITextPosition *textPosition = [inputField.internalTextView positionFromPosition:inputField.internalTextView.beginningOfDocument offset:candidateHashtagRange.location + replacementText.length]; - [inputField.internalTextView setSelectedTextRange:[inputField.internalTextView textRangeFromPosition:textPosition toPosition:textPosition]]; - } -} - -@end diff --git a/submodules/LegacyComponents/Sources/HPTextViewInternal.m b/submodules/LegacyComponents/Sources/HPTextViewInternal.m deleted file mode 100644 index a7a02ae7bd..0000000000 --- a/submodules/LegacyComponents/Sources/HPTextViewInternal.m +++ /dev/null @@ -1,290 +0,0 @@ -#import "HPTextViewInternal.h" - -#import "LegacyComponentsInternal.h" -#import "TGHacks.h" -#import "FreedomUIKit.h" - -#import "HPGrowingTextView.h" - -#import -#import - -#import - -#import "TGKeyCommandController.h" - -@interface HPTextViewInternal () { - __weak TGKeyCommandController *_keyCommandController; -} - -@end - -@implementation HPTextViewInternal - -- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController { - self = [super initWithFrame:CGRectZero]; - if (self != nil) { - _keyCommandController = keyCommandController; - } - return self; -} - -- (void)setText:(NSString *)text -{ - BOOL originalValue = self.scrollEnabled; - //If one of GrowingTextView's superviews is a scrollView, and self.scrollEnabled == NO, - //setting the text programatically will cause UIKit to search upwards until it finds a scrollView with scrollEnabled==yes - //then scroll it erratically. Setting scrollEnabled temporarily to YES prevents this. - [self setScrollEnabled:YES]; - [super setText:text]; - [self setScrollEnabled:originalValue]; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText { - BOOL originalValue = self.scrollEnabled; - //If one of GrowingTextView's superviews is a scrollView, and self.scrollEnabled == NO, - //setting the text programatically will cause UIKit to search upwards until it finds a scrollView with scrollEnabled==yes - //then scroll it erratically. Setting scrollEnabled temporarily to YES prevents this. - [self setScrollEnabled:YES]; - [super setAttributedText:attributedText]; - [self setScrollEnabled:originalValue]; -} - -- (void)setScrollable:(BOOL)isScrollable -{ - [super setScrollEnabled:isScrollable]; -} - -- (void)scrollRectToVisible:(CGRect)__unused rect animated:(BOOL)__unused animated -{ - -} - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated -{ - if (_freezeContentOffset) - return; - - [super setContentOffset:contentOffset animated:_disableContentOffsetAnimation ? false : animated]; -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; -} - --(void)setContentOffset:(CGPoint)s -{ - if (_freezeContentOffset) - return; - - [super setContentOffset:s]; -} - -- (void)textViewEnsureSelectionVisible -{ - if (iosMajorVersion() >= 9) { - dispatch_async(dispatch_get_main_queue(), ^{ - CGRect caretFrame = [self caretRectForPosition:self.selectedTextRange.end]; - if (caretFrame.origin.x < CGFLOAT_MAX && caretFrame.origin.y < CGFLOAT_MAX && !CGRectIsInfinite(caretFrame)) - { - UIEdgeInsets implicitInset = UIEdgeInsetsMake(8, 0, 8, 0); - - caretFrame.origin.y -= implicitInset.top; - caretFrame.size.height += implicitInset.top + implicitInset.bottom; - caretFrame.origin.y = CGFloor(caretFrame.origin.y * 2.0f) / 2.0f; - caretFrame.size.height = CGFloor(caretFrame.size.height * 2.0f) / 2.0f; - - CGFloat frameHeight = self.frame.size.height; - CGPoint contentOffset = self.contentOffset; - - if (caretFrame.origin.y < contentOffset.y) - contentOffset.y = caretFrame.origin.y; - if (caretFrame.origin.y + caretFrame.size.height > contentOffset.y + frameHeight) - contentOffset.y = caretFrame.origin.y + caretFrame.size.height - frameHeight; - contentOffset.y = MAX(0, contentOffset.y); - - if (!CGPointEqualToPoint(contentOffset, self.contentOffset)) - self.contentOffset = contentOffset; - } - }); - } else { - CGRect caretFrame = [self caretRectForPosition:self.selectedTextRange.end]; - if (caretFrame.origin.x < CGFLOAT_MAX && caretFrame.origin.y < CGFLOAT_MAX && !CGRectIsInfinite(caretFrame)) - { - UIEdgeInsets implicitInset = UIEdgeInsetsMake(8, 0, 8, 0); - - caretFrame.origin.y -= implicitInset.top; - caretFrame.size.height += implicitInset.top + implicitInset.bottom; - caretFrame.origin.y = CGFloor(caretFrame.origin.y * 2.0f) / 2.0f; - caretFrame.size.height = CGFloor(caretFrame.size.height * 2.0f) / 2.0f; - - CGFloat frameHeight = self.frame.size.height; - CGPoint contentOffset = self.contentOffset; - - if (caretFrame.origin.y < contentOffset.y) - contentOffset.y = caretFrame.origin.y; - if (caretFrame.origin.y + caretFrame.size.height > contentOffset.y + frameHeight) - contentOffset.y = caretFrame.origin.y + caretFrame.size.height - frameHeight; - contentOffset.y = MAX(0, contentOffset.y); - - if (!CGPointEqualToPoint(contentOffset, self.contentOffset)) - self.contentOffset = contentOffset; - } - } -} - -- (void)setContentSize:(CGSize)contentSize -{ - [super setContentSize:contentSize]; - - [self textViewEnsureSelectionVisible]; -} - -- (BOOL)canBecomeFirstResponder -{ - if (!_enableFirstResponder) - return false; - return true; -} - -- (BOOL)becomeFirstResponder -{ - if (!_enableFirstResponder) - return false; - - __block BOOL result = false; - freedomUIKitTest4(^ - { - if ([self.delegate respondsToSelector:@selector(textViewShouldBeginEditing:)] && ![self.delegate textViewShouldBeginEditing:self]) - result = false; - else - result = [super becomeFirstResponder]; - }); - - if (result) - { - id delegate = _responderStateDelegate.object; - if (delegate != nil && [delegate conformsToProtocol:@protocol(HPTextViewInternalDelegate)]) - { - [(id)delegate hpTextViewChangedResponderState:true]; - } - } - return result; -} - -- (BOOL)resignFirstResponder -{ - __block BOOL result = false; - freedomUIKitTest4(^ - { - result = [super resignFirstResponder]; - }); - - if (result) - { - id delegate = _responderStateDelegate.object; - if (delegate != nil && [delegate conformsToProtocol:@protocol(HPTextViewInternalDelegate)]) - { - [(id)delegate hpTextViewChangedResponderState:false]; - } - } - return result; -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - if (action == @selector(paste:)) - return true; - - if (action == @selector(toggleUnderline:)) { - return false; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - static SEL promptForReplaceSelector; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - promptForReplaceSelector = NSSelectorFromString(@"_promptForReplace:"); - }); - if (action == promptForReplaceSelector) { - return false; - } -#pragma clang diagnostic pop - - return [super canPerformAction:action withSender:sender]; -} - -- (id)targetForAction:(SEL)action withSender:(id)__unused sender -{ - if (action == @selector(processKeyCommand:)) { - TGKeyCommandController *keyCommandController = _keyCommandController; - return [keyCommandController targetForAction:action withSender:sender]; - } - - return [super targetForAction:action withSender:sender]; -} - -- (void)paste:(id)sender -{ - UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard]; - - NSData *gifData = [pasteBoard dataForPasteboardType:@"com.compuserve.gif"]; - if (gifData != nil) - { - id delegate = self.delegate; - if ([delegate isKindOfClass:[HPGrowingTextView class]]) - { - HPGrowingTextView *textView = delegate; - NSObject *textViewDelegate = (NSObject *)textView.delegate; - if ([textViewDelegate respondsToSelector:@selector(growingTextView:didPasteData:)]) - [textViewDelegate growingTextView:textView didPasteData:gifData]; - } - } - else - { - NSMutableArray *images = [NSMutableArray arrayWithCapacity:1]; - NSString *text = nil; - - for (NSDictionary *item in pasteBoard.items) { - if (item[(__bridge NSString *)kUTTypeJPEG] != nil) { - [images addObject:item[(__bridge NSString *)kUTTypeJPEG]]; - } else if (item[(__bridge NSString *)kUTTypePNG] != nil) { - [images addObject:item[(__bridge NSString *)kUTTypePNG]]; - } else if (item[(__bridge NSString *)kUTTypeGIF] != nil) { - [images addObject:item[(__bridge NSString *)kUTTypeGIF]]; - } else if (item[(__bridge NSString *)kUTTypeURL] != nil) { - id url = item[(__bridge NSString *)kUTTypeURL]; - if ([url respondsToSelector:@selector(characterAtIndex:)]) { - text = url; - } else if ([url isKindOfClass:[NSURL class]]) { - text = ((NSURL *)url).absoluteString; - } - } - } - - if (images.count != 0) - { - id delegate = self.delegate; - if ([delegate isKindOfClass:[HPGrowingTextView class]]) - { - HPGrowingTextView *textView = delegate; - NSObject *textViewDelegate = (NSObject *)textView.delegate; - if ([textViewDelegate respondsToSelector:@selector(growingTextView:didPasteImages:andText:)]) - [textViewDelegate growingTextView:textView didPasteImages:images andText:text]; - } - } - else - { - _isPasting = true; - bool previousAllowsEditingTextAttributes = self.allowsEditingTextAttributes; - self.allowsEditingTextAttributes = false; - [super paste:sender]; - self.allowsEditingTextAttributes = previousAllowsEditingTextAttributes; - _isPasting = false; - } - } -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGAlphacodePanelCell.h b/submodules/LegacyComponents/Sources/TGAlphacodePanelCell.h deleted file mode 100644 index 9c169a2221..0000000000 --- a/submodules/LegacyComponents/Sources/TGAlphacodePanelCell.h +++ /dev/null @@ -1,14 +0,0 @@ -#import - -extern NSString *const TGAlphacodePanelCellKind; - -@class TGConversationAssociatedInputPanelPallete; - -@interface TGAlphacodePanelCell : UITableViewCell - -@property (nonatomic, strong) TGConversationAssociatedInputPanelPallete *pallete; - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style; -- (void)setEmoji:(NSString *)emoji label:(NSString *)label; - -@end diff --git a/submodules/LegacyComponents/Sources/TGAlphacodePanelCell.m b/submodules/LegacyComponents/Sources/TGAlphacodePanelCell.m deleted file mode 100644 index 792388471b..0000000000 --- a/submodules/LegacyComponents/Sources/TGAlphacodePanelCell.m +++ /dev/null @@ -1,116 +0,0 @@ -#import "TGAlphacodePanelCell.h" - -#import "LegacyComponentsInternal.h" -#import "TGColor.h" -#import "TGFont.h" - -#import "TGModernConversationAssociatedInputPanel.h" - -NSString *const TGAlphacodePanelCellKind = @"TGAlphacodePanelCell"; - -@interface TGAlphacodePanelCell () { - UILabel *_emojiLabel; - UILabel *_descriptionLabel; -} - -@end - -@implementation TGAlphacodePanelCell - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style { - self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TGAlphacodePanelCellKind]; - if (self != nil) { - UIColor *backgroundColor = [UIColor whiteColor]; - UIColor *nameColor = [UIColor blackColor]; - UIColor *usernameColor = [UIColor blackColor]; - UIColor *selectionColor = TGSelectionColor(); - - if (style == TGModernConversationAssociatedInputPanelDarkStyle) - { - backgroundColor = UIColorRGB(0x171717); - nameColor = [UIColor whiteColor]; - usernameColor = UIColorRGB(0x828282); - selectionColor = UIColorRGB(0x292929); - } - else if (style == TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - backgroundColor = [UIColor clearColor]; - nameColor = [UIColor whiteColor]; - usernameColor = UIColorRGB(0x828282); - selectionColor = UIColorRGB(0x3d3d3d); - } - - self.backgroundColor = backgroundColor; - self.backgroundView = [[UIView alloc] init]; - self.backgroundView.backgroundColor = backgroundColor; - self.backgroundView.opaque = false; - - self.selectedBackgroundView = [[UIView alloc] init]; - self.selectedBackgroundView.backgroundColor = selectionColor; - - _emojiLabel = [[UILabel alloc] init]; - _emojiLabel.backgroundColor = [UIColor clearColor]; - _emojiLabel.textColor = nameColor; - _emojiLabel.font = TGSystemFontOfSize(14.0f); - _emojiLabel.lineBreakMode = NSLineBreakByTruncatingTail; - [self.contentView addSubview:_emojiLabel]; - - _descriptionLabel = [[UILabel alloc] init]; - _descriptionLabel.backgroundColor = [UIColor clearColor]; - _descriptionLabel.textColor = usernameColor; - _descriptionLabel.font = TGSystemFontOfSize(14.0f); - _descriptionLabel.lineBreakMode = NSLineBreakByTruncatingTail; - [self.contentView addSubview:_descriptionLabel]; - } - return self; -} - -- (void)setPallete:(TGConversationAssociatedInputPanelPallete *)pallete -{ - if (pallete == nil || _pallete == pallete) - return; - - _pallete = pallete; - - _emojiLabel.textColor = pallete.textColor; - _descriptionLabel.textColor = pallete.textColor; - - self.backgroundColor = pallete.backgroundColor; - self.backgroundView.backgroundColor = self.backgroundColor; - self.selectedBackgroundView.backgroundColor = pallete.selectionColor; -} - -- (void)setEmoji:(NSString *)emoji label:(NSString *)label { - _emojiLabel.text = emoji; - _descriptionLabel.text = label; - - [self setNeedsLayout]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - CGSize boundsSize = self.bounds.size; - - CGFloat leftInset = 11.0f; - CGFloat rightInset = 6.0f; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize titleSize = [_emojiLabel.text sizeWithFont:_emojiLabel.font]; -#pragma clang diagnostic pop - titleSize.width = CGCeil(MIN((boundsSize.width - leftInset - rightInset) * 3.0f / 4.0f, titleSize.width)); - titleSize.height = CGCeil(titleSize.height); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize descriptionSize = [_descriptionLabel.text sizeWithFont:_descriptionLabel.font]; -#pragma clang diagnostic pop - descriptionSize.width = CGCeil(MIN(boundsSize.width - leftInset - 40.0f, descriptionSize.width)); - - _emojiLabel.frame = CGRectMake(leftInset, CGFloor((boundsSize.height - titleSize.height) / 2.0f), titleSize.width, titleSize.height); - _descriptionLabel.frame = CGRectMake(40.0f, CGFloor((boundsSize.height - descriptionSize.height) / 2.0f), descriptionSize.width, descriptionSize.height); -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 8e6f3bdd73..6c277a4c08 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -2293,6 +2293,9 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus if (strongSelf == nil) return; + if (strongSelf.finishedTransitionOut != nil) + strongSelf.finishedTransitionOut(); + [strongSelf dismiss]; }]; return; @@ -2371,13 +2374,13 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus { self.view.userInteractionEnabled = false; - const CGFloat minVelocity = 2000.0f; + const CGFloat minVelocity = 4000.0f; if (ABS(velocity) < minVelocity) velocity = (velocity < 0.0f ? -1.0f : 1.0f) * minVelocity; CGFloat distance = (velocity < FLT_EPSILON ? -1.0f : 1.0f) * self.view.frame.size.height; CGRect targetFrame = (CGRect){{_previewView.frame.origin.x, distance}, _previewView.frame.size}; - [UIView animateWithDuration:ABS(distance / velocity) animations:^ + [UIView animateWithDuration:ABS(distance / velocity) delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^ { _previewView.frame = targetFrame; _cornersView.frame = targetFrame; diff --git a/submodules/LegacyComponents/Sources/TGHashtagPanelCell.h b/submodules/LegacyComponents/Sources/TGHashtagPanelCell.h deleted file mode 100644 index 7ad4d5f0bc..0000000000 --- a/submodules/LegacyComponents/Sources/TGHashtagPanelCell.h +++ /dev/null @@ -1,16 +0,0 @@ -#import - -@class TGConversationAssociatedInputPanelPallete; - -@interface TGHashtagPanelCell : UITableViewCell - -@property (nonatomic, strong) TGConversationAssociatedInputPanelPallete *pallete; - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style; - -- (void)setDisplaySeparator:(bool)displaySeparator; -- (void)setHashtag:(NSString *)hashtag; - -@end - -extern NSString *const TGHashtagPanelCellKind; diff --git a/submodules/LegacyComponents/Sources/TGHashtagPanelCell.m b/submodules/LegacyComponents/Sources/TGHashtagPanelCell.m deleted file mode 100644 index ea19909e4c..0000000000 --- a/submodules/LegacyComponents/Sources/TGHashtagPanelCell.m +++ /dev/null @@ -1,121 +0,0 @@ -#import "TGHashtagPanelCell.h" - -#import "LegacyComponentsInternal.h" -#import "TGColor.h" -#import "TGFont.h" -#import "TGImageUtils.h" - -#import "TGModernConversationAssociatedInputPanel.h" - -NSString *const TGHashtagPanelCellKind = @"TGHashtagPanelCell"; - -@interface TGHashtagPanelCell () -{ - TGModernConversationAssociatedInputPanelStyle _style; - UILabel *_label; - UIView *_separatorView; -} - -@end - -@implementation TGHashtagPanelCell - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style -{ - self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TGHashtagPanelCellKind]; - if (self != nil) - { - _style = style; - - UIColor *backgroundColor = [UIColor whiteColor]; - UIColor *textColor = [UIColor blackColor]; - UIColor *selectionColor = TGSelectionColor(); - - if (style == TGModernConversationAssociatedInputPanelDarkStyle) - { - backgroundColor = UIColorRGB(0x171717); - textColor = [UIColor whiteColor]; - selectionColor = UIColorRGB(0x292929); - } - else if (style == TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - backgroundColor = [UIColor clearColor]; - textColor = [UIColor whiteColor]; - selectionColor = UIColorRGB(0x3d3d3d); - } - - self.backgroundColor = backgroundColor; - self.backgroundView = [[UIView alloc] init]; - self.backgroundView.backgroundColor = backgroundColor; - self.backgroundView.opaque = false; - - self.selectedBackgroundView = [[UIView alloc] init]; - self.selectedBackgroundView.backgroundColor = selectionColor; - - _label = [[UILabel alloc] init]; - _label.backgroundColor = [UIColor clearColor]; - _label.textColor = textColor; - _label.font = TGSystemFontOfSize(14.0f); - [self.contentView addSubview:_label]; - } - return self; -} - -- (void)setPallete:(TGConversationAssociatedInputPanelPallete *)pallete -{ - if (pallete == nil || _pallete == pallete) - return; - - _pallete = pallete; - - _label.textColor = pallete.textColor; - - self.backgroundColor = pallete.backgroundColor; - self.backgroundView.backgroundColor = self.backgroundColor; - self.selectedBackgroundView.backgroundColor = pallete.selectionColor; - - _separatorView.backgroundColor = pallete.separatorColor; -} - -- (void)setDisplaySeparator:(bool)displaySeparator -{ - if (displaySeparator && _separatorView == nil) - { - UIColor *separatorColor = _pallete != nil ? _pallete.separatorColor : TGSeparatorColor(); - if (_style == TGModernConversationAssociatedInputPanelDarkStyle) - separatorColor = UIColorRGB(0x292929); - - _separatorView = [[UIView alloc] init]; - _separatorView.backgroundColor = separatorColor; - [self insertSubview:_separatorView belowSubview:self.contentView]; - [self setNeedsLayout]; - } -} - -- (void)setHashtag:(NSString *)hashtag -{ - _label.text = [[NSString alloc] initWithFormat:@"#%@", hashtag]; - [self setNeedsLayout]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - CGFloat inset = 15.0f; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize labelSize = [_label.text sizeWithFont:_label.font]; -#pragma clang diagnostic pop - labelSize.width = CGCeil(MIN(labelSize.width, self.frame.size.width - inset * 2.0f)); - labelSize.height = CGCeil(labelSize.height); - _label.frame = CGRectMake(inset, CGFloor((self.frame.size.height - labelSize.height) / 2.0f), labelSize.width, labelSize.height); - - if (_separatorView != nil) - { - CGFloat separatorHeight = TGScreenPixel; - _separatorView.frame = CGRectMake(inset, self.frame.size.height - separatorHeight, self.frame.size.width - inset, separatorHeight); - } -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 6ca2df7596..67fda6a816 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -274,8 +274,11 @@ [groupsController setIsFirstInStack:true]; [pickerController setIsFirstInStack:false]; -// [assetsController setViewControllers:@[ groupsController, pickerController ]]; - [assetsController setViewControllers:@[ pickerController ]]; + if (intent == TGMediaAssetsControllerSendMediaIntent) { + [assetsController setViewControllers:@[ pickerController ]]; + } else { + [assetsController setViewControllers:@[ groupsController, pickerController ]]; + } ((TGNavigationBar *)assetsController.navigationBar).navigationController = assetsController; assetsController.recipientName = recipientName; @@ -1391,23 +1394,37 @@ #pragma mark - +- (UIBarButtonItem *)leftBarButtonItem +{ + if (_intent == TGMediaAssetsControllerSendMediaIntent) { + return [[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelButtonPressed)]; + } + return nil; +} + - (UIBarButtonItem *)rightBarButtonItem { - if (_intent == TGMediaAssetsControllerSendFileIntent) - return nil; - if (self.requestSearchController == nil) { - return nil; - } - - if (iosMajorVersion() < 7) - { - TGModernBarButton *searchButton = [[TGModernBarButton alloc] initWithImage:TGComponentsImageNamed(@"NavigationSearchIcon.png")]; - searchButton.portraitAdjustment = CGPointMake(-7, -5); - [searchButton addTarget:self action:@selector(searchButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - return [[UIBarButtonItem alloc] initWithCustomView:searchButton]; - } - - return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(searchButtonPressed)]; + return nil; +// if (_intent == TGMediaAssetsControllerSendFileIntent) +// return nil; +// if (self.requestSearchController == nil) { +// return nil; +// } +// +// if (iosMajorVersion() < 7) +// { +// TGModernBarButton *searchButton = [[TGModernBarButton alloc] initWithImage:TGComponentsImageNamed(@"NavigationSearchIcon.png")]; +// searchButton.portraitAdjustment = CGPointMake(-7, -5); +// [searchButton addTarget:self action:@selector(searchButtonPressed) forControlEvents:UIControlEventTouchUpInside]; +// return [[UIBarButtonItem alloc] initWithCustomView:searchButton]; +// } +// +// return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(searchButtonPressed)]; +} + +- (void)cancelButtonPressed +{ + [self dismiss]; } - (void)searchButtonPressed @@ -1417,6 +1434,18 @@ } } +- (void)send:(bool)silently +{ + [self completeWithCurrentItem:nil silentPosting:silently scheduleTime:0]; +} + +- (void)schedule { + __weak TGMediaAssetsController *weakSelf = self; + self.presentScheduleController(^(int32_t scheduleTime) { + [weakSelf completeWithCurrentItem:nil silentPosting:false scheduleTime:scheduleTime]; + }); +} + - (void)navigationController:(UINavigationController *)__unused navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)__unused animated { if (_searchController == nil) diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsModernLibrary.m b/submodules/LegacyComponents/Sources/TGMediaAssetsModernLibrary.m index 6cabd8e428..c6cc721f93 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsModernLibrary.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsModernLibrary.m @@ -97,38 +97,22 @@ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { PHFetchOptions *options = [PHFetchOptions new]; + PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil]; + PHAssetCollection *assetCollection = fetchResult.firstObject; - if (iosMajorVersion() == 8 && iosMinorVersion() < 1) + if (assetCollection != nil) { - PHFetchResult *fetchResult = nil; + if (assetType != TGMediaAssetAnyType) + options.predicate = [NSPredicate predicateWithFormat:@"mediaType = %i", [TGMediaAsset assetMediaTypeForAssetType:assetType]]; - if (assetType == TGMediaAssetAnyType) - fetchResult = [PHAsset fetchAssetsWithOptions:options]; - else - fetchResult = [PHAsset fetchAssetsWithMediaType:[TGMediaAsset assetMediaTypeForAssetType:assetType] options:options]; + PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options]; - [subscriber putNext:[[TGMediaAssetGroup alloc] initWithPHFetchResult:fetchResult]]; + [subscriber putNext:[[TGMediaAssetGroup alloc] initWithPHAssetCollection:assetCollection fetchResult:assetsFetchResult]]; [subscriber putCompletion]; } else { - PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil]; - PHAssetCollection *assetCollection = fetchResult.firstObject; - - if (assetCollection != nil) - { - if (assetType != TGMediaAssetAnyType) - options.predicate = [NSPredicate predicateWithFormat:@"mediaType = %i", [TGMediaAsset assetMediaTypeForAssetType:assetType]]; - - PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options]; - - [subscriber putNext:[[TGMediaAssetGroup alloc] initWithPHAssetCollection:assetCollection fetchResult:assetsFetchResult]]; - [subscriber putCompletion]; - } - else - { - [subscriber putError:nil]; - } + [subscriber putError:nil]; } return nil; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m index 0e2ec297fc..55fb746f20 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -89,8 +89,11 @@ _assetGroup = assetGroup; _intent = intent; - [self setTitle:@"Gallery"]; -// [self setTitle:_assetGroup.title]; + if (_intent == TGMediaAssetsControllerSendMediaIntent) { + [self setTitle:TGLocalized(@"Attachment.Gallery")]; + } else { + [self setTitle:assetGroup.title]; + } _assetsDisposable = [[SMetaDisposable alloc] init]; } @@ -153,10 +156,13 @@ [super viewDidLoad]; SSignal *groupSignal = nil; - if (_assetGroup != nil) + bool reversed = false; + if (_assetGroup != nil) { groupSignal = [SSignal single:_assetGroup]; - else + } else { groupSignal = [_assetsLibrary cameraRollGroup]; + reversed = true; + } __weak TGMediaAssetsPickerController *weakSelf = self; [_assetsDisposable setDisposable:[[[[groupSignal deliverOn:[SQueue mainQueue]] mapToSignal:^SSignal *(TGMediaAssetGroup *assetGroup) @@ -168,9 +174,11 @@ if (strongSelf->_assetGroup == nil) strongSelf->_assetGroup = assetGroup; - [self setTitle:@"Gallery"]; -// [strongSelf setTitle:assetGroup.title]; - + if (strongSelf->_intent == TGMediaAssetsControllerSendMediaIntent) { + [strongSelf setTitle:TGLocalized(@"Attachment.Gallery")]; + } else { + [strongSelf setTitle:assetGroup.title]; + } return [strongSelf->_assetsLibrary assetsOfAssetGroup:assetGroup reversed:false]; }] deliverOn:[SQueue mainQueue]] startWithNext:^(id next) { @@ -192,15 +200,18 @@ { TGMediaAssetFetchResult *fetchResult = (TGMediaAssetFetchResult *)next; - bool scrollToBottom = (strongSelf->_fetchResult == nil); + bool scrollToTop = (strongSelf->_fetchResult == nil && strongSelf->_intent == TGMediaAssetsControllerSendMediaIntent); + bool scrollToBottom = (strongSelf->_fetchResult == nil && strongSelf->_intent != TGMediaAssetsControllerSendMediaIntent); strongSelf->_fetchResult = fetchResult; [strongSelf->_collectionView reloadData]; - - if (scrollToBottom) - { + + if (scrollToTop) { [strongSelf->_collectionView layoutSubviews]; -// [strongSelf _adjustContentOffsetToBottom]; + [strongSelf->_collectionView setContentOffset:CGPointMake(0.0, -strongSelf->_collectionView.contentInset.top) animated:false]; + } else if (scrollToBottom) { + [strongSelf->_collectionView layoutSubviews]; + [strongSelf _adjustContentOffsetToBottom]; } } else if ([next isKindOfClass:[TGMediaAssetFetchResultChange class]]) @@ -221,6 +232,7 @@ [super viewWillAppear:animated]; [self setup3DTouch]; + [self setLeftBarButtonItem:[(TGMediaAssetsController *)self.navigationController leftBarButtonItem]]; [self setRightBarButtonItem:[(TGMediaAssetsController *)self.navigationController rightBarButtonItem]]; } @@ -371,6 +383,8 @@ - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [self.view.window endEditing:true]; + TGMediaAsset *asset = [self _itemAtIndexPath:indexPath]; TGMediaSelectionContext *selectionContext = ((TGMediaAssetsController *)self.navigationController).selectionContext; diff --git a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m index fd3e31d9fe..344d346a2d 100644 --- a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m @@ -406,9 +406,34 @@ return _forcedCaption.string.length > 0; } +- (SSignal *)forcedCaption +{ + __weak TGMediaEditingContext *weakSelf = self; + SSignal *updateSignal = [_captionPipe.signalProducer() map:^NSAttributedString *(TGMediaCaptionUpdate *update) + { + __strong TGMediaEditingContext *strongSelf = weakSelf; + if (strongSelf.isForcedCaption) { + return strongSelf->_forcedCaption; + } else { + return nil; + } + }]; + + return [[SSignal single:_forcedCaption] then:updateSignal]; +} + - (void)setForcedCaption:(NSAttributedString *)caption +{ + [self setForcedCaption:caption skipUpdate:false]; +} + +- (void)setForcedCaption:(NSAttributedString *)caption skipUpdate:(bool)skipUpdate { _forcedCaption = caption; + + if (!skipUpdate) { + _captionPipe.sink([TGMediaCaptionUpdate captionUpdateWithItem:nil caption:caption]); + } } - (SSignal *)captionSignalForItem:(NSObject *)item diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m b/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m deleted file mode 100644 index de6fc90aa8..0000000000 --- a/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m +++ /dev/null @@ -1,1077 +0,0 @@ -#import "TGMediaPickerCaptionInputPanel.h" - -#import "TGMessage.h" -#import "TGInputTextTag.h" - -#import "LegacyComponentsInternal.h" -#import "TGImageUtils.h" -#import "TGFont.h" -#import "TGViewController.h" -#import "TGHacks.h" -#import "TGModernButton.h" - -#import "TGPhotoEditorInterfaceAssets.h" -#import "TGMediaAssetsController.h" - -#import "HPTextViewInternal.h" - -#import "TGModernConversationAssociatedInputPanel.h" - -const NSInteger TGMediaPickerCaptionInputPanelCaptionLimit = 1024; - -static void setViewFrame(UIView *view, CGRect frame) -{ - CGAffineTransform transform = view.transform; - view.transform = CGAffineTransformIdentity; - if (!CGRectEqualToRect(view.frame, frame)) - view.frame = frame; - view.transform = transform; -} - -@interface TGMediaPickerCaptionInputPanel () -{ - CGFloat _keyboardHeight; - NSString *_caption; - bool _dismissing; - bool _dismissDisabled; - - NSArray *_entities; - - UIView *_wrapperView; - UIView *_backgroundView; - UIImageView *_fieldBackground; - UIView *_inputFieldClippingContainer; - HPGrowingTextView *_inputField; - UILabel *_placeholderLabel; - - UIView *_doneButtonWrapper; - TGModernButton *_doneButton; - - UILabel *_inputFieldOnelineLabel; - - UILabel *_counterLabel; - - TGModernConversationAssociatedInputPanel *_associatedPanel; - - CGFloat _contentAreaHeight; - - __weak TGKeyCommandController *_keyCommandController; -} - -@end - -@implementation TGMediaPickerCaptionInputPanel - -- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController frame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - _keyCommandController = keyCommandController; - static UIImage *fieldBackgroundImage = nil; - static UIImage *placeholderImage = nil; - - static NSString *localizationPlaceholderText = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(33, 33), false, 0.0f); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, UIColorRGBA(0xffffff, 0.1f).CGColor); - - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 33, 33) cornerRadius:16.5f]; - [path fill]; - - fieldBackgroundImage = [UIGraphicsGetImageFromCurrentImageContext() resizableImageWithCapInsets:UIEdgeInsetsMake(16, 16, 16, 16)]; - UIGraphicsEndImageContext(); - }); - - if (placeholderImage == nil || localizationPlaceholderText != TGLocalized(@"MediaPicker.AddCaption")) - { - localizationPlaceholderText = TGLocalized(@"MediaPicker.AddCaption"); - NSString *placeholderText = TGLocalized(@"MediaPicker.AddCaption"); - UIFont *placeholderFont = TGSystemFontOfSize(17); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize placeholderSize = [placeholderText sizeWithFont:placeholderFont]; -#pragma clang diagnostic pop - placeholderSize.width += 2.0f; - placeholderSize.height += 2.0f; - - UIGraphicsBeginImageContextWithOptions(placeholderSize, false, 0.0f); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, UIColorRGB(0xffffff).CGColor); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [placeholderText drawAtPoint:CGPointMake(1.0f, 1.0f) withFont:placeholderFont]; -#pragma clang diagnostic pop - placeholderImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - } - - UIView *backgroundWrapperView = [[UIView alloc] initWithFrame:frame]; - backgroundWrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - backgroundWrapperView.clipsToBounds = true; - [self addSubview:backgroundWrapperView]; - - _wrapperView = [[UIView alloc] initWithFrame:frame]; - _wrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [self addSubview:_wrapperView]; - - _backgroundView = [[UIView alloc] initWithFrame:_wrapperView.bounds]; - _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor]; - [backgroundWrapperView addSubview:_backgroundView]; - - _fieldBackground = [[UIImageView alloc] initWithImage:fieldBackgroundImage]; - _fieldBackground.alpha = 0.0f; - _fieldBackground.userInteractionEnabled = true; - [_wrapperView addSubview:_fieldBackground]; - - _placeholderLabel = [[UILabel alloc] init]; - _placeholderLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; - _placeholderLabel.backgroundColor = [UIColor clearColor]; - _placeholderLabel.font = TGSystemFontOfSize(17); - _placeholderLabel.textColor = UIColorRGB(0x7f7f7f); - _placeholderLabel.text = TGLocalized(@"MediaPicker.AddCaption"); - _placeholderLabel.userInteractionEnabled = true; - [_placeholderLabel sizeToFit]; - [_wrapperView addSubview:_placeholderLabel]; - - _inputFieldOnelineLabel = [[UILabel alloc] init]; - _inputFieldOnelineLabel.backgroundColor = [UIColor clearColor]; - _inputFieldOnelineLabel.font = TGSystemFontOfSize(17); - _inputFieldOnelineLabel.hidden = true; - _inputFieldOnelineLabel.numberOfLines = 1; - _inputFieldOnelineLabel.textColor = [UIColor whiteColor]; - _inputFieldOnelineLabel.userInteractionEnabled = false; - [_wrapperView addSubview:_inputFieldOnelineLabel]; - - _counterLabel = [[UILabel alloc] initWithFrame:CGRectMake(_fieldBackground.frame.size.width - 45, 5, 36, 16)]; - _counterLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; - _counterLabel.backgroundColor = [UIColor clearColor]; - _counterLabel.font = TGSystemFontOfSize(12); - _counterLabel.hidden = true; - _counterLabel.textAlignment = NSTextAlignmentRight; - _counterLabel.textColor = UIColorRGB(0x828282); - _counterLabel.highlightedTextColor = UIColorRGB(0xff4848); - _counterLabel.userInteractionEnabled = false; - [_fieldBackground addSubview:_counterLabel]; - - _doneButtonWrapper = [[UIView alloc] init]; - _doneButtonWrapper.alpha = 0.0f; - _doneButtonWrapper.userInteractionEnabled = false; - [_wrapperView addSubview:_doneButtonWrapper]; - - CGSize buttonSize = CGSizeMake(49.0f, 49.0f); - _doneButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, buttonSize.width, buttonSize.height)]; - _doneButton.exclusiveTouch = true; - _doneButton.adjustsImageWhenHighlighted = false; - [_doneButton addTarget:self action:@selector(setButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - [_doneButtonWrapper addSubview:_doneButton]; - - TGMediaAssetsPallete *pallete = nil; - if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)]) - pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete]; - - UIImage *doneImage = pallete != nil ? pallete.doneIconImage : TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]); - [_doneButton setImage:doneImage forState:UIControlStateNormal]; - - [_wrapperView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleFieldBackgroundTap:)]]; - } - return self; -} - -- (void)createInputFieldIfNeeded -{ - if (_inputField != nil) - return; - - CGRect inputFieldClippingFrame = _fieldBackground.frame; - _inputFieldClippingContainer = [[UIView alloc] initWithFrame:inputFieldClippingFrame]; - _inputFieldClippingContainer.clipsToBounds = true; - [_wrapperView addSubview:_inputFieldClippingContainer]; - - UIEdgeInsets inputFieldInternalEdgeInsets = [self _inputFieldInternalEdgeInsets]; - _inputField = [[HPGrowingTextView alloc] initWithKeyCommandController:_keyCommandController]; - _inputField.frame = CGRectMake(inputFieldInternalEdgeInsets.left, inputFieldInternalEdgeInsets.top + TGRetinaPixel, _inputFieldClippingContainer.frame.size.width - inputFieldInternalEdgeInsets.left - 36, _inputFieldClippingContainer.frame.size.height); - _inputField.textColor = [UIColor whiteColor]; - _inputField.disableFormatting = !_allowEntities; - _inputField.placeholderView = _placeholderLabel; - _inputField.font = TGSystemFontOfSize(17); - _inputField.accentColor = UIColorRGB(0x78b1f9); - _inputField.clipsToBounds = true; - _inputField.backgroundColor = nil; - _inputField.opaque = false; - _inputField.showPlaceholderWhenFocussed = true; - _inputField.internalTextView.returnKeyType = UIReturnKeyDefault; - _inputField.internalTextView.backgroundColor = nil; - _inputField.internalTextView.opaque = false; - _inputField.internalTextView.contentMode = UIViewContentModeLeft; - if (iosMajorVersion() >= 7) - _inputField.internalTextView.keyboardAppearance = UIKeyboardAppearanceDark; - else - _inputField.internalTextView.keyboardAppearance = UIKeyboardAppearanceAlert; - _inputField.maxNumberOfLines = [self _maxNumberOfLinesForSize:CGSizeMake(320.0f, 480.0f)]; - _inputField.delegate = self; - - _inputField.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(-inputFieldInternalEdgeInsets.top, 0, 5 - TGRetinaPixel, 0); - - [_inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:17.0f] keepFormatting:true animated:false]; - - [_inputFieldClippingContainer addSubview:_inputField]; -} - -- (void)setAllowEntities:(bool)allowEntities -{ - _allowEntities = allowEntities; - _inputField.disableFormatting = !_allowEntities; -} - -- (void)handleFieldBackgroundTap:(UITapGestureRecognizer *)__unused gestureRecognizer -{ - bool shouldBecomeFirstResponder = true; - - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(inputPanelShouldBecomeFirstResponder:)]) - shouldBecomeFirstResponder = [delegate inputPanelShouldBecomeFirstResponder:self]; - - if (!shouldBecomeFirstResponder || self.isCollapsed) - return; - - [self createInputFieldIfNeeded]; - _inputFieldClippingContainer.hidden = false; - _inputField.internalTextView.enableFirstResponder = true; - [_inputField.internalTextView becomeFirstResponder]; -} - -- (bool)setButtonPressed -{ - if (_dismissDisabled) - return false; - - if (_inputField.text.length > TGMediaPickerCaptionInputPanelCaptionLimit) - { - [self shakeControls]; - return false; - } - - if (_inputField.internalTextView.isFirstResponder) - [TGHacks applyCurrentKeyboardAutocorrectionVariant:_inputField.internalTextView]; - - NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithAttributedString:_inputField.text == nil ? [[NSAttributedString alloc] initWithString:@""] : _inputField.attributedText]; - NSMutableString *usualString = [text.string mutableCopy]; - int textLength = (int)text.length; - for (int i = 0; i < textLength; i++) - { - unichar c = [usualString characterAtIndex:i]; - - if (c == ' ' || c == '\t' || c == '\n') - { - [text deleteCharactersInRange:NSMakeRange(i, 1)]; - [usualString deleteCharactersInRange:NSMakeRange(i, 1)]; - i--; - textLength--; - } - else - break; - } - - _inputField.internalTextView.attributedText = text; - - __autoreleasing NSArray *entities = nil; - NSString *finalText = [_inputField textWithEntities:&entities]; - - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(inputPanelRequestedSetCaption:text:entities:)]) - [delegate inputPanelRequestedSetCaption:self text:finalText entities:entities]; - - _dismissing = true; - - [_inputField.internalTextView resignFirstResponder]; - - return true; -} - -- (void)dismiss -{ - [self setButtonPressed]; -} - -#pragma mark - - -- (void)setCollapsed:(bool)collapsed -{ - [self setCollapsed:collapsed animated:false]; -} - -- (void)setCollapsed:(bool)collapsed animated:(bool)animated -{ - _collapsed = collapsed; - - void (^frameChangeBlock)(void) = ^ - { - _backgroundView.frame = CGRectMake(_backgroundView.frame.origin.x, - collapsed ? self.frame.size.height : 0, - _backgroundView.frame.size.width, _backgroundView.frame.size.height); - _wrapperView.frame = CGRectMake(_wrapperView.frame.origin.x, - collapsed ? self.frame.size.height : 0, - _wrapperView.frame.size.width, _wrapperView.frame.size.height); - }; - - void (^visibilityChangeBlock)(void) = ^ - { - CGFloat alpha = collapsed ? 0.0f : 1.0f; - _wrapperView.alpha = alpha; - }; - - if (animated) - { - [UIView animateWithDuration:0.3f delay:0.0f options:[TGViewController preferredAnimationCurve] << 16 animations:frameChangeBlock completion:nil]; - [UIView animateWithDuration:0.25f delay:collapsed ? 0.0f : 0.05f options:kNilOptions animations:visibilityChangeBlock completion:nil]; - } - else - { - frameChangeBlock(); - visibilityChangeBlock(); - } -} - -#pragma mark - - -- (void)adjustForOrientation:(UIInterfaceOrientation)orientation keyboardHeight:(CGFloat)keyboardHeight duration:(NSTimeInterval)duration animationCurve:(NSInteger)animationCurve -{ - [self adjustForOrientation:orientation keyboardHeight:keyboardHeight duration:duration animationCurve:animationCurve completion:nil]; -} - -- (void)adjustForOrientation:(UIInterfaceOrientation)__unused orientation keyboardHeight:(CGFloat)keyboardHeight duration:(NSTimeInterval)duration animationCurve:(NSInteger)animationCurve completion:(void (^)(void))completion -{ - _keyboardHeight = keyboardHeight; - - void(^changeBlock)(void) = ^ - { - bool isKeyboardVisible = (keyboardHeight > FLT_EPSILON); - CGFloat inputContainerHeight = [self heightForInputFieldHeight:[self isFirstResponder] ? _inputField.frame.size.height : 0]; - CGSize screenSize = self.superview.frame.size; - - if (isKeyboardVisible) - { - self.frame = CGRectMake(self.frame.origin.x, screenSize.height - keyboardHeight - inputContainerHeight, self.frame.size.width, inputContainerHeight + keyboardHeight - self.bottomMargin); - _backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarBackgroundColor]; - } - else - { - self.frame = CGRectMake(self.frame.origin.x, screenSize.height - self.bottomMargin - inputContainerHeight, self.frame.size.width, inputContainerHeight); - _backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor]; - } - - [self layoutSubviews]; - }; - - void (^finishedBlock)(BOOL) = ^(__unused BOOL finished) - { - if (completion != nil) - completion(); - }; - - if (duration > DBL_EPSILON) - { - [UIView animateWithDuration:duration delay:0.0f options:animationCurve animations:changeBlock completion:finishedBlock]; - } - else - { - changeBlock(); - finishedBlock(true); - } -} - -#pragma mark - - -- (NSString *)caption -{ - return _caption; -} - -- (void)setCaption:(NSString *)caption -{ - [self setCaption:caption entities:nil animated:false]; -} - -- (void)setCaption:(NSString *)caption entities:(NSArray *)entities animated:(bool)animated -{ - NSString *previousCaption = _caption; - _caption = caption; - _entities = entities; - - if (animated) - { - _inputFieldOnelineLabel.attributedText = [self oneLinedCaptionForText:caption entities:entities]; - - if ([previousCaption isEqualToString:caption] || (previousCaption.length == 0 && caption.length == 0)) - return; - - UIView *snapshotView = nil; - UIView *snapshottedView = nil; - UIView *fadingInView = nil; - if (previousCaption.length > 0) - snapshottedView = _inputFieldOnelineLabel; - else - snapshottedView = _placeholderLabel; - - snapshotView = [snapshottedView snapshotViewAfterScreenUpdates:false]; - snapshotView.frame = snapshottedView.frame; - [snapshottedView.superview addSubview:snapshotView]; - - if (previousCaption.length > 0 && caption.length == 0) - fadingInView = _placeholderLabel; - else - fadingInView = _inputFieldOnelineLabel; - - fadingInView.hidden = false; - fadingInView.alpha = 0.0f; - - _placeholderLabel.hidden = (caption.length > 0); - - [UIView animateWithDuration:0.3f delay:0.05f options:UIViewAnimationOptionCurveEaseInOut animations:^ - { - fadingInView.alpha = 1.0f; - } completion:nil]; - - [UIView animateWithDuration:0.21f delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^ - { - snapshotView.alpha = 0.0f; - _fieldBackground.alpha = _placeholderLabel.hidden ? 1.0f : 0.0f; - } completion:^(__unused BOOL finished) - { - [snapshotView removeFromSuperview]; - }]; - } - else - { - _inputFieldOnelineLabel.attributedText = [self oneLinedCaptionForText:caption entities:entities]; - _inputFieldOnelineLabel.hidden = (caption.length == 0); - _placeholderLabel.hidden = !_inputFieldOnelineLabel.hidden; - _fieldBackground.alpha = _placeholderLabel.hidden ? 1.0f : 0.0f; - } - - [self.inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:17.0f] keepFormatting:true animated:false]; -} - -+ (NSAttributedString *)attributedStringForText:(NSString *)text entities:(NSArray *)entities fontSize:(CGFloat)fontSize { - if (text == nil) { - return nil; - } - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: TGSystemFontOfSize(fontSize)}]; - for (id entity in entities) { - if ([entity isKindOfClass:[TGMessageEntityMentionName class]]) { - TGMessageEntityMentionName *mentionEntity = entity; - static int64_t nextId = 1000000; - int64_t uniqueId = nextId; - nextId++; - @try { - [attributedString addAttributes:@{TGMentionUidAttributeName: [[TGInputTextTag alloc] initWithUniqueId:uniqueId left:true attachment:@(mentionEntity.userId)]} range:mentionEntity.range]; - } @catch(NSException *e) { - } - } - else if (iosMajorVersion() >= 7) { - if ([entity isKindOfClass:[TGMessageEntityBold class]]) { - TGMessageEntityBold *boldEntity = entity; - @try { - [attributedString addAttributes:@{NSFontAttributeName: TGBoldSystemFontOfSize(fontSize)} range:boldEntity.range]; - } @catch(NSException *e) { - } - } else if ([entity isKindOfClass:[TGMessageEntityItalic class]]) { - TGMessageEntityItalic *italicEntity = entity; - @try { - [attributedString addAttributes:@{NSFontAttributeName: TGItalicSystemFontOfSize(fontSize)} range:italicEntity.range]; - } @catch(NSException *e) { - } - } - } - } - return attributedString; -} - -- (void)updateCounterWithText:(NSString *)text -{ - bool appearance = false; - - NSInteger textLength = text.length; - _counterLabel.text = [NSString stringWithFormat:@"%d", (int)(TGMediaPickerCaptionInputPanelCaptionLimit - textLength)]; - - bool hidden = (text.length < (TGMediaPickerCaptionInputPanelCaptionLimit - 100)); - if (hidden != _counterLabel.hidden) - { - appearance = true; - - [UIView transitionWithView:_counterLabel duration:0.16f options:UIViewAnimationOptionTransitionCrossDissolve animations:^ - { - _counterLabel.hidden = hidden; - } completion:nil]; - } - - bool highlighted = (textLength > TGMediaPickerCaptionInputPanelCaptionLimit); - if (highlighted != _counterLabel.highlighted) - { - if (!appearance) - { - [UIView transitionWithView:_counterLabel duration:0.16f options:UIViewAnimationOptionTransitionCrossDissolve animations:^ - { - _counterLabel.highlighted = highlighted; - } completion:nil]; - } - else - { - _counterLabel.highlighted = highlighted; - } - } - - _counterLabel.hidden = ![self isFirstResponder] || textLength < (TGMediaPickerCaptionInputPanelCaptionLimit - 100); -} - -- (void)shakeControls -{ - CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"]; - NSMutableArray *values = [[NSMutableArray alloc] init]; - for (NSUInteger i = 0; i < 6; i++) - [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(i % 2 == 0 ? -3.0f : 3.0f, 0.0f, 0.0f)]]; - [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0f, 0.0f, 0.0f)]]; - animation.values = values; - NSMutableArray *keyTimes = [[NSMutableArray alloc] init]; - for (NSUInteger i = 0; i < animation.values.count; i++) - [keyTimes addObject:@((NSTimeInterval)i / (animation.values.count - 1.0))]; - animation.keyTimes = keyTimes; - animation.duration = 0.3; - [_wrapperView.layer addAnimation:animation forKey:@"transform"]; - - _dismissDisabled = true; - TGDispatchAfter(0.3, dispatch_get_main_queue(), ^ - { - _dismissDisabled = false; - }); -} - -#pragma mark - - -- (BOOL)becomeFirstResponder -{ - [self handleFieldBackgroundTap:nil]; - return true; -} - -- (BOOL)isFirstResponder -{ - return _inputField.internalTextView.isFirstResponder && !_dismissing; -} - -- (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)__unused growingTextView -{ - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(inputPanelFocused:)]) - [delegate inputPanelFocused:self]; - - [self updateCounterWithText:_caption]; - - _inputField.alpha = 0.0f; - _doneButtonWrapper.userInteractionEnabled = true; - [UIView animateWithDuration:0.2f animations:^ - { - _inputField.alpha = 1.0f; - _inputFieldOnelineLabel.alpha = 0.0f; - _fieldBackground.alpha = 1.0f; - _doneButtonWrapper.alpha = 1.0; - } completion:^(BOOL finished) - { - if (finished) - { - _inputFieldOnelineLabel.alpha = 1.0f; - _inputFieldOnelineLabel.hidden = true; - } - }]; - - if (_keyboardHeight < FLT_EPSILON) - { - [self adjustForOrientation:UIInterfaceOrientationPortrait keyboardHeight:0 duration:0.2f animationCurve:[TGViewController preferredAnimationCurve]]; - } - - [_inputField refreshHeight:false]; -} - -- (void)growingTextViewDidEndEditing:(HPGrowingTextView *)__unused growingTextView -{ - _caption = _inputField.text; - - __autoreleasing NSArray *entities = nil; - [_inputField textWithEntities:&entities]; - _entities = entities; - - _inputFieldOnelineLabel.attributedText = [self oneLinedCaptionForText:_caption entities:_entities]; - _inputFieldOnelineLabel.alpha = 0.0f; - _inputFieldOnelineLabel.hidden = false; - - [self updateCounterWithText:_caption]; - - _doneButtonWrapper.userInteractionEnabled = false; - [UIView animateWithDuration:0.2f animations:^ - { - _inputField.alpha = 0.0f; - _inputFieldOnelineLabel.alpha = 1.0f; - _doneButtonWrapper.alpha = 0.0; - - if (_caption.length == 0) - _fieldBackground.alpha = 0.0f; - } completion:^(BOOL finished) - { - if (finished) - { - _inputField.alpha = 1.0f; - _inputFieldClippingContainer.hidden = true; - } - }]; - - [self setAssociatedPanel:nil animated:true]; - - [self setButtonPressed]; -} - -- (void)growingTextView:(HPGrowingTextView *)__unused growingTextView willChangeHeight:(CGFloat)height duration:(NSTimeInterval)duration animationCurve:(int)animationCurve -{ - UIEdgeInsets inputFieldInsets = [self _inputFieldInsets]; - CGFloat inputContainerHeight = MAX([self baseHeight], height - 8 + inputFieldInsets.top + inputFieldInsets.bottom); - - id delegate = (id)self.delegate; - if ([delegate respondsToSelector:@selector(inputPanelWillChangeHeight:height:duration:animationCurve:)]) - { - [delegate inputPanelWillChangeHeight:self height:inputContainerHeight duration:duration animationCurve:animationCurve]; - } -} - -- (void)growingTextViewDidChange:(HPGrowingTextView *)__unused growingTextView afterSetText:(bool)__unused afterSetText afterPastingText:(bool)__unused afterPastingText -{ - id delegate = (id)self.delegate; - - int textLength = (int)growingTextView.text.length; - NSString *text = growingTextView.text; - - UITextRange *selRange = _inputField.internalTextView.selectedTextRange; - UITextPosition *selStartPos = selRange.start; - NSInteger idx = [_inputField.internalTextView offsetFromPosition:_inputField.internalTextView.beginningOfDocument toPosition:selStartPos]; - idx--; - - NSString *candidateMention = nil; - bool candidateMentionStartOfLine = false; - NSString *candidateHashtag = nil; - NSString *candidateAlphacode = nil; - - if (idx >= 0 && idx < textLength) - { - for (NSInteger i = idx; i >= 0; i--) - { - unichar c = [text characterAtIndex:i]; - if (c == '@') - { - if (i == idx){ - candidateMention = @""; - candidateMentionStartOfLine = i == 0; - } - else - { - @try { - candidateMention = [text substringWithRange:NSMakeRange(i + 1, idx - i)]; - candidateMentionStartOfLine = i == 0; - } @catch(NSException *e) { } - } - break; - } - - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) - break; - } - } - - if (candidateMention == nil) - { - static NSCharacterSet *characterSet = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - characterSet = [NSCharacterSet alphanumericCharacterSet]; - }); - - if (idx >= 0 && idx < textLength) - { - for (NSInteger i = idx; i >= 0; i--) - { - unichar c = [text characterAtIndex:i]; - if (c == '#') - { - if (i == idx) - candidateHashtag = @""; - else - { - @try { - candidateHashtag = [text substringWithRange:NSMakeRange(i + 1, idx - i)]; - } @catch(NSException *e) { } - } - - break; - } - - if (c == ' ' || (![characterSet characterIsMember:c] && c != '_')) - break; - } - } - - if (candidateHashtag == nil) - { - if (idx >= 0 && idx < textLength) - { - for (NSInteger i = idx; i >= 0; i--) - { - unichar c = [text characterAtIndex:i]; - unichar previousC = 0; - if (i > 0) - previousC = [text characterAtIndex:i - 1]; - if (c == ':' && (previousC == 0 || ![characterSet characterIsMember:previousC])) - { - if (i == idx) { - candidateAlphacode = nil; - } - else - { - @try { - candidateAlphacode = [text substringWithRange:NSMakeRange(i + 1, idx - i)]; - } @catch(NSException *e) { } - } - break; - } - - if (c == ' ' || (![characterSet characterIsMember:c])) - break; - } - } - } -} - - if ([delegate respondsToSelector:@selector(inputPanelMentionEntered:mention:startOfLine:)]) - [delegate inputPanelMentionEntered:self mention:candidateMention startOfLine:candidateMentionStartOfLine]; - - if ([delegate respondsToSelector:@selector(inputPanelHashtagEntered:hashtag:)]) - [delegate inputPanelHashtagEntered:self hashtag:candidateHashtag]; - - if ([delegate respondsToSelector:@selector(inputPanelAlphacodeEntered:alphacode:)]) - [delegate inputPanelAlphacodeEntered:self alphacode:candidateAlphacode]; - - if ([delegate respondsToSelector:@selector(inputPanelTextChanged:text:)]) - [delegate inputPanelTextChanged:self text:text]; - - [self updateCounterWithText:text]; -} - -- (void)addNewLine -{ - self.caption = [NSString stringWithFormat:@"%@\n", self.caption]; -} - -- (NSMutableAttributedString *)oneLinedCaptionForText:(NSString *)text entities:(NSArray *)entities -{ - static NSString *tokenString = nil; - if (tokenString == nil) - { - unichar tokenChar = 0x2026; - tokenString = [[NSString alloc] initWithCharacters:&tokenChar length:1]; - } - - if (text == nil) - return nil; - - NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithAttributedString:[TGMediaPickerCaptionInputPanel attributedStringForText:text entities:entities fontSize:17.0f]]; - - for (NSUInteger i = 0; i < string.length; i++) - { - unichar c = [text characterAtIndex:i]; - if (c == '\t' || c == '\n') - { - [string insertAttributedString:[[NSAttributedString alloc] initWithString:tokenString attributes:@{NSFontAttributeName:TGSystemFontOfSize(17.0f)}] atIndex:i]; - break; - } - } - - return string; -} - -- (NSInteger)textCaretPosition { - UITextRange *selRange = _inputField.internalTextView.selectedTextRange; - UITextPosition *selStartPos = selRange.start; - NSInteger idx = [_inputField.internalTextView offsetFromPosition:_inputField.internalTextView.beginningOfDocument toPosition:selStartPos]; - return idx; -} - -#pragma mark - - -- (void)replaceMention:(NSString *)mention -{ - [HPGrowingTextView replaceMention:mention inputField:_inputField username:true userId:0]; -} - -- (void)replaceMention:(NSString *)mention username:(bool)username userId:(int64_t)userId { - [HPGrowingTextView replaceMention:mention inputField:_inputField username:username userId:userId]; -} - -- (void)replaceHashtag:(NSString *)hashtag -{ - [HPGrowingTextView replaceHashtag:hashtag inputField:_inputField]; -} - -- (bool)shouldDisplayPanels -{ - return true; -} - -- (TGModernConversationAssociatedInputPanel *)associatedPanel -{ - return _associatedPanel; -} - -- (void)setAssociatedPanel:(TGModernConversationAssociatedInputPanel *)associatedPanel animated:(bool)animated -{ - if (_associatedPanel != associatedPanel) - { - TGModernConversationAssociatedInputPanel *currentPanel = _associatedPanel; - if (currentPanel != nil) - { - if (animated) - { - [UIView animateWithDuration:0.18 animations:^ - { - currentPanel.alpha = 0.0f; - } completion:^(BOOL finished) - { - if (finished) - [currentPanel removeFromSuperview]; - }]; - } - else - [currentPanel removeFromSuperview]; - } - - _associatedPanel = associatedPanel; - if (_associatedPanel != nil) - { - if ([_associatedPanel fillsAvailableSpace]) { - CGFloat inputContainerHeight = [self heightForInputFieldHeight:[self isFirstResponder] ? _inputField.frame.size.height : 0]; - _associatedPanel.frame = CGRectMake(0.0f, -_contentAreaHeight + inputContainerHeight, self.frame.size.width, _contentAreaHeight - inputContainerHeight); - } else { - __weak TGMediaPickerCaptionInputPanel *weakSelf = self; - _associatedPanel.preferredHeightUpdated = ^ - { - __strong TGMediaPickerCaptionInputPanel *strongSelf = weakSelf; - if (strongSelf != nil) - { - strongSelf->_associatedPanel.frame = CGRectMake(0.0f, -[strongSelf->_associatedPanel preferredHeight], strongSelf.frame.size.width, [strongSelf shouldDisplayPanels] ? [strongSelf->_associatedPanel preferredHeight] : 0.0f); - } - }; - _associatedPanel.frame = CGRectMake(0.0f, -[_associatedPanel preferredHeight], self.frame.size.width, [self shouldDisplayPanels] ? [_associatedPanel preferredHeight] : 0.0f); - } - - [self addSubview:_associatedPanel]; - if (animated) - { - _associatedPanel.alpha = 0.0f; - [UIView animateWithDuration:0.18 animations:^ - { - _associatedPanel.alpha = 1.0f; - }]; - } - else - { - _associatedPanel.alpha = 1.0f; - } - } - } -} - -- (void)setContentAreaHeight:(CGFloat)contentAreaHeight -{ - _contentAreaHeight = contentAreaHeight; - [self setNeedsLayout]; - - _dismissing = false; -} - -#pragma mark - Style - -- (UIEdgeInsets)_inputFieldInsets -{ - static UIEdgeInsets insets; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) - insets = UIEdgeInsetsMake(6.0f, 6.0f, 6.0f, 6.0f); - else - insets = UIEdgeInsetsMake(11.0f, 11.0f, 11.0f, 11.0f); - }); - - return insets; -} - -- (UIEdgeInsets)_inputFieldInternalEdgeInsets -{ - static UIEdgeInsets insets; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - if (!TGIsPad()) - insets = UIEdgeInsetsMake(-3.0f, 8.0f, 0.0f, 0.0f); - else - insets = UIEdgeInsetsMake(-2.0f, 8.0f, 0.0f, 0.0f); - }); - - return insets; -} - -- (CGPoint)_inputFieldPlaceholderOffset -{ - static CGPoint offset; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - if (!TGIsPad()) - offset = CGPointMake(12.0f, 5.0f + TGScreenPixel); - else - offset = CGPointMake(12.0f, 6.0f); - }); - - return offset; -} - -- (CGFloat)heightForInputFieldHeight:(CGFloat)inputFieldHeight -{ - if (inputFieldHeight < FLT_EPSILON) - inputFieldHeight = 36; - - if (TGIsPad()) - inputFieldHeight += 4; - - UIEdgeInsets inputFieldInsets = [self _inputFieldInsets]; - CGFloat height = MAX([self baseHeight], inputFieldHeight - 4 + inputFieldInsets.top + inputFieldInsets.bottom); - - return height; -} - -- (CGFloat)baseHeight -{ - static CGFloat value = 0.0f; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - value = !TGIsPad() ? 45.0f : 56.0f; - }); - - return value; -} - -- (CGPoint)_setButtonOffset -{ - static CGPoint offset; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) - offset = CGPointZero; - else - offset = CGPointMake(-11.0f, -6.0f); - }); - - return offset; -} - -- (int)_maxNumberOfLinesForSize:(CGSize)size -{ - if (size.height <= 320.0f - FLT_EPSILON) { - return 3; - } else if (size.height <= 480.0f - FLT_EPSILON) { - return 5; - } else { - return 7; - } -} - -#pragma mark - - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - if (_associatedPanel != nil) - { - UIView *result = [_associatedPanel hitTest:[self convertPoint:point toView:_associatedPanel] withEvent:event]; - if (result != nil) - return result; - } - - return [super hitTest:point withEvent:event]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - CGRect frame = self.frame; - - if (_associatedPanel != nil) - { - CGFloat inputContainerHeight = [self heightForInputFieldHeight:[self isFirstResponder] ? _inputField.frame.size.height : 0]; - - CGRect associatedPanelFrame = CGRectZero; - if ([_associatedPanel fillsAvailableSpace]) { - associatedPanelFrame = CGRectMake(0.0f, -_contentAreaHeight + inputContainerHeight, self.frame.size.width, _contentAreaHeight - inputContainerHeight); - } else { - associatedPanelFrame = CGRectMake(0.0f, -[_associatedPanel preferredHeight], frame.size.width, [self shouldDisplayPanels] ? [_associatedPanel preferredHeight] : 0.0f); - } - - if (!CGRectEqualToRect(associatedPanelFrame, _associatedPanel.frame)) - _associatedPanel.frame = associatedPanelFrame; - } - - UIEdgeInsets visibleInputFieldInsets = [self _inputFieldInsets]; - if (self.isFirstResponder) { - visibleInputFieldInsets.right += 41.0; - } - UIEdgeInsets actualInputFieldInsets = [self _inputFieldInsets]; - actualInputFieldInsets.right += 41.0; - - CGFloat inputContainerHeight = [self heightForInputFieldHeight:self.isFirstResponder ? _inputField.frame.size.height : 0]; - CGRect fieldBackgroundFrame = CGRectMake(visibleInputFieldInsets.left, visibleInputFieldInsets.top, frame.size.width - visibleInputFieldInsets.left - visibleInputFieldInsets.right, inputContainerHeight - visibleInputFieldInsets.top - visibleInputFieldInsets.bottom); - - CGRect actualFieldBackgroundFrame = CGRectMake(actualInputFieldInsets.left, actualInputFieldInsets.top, frame.size.width - actualInputFieldInsets.left - actualInputFieldInsets.right, inputContainerHeight - actualInputFieldInsets.top - actualInputFieldInsets.bottom); - - setViewFrame(_fieldBackground, fieldBackgroundFrame); - - UIEdgeInsets inputFieldInternalEdgeInsets = [self _inputFieldInternalEdgeInsets]; - CGRect onelineFrame = _fieldBackground.frame; - onelineFrame.origin.x += inputFieldInternalEdgeInsets.left + 5; - onelineFrame.origin.y += inputFieldInternalEdgeInsets.top + TGScreenPixel; - onelineFrame.size.width -= inputFieldInternalEdgeInsets.left * 2 + 10; - onelineFrame.size.height = 36; - setViewFrame(_inputFieldOnelineLabel, onelineFrame); - - CGRect placeholderFrame = CGRectMake(floor((self.frame.size.width - _placeholderLabel.frame.size.width) / 2.0f), floor(([self baseHeight] - _placeholderLabel.frame.size.height) / 2.0f), _placeholderLabel.frame.size.width, _placeholderLabel.frame.size.height); - if (self.isFirstResponder) - placeholderFrame.origin.x = onelineFrame.origin.x; - setViewFrame(_placeholderLabel, placeholderFrame); - - CGRect inputFieldClippingFrame = actualFieldBackgroundFrame; - setViewFrame(_inputFieldClippingContainer, inputFieldClippingFrame); - - CGFloat inputFieldWidth = _inputFieldClippingContainer.frame.size.width - inputFieldInternalEdgeInsets.left - 36; - if (fabs(inputFieldWidth - _inputField.frame.size.width) > FLT_EPSILON) - { - CGRect inputFieldFrame = CGRectMake(inputFieldInternalEdgeInsets.left, inputFieldInternalEdgeInsets.top + TGRetinaPixel, inputFieldWidth, _inputFieldClippingContainer.frame.size.height); - setViewFrame(_inputField, inputFieldFrame); - } - - _doneButtonWrapper.frame = CGRectMake(self.frame.size.width - 47.0, CGRectGetMaxY(_fieldBackground.frame) - _doneButton.frame.size.height + (TGIsPad() ? 7.0 : 8.0), _doneButton.frame.size.width, _doneButton.frame.size.height); -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGMentionPanelCell.h b/submodules/LegacyComponents/Sources/TGMentionPanelCell.h deleted file mode 100644 index e41ba8b432..0000000000 --- a/submodules/LegacyComponents/Sources/TGMentionPanelCell.h +++ /dev/null @@ -1,15 +0,0 @@ -#import - -@class TGUser; -@class TGConversationAssociatedInputPanelPallete; - -@interface TGMentionPanelCell : UITableViewCell - -@property (nonatomic, strong) TGUser *user; -@property (nonatomic, strong) TGConversationAssociatedInputPanelPallete *pallete; - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style; - -@end - -extern NSString *const TGMentionPanelCellKind; diff --git a/submodules/LegacyComponents/Sources/TGMentionPanelCell.m b/submodules/LegacyComponents/Sources/TGMentionPanelCell.m deleted file mode 100644 index 2d75e3c26f..0000000000 --- a/submodules/LegacyComponents/Sources/TGMentionPanelCell.m +++ /dev/null @@ -1,175 +0,0 @@ -#import "TGMentionPanelCell.h" - -#import "LegacyComponentsInternal.h" -#import "TGColor.h" -#import "TGFont.h" -#import "TGUser.h" -#import "TGImageUtils.h" - -#import "TGLetteredAvatarView.h" -#import "TGModernConversationAssociatedInputPanel.h" - -NSString *const TGMentionPanelCellKind = @"TGMentionPanelCell"; - -@interface TGMentionPanelCell () -{ - TGModernConversationAssociatedInputPanelStyle _style; - TGLetteredAvatarView *_avatarView; - UILabel *_nameLabel; - UILabel *_usernameLabel; -} - -@end - -@implementation TGMentionPanelCell - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style -{ - self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TGMentionPanelCellKind]; - if (self != nil) - { - _style = style; - - UIColor *backgroundColor = [UIColor whiteColor]; - UIColor *nameColor = [UIColor blackColor]; - UIColor *usernameColor = [UIColor blackColor]; - UIColor *selectionColor = TGSelectionColor(); - - if (style == TGModernConversationAssociatedInputPanelDarkStyle) - { - backgroundColor = UIColorRGB(0x171717); - nameColor = [UIColor whiteColor]; - usernameColor = UIColorRGB(0x828282); - selectionColor = UIColorRGB(0x292929); - } - else if (style == TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - backgroundColor = [UIColor clearColor]; - nameColor = [UIColor whiteColor]; - usernameColor = UIColorRGB(0x828282); - selectionColor = UIColorRGB(0x3d3d3d); - } - - self.backgroundColor = backgroundColor; - self.backgroundView = [[UIView alloc] init]; - self.backgroundView.backgroundColor = backgroundColor; - self.backgroundView.opaque = false; - - self.selectedBackgroundView = [[UIView alloc] init]; - self.selectedBackgroundView.backgroundColor = selectionColor; - - _avatarView = [[TGLetteredAvatarView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 32.0f, 32.0f)]; - [_avatarView setSingleFontSize:16.0f doubleFontSize:16.0f useBoldFont:false]; - _avatarView.fadeTransition = true; - [self.contentView addSubview:_avatarView]; - - _nameLabel = [[UILabel alloc] init]; - _nameLabel.backgroundColor = [UIColor clearColor]; - _nameLabel.textColor = nameColor; - _nameLabel.font = TGMediumSystemFontOfSize(14.0f); - _nameLabel.lineBreakMode = NSLineBreakByTruncatingTail; - [self.contentView addSubview:_nameLabel]; - - _usernameLabel = [[UILabel alloc] init]; - _usernameLabel.backgroundColor = [UIColor clearColor]; - _usernameLabel.textColor = usernameColor; - _usernameLabel.font = TGSystemFontOfSize(14.0f); - _usernameLabel.lineBreakMode = NSLineBreakByTruncatingTail; - [self.contentView addSubview:_usernameLabel]; - - } - return self; -} - -- (void)setPallete:(TGConversationAssociatedInputPanelPallete *)pallete -{ - if (pallete == nil || _pallete == pallete) - return; - - _pallete = pallete; - - _nameLabel.textColor = pallete.textColor; - _usernameLabel.textColor = pallete.textColor; - - self.backgroundColor = pallete.backgroundColor; - self.backgroundView.backgroundColor = self.backgroundColor; - self.selectedBackgroundView.backgroundColor = pallete.selectionColor; -} - -- (void)setUser:(TGUser *)user -{ - _user = user; - - _nameLabel.text = user.displayName; - _usernameLabel.text = user.userName.length == 0 ? @"" : [[NSString alloc] initWithFormat:@"@%@", user.userName]; - - NSString *avatarUrl = user.photoFullUrlSmall; - - CGFloat diameter = 32.0f; - - static UIImage *staticPlaceholder = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), false, 0.0f); - CGContextRef context = UIGraphicsGetCurrentContext(); - - //!placeholder - CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); - CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, diameter, diameter)); - CGContextSetStrokeColorWithColor(context, UIColorRGB(0xd9d9d9).CGColor); - CGContextSetLineWidth(context, 1.0f); - CGContextStrokeEllipseInRect(context, CGRectMake(0.5f, 0.5f, diameter - 1.0f, diameter - 1.0f)); - - staticPlaceholder = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - }); - - UIImage *placeholder = staticPlaceholder; - if (self.pallete != nil) - placeholder = self.pallete.avatarPlaceholder; - - if (avatarUrl.length != 0) - { - _avatarView.fadeTransitionDuration = 0.3; - if (![avatarUrl isEqualToString:_avatarView.currentUrl]) - [_avatarView loadImage:avatarUrl filter:@"circle:32x32" placeholder:placeholder]; - } - else - { - [_avatarView loadUserPlaceholderWithSize:CGSizeMake(diameter, diameter) uid:user.uid firstName:user.firstName lastName:user.lastName placeholder:placeholder]; - } - - [self setNeedsLayout]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - CGSize boundsSize = self.bounds.size; - - _avatarView.frame = CGRectMake(7.0f + TGRetinaPixel, TGRetinaFloor((boundsSize.height - _avatarView.frame.size.height) / 2.0f), _avatarView.frame.size.width, _avatarView.frame.size.height); - - CGFloat leftInset = 51.0f; - CGFloat spacing = 6.0f; - CGFloat rightInset = 6.0f; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize nameSize = [_nameLabel.text sizeWithFont:_nameLabel.font]; -#pragma clang diagnostic pop - nameSize.width = CGCeil(MIN((boundsSize.width - leftInset - rightInset) * 3.0f / 4.0f, nameSize.width)); - nameSize.height = CGCeil(nameSize.height); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize usernameSize = [_usernameLabel.text sizeWithFont:_usernameLabel.font]; -#pragma clang diagnostic pop - usernameSize.width = CGCeil(MIN(boundsSize.width - leftInset - rightInset - nameSize.width - spacing, usernameSize.width)); - - _nameLabel.frame = CGRectMake(leftInset, CGFloor((boundsSize.height - nameSize.height) / 2.0f), nameSize.width, nameSize.height); - _usernameLabel.frame = CGRectMake(leftInset + nameSize.width + spacing, CGFloor((boundsSize.height - usernameSize.height) / 2.0f), usernameSize.width, usernameSize.height); -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGModernConversationAlphacodeAssociatedPanel.m b/submodules/LegacyComponents/Sources/TGModernConversationAlphacodeAssociatedPanel.m deleted file mode 100644 index b4f842a399..0000000000 --- a/submodules/LegacyComponents/Sources/TGModernConversationAlphacodeAssociatedPanel.m +++ /dev/null @@ -1,488 +0,0 @@ -#import "TGModernConversationAlphacodeAssociatedPanel.h" - -#import "LegacyComponentsInternal.h" -#import "TGColor.h" -#import "TGImageUtils.h" -#import "TGFont.h" -#import "TGViewController.h" - -#import "TGAlphacodePanelCell.h" - -#import "TGAlphacode.h" - -@interface TGModernConversationAlphacodeAssociatedPanel () -{ - SMetaDisposable *_disposable; - NSArray *_codeList; - - UIView *_backgroundView; - UIView *_effectView; - - UITableView *_tableView; - UIView *_stripeView; - UIView *_separatorView; - - UIView *_bottomView; - UIView *_tableViewBackground; - UIView *_tableViewSeparator; - - bool _resetOffsetOnLayout; - bool _animatingOut; -} - -@end - -@implementation TGModernConversationAlphacodeAssociatedPanel - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style -{ - self = [super initWithStyle:style]; - if (self != nil) - { - _disposable = [[SMetaDisposable alloc] init]; - - UIColor *backgroundColor = [UIColor whiteColor]; - UIColor *bottomColor = UIColorRGBA(0xfafafa, 0.98f); - UIColor *separatorColor = UIColorRGB(0xc5c7d0); - UIColor *cellSeparatorColor = UIColorRGB(0xdbdbdb); - - self.clipsToBounds = true; - - if (self.style == TGModernConversationAssociatedInputPanelDarkStyle) - { - backgroundColor = UIColorRGB(0x171717); - bottomColor = backgroundColor; - separatorColor = UIColorRGB(0x292929); - cellSeparatorColor = separatorColor; - } - else if (self.style == TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - backgroundColor = [UIColor clearColor]; - separatorColor = UIColorRGBA(0xb2b2b2, 0.7f); - cellSeparatorColor = UIColorRGBA(0xb2b2b2, 0.4f); - bottomColor = [UIColor clearColor]; - - CGFloat backgroundAlpha = 0.8f; - if (iosMajorVersion() >= 8) - { - UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; - blurEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - blurEffectView.frame = self.bounds; - [self addSubview:blurEffectView]; - _effectView = blurEffectView; - - backgroundAlpha = 0.4f; - } - - _backgroundView = [[UIView alloc] initWithFrame:self.bounds]; - _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _backgroundView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:backgroundAlpha]; - [self addSubview:_backgroundView]; - } - - _bottomView = [[UIView alloc] init]; - _bottomView.backgroundColor = bottomColor; - [self addSubview:_bottomView]; - - _tableViewBackground = [[UIView alloc] init]; - _tableViewBackground.backgroundColor = backgroundColor; - [self addSubview:_tableViewBackground]; - - _tableView = [[UITableView alloc] init]; - _tableView.delegate = self; - _tableView.dataSource = self; - _tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; - _tableView.tableFooterView = [[UIView alloc] init]; - if (iosMajorVersion() >= 7) - { - _tableView.separatorColor = cellSeparatorColor; - _tableView.separatorInset = UIEdgeInsetsMake(0.0f, 40.0f, 0.0f, 0.0f); - } - _tableView.backgroundColor = nil; - _tableView.opaque = false; - _tableView.showsVerticalScrollIndicator = false; - _tableView.showsHorizontalScrollIndicator = false; - - [self addSubview:_tableView]; - - _tableViewSeparator = [[UIView alloc] init]; - _tableViewSeparator.backgroundColor = separatorColor; - [self addSubview:_tableViewSeparator]; - - _stripeView = [[UIView alloc] init]; - _stripeView.backgroundColor = separatorColor; - [self addSubview:_stripeView]; - - if (self.style != TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - _separatorView = [[UIView alloc] init]; - _separatorView.backgroundColor = separatorColor; - [self addSubview:_separatorView]; - } - } - return self; -} - -- (void)dealloc -{ - [_disposable dispose]; -} - -- (void)setPallete:(TGConversationAssociatedInputPanelPallete *)pallete -{ - [super setPallete:pallete]; - if (self.pallete == nil) - return; - - _bottomView.backgroundColor = pallete.barBackgroundColor; - _tableViewBackground.backgroundColor = pallete.backgroundColor; - _tableViewSeparator.backgroundColor = pallete.barSeparatorColor; - _tableView.separatorColor = pallete.separatorColor; - _stripeView.backgroundColor = pallete.barSeparatorColor; - _separatorView.backgroundColor = pallete.barSeparatorColor; -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - - self.alpha = frame.size.height >= FLT_EPSILON; -} - -- (bool)fillsAvailableSpace { - return true;//iosMajorVersion() >= 9; -} - -- (CGFloat)preferredHeight { - return [self preferredHeightAndOverlayHeight:NULL]; -} - -- (CGFloat)preferredHeightAndOverlayHeight:(CGFloat *)overlayHeight -{ - CGFloat height = 0.0f; - CGFloat lastHeight = 0.0f; - NSInteger lastIndex = MIN([TGViewController isWidescreen] ? 4 : 3, (NSInteger)_codeList.count - 1); - for (NSInteger i = 0; i <= lastIndex; i++) { - CGFloat rowHeight = [self tableView:_tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; - if (i == lastIndex) { - lastHeight = rowHeight; - } else { - height += rowHeight; - } - } - - CGFloat completeHeight = 0.0f; - for (NSInteger i = 0; i < (NSInteger)_codeList.count; i++) { - CGFloat rowHeight = [self tableView:_tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; - completeHeight += rowHeight; - } - - CGFloat maxHeight = CGFloor(self.frame.size.height * 2.0f / 3.0f); - - height = completeHeight; - - CGFloat overlayHeightValue = 0.0f; - if (height + self.barInset < self.frame.size.height) { - overlayHeightValue = self.barInset; - } - - if (overlayHeight) { - *overlayHeight = overlayHeightValue; - } - - height += overlayHeightValue; - - if (lastIndex > 0) { - return MIN(maxHeight, CGFloor(height)); - } else { - return MIN(maxHeight, height); - } -} - -- (void)setAlphacodeListSignal:(SSignal *)alphacodeListSignal -{ - if (alphacodeListSignal == nil) - { - [_disposable setDisposable:nil]; - [self setCodeList:@[]]; - } - else - { - __weak TGModernConversationAlphacodeAssociatedPanel *weakSelf = self; - [_disposable setDisposable:[[alphacodeListSignal deliverOn:[SQueue mainQueue]] startWithNext:^(NSArray *userList) - { - __strong TGModernConversationAlphacodeAssociatedPanel *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf setCodeList:userList]; - }]]; - } -} - -- (void)setCodeList:(NSArray *)codeList -{ - bool wasEmpty = _codeList.count == 0; - _codeList = codeList; - - if (iosMajorVersion() >= 7) { - _tableView.separatorStyle = _codeList.count <= 1 ? UITableViewCellSeparatorStyleNone : UITableViewCellSeparatorStyleSingleLine; - } - - [_tableView reloadData]; - - [self setNeedsPreferredHeightUpdate]; - - _stripeView.hidden = _codeList.count == 0; - _separatorView.hidden = _codeList.count == 0; - _bottomView.hidden = _codeList.count == 0; - - [self scrollViewDidScroll:_tableView]; - - if (_codeList.count != 0 && wasEmpty) { - [self animateIn]; - } else { - [self layoutSubviews]; - } -} - -- (NSInteger)tableView:(UITableView *)__unused tableView numberOfRowsInSection:(NSInteger)__unused section -{ - return _codeList.count; -} - -- (CGFloat)tableView:(UITableView *)__unused tableView heightForRowAtIndexPath:(NSIndexPath *)__unused indexPath { - return 41.0f; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - TGAlphacodePanelCell *cell = (TGAlphacodePanelCell *)[tableView dequeueReusableCellWithIdentifier:TGAlphacodePanelCellKind]; - if (cell == nil) - cell = [[TGAlphacodePanelCell alloc] initWithStyle:self.style]; - cell.pallete = self.pallete; - - TGAlphacodeEntry *entry = _codeList[indexPath.row]; - [cell setEmoji:entry.emoji label:entry.code]; - - return cell; -} - -- (void)tableView:(UITableView *)__unused tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - TGAlphacodeEntry *entry = _codeList[indexPath.row]; - if (_alphacodeSelected) { - _alphacodeSelected(entry); - } -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - if (scrollView == _tableView) { - [self updateTableBackground]; - } -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - if (_animatingOut) { - return; - } - - _backgroundView.frame = CGRectMake(-1000, 0, self.frame.size.width + 2000, self.frame.size.height); - _effectView.frame = CGRectMake(-1000, 0, self.frame.size.width + 2000, self.frame.size.height); - - CGFloat separatorHeight = TGScreenPixel; - _separatorView.frame = CGRectMake(0.0f, self.frame.size.height - separatorHeight, self.frame.size.width, separatorHeight); - - UIEdgeInsets previousInset = _tableView.contentInset; - - _tableView.frame = CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height); - - if ([self fillsAvailableSpace]) { - CGFloat overlayHeight = 0.0; - CGFloat preferredHeight = [self preferredHeightAndOverlayHeight:&overlayHeight]; - - CGFloat topInset = MAX(0.0f, self.frame.size.height - preferredHeight); - CGFloat insetDifference = topInset - _tableView.contentInset.top; - UIEdgeInsets finalInset = UIEdgeInsetsMake(topInset, 0.0f, MAX(0.0f, overlayHeight - 1.0f / TGScreenScaling()), 0.0f); - - if (_resetOffsetOnLayout) { - _resetOffsetOnLayout = false; - _tableView.contentInset = finalInset; - [_tableView setContentOffset:CGPointMake(0.0f, -_tableView.contentInset.top) animated:false]; - } else if (ABS(insetDifference) > FLT_EPSILON) { - //if (ABS(insetDifference) <= 36.0f + 0.1) { - { - [self _autoAdjustInsetsForScrollView:_tableView finalInset:finalInset previousInset:previousInset]; - - //contentOffset.y -= insetDifference; - //_tableView.contentOffset = contentOffset; - } - } - } else { - _tableView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f); - } - - _bottomView.frame = CGRectMake(0.0f, self.frame.size.height, self.frame.size.width, 4.0f); - - [self updateTableBackground]; -} - -- (void)_autoAdjustInsetsForScrollView:(UIScrollView *)scrollView finalInset:(UIEdgeInsets)finalInset previousInset:(UIEdgeInsets)previousInset -{ - CGPoint contentOffset = scrollView.contentOffset; - - scrollView.contentInset = finalInset; - if (iosMajorVersion() <= 8 && scrollView.subviews.count != 0) { - if ([NSStringFromClass([scrollView.subviews.firstObject class]) hasPrefix:@"UITableViewWra"]) { - CGRect frame = scrollView.subviews.firstObject.frame; - frame.origin = CGPointZero; - scrollView.subviews.firstObject.frame = frame; - } - } - - if (!UIEdgeInsetsEqualToEdgeInsets(previousInset, UIEdgeInsetsZero)) - { - CGFloat maxOffset = scrollView.contentSize.height - (scrollView.frame.size.height - finalInset.bottom); - - contentOffset.y += previousInset.top - finalInset.top; - contentOffset.y = MAX(-finalInset.top, MIN(contentOffset.y, maxOffset)); - [scrollView setContentOffset:contentOffset animated:false]; - } - else if (contentOffset.y < finalInset.top) - { - contentOffset.y = -finalInset.top; - [scrollView setContentOffset:contentOffset animated:false]; - } -} - -- (void)updateTableBackground { - if (_animatingOut) { - return; - } - - CGFloat backgroundOriginY = MAX(0.0f, -_tableView.contentOffset.y); - _tableViewBackground.frame = CGRectMake(0.0f, backgroundOriginY, self.frame.size.width, self.frame.size.height - backgroundOriginY); - _tableViewSeparator.frame = CGRectMake(0.0f, backgroundOriginY - 0.5f, self.frame.size.width, 0.5f); - - _tableView.scrollIndicatorInsets = UIEdgeInsetsMake(backgroundOriginY, 0.0f, 0.0f, 0.0f); - - self.overlayBarOffset = _tableView.contentOffset.y + _tableView.contentInset.top; - if (self.updateOverlayBarOffset) { - self.updateOverlayBarOffset(self.overlayBarOffset); - } -} - -- (CGRect)tableBackgroundFrame { - return _tableViewBackground.frame; -} - -- (bool)hasSelectedItem -{ - return _tableView.indexPathForSelectedRow != nil; -} - -- (void)selectPreviousItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *newIndexPath = _tableView.indexPathForSelectedRow; - - if (newIndexPath == nil) - newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - else if (newIndexPath.row > 0) - newIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row - 1 inSection:0]; - - if (_tableView.indexPathForSelectedRow != nil) - [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:false]; - - if (newIndexPath != nil) - [_tableView selectRowAtIndexPath:newIndexPath animated:false scrollPosition:UITableViewScrollPositionBottom]; -} - -- (void)selectNextItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *newIndexPath = _tableView.indexPathForSelectedRow; - - if (newIndexPath == nil) - newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - else if (newIndexPath.row < [self tableView:_tableView numberOfRowsInSection:newIndexPath.section] - 1) - newIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row + 1 inSection:0]; - - if (_tableView.indexPathForSelectedRow != nil) - [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:false]; - - if (newIndexPath != nil) - [_tableView selectRowAtIndexPath:newIndexPath animated:false scrollPosition:UITableViewScrollPositionBottom]; -} - -- (void)commitSelectedItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *selectedIndexPath = _tableView.indexPathForSelectedRow; - if (selectedIndexPath == nil) - selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - - [self tableView:_tableView didSelectRowAtIndexPath:selectedIndexPath]; -} - -- (void)animateIn { - [self layoutSubviews]; - CGFloat offset = [self preferredHeight]; - CGRect normalFrame = _tableView.frame; - _tableView.frame = CGRectMake(normalFrame.origin.x, normalFrame.origin.y + offset, normalFrame.size.width, normalFrame.size.height); - CGRect normalBackgroundFrame = _tableViewBackground.frame; - _tableViewBackground.frame = CGRectMake(normalBackgroundFrame.origin.x, normalBackgroundFrame.origin.y + offset, normalBackgroundFrame.size.width, normalBackgroundFrame.size.height); - CGRect normalSeparatorFrame = _tableViewSeparator.frame; - _tableViewSeparator.frame = CGRectMake(normalSeparatorFrame.origin.x, normalSeparatorFrame.origin.y + offset, normalSeparatorFrame.size.width, normalSeparatorFrame.size.height); - [UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{ - _tableView.frame = normalFrame; - _tableViewBackground.frame = normalBackgroundFrame; - _tableViewSeparator.frame = normalSeparatorFrame; - } completion:nil]; -} - -- (void)animateOut:(void (^)())completion { - CGFloat offset = self.frame.size.height - _tableViewBackground.frame.origin.y; - CGRect normalFrame = _tableView.frame; - CGRect normalBackgroundFrame = _tableViewBackground.frame; - CGRect normalSeparatorFrame = _tableViewSeparator.frame; - _animatingOut = true; - - [UIView animateWithDuration:0.15 delay:0.0 options:0 animations:^{ - _tableView.frame = CGRectMake(normalFrame.origin.x, normalFrame.origin.y + offset, normalFrame.size.width, normalFrame.size.height); - _tableViewBackground.frame = CGRectMake(normalBackgroundFrame.origin.x, normalBackgroundFrame.origin.y + offset, normalBackgroundFrame.size.width, normalBackgroundFrame.size.height); - _tableViewSeparator.frame = CGRectMake(normalSeparatorFrame.origin.x, normalSeparatorFrame.origin.y + offset, normalSeparatorFrame.size.width, normalSeparatorFrame.size.height); - } completion:^(__unused BOOL finished) { - completion(); - }]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - if (CGRectContainsPoint(_tableViewBackground.frame, point)) { - return [super hitTest:point withEvent:event]; - } - return nil; -} - -- (void)setBarInset:(CGFloat)barInset animated:(bool)animated { - if (ABS(barInset - self.barInset) > FLT_EPSILON) { - [super setBarInset:barInset animated:animated]; - - if (animated) { - [self layoutSubviews]; - } else { - [UIView animateWithDuration:0.3 animations:^{ - [self layoutSubviews]; - }]; - } - } -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGModernConversationAssociatedInputPanel.m b/submodules/LegacyComponents/Sources/TGModernConversationAssociatedInputPanel.m deleted file mode 100644 index d52b4be8c8..0000000000 --- a/submodules/LegacyComponents/Sources/TGModernConversationAssociatedInputPanel.m +++ /dev/null @@ -1,105 +0,0 @@ -#import "TGModernConversationAssociatedInputPanel.h" - -@implementation TGModernConversationAssociatedInputPanel - -- (instancetype)initWithStyle:(TGModernConversationAssociatedInputPanelStyle)style -{ - _style = style; - return [self initWithFrame:CGRectZero]; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - } - return self; -} - -- (CGFloat)preferredHeight -{ - return 75.0f; -} - -- (bool)displayForTextEntryOnly { - return false; -} - -- (bool)fillsAvailableSpace { - return false; -} - -- (void)setNeedsPreferredHeightUpdate -{ - if (_preferredHeightUpdated) - _preferredHeightUpdated(); -} - -- (void)setSendAreaWidth:(CGFloat)__unused sendAreaWidth attachmentAreaWidth:(CGFloat)__unused attachmentAreaWidth -{ -} - -- (void)setContentAreaHeight:(CGFloat)__unused contentAreaHeight { -} - -- (bool)hasSelectedItem -{ - return false; -} - -- (void)selectPreviousItem -{ -} - -- (void)selectNextItem -{ -} - -- (void)commitSelectedItem -{ -} - -- (void)animateIn { -} - -- (void)animateOut:(void (^)())completion { - if (completion) { - completion(); - } -} - -- (void)setBarInset:(CGFloat)barInset { - [self setBarInset:barInset animated:false]; -} - -- (void)setBarInset:(CGFloat)barInset animated:(bool)__unused animated { - _barInset = barInset; -} - -@end - - -@implementation TGConversationAssociatedInputPanelPallete - -+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor separatorColor:(UIColor *)separatorColor selectionColor:(UIColor *)selectionColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor placeholderBackgroundColor:(UIColor *)placeholderBackgroundColor placeholderIconColor:(UIColor *)placeholderIconColor avatarPlaceholder:(UIImage *)avatarPlaceholder closeIcon:(UIImage *)closeIcon largeCloseIcon:(UIImage *)largeCloseIcon -{ - TGConversationAssociatedInputPanelPallete *pallete = [[TGConversationAssociatedInputPanelPallete alloc] init]; - pallete->_isDark = dark; - pallete->_backgroundColor = backgroundColor; - pallete->_separatorColor = separatorColor; - pallete->_selectionColor = selectionColor; - pallete->_barBackgroundColor = barBackgroundColor; - pallete->_barSeparatorColor = barSeparatorColor; - pallete->_textColor = textColor; - pallete->_secondaryTextColor = secondaryTextColor; - pallete->_accentColor = accentColor; - pallete->_placeholderBackgroundColor = placeholderBackgroundColor; - pallete->_placeholderIconColor = placeholderIconColor; - pallete->_avatarPlaceholder = avatarPlaceholder; - pallete->_closeIcon = closeIcon; - pallete->_largeCloseIcon = largeCloseIcon; - return pallete; -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGModernConversationHashtagsAssociatedPanel.m b/submodules/LegacyComponents/Sources/TGModernConversationHashtagsAssociatedPanel.m deleted file mode 100644 index 969b62ee10..0000000000 --- a/submodules/LegacyComponents/Sources/TGModernConversationHashtagsAssociatedPanel.m +++ /dev/null @@ -1,267 +0,0 @@ -#import "TGModernConversationHashtagsAssociatedPanel.h" - -#import "LegacyComponentsInternal.h" -#import "TGImageUtils.h" - -#import "TGHashtagPanelCell.h" - -@interface TGModernConversationHashtagsAssociatedPanel () -{ - SMetaDisposable *_disposable; - NSArray *_hashtagList; - - UIView *_backgroundView; - UIView *_effectView; - - UITableView *_tableView; - UIView *_stripeView; - UIView *_separatorView; - - UIView *_bottomView; -} - -@end - -@implementation TGModernConversationHashtagsAssociatedPanel - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - _disposable = [[SMetaDisposable alloc] init]; - - UIColor *backgroundColor = [UIColor whiteColor]; - UIColor *bottomColor = UIColorRGBA(0xfafafa, 0.98f); - UIColor *separatorColor = UIColorRGB(0xc5c7d0); - UIColor *cellSeparatorColor = UIColorRGB(0xdbdbdb); - - if (self.style == TGModernConversationAssociatedInputPanelDarkStyle) - { - backgroundColor = UIColorRGB(0x171717); - bottomColor = backgroundColor; - separatorColor = UIColorRGB(0x292929); - cellSeparatorColor = separatorColor; - } - else if (self.style == TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - backgroundColor = [UIColor clearColor]; - bottomColor = [UIColor clearColor]; - separatorColor = UIColorRGBA(0xb2b2b2, 0.7f); - cellSeparatorColor = separatorColor; - - CGFloat backgroundAlpha = 0.8f; - if (iosMajorVersion() >= 8) - { - UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; - blurEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - blurEffectView.frame = self.bounds; - [self addSubview:blurEffectView]; - _effectView = blurEffectView; - - backgroundAlpha = 0.4f; - } - - _backgroundView = [[UIView alloc] initWithFrame:self.bounds]; - _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _backgroundView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:backgroundAlpha]; - [self addSubview:_backgroundView]; - } - - self.backgroundColor = backgroundColor; - - _bottomView = [[UIView alloc] init]; - _bottomView.backgroundColor = bottomColor; - [self addSubview:_bottomView]; - - _tableView = [[UITableView alloc] init]; - if (@available(iOS 11.0, *)) { - _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } - _tableView.delegate = self; - _tableView.dataSource = self; - _tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; - _tableView.tableFooterView = [[UIView alloc] init]; - if (iosMajorVersion() >= 7) - { - _tableView.separatorColor = cellSeparatorColor; - _tableView.separatorInset = UIEdgeInsetsMake(0.0f, 15.0f, 0.0f, 0.0f); - } - _tableView.backgroundColor = nil; - _tableView.rowHeight = 41.0f; - _tableView.opaque = false; - - [self addSubview:_tableView]; - - _stripeView = [[UIView alloc] init]; - _stripeView.backgroundColor = separatorColor; - [self addSubview:_stripeView]; - - if (self.style != TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - _separatorView = [[UIView alloc] init]; - _separatorView.backgroundColor = separatorColor; - [self addSubview:_separatorView]; - } - } - return self; -} - -- (void)dealloc -{ - [_disposable dispose]; -} - -- (void)setPallete:(TGConversationAssociatedInputPanelPallete *)pallete -{ - [super setPallete:pallete]; - if (self.pallete == nil) - return; - - self.backgroundColor = pallete.backgroundColor; - _bottomView.backgroundColor = pallete.barBackgroundColor; - _tableView.separatorColor = pallete.separatorColor; - _stripeView.backgroundColor = pallete.barSeparatorColor; - _separatorView.backgroundColor = pallete.barSeparatorColor; -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - - self.alpha = frame.size.height >= FLT_EPSILON; -} - -- (CGFloat)preferredHeight -{ - return 41.0f * MIN(3.5f, (CGFloat)_hashtagList.count); -} - -- (void)setHashtagListSignal:(SSignal *)hashtagListSignal -{ - if (hashtagListSignal == nil) - { - [_disposable setDisposable:nil]; - [self setHashtagList:@[]]; - } - else - { - __weak TGModernConversationHashtagsAssociatedPanel *weakSelf = self; - [_disposable setDisposable:[hashtagListSignal startWithNext:^(NSArray *hashtagList) - { - __strong TGModernConversationHashtagsAssociatedPanel *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf setHashtagList:hashtagList]; - }]]; - } -} - -- (void)setHashtagList:(NSArray *)hashtagList -{ - _hashtagList = hashtagList; - - [_tableView reloadData]; - - [self setNeedsPreferredHeightUpdate]; - - _stripeView.hidden = hashtagList.count == 0; - _separatorView.hidden = hashtagList.count == 0; - _bottomView.hidden = hashtagList.count == 0; -} - -- (NSInteger)tableView:(UITableView *)__unused tableView numberOfRowsInSection:(NSInteger)__unused section -{ - return _hashtagList.count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - TGHashtagPanelCell *cell = (TGHashtagPanelCell *)[tableView dequeueReusableCellWithIdentifier:TGHashtagPanelCellKind]; - if (cell == nil) - cell = [[TGHashtagPanelCell alloc] initWithStyle:self.style]; - cell.pallete = self.pallete; - - [cell setHashtag:_hashtagList[indexPath.row]]; - - return cell; -} - -- (void)tableView:(UITableView *)__unused tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSString *hashtag = _hashtagList[indexPath.row]; - if (_hashtagSelected) - _hashtagSelected(hashtag); -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - _backgroundView.frame = CGRectMake(-1000, 0, self.frame.size.width + 2000, self.frame.size.height); - _effectView.frame = CGRectMake(-1000, 0, self.frame.size.width + 2000, self.frame.size.height); - - CGFloat separatorHeight = TGScreenPixel; - _stripeView.frame = CGRectMake(0.0f, 0.0f, self.frame.size.width, separatorHeight); - _separatorView.frame = CGRectMake(0.0f, self.frame.size.height - separatorHeight, self.frame.size.width, separatorHeight); - - _tableView.frame = CGRectMake(0.0f, separatorHeight, self.frame.size.width, self.frame.size.height - separatorHeight); - - _bottomView.frame = CGRectMake(0.0f, self.frame.size.height, self.frame.size.width, 4.0f); -} - -- (bool)hasSelectedItem -{ - return _tableView.indexPathForSelectedRow != nil; -} - -- (void)selectPreviousItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *newIndexPath = _tableView.indexPathForSelectedRow; - - if (newIndexPath == nil) - newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - else if (newIndexPath.row > 0) - newIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row - 1 inSection:0]; - - if (_tableView.indexPathForSelectedRow != nil) - [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:false]; - - if (newIndexPath != nil) - [_tableView selectRowAtIndexPath:newIndexPath animated:false scrollPosition:UITableViewScrollPositionBottom]; -} - -- (void)selectNextItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *newIndexPath = _tableView.indexPathForSelectedRow; - - if (newIndexPath == nil) - newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - else if (newIndexPath.row < [self tableView:_tableView numberOfRowsInSection:newIndexPath.section] - 1) - newIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row + 1 inSection:0]; - - if (_tableView.indexPathForSelectedRow != nil) - [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:false]; - - if (newIndexPath != nil) - [_tableView selectRowAtIndexPath:newIndexPath animated:false scrollPosition:UITableViewScrollPositionBottom]; -} - -- (void)commitSelectedItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *selectedIndexPath = _tableView.indexPathForSelectedRow; - if (selectedIndexPath == nil) - selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - - [self tableView:_tableView didSelectRowAtIndexPath:selectedIndexPath]; -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGModernConversationMentionsAssociatedPanel.m b/submodules/LegacyComponents/Sources/TGModernConversationMentionsAssociatedPanel.m deleted file mode 100644 index 1deb889455..0000000000 --- a/submodules/LegacyComponents/Sources/TGModernConversationMentionsAssociatedPanel.m +++ /dev/null @@ -1,515 +0,0 @@ -#import "TGModernConversationMentionsAssociatedPanel.h" - -#import "LegacyComponentsInternal.h" - -#import -#import -#import - -#import "TGMentionPanelCell.h" - -@interface TGModernConversationMentionsAssociatedPanel () -{ - SMetaDisposable *_disposable; - NSArray *_userList; - - UIView *_backgroundView; - UIView *_effectView; - - UITableView *_tableView; - UIView *_stripeView; - UIView *_separatorView; - - UIView *_bottomView; - UIView *_tableViewBackground; - UIView *_tableViewSeparator; - - bool _resetOffsetOnLayout; - bool _animatingOut; -} - -@end - -@implementation TGModernConversationMentionsAssociatedPanel - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - _disposable = [[SMetaDisposable alloc] init]; - - UIColor *backgroundColor = [UIColor whiteColor]; - UIColor *bottomColor = UIColorRGBA(0xfafafa, 0.98f); - UIColor *separatorColor = UIColorRGB(0xc5c7d0); - UIColor *cellSeparatorColor = UIColorRGB(0xdbdbdb); - - self.clipsToBounds = true; - - if (self.style == TGModernConversationAssociatedInputPanelDarkStyle) - { - backgroundColor = UIColorRGB(0x171717); - bottomColor = backgroundColor; - separatorColor = UIColorRGB(0x292929); - cellSeparatorColor = separatorColor; - } - else if (self.style == TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - backgroundColor = [UIColor clearColor]; - separatorColor = UIColorRGBA(0xb2b2b2, 0.7f); - cellSeparatorColor = UIColorRGBA(0xb2b2b2, 0.4f); - bottomColor = [UIColor clearColor]; - - CGFloat backgroundAlpha = 0.8f; - if (iosMajorVersion() >= 8) - { - UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; - blurEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - blurEffectView.frame = self.bounds; - [self addSubview:blurEffectView]; - _effectView = blurEffectView; - - backgroundAlpha = 0.4f; - } - - _backgroundView = [[UIView alloc] initWithFrame:self.bounds]; - _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _backgroundView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:backgroundAlpha]; - [self addSubview:_backgroundView]; - } - - _bottomView = [[UIView alloc] init]; - _bottomView.backgroundColor = bottomColor; - [self addSubview:_bottomView]; - - _tableViewBackground = [[UIView alloc] init]; - _tableViewBackground.backgroundColor = backgroundColor; - [self addSubview:_tableViewBackground]; - - _tableView = [[UITableView alloc] init]; - if (@available(iOS 11.0, *)) { - _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } - _tableView.delegate = self; - _tableView.dataSource = self; - _tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; - _tableView.tableFooterView = [[UIView alloc] init]; - if (iosMajorVersion() >= 7) - { - _tableView.separatorColor = cellSeparatorColor; - _tableView.separatorInset = UIEdgeInsetsMake(0.0f, 52.0f, 0.0f, 0.0f); - } - _tableView.backgroundColor = nil; - _tableView.opaque = false; - _tableView.showsVerticalScrollIndicator = false; - _tableView.showsHorizontalScrollIndicator = false; - - [self addSubview:_tableView]; - - _tableViewSeparator = [[UIView alloc] init]; - _tableViewSeparator.backgroundColor = separatorColor; - [self addSubview:_tableViewSeparator]; - - _stripeView = [[UIView alloc] init]; - _stripeView.backgroundColor = separatorColor; - [self addSubview:_stripeView]; - - if (self.style != TGModernConversationAssociatedInputPanelDarkBlurredStyle) - { - _separatorView = [[UIView alloc] init]; - _separatorView.backgroundColor = separatorColor; - [self addSubview:_separatorView]; - } - } - return self; -} - -- (void)dealloc -{ - [_disposable dispose]; -} - -- (void)setPallete:(TGConversationAssociatedInputPanelPallete *)pallete -{ - [super setPallete:pallete]; - if (self.pallete == nil) - return; - - _bottomView.backgroundColor = pallete.barBackgroundColor; - _tableViewBackground.backgroundColor = pallete.backgroundColor; - _tableViewSeparator.backgroundColor = pallete.barSeparatorColor; - _tableView.separatorColor = pallete.separatorColor; - _stripeView.backgroundColor = pallete.barSeparatorColor; - _separatorView.backgroundColor = pallete.barSeparatorColor; -} - -- (void)setInverted:(bool)inverted { - if (_inverted != inverted) { - _inverted = inverted; - - if (_inverted) { - self.transform = CGAffineTransformMakeRotation((CGFloat)M_PI); - } else { - self.transform = CGAffineTransformIdentity; - } - } -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - - self.alpha = frame.size.height >= FLT_EPSILON; -} - -- (bool)fillsAvailableSpace { - return true;//iosMajorVersion() >= 9; -} - -- (CGFloat)preferredHeight { - return [self preferredHeightAndOverlayHeight:NULL]; -} - -- (CGFloat)preferredHeightAndOverlayHeight:(CGFloat *)overlayHeight -{ - CGFloat height = 0.0f; - CGFloat lastHeight = 0.0f; - NSInteger lastIndex = MIN([TGViewController isWidescreen] ? 4 : 3, (NSInteger)_userList.count - 1); - for (NSInteger i = 0; i <= lastIndex; i++) { - CGFloat rowHeight = [self tableView:_tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; - if (i == lastIndex) { - lastHeight = rowHeight; - } else { - height += rowHeight; - } - } - - CGFloat completeHeight = 0.0f; - for (NSInteger i = 0; i < (NSInteger)_userList.count; i++) { - CGFloat rowHeight = [self tableView:_tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; - completeHeight += rowHeight; - } - - CGFloat maxHeight = CGFloor(self.frame.size.height * 2.0f / 3.0f); - - height = completeHeight; - - CGFloat overlayHeightValue = 0.0f; - if (height + self.barInset < self.frame.size.height) { - overlayHeightValue = self.barInset; - } - - if (overlayHeight) { - *overlayHeight = overlayHeightValue; - } - - height += overlayHeightValue; - - if (lastIndex > 0) { - return MIN(maxHeight, CGFloor(height)); - } else { - return MIN(maxHeight, height); - } -} - -- (void)setUserListSignal:(SSignal *)userListSignal -{ - if (userListSignal == nil) - { - [_disposable setDisposable:nil]; - [self setUserList:@[]]; - } - else - { - __weak TGModernConversationMentionsAssociatedPanel *weakSelf = self; - [_disposable setDisposable:[[userListSignal deliverOn:[SQueue mainQueue]] startWithNext:^(NSArray *userList) - { - __strong TGModernConversationMentionsAssociatedPanel *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf setUserList:userList]; - }]]; - } -} - -- (void)setUserList:(NSArray *)userList -{ - bool wasEmpty = _userList.count == 0; - _userList = userList; - - if (iosMajorVersion() >= 7) { - _tableView.separatorStyle = _userList.count <= 1 ? UITableViewCellSeparatorStyleNone : UITableViewCellSeparatorStyleSingleLine; - } - - [_tableView reloadData]; - - [self setNeedsPreferredHeightUpdate]; - - _stripeView.hidden = userList.count == 0; - _separatorView.hidden = userList.count == 0 || _inverted; - _bottomView.hidden = userList.count == 0; - - [self scrollViewDidScroll:_tableView]; - - if (_userList.count != 0 && wasEmpty) { - [self animateIn]; - } else { - [self layoutSubviews]; - } -} - -- (NSInteger)tableView:(UITableView *)__unused tableView numberOfRowsInSection:(NSInteger)__unused section -{ - return _userList.count; -} - -- (CGFloat)tableView:(UITableView *)__unused tableView heightForRowAtIndexPath:(NSIndexPath *)__unused indexPath { - return 41.0f; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - TGMentionPanelCell *cell = (TGMentionPanelCell *)[tableView dequeueReusableCellWithIdentifier:TGMentionPanelCellKind]; - if (cell == nil) { - cell = [[TGMentionPanelCell alloc] initWithStyle:self.style]; - if (_inverted) { - [UIView performWithoutAnimation:^{ - cell.transform = CGAffineTransformMakeRotation((CGFloat)M_PI); - }]; - } else { - cell.transform = CGAffineTransformIdentity; - } - } - cell.pallete = self.pallete; - if (iosMajorVersion() >= 7) - { - if (indexPath.row == 0 && _inverted) { - cell.separatorInset = UIEdgeInsetsMake(0.0f, 2000.0f, 0.0f, 0.0f); - } else { - cell.separatorInset = tableView.separatorInset; - } - } - - [cell setUser:_userList[indexPath.row]]; - - return cell; -} - -- (void)tableView:(UITableView *)__unused tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - TGUser *user = _userList[indexPath.row]; - if (_userSelected) - _userSelected(user); -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - if (scrollView == _tableView) { - [self updateTableBackground]; - } -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - if (_animatingOut) { - return; - } - - _backgroundView.frame = CGRectMake(-1000, 0, self.frame.size.width + 2000, self.frame.size.height); - _effectView.frame = CGRectMake(-1000, 0, self.frame.size.width + 2000, self.frame.size.height); - - CGFloat separatorHeight = TGScreenPixel; - _separatorView.frame = CGRectMake(0.0f, self.frame.size.height - separatorHeight, self.frame.size.width, separatorHeight); - - UIEdgeInsets previousInset = _tableView.contentInset; - - _tableView.frame = CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height); - - if ([self fillsAvailableSpace]) { - CGFloat overlayHeight = 0.0; - CGFloat preferredHeight = [self preferredHeightAndOverlayHeight:&overlayHeight]; - - CGFloat topInset = MAX(0.0f, self.frame.size.height - preferredHeight); - CGFloat insetDifference = topInset - _tableView.contentInset.top; - UIEdgeInsets finalInset = UIEdgeInsetsMake(topInset, 0.0f, MAX(0.0f, overlayHeight - 1.0f / TGScreenScaling()), 0.0f); - - if (_resetOffsetOnLayout) { - _resetOffsetOnLayout = false; - _tableView.contentInset = finalInset; - [_tableView setContentOffset:CGPointMake(0.0f, -_tableView.contentInset.top) animated:false]; - } else if (ABS(insetDifference) > FLT_EPSILON) { - //if (ABS(insetDifference) <= 36.0f + 0.1) { - { - [self _autoAdjustInsetsForScrollView:_tableView finalInset:finalInset previousInset:previousInset]; - - //contentOffset.y -= insetDifference; - //_tableView.contentOffset = contentOffset; - } - } - } else { - _tableView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f); - } - - _bottomView.frame = CGRectMake(0.0f, self.frame.size.height, self.frame.size.width, 4.0f); - - [self updateTableBackground]; -} - -- (void)_autoAdjustInsetsForScrollView:(UIScrollView *)scrollView finalInset:(UIEdgeInsets)finalInset previousInset:(UIEdgeInsets)previousInset -{ - CGPoint contentOffset = scrollView.contentOffset; - - scrollView.contentInset = finalInset; - if (iosMajorVersion() <= 8 && scrollView.subviews.count != 0) { - if ([NSStringFromClass([scrollView.subviews.firstObject class]) hasPrefix:@"UITableViewWra"]) { - CGRect frame = scrollView.subviews.firstObject.frame; - frame.origin = CGPointZero; - scrollView.subviews.firstObject.frame = frame; - } - } - - if (!UIEdgeInsetsEqualToEdgeInsets(previousInset, UIEdgeInsetsZero)) - { - CGFloat maxOffset = scrollView.contentSize.height - (scrollView.frame.size.height - finalInset.bottom); - - contentOffset.y += previousInset.top - finalInset.top; - contentOffset.y = MAX(-finalInset.top, MIN(contentOffset.y, maxOffset)); - [scrollView setContentOffset:contentOffset animated:false]; - } - else if (contentOffset.y < finalInset.top) - { - contentOffset.y = -finalInset.top; - [scrollView setContentOffset:contentOffset animated:false]; - } -} - -- (void)updateTableBackground { - if (_animatingOut) { - return; - } - - CGFloat backgroundOriginY = MAX(0.0f, -_tableView.contentOffset.y); - _tableViewBackground.frame = CGRectMake(0.0f, backgroundOriginY, self.frame.size.width, self.frame.size.height - backgroundOriginY); - _tableViewSeparator.frame = CGRectMake(0.0f, backgroundOriginY - 0.5f, self.frame.size.width, 0.5f); - - _tableView.scrollIndicatorInsets = UIEdgeInsetsMake(backgroundOriginY, 0.0f, 0.0f, 0.0f); - - self.overlayBarOffset = _tableView.contentOffset.y + _tableView.contentInset.top; - if (self.updateOverlayBarOffset) { - self.updateOverlayBarOffset(self.overlayBarOffset); - } -} - -- (CGRect)tableBackgroundFrame { - return _tableViewBackground.frame; -} - -- (bool)hasSelectedItem -{ - return _tableView.indexPathForSelectedRow != nil; -} - -- (void)selectPreviousItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *newIndexPath = _tableView.indexPathForSelectedRow; - - if (newIndexPath == nil) - newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - else if (newIndexPath.row > 0) - newIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row - 1 inSection:0]; - - if (_tableView.indexPathForSelectedRow != nil) - [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:false]; - - if (newIndexPath != nil) - [_tableView selectRowAtIndexPath:newIndexPath animated:false scrollPosition:UITableViewScrollPositionBottom]; -} - -- (void)selectNextItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *newIndexPath = _tableView.indexPathForSelectedRow; - - if (newIndexPath == nil) - newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - else if (newIndexPath.row < [self tableView:_tableView numberOfRowsInSection:newIndexPath.section] - 1) - newIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row + 1 inSection:0]; - - if (_tableView.indexPathForSelectedRow != nil) - [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:false]; - - if (newIndexPath != nil) - [_tableView selectRowAtIndexPath:newIndexPath animated:false scrollPosition:UITableViewScrollPositionBottom]; -} - -- (void)commitSelectedItem -{ - if ([self tableView:_tableView numberOfRowsInSection:0] == 0) - return; - - NSIndexPath *selectedIndexPath = _tableView.indexPathForSelectedRow; - if (selectedIndexPath == nil) - selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - - [self tableView:_tableView didSelectRowAtIndexPath:selectedIndexPath]; -} - -- (void)animateIn { - [self layoutSubviews]; - CGFloat offset = [self preferredHeight]; - CGRect normalFrame = _tableView.frame; - _tableView.frame = CGRectMake(normalFrame.origin.x, normalFrame.origin.y + offset, normalFrame.size.width, normalFrame.size.height); - CGRect normalBackgroundFrame = _tableViewBackground.frame; - _tableViewBackground.frame = CGRectMake(normalBackgroundFrame.origin.x, normalBackgroundFrame.origin.y + offset, normalBackgroundFrame.size.width, normalBackgroundFrame.size.height); - CGRect normalSeparatorFrame = _tableViewSeparator.frame; - _tableViewSeparator.frame = CGRectMake(normalSeparatorFrame.origin.x, normalSeparatorFrame.origin.y + offset, normalSeparatorFrame.size.width, normalSeparatorFrame.size.height); - [UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{ - _tableView.frame = normalFrame; - _tableViewBackground.frame = normalBackgroundFrame; - _tableViewSeparator.frame = normalSeparatorFrame; - } completion:nil]; -} - -- (void)animateOut:(void (^)())completion { - CGFloat offset = self.frame.size.height - _tableViewBackground.frame.origin.y; - CGRect normalFrame = _tableView.frame; - CGRect normalBackgroundFrame = _tableViewBackground.frame; - CGRect normalSeparatorFrame = _tableViewSeparator.frame; - _animatingOut = true; - - [UIView animateWithDuration:0.15 delay:0.0 options:0 animations:^{ - _tableView.frame = CGRectMake(normalFrame.origin.x, normalFrame.origin.y + offset, normalFrame.size.width, normalFrame.size.height); - _tableViewBackground.frame = CGRectMake(normalBackgroundFrame.origin.x, normalBackgroundFrame.origin.y + offset, normalBackgroundFrame.size.width, normalBackgroundFrame.size.height); - _tableViewSeparator.frame = CGRectMake(normalSeparatorFrame.origin.x, normalSeparatorFrame.origin.y + offset, normalSeparatorFrame.size.width, normalSeparatorFrame.size.height); - } completion:^(__unused BOOL finished) { - completion(); - }]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - if (CGRectContainsPoint(_tableViewBackground.frame, point)) { - return [super hitTest:point withEvent:event]; - } - return nil; -} - -- (void)setBarInset:(CGFloat)barInset animated:(bool)animated { - if (ABS(barInset - self.barInset) > FLT_EPSILON) { - [super setBarInset:barInset animated:animated]; - - if (animated) { - [self layoutSubviews]; - } else { - [UIView animateWithDuration:0.3 animations:^{ - [self layoutSubviews]; - }]; - } - } -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m index 8c7ec74d19..e783367a7f 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m @@ -7,10 +7,6 @@ #import "TGSuggestionContext.h" #import "TGPhotoPaintStickersContext.h" -#import "TGModernConversationMentionsAssociatedPanel.h" -#import "TGModernConversationHashtagsAssociatedPanel.h" -#import "TGModernConversationAlphacodeAssociatedPanel.h" - @interface TGPhotoCaptionInputMixin () { TGObserverProxy *_keyboardWillChangeFrameProxy; @@ -167,165 +163,9 @@ #pragma mark - Input Panel Delegate - -//- (void)inputPanelMentionEntered:(TGMediaPickerCaptionInputPanel *)__unused inputTextPanel mention:(NSString *)mention startOfLine:(bool)__unused startOfLine -//{ -// if (mention == nil) -// { -// if ([[inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMentionsAssociatedPanel class]]) -// [inputTextPanel setAssociatedPanel:nil animated:true]; -// } -// else -// { -// TGModernConversationMentionsAssociatedPanel *panel = nil; -// if ([[inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMentionsAssociatedPanel class]]) -// panel = (TGModernConversationMentionsAssociatedPanel *)[inputTextPanel associatedPanel]; -// else -// { -// panel = [[TGModernConversationMentionsAssociatedPanel alloc] initWithStyle:TGModernConversationAssociatedInputPanelDarkStyle]; -// -// __weak TGPhotoCaptionInputMixin *weakSelf = self; -// panel.userSelected = ^(TGUser *user) -// { -// __strong TGPhotoCaptionInputMixin *strongSelf = weakSelf; -// if (strongSelf != nil) -// { -// if ([[strongSelf->_inputPanel associatedPanel] isKindOfClass:[TGModernConversationMentionsAssociatedPanel class]]) -// [strongSelf->_inputPanel setAssociatedPanel:nil animated:false]; -// -// if (user.userName.length == 0) { -// [strongSelf->_inputPanel replaceMention:[[NSString alloc] initWithFormat:@"%@", user.displayFirstName] username:false userId:user.uid]; -// } else { -// [strongSelf->_inputPanel replaceMention:[[NSString alloc] initWithFormat:@"%@", user.userName] username:true userId:user.uid]; -// } -// } -// }; -// } -// -// SSignal *userListSignal = nil; -// if (self.suggestionContext.userListSignal != nil) -// userListSignal = self.suggestionContext.userListSignal(mention); -// -// [panel setUserListSignal:userListSignal]; -// -// [inputTextPanel setAssociatedPanel:panel animated:true]; -// } -//} -// -//- (void)inputPanelHashtagEntered:(TGMediaPickerCaptionInputPanel *)inputTextPanel hashtag:(NSString *)hashtag -//{ -// if (hashtag == nil) -// { -// if ([[inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationHashtagsAssociatedPanel class]]) -// [inputTextPanel setAssociatedPanel:nil animated:true]; -// } -// else -// { -// TGModernConversationHashtagsAssociatedPanel *panel = nil; -// if ([[inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationHashtagsAssociatedPanel class]]) -// panel = (TGModernConversationHashtagsAssociatedPanel *)[inputTextPanel associatedPanel]; -// else -// { -// panel = [[TGModernConversationHashtagsAssociatedPanel alloc] initWithStyle:TGModernConversationAssociatedInputPanelDarkStyle]; -// -// __weak TGPhotoCaptionInputMixin *weakSelf = self; -// panel.hashtagSelected = ^(NSString *hashtag) -// { -// __strong TGPhotoCaptionInputMixin *strongSelf = weakSelf; -// if (strongSelf != nil) -// { -// if ([[strongSelf->_inputPanel associatedPanel] isKindOfClass:[TGModernConversationHashtagsAssociatedPanel class]]) -// [strongSelf->_inputPanel setAssociatedPanel:nil animated:false]; -// -// [strongSelf->_inputPanel replaceHashtag:hashtag]; -// } -// }; -// [inputTextPanel setAssociatedPanel:panel animated:true]; -// } -// -// SSignal *hashtagListSignal = nil; -// if (self.suggestionContext.hashtagListSignal != nil) -// hashtagListSignal = self.suggestionContext.hashtagListSignal(hashtag); -// -// [panel setHashtagListSignal:hashtagListSignal]; -// } -//} -// -//- (void)inputPanelAlphacodeEntered:(TGMediaPickerCaptionInputPanel *)inputTextPanel alphacode:(NSString *)alphacode -//{ -// if (alphacode == nil) -// { -// if ([[inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationAlphacodeAssociatedPanel class]]) -// [inputTextPanel setAssociatedPanel:nil animated:true]; -// } -// else -// { -// TGModernConversationAlphacodeAssociatedPanel *panel = nil; -// if ([[inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationAlphacodeAssociatedPanel class]]) -// panel = ((TGModernConversationAlphacodeAssociatedPanel *)[inputTextPanel associatedPanel]); -// else -// { -// panel = [[TGModernConversationAlphacodeAssociatedPanel alloc] initWithStyle:TGModernConversationAssociatedInputPanelDarkStyle]; -// __weak TGPhotoCaptionInputMixin *weakSelf = self; -// panel.alphacodeSelected = ^(TGAlphacodeEntry *entry) -// { -// __strong TGPhotoCaptionInputMixin *strongSelf = weakSelf; -// if (strongSelf != nil) -// { -// if ([[strongSelf->_inputPanel associatedPanel] isKindOfClass:[TGModernConversationAlphacodeAssociatedPanel class]]) -// { -// [strongSelf->_inputPanel setAssociatedPanel:nil animated:false]; -// } -// -// NSString *codeText = entry.emoji; -// -// [strongSelf appendAlphacode:[codeText stringByAppendingString:@" "]]; -// } -// }; -// [inputTextPanel setAssociatedPanel:panel animated:true]; -// } -// -// SSignal *alphacodeListSignal = nil; -// if (self.suggestionContext.alphacodeSignal != nil) -// alphacodeListSignal = self.suggestionContext.alphacodeSignal(alphacode, inputTextPanel.inputField.textInputMode.primaryLanguage); -// -// [panel setAlphacodeListSignal:alphacodeListSignal]; -// } -//} -// -//- (void)appendAlphacode:(NSString *)alphacode -//{ -// NSString *currentText = [_inputPanel inputField].text; -// NSRange selectRange = NSMakeRange(0, 0); -// -// if (currentText.length == 0) -// currentText = alphacode; -// else -// { -// NSInteger caretIndex = [_inputPanel textCaretPosition]; -// -// for (NSInteger i = caretIndex - 1; i >= 0; i--) -// { -// if ([currentText characterAtIndex:i] == ':') { -// currentText = [currentText stringByReplacingCharactersInRange:NSMakeRange(i, caretIndex - i) withString:alphacode]; -// selectRange = NSMakeRange(i + alphacode.length, 0); -// break; -// } -// } -// } -// -// [[_inputPanel inputField] setAttributedText:[[NSAttributedString alloc] initWithString:currentText] animated:false]; -// [[_inputPanel inputField] selectRange:selectRange force:true]; -// -// [_inputPanel inputField].internalTextView.enableFirstResponder = true; -//} -// - (void)setContentAreaHeight:(CGFloat)contentAreaHeight { _contentAreaHeight = contentAreaHeight; - - CGFloat finalHeight = _contentAreaHeight - _keyboardHeight; -// [_inputPanel setContentAreaHeight:finalHeight]; } - (UIView *)_parentView diff --git a/submodules/LegacyMediaPickerUI/BUILD b/submodules/LegacyMediaPickerUI/BUILD index 18c9a09829..0da1b9d1bc 100644 --- a/submodules/LegacyMediaPickerUI/BUILD +++ b/submodules/LegacyMediaPickerUI/BUILD @@ -28,6 +28,7 @@ swift_library( "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/StickerResources:StickerResources", "//submodules/TextFormat:TextFormat", + "//submodules/AttachmentUI:AttachmentUI", ], visibility = [ "//visibility:public", diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index f0ba3f1667..dbed3e0715 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -14,6 +14,7 @@ import MimeTypes import LocalMediaResources import LegacyUI import TextFormat +import AttachmentUI public func guessMimeTypeByFileExtension(_ ext: String) -> String { return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary" @@ -54,8 +55,6 @@ public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, co done?(time) } } - controller.dismissalBlock = { - } controller.selectionLimitExceeded = { presentSelectionLimitExceeded() } @@ -69,6 +68,52 @@ public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, co } } +public class LegacyAssetPickerContext: AttachmentMediaPickerContext { + private weak var controller: TGMediaAssetsController? + + public var selectionCount: Signal { + return Signal { [weak self] subscriber in + let disposable = self?.controller?.selectionContext.selectionChangedSignal().start(next: { [weak self] value in + subscriber.putNext(Int(self?.controller?.selectionContext.count() ?? 0)) + }, error: { _ in }, completed: { }) + return ActionDisposable { + disposable?.dispose() + } + } + } + + public var caption: Signal { + return Signal { [weak self] subscriber in + let disposable = self?.controller?.editingContext.forcedCaption().start(next: { caption in + if let caption = caption as? NSAttributedString { + subscriber.putNext(caption) + } else { + subscriber.putNext(nil) + } + }, error: { _ in }, completed: { }) + return ActionDisposable { + disposable?.dispose() + } + } + } + + public init(controller: TGMediaAssetsController) { + self.controller = controller + } + + public func setCaption(_ caption: NSAttributedString) { + self.controller?.editingContext.setForcedCaption(caption, skipUpdate: true) + } + + public func send(silently: Bool) { + self.controller?.send(silently) + } + + public func schedule() { + self.controller?.schedule() + } +} + public func legacyAssetPicker(context: AccountContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool, selectionLimit: Int) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> { let isSecretChat = (peer?.id.namespace._internalGetInt32Value() ?? 0) == Namespaces.Peer.SecretChat._internalGetInt32Value() diff --git a/submodules/LegacyUI/BUILD b/submodules/LegacyUI/BUILD index d398f76aed..990dc5d333 100644 --- a/submodules/LegacyUI/BUILD +++ b/submodules/LegacyUI/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/DeviceAccess:DeviceAccess", "//submodules/LegacyComponents:LegacyComponents", "//submodules/StickerResources:StickerResources", + "//submodules/AttachmentUI:AttachmentUI", ], visibility = [ "//visibility:public", diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 9b389c5d5c..63660f66c4 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -4,6 +4,7 @@ import Display import SwiftSignalKit import LegacyComponents import TelegramPresentationData +import AttachmentUI public enum LegacyControllerPresentation { case custom @@ -375,7 +376,7 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext { } } -open class LegacyController: ViewController, PresentableController { +open class LegacyController: ViewController, PresentableController, AttachmentContainable { public private(set) var legacyController: UIViewController! private let presentation: LegacyControllerPresentation @@ -390,6 +391,8 @@ open class LegacyController: ViewController, PresentableController { fileprivate var validLayout: ContainerViewLayout? + public var parentInsets: UIEdgeInsets = UIEdgeInsets() + public var controllerLoaded: (() -> Void)? public var presentationCompleted: (() -> Void)? @@ -402,6 +405,8 @@ open class LegacyController: ViewController, PresentableController { public var disposables = DisposableSet() + open var requestAttachmentMenuExpansion: () -> Void = {} + public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) { self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber)) self.presentation = presentation @@ -454,7 +459,7 @@ open class LegacyController: ViewController, PresentableController { if self.controllerNode.controllerView == nil { if self.controllerNode.frame.width == 0.0, let layout = self.validLayout { - self.controllerNode.frame = CGRect(origin: CGPoint(), size: layout.size) + self.controllerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width - self.parentInsets.left - self.parentInsets.right, height: layout.size.height)) } self.controllerNode.controllerView = self.legacyController.view @@ -558,10 +563,12 @@ open class LegacyController: ViewController, PresentableController { orientation = .landscapeRight } - legacyTelegramController.intrinsicSize = layout.size + let size = CGSize(width: layout.size.width - layout.intrinsicInsets.left - layout.intrinsicInsets.right, height: layout.size.height) + + legacyTelegramController.intrinsicSize = size legacyTelegramController._updateInset(for: orientation, force: false, notify: true) if self.enableContainerLayoutUpdates { - legacyTelegramController.layoutController(for: layout.size, duration: duration) + legacyTelegramController.layoutController(for: size, duration: duration) } } let updatedSizeClass: UIUserInterfaceSizeClass diff --git a/submodules/LegacyUI/Sources/LegacyControllerNode.swift b/submodules/LegacyUI/Sources/LegacyControllerNode.swift index 7017078654..e5a7bb2a2c 100644 --- a/submodules/LegacyUI/Sources/LegacyControllerNode.swift +++ b/submodules/LegacyUI/Sources/LegacyControllerNode.swift @@ -8,8 +8,9 @@ final class LegacyControllerNode: ASDisplayNode { var controllerView: UIView? { didSet { - if let controllerView = self.controllerView, let containerLayout = self.containerLayout { - controllerView.frame = CGRect(origin: CGPoint(), size: containerLayout.size) + if let controllerView = self.controllerView, let layout = self.containerLayout { + let size = CGSize(width: layout.size.width - layout.intrinsicInsets.left - layout.intrinsicInsets.right, height: layout.size.height) + controllerView.frame = CGRect(origin: CGPoint(x: layout.intrinsicInsets.left, y: 0.0), size: size) } } } @@ -21,13 +22,14 @@ final class LegacyControllerNode: ASDisplayNode { return UITracingLayerView() }) - self.clipsToBounds = true +// self.clipsToBounds = true } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.containerLayout = layout + let size = CGSize(width: layout.size.width - layout.intrinsicInsets.left - layout.intrinsicInsets.right, height: layout.size.height) if let controllerView = self.controllerView { - controllerView.frame = CGRect(origin: CGPoint(), size: layout.size) + controllerView.frame = CGRect(origin: CGPoint(x: layout.intrinsicInsets.left, y: 0.0), size: size) } } diff --git a/submodules/LocationUI/BUILD b/submodules/LocationUI/BUILD index ace157cd67..eb788ecccd 100644 --- a/submodules/LocationUI/BUILD +++ b/submodules/LocationUI/BUILD @@ -42,6 +42,7 @@ swift_library( "//submodules/TelegramNotices:TelegramNotices", "//submodules/TooltipUI:TooltipUI", "//submodules/UndoUI:UndoUI", + "//submodules/AttachmentUI:AttachmentUI", ], visibility = [ "//visibility:public", diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 6b77ceb5e6..e4f1302b6c 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -11,6 +11,7 @@ import AppBundle import CoreLocation import PresentationDataUtils import DeviceAccess +import AttachmentUI public enum LocationPickerMode { case share(peer: Peer?, selfPeer: Peer?, hasLiveLocation: Bool) @@ -51,7 +52,7 @@ class LocationPickerInteraction { } } -public final class LocationPickerController: ViewController { +public final class LocationPickerController: ViewController, AttachmentContainable { private var controllerNode: LocationPickerControllerNode { return self.displayNode as! LocationPickerControllerNode } @@ -69,6 +70,8 @@ public final class LocationPickerController: ViewController { private var permissionDisposable: Disposable? private var interaction: LocationPickerInteraction? + + public var requestAttachmentMenuExpansion: () -> Void = {} public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: LocationPickerMode, completion: @escaping (TelegramMediaMap, String?) -> Void) { self.context = context @@ -326,6 +329,8 @@ public final class LocationPickerController: ViewController { } @objc private func searchPressed() { + self.requestAttachmentMenuExpansion() + self.interaction?.openSearch() } } diff --git a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift index 7f976edcad..f6c79e6238 100644 --- a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift +++ b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift @@ -266,7 +266,7 @@ final class LocationSearchContainerNode: ASDisplayNode { transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.intrinsicInsets.right), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) let padding: CGFloat = 16.0 let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) diff --git a/submodules/Pasteboard/BUILD b/submodules/Pasteboard/BUILD new file mode 100644 index 0000000000..ff4364f8aa --- /dev/null +++ b/submodules/Pasteboard/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "Pasteboard", + module_name = "Pasteboard", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display:Display", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TextFormat:TextFormat", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/Pasteboard.swift b/submodules/Pasteboard/Sources/Pasteboard.swift similarity index 92% rename from submodules/TelegramUI/Sources/Pasteboard.swift rename to submodules/Pasteboard/Sources/Pasteboard.swift index ee9c88ccc9..203d275a49 100644 --- a/submodules/TelegramUI/Sources/Pasteboard.swift +++ b/submodules/Pasteboard/Sources/Pasteboard.swift @@ -23,13 +23,6 @@ private func rtfStringWithAppliedEntities(_ text: String, entities: [MessageText } } -func chatInputStateStringFromRTF(_ data: Data, type: NSAttributedString.DocumentType) -> NSAttributedString? { - if let attributedString = try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: type], documentAttributes: nil) { - return chatInputStateString(attributedString: attributedString) - } - return nil -} - private func chatInputStateString(attributedString: NSAttributedString) -> NSAttributedString? { let string = NSMutableAttributedString(string: attributedString.string) attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: [], using: { attributes, range, _ in @@ -62,7 +55,14 @@ private func chatInputStateString(attributedString: NSAttributedString) -> NSAtt return string } -func storeMessageTextInPasteboard(_ text: String, entities: [MessageTextEntity]?) { +public func chatInputStateStringFromRTF(_ data: Data, type: NSAttributedString.DocumentType) -> NSAttributedString? { + if let attributedString = try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: type], documentAttributes: nil) { + return chatInputStateString(attributedString: attributedString) + } + return nil +} + +public func storeMessageTextInPasteboard(_ text: String, entities: [MessageTextEntity]?) { var items: [String: Any] = [:] items[kUTTypeUTF8PlainText as String] = text @@ -72,14 +72,14 @@ func storeMessageTextInPasteboard(_ text: String, entities: [MessageTextEntity]? UIPasteboard.general.items = [items] } -func storeAttributedTextInPasteboard(_ text: NSAttributedString) { +public func storeAttributedTextInPasteboard(_ text: NSAttributedString) { if let inputText = chatInputStateString(attributedString: text) { let entities = generateChatInputTextEntities(inputText) storeMessageTextInPasteboard(inputText.string, entities: entities) } } -func storeInputTextInPasteboard(_ text: NSAttributedString) { +public func storeInputTextInPasteboard(_ text: NSAttributedString) { let entities = generateChatInputTextEntities(text) storeMessageTextInPasteboard(text.string, entities: entities) } diff --git a/submodules/RMIntro/PublicHeaders/RMIntro/RMIntroViewController.h b/submodules/RMIntro/PublicHeaders/RMIntro/RMIntroViewController.h index 9db0510b81..a279aae01e 100644 --- a/submodules/RMIntro/PublicHeaders/RMIntro/RMIntroViewController.h +++ b/submodules/RMIntro/PublicHeaders/RMIntro/RMIntroViewController.h @@ -35,7 +35,7 @@ @interface RMIntroViewController : UIViewController { - EAGLContext *context; + EAGLContext *_context; GLKView *_glkView; diff --git a/submodules/RMIntro/Sources/core/animations.c b/submodules/RMIntro/Sources/core/animations.c index 135b896bda..6dc53f1ccc 100644 --- a/submodules/RMIntro/Sources/core/animations.c +++ b/submodules/RMIntro/Sources/core/animations.c @@ -286,7 +286,7 @@ float t_local(float start_value, float end_value, float start_time, float durati -static int ribbonLength = 86.5; +static int ribbonLength = 86; static int starsFar=500; static float scroll_offset; diff --git a/submodules/RMIntro/Sources/platform/ios/RMIntroViewController.m b/submodules/RMIntro/Sources/platform/ios/RMIntroViewController.m index e45854cf61..260804e57c 100644 --- a/submodules/RMIntro/Sources/platform/ios/RMIntroViewController.m +++ b/submodules/RMIntro/Sources/platform/ios/RMIntroViewController.m @@ -29,14 +29,6 @@ typedef enum { iPadPro = 6 } DeviceScreen; -static void TGDispatchOnMainThread(dispatch_block_t block) { - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_async(dispatch_get_main_queue(), block); - } -} - @interface UIScrollView (CurrentPage) - (int)currentPage; - (void)setPage:(NSInteger)page; @@ -242,8 +234,8 @@ static void TGDispatchOnMainThread(dispatch_block_t block) { if (/*[[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground*/true && !_isOpenGLLoaded) { - context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - if (!context) + _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + if (!_context) NSLog(@"Failed to create ES context"); bool isIpad = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad); @@ -256,7 +248,7 @@ static void TGDispatchOnMainThread(dispatch_block_t block) { if (isIpad) height += 138 / 2; - _glkView = [[GLKView alloc] initWithFrame:CGRectMake(self.view.bounds.size.width / 2 - size / 2, height, size, size) context:context]; + _glkView = [[GLKView alloc] initWithFrame:CGRectMake(self.view.bounds.size.width / 2 - size / 2, height, size, size) context:_context]; _glkView.backgroundColor = _backgroundColor; _glkView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; _glkView.drawableDepthFormat = GLKViewDrawableDepthFormat24; @@ -284,7 +276,7 @@ static void TGDispatchOnMainThread(dispatch_block_t block) { [EAGLContext setCurrentContext:nil]; _glkView.context = nil; - context = nil; + _context = nil; [_glkView removeFromSuperview]; _glkView = nil; _isOpenGLLoaded = false; diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index fb22f22727..1c377fc12b 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -257,6 +257,11 @@ swift_library( "//submodules/SoftwareVideo:SoftwareVideo", "//submodules/ManagedFile:ManagedFile", "//submodules/AttachmentUI:AttachmentUI", + "//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode", + "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + "//submodules/Pasteboard:Pasteboard", + "//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI", + "//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Sources/AccessoryPanelNode.swift b/submodules/TelegramUI/Sources/AccessoryPanelNode.swift index 48a486cad2..0a2d8c54d8 100644 --- a/submodules/TelegramUI/Sources/AccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/AccessoryPanelNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramPresentationData +import ChatPresentationInterfaceState class AccessoryPanelNode: ASDisplayNode { var originalFrameBeforeDismissed: CGRect? diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index a11e45e455..d5ce069db7 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -11,6 +11,7 @@ import ItemListUI import PresentationDataUtils import AccountContext import ItemListPeerActionItem +import AttachmentUI private final class AttachmentFileControllerArguments { let openGallery: () -> Void @@ -103,12 +104,16 @@ private func attachmentFileControllerEntries(presentationData: PresentationData) entries.append(.selectFromGallery(presentationData.theme, presentationData.strings.Attachment_SelectFromGallery)) entries.append(.selectFromFiles(presentationData.theme, presentationData.strings.Attachment_SelectFromFiles)) - entries.append(.recentHeader(presentationData.theme, "RECENTLY SENT FILES".uppercased())) +// entries.append(.recentHeader(presentationData.theme, "RECENTLY SENT FILES".uppercased())) return entries } -public func attachmentFileController(context: AccountContext, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void) -> ViewController { +private class AttachmentFileControllerImpl: ItemListController, AttachmentContainable { + public var requestAttachmentMenuExpansion: () -> Void = {} +} + +public func attachmentFileController(context: AccountContext, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void) -> AttachmentContainable { let actionsDisposable = DisposableSet() var dismissImpl: (() -> Void)? @@ -131,7 +136,7 @@ public func attachmentFileController(context: AccountContext, presentGallery: @e actionsDisposable.dispose() } - let controller = ItemListController(context: context, state: signal) + let controller = AttachmentFileControllerImpl(context: context, state: signal) dismissImpl = { [weak controller] in controller?.dismiss(animated: true) } diff --git a/submodules/TelegramUI/Sources/AudioWaveformNode.swift b/submodules/TelegramUI/Sources/AudioWaveformNode.swift index 174c957ad0..a42ec30ee8 100644 --- a/submodules/TelegramUI/Sources/AudioWaveformNode.swift +++ b/submodules/TelegramUI/Sources/AudioWaveformNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit +import ChatPresentationInterfaceState private final class AudioWaveformNodeParameters: NSObject { let waveform: AudioWaveform? diff --git a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift index e887368037..ddcaf3eb1e 100644 --- a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift @@ -6,6 +6,7 @@ import TelegramCore import Postbox import SwiftSignalKit import TelegramPresentationData +import ChatPresentationInterfaceState final class ChatBotStartInputPanelNode: ChatInputPanelNode { private let button: HighlightableButtonNode diff --git a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift index ac5d2f049a..ee3274981a 100644 --- a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift @@ -7,6 +7,7 @@ import TelegramCore import SwiftSignalKit import TelegramPresentationData import AccountContext +import ChatPresentationInterfaceState private final class ChatButtonKeyboardInputButtonNode: ASButtonNode { var button: ReplyMarkupButton? diff --git a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift index b21b3b2adc..962f665435 100644 --- a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -10,6 +10,7 @@ import AlertUI import PresentationDataUtils import PeerInfoUI import UndoUI +import ChatPresentationInterfaceState private enum SubscriberAction: Equatable { case join diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4d9c24ed39..375de93813 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -69,22 +69,16 @@ import ReactionSelectionNode import LottieMeshSwift import ReactionListContextMenuContent import AttachmentUI +import AttachmentTextInputPanelNode +import ChatPresentationInterfaceState +import Pasteboard +import ChatSendMessageActionUI +import ChatTextLinkEditUI #if DEBUG import os.signpost #endif -extension ChatLocation { - var peerId: PeerId { - switch self { - case let .peer(peerId): - return peerId - case let .replyThread(replyThreadMessage): - return replyThreadMessage.messageId.peerId - } - } -} - public enum ChatControllerPeekActions { case standard case remove(() -> Void) @@ -177,11 +171,6 @@ private struct ScrolledToMessageId: Equatable { var allowedReplacementDirection: AllowedReplacementDirections } -enum ChatLoadingMessageSubject { - case generic - case pinnedMessage -} - #if DEBUG private final class SignpostData { @available(iOSApplicationExtension 12.0, iOS 12.0, *) @@ -3177,7 +3166,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) } else { - strongSelf.presentMediaPicker(fileMode: false, editingMedia: true, present: { [weak self] c in + strongSelf.presentMediaPicker(fileMode: false, editingMedia: true, present: { [weak self] c, _ in self?.effectiveNavigationController?.pushViewController(c) }, completion: { signals, _, _ in self?.interfaceInteraction?.setupEditMessage(messageId, { _ in }) @@ -5790,7 +5779,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } - strongSelf.presentAttachmentMenu(editMediaOptions: options, editMediaReference: originalMediaReference) + strongSelf.oldPresentAttachmentMenu(editMediaOptions: options, editMediaReference: originalMediaReference) }) } else { strongSelf.presentAttachmentMenu(editMediaOptions: nil, editMediaReference: nil) @@ -10215,8 +10204,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - let inputPanelNode = PeerSelectionTextInputPanelNode(presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { _ in }) - inputPanelNode.context = self.context + let inputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { _ in }) inputPanelNode.interfaceInteraction = interfaceInteraction inputPanelNode.effectivePresentationInterfaceState = { return presentationInterfaceState @@ -10251,18 +10239,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let storeEditedPhotos = false - let initialCaption: String = "" + let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText - presentedLegacyCamera(context: self.context, peer: peer, chatLocation: self.chatLocation, cameraView: nil, menuController: nil, parentController: self, editingMedia: false, saveCapturedPhotos: storeEditedPhotos, mediaGrouping: true, initialCaption: initialCaption, hasSchedule: self.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + presentedLegacyCamera(context: self.context, peer: peer, chatLocation: self.chatLocation, cameraView: nil, menuController: nil, parentController: self, editingMedia: false, saveCapturedPhotos: storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: self.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in if let strongSelf = self { // if editMediaOptions != nil { // strongSelf.editMessageMediaWithLegacySignals(signals!) // } else { strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) // } -// if !inputText.string.isEmpty { -// strongSelf.clearInputText() -// } + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } } }, recognizedQRCode: { [weak self] code in if let strongSelf = self { @@ -10311,21 +10299,44 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let currentLocationController = Atomic(value: nil) - let attachmentController = AttachmentController(context: self.context) + var canSendPolls = true + if let _ = peer as? TelegramUser { + canSendPolls = false + } else if let channel = peer as? TelegramChannel { + if channel.hasBannedPermission(.banSendPolls) != nil { + canSendPolls = false + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendPolls) { + canSendPolls = false + } + } + + var buttons: [AttachmentButtonType] = [.camera, .gallery, .file, .location, .contact] + if canSendPolls { + buttons.append(.poll) + } + + let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText + + let attachmentController = AttachmentController(context: self.context, buttons: buttons) attachmentController.requestController = { [weak self, weak attachmentController] type, completion in guard let strongSelf = self else { return } switch type { case .camera: - completion(nil) + completion(nil, nil) attachmentController?.dismiss(animated: true) strongSelf.openCamera() strongSelf.controllerNavigationDisposable.set(nil) case .gallery: - strongSelf.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, present: { c in - completion(c) + strongSelf.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, present: { controller, mediaPickerContext in + completion(controller, mediaPickerContext) }, completion: { [weak self] signals, silentPosting, scheduleTime in + if !inputText.string.isEmpty { + self?.clearInputText() + } self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) }) strongSelf.controllerNavigationDisposable.set(nil) @@ -10337,13 +10348,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G attachmentController?.dismiss(animated: true) self?.presentICloudFileGallery() }) - completion(controller) + completion(controller, nil) strongSelf.controllerNavigationDisposable.set(nil) case .location: strongSelf.controllerNavigationDisposable.set(nil) let existingController = currentLocationController.with { $0 } if let controller = existingController { - completion(controller) + completion(controller, nil) return } let selfPeerId: PeerId @@ -10377,14 +10388,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, nil) strongSelf.sendMessages([message]) }) - completion(controller) + completion(controller, nil) let _ = currentLocationController.swap(controller) }) case .contact: let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true)) contactsController.navigationPresentation = .modal - completion(contactsController) + completion(contactsController, nil) strongSelf.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).start(next: { [weak self] peers in if let strongSelf = self, let (peers, _) = peers { @@ -10510,12 +10521,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) case .poll: let controller = strongSelf.configurePollCreation() - completion(controller) + completion(controller, nil) strongSelf.controllerNavigationDisposable.set(nil) case .app: - let controller = GameController(context: strongSelf.context, url: "https://wallet.ton.org", message: nil) - completion(controller) - strongSelf.controllerNavigationDisposable.set(nil) + return } } self.present(attachmentController, in: .window(.root)) @@ -10619,7 +10628,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: { - self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, present: { [weak self] c in + self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, present: { [weak self] c, _ in self?.effectiveNavigationController?.pushViewController(c) }, completion: { signals, silentPosting, scheduleTime in if !inputText.string.isEmpty { @@ -10805,7 +10814,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func presentFileGallery(editingMessage: Bool = false) { - self.presentMediaPicker(fileMode: true, editingMedia: editingMessage, present: { [weak self] c in + self.presentMediaPicker(fileMode: true, editingMedia: editingMessage, present: { [weak self] c, _ in self?.effectiveNavigationController?.pushViewController(c) }, completion: { [weak self] signals, silentPosting, scheduleTime in if editingMessage { @@ -10921,7 +10930,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(fileMode: Bool, editingMedia: Bool, present: @escaping (ViewController) -> Void, completion: @escaping ([Any], Bool, Int32) -> Void) { + private func presentMediaPicker(fileMode: Bool, editingMedia: Bool, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext) -> Void, completion: @escaping ([Any], Bool, Int32) -> Void) { let postbox = self.context.account.postbox let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Signal<(GeneratedMediaStoreSettings, SearchBotsConfiguration), NoError> in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) @@ -10945,7 +10954,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).start(next: { generator in if let strongSelf = self { - let legacyController = LegacyController(presentation: .navigation, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) + let legacyController = LegacyController(presentation: fileMode ? .navigation : .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) legacyController.navigationPresentation = .modal legacyController.statusBar.statusBarStyle = strongSelf.presentationData.theme.rootController.statusBarStyle.style legacyController.controllerLoaded = { [weak legacyController] in @@ -10953,9 +10962,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G legacyController?.view.disablesInteractiveModalDismiss = true } let controller = generator(legacyController.context) + legacyController.bind(controller: controller) legacyController.deferScreenEdgeGestures = [.top] - + configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak self, weak legacyController] in if let strongSelf = self { let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: searchBotsConfiguration, mode: .media(completion: { results, selectionState, editingState, silentPosting in @@ -11040,17 +11050,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controller.descriptionGenerator = legacyAssetPickerItemGenerator() controller.completionBlock = { [weak legacyController] signals, silentPosting, scheduleTime in if let legacyController = legacyController { - legacyController.dismiss() + legacyController.dismiss(animated: true) completion(signals!, silentPosting, scheduleTime) } } controller.dismissalBlock = { [weak legacyController] in if let legacyController = legacyController { - legacyController.dismiss() + legacyController.dismiss(animated: true) } } strongSelf.chatDisplayNode.dismissInput() - present(legacyController) + present(legacyController, LegacyAssetPickerContext(controller: controller)) } }) }) @@ -11568,7 +11578,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(tooltipScreen, in: .current) } - private func configurePollCreation(isQuiz: Bool? = nil) -> ViewController? { + private func configurePollCreation(isQuiz: Bool? = nil) -> AttachmentContainable? { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return nil } diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index 781b6c80bf..acff7b34b1 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -12,6 +12,7 @@ import ContextUI import ChatInterfaceState import UndoUI import TelegramPresentationData +import ChatPresentationInterfaceState struct ChatInterfaceHighlightedState: Equatable { let messageStableId: UInt32 diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 9bdd752b63..35cf0b67f8 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -17,6 +17,7 @@ import ConfettiEffect import WallpaperBackgroundNode import GridMessageSelectionNode import SparseItemGrid +import ChatPresentationInterfaceState final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode diff --git a/submodules/TelegramUI/Sources/ChatEditInterfaceMessageState.swift b/submodules/TelegramUI/Sources/ChatEditInterfaceMessageState.swift deleted file mode 100644 index c272bfb5eb..0000000000 --- a/submodules/TelegramUI/Sources/ChatEditInterfaceMessageState.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation -import UIKit -import Postbox -import TelegramCore - -enum ChatEditInterfaceMessageStateContent: Equatable { - case plaintext - case media(mediaOptions: MessageMediaEditingOptions) -} - -final class ChatEditInterfaceMessageState: Equatable { - let content: ChatEditInterfaceMessageStateContent - let mediaReference: AnyMediaReference? - - init(content: ChatEditInterfaceMessageStateContent, mediaReference: AnyMediaReference?) { - self.content = content - self.mediaReference = mediaReference - } - - static func ==(lhs: ChatEditInterfaceMessageState, rhs: ChatEditInterfaceMessageState) -> Bool { - if lhs.content != rhs.content { - return false - } - if let lhsMedia = lhs.mediaReference, let rhsMedia = rhs.mediaReference { - if !lhsMedia.media.isEqual(to: rhsMedia.media) { - return false - } - } else if (lhs.mediaReference != nil) != (rhs.mediaReference != nil) { - return false - } - return true - } -} diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 978ce96ad3..bdc54d1e79 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -10,6 +10,7 @@ import AppBundle import LocalizedPeerData import TelegramStringFormatting import AccountContext +import ChatPresentationInterfaceState private protocol ChatEmptyNodeContent { func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize diff --git a/submodules/TelegramUI/Sources/ChatFeedNavigationInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatFeedNavigationInputPanelNode.swift index 1a936b7428..0a65d48122 100644 --- a/submodules/TelegramUI/Sources/ChatFeedNavigationInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatFeedNavigationInputPanelNode.swift @@ -6,6 +6,7 @@ import TelegramCore import Postbox import SwiftSignalKit import TelegramPresentationData +import ChatPresentationInterfaceState final class ChatFeedNavigationInputPanelNode: ChatInputPanelNode { private let button: HighlightableButtonNode diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index d10059d0f3..d9cd232219 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -19,6 +19,7 @@ import ChatInterfaceState import ChatListUI import ComponentFlow import ReactionSelectionNode +import ChatPresentationInterfaceState extension ChatReplyThreadMessage { var effectiveTopId: MessageId { diff --git a/submodules/TelegramUI/Sources/ChatHistoryNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNode.swift index 66fe663f10..d30323ee12 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNode.swift @@ -4,11 +4,7 @@ import AsyncDisplayKit import Postbox import SwiftSignalKit import Display - -public enum ChatHistoryNodeHistoryState: Equatable { - case loading - case loaded(isEmpty: Bool) -} +import ChatPresentationInterfaceState public enum ChatHistoryNodeLoadState: Equatable { public enum EmptyType: Equatable { diff --git a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift index de3fd66a49..e6d6af2b09 100644 --- a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import Postbox import TelegramCore import TelegramPresentationData +import ChatPresentationInterfaceState private enum ChatInfoTitleButton { case search diff --git a/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift index 9e0a727f51..9cf93a9c9c 100644 --- a/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift @@ -6,6 +6,7 @@ import TelegramCore import TelegramPresentationData import TelegramUIPreferences import AccountContext +import ChatPresentationInterfaceState enum ChatInputContextPanelPlacement { case overPanels diff --git a/submodules/TelegramUI/Sources/ChatInputNode.swift b/submodules/TelegramUI/Sources/ChatInputNode.swift index d87af58c89..0b955aecc1 100644 --- a/submodules/TelegramUI/Sources/ChatInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatInputNode.swift @@ -3,6 +3,7 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit +import ChatPresentationInterfaceState class ChatInputNode: ASDisplayNode { var interfaceInteraction: ChatPanelInterfaceInteraction? diff --git a/submodules/TelegramUI/Sources/ChatInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatInputPanelNode.swift index 1469c04af2..83e77b2e6b 100644 --- a/submodules/TelegramUI/Sources/ChatInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInputPanelNode.swift @@ -5,6 +5,7 @@ import Display import Postbox import TelegramCore import AccountContext +import ChatPresentationInterfaceState class ChatInputPanelNode: ASDisplayNode { var context: AccountContext? diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift index fb50e69ca7..ddc5922a60 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import TelegramCore import AccountContext +import ChatPresentationInterfaceState private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult) -> (Int, Bool) { switch result { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index fdc6118485..9d51e3ff66 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -6,6 +6,7 @@ import Display import AccountContext import Emoji import ChatInterfaceState +import ChatPresentationInterfaceState struct PossibleContextQueryTypes: OptionSet { var rawValue: Int32 diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index 0960ba0fb8..d3c256508e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit import TelegramCore import Postbox import AccountContext +import ChatPresentationInterfaceState func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentNode: ChatInputNode?, interfaceInteraction: ChatPanelInterfaceInteraction?, inputMediaNode: ChatMediaInputNode?, controllerInteraction: ChatControllerInteraction, inputPanelNode: ChatInputPanelNode?) -> ChatInputNode? { if !(inputPanelNode is ChatTextInputPanelNode) { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index c8b91cf59b..54f4784e65 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -3,6 +3,7 @@ import UIKit import AsyncDisplayKit import TelegramCore import AccountContext +import ChatPresentationInterfaceState func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AccessoryPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? { if let _ = chatPresentationInterfaceState.interfaceState.selectionState { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 8be4753e49..59eef3fa7e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -26,6 +26,8 @@ import TelegramNotices import ReactionListContextMenuContent import TelegramUIPreferences import Translate +import ChatPresentationInterfaceState +import Pasteboard private struct MessageContextMenuData { let starStatus: Bool? @@ -290,17 +292,6 @@ enum ChatMessageContextMenuAction { case sheet(ChatMessageContextMenuSheetAction) } -struct MessageMediaEditingOptions: OptionSet { - var rawValue: Int32 - - init(rawValue: Int32) { - self.rawValue = rawValue - } - - static let imageOrVideo = MessageMediaEditingOptions(rawValue: 1 << 0) - static let file = MessageMediaEditingOptions(rawValue: 1 << 1) -} - func messageMediaEditingOptions(message: Message) -> MessageMediaEditingOptions { if message.id.peerId.namespace == Namespaces.Peer.SecretChat { return [] diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index d1b2f3aad3..6a5ce76378 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -11,6 +11,7 @@ import Emoji import SearchPeerMembers import DeviceLocationManager import TelegramNotices +import ChatPresentationInterfaceState enum ChatContextQueryError { case generic diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 88032e307a..0e08faf9f9 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -3,6 +3,7 @@ import UIKit import AsyncDisplayKit import TelegramCore import AccountContext +import ChatPresentationInterfaceState 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 { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index f097b56ae6..3a40fb7a22 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -5,6 +5,7 @@ import Postbox import TelegramCore import TelegramPresentationData import AccountContext +import ChatPresentationInterfaceState enum ChatNavigationButtonAction: Equatable { case openChatInfo(expandAvatar: Bool) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 30fcdd0d04..2059729571 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import TelegramCore import AccountContext +import ChatPresentationInterfaceState func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatTitleAccessoryPanelNode? { if case .overlay = chatPresentationInterfaceState.mode { diff --git a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift index 190f43aff5..10e6c6ffcd 100644 --- a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift @@ -10,6 +10,7 @@ import TelegramStringFormatting import TelegramNotices import AnimatedAvatarSetNode import AccountContext +import ChatPresentationInterfaceState private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode { private var theme: PresentationTheme? diff --git a/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift index 8b99eb83bd..2285d3eebe 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift @@ -8,6 +8,7 @@ import SwiftSignalKit import TelegramPresentationData import ContextUI import AccountContext +import ChatPresentationInterfaceState private func fixListScrolling(_ multiplexedNode: MultiplexedVideoNode) { let searchBarHeight: CGFloat = 56.0 diff --git a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift index 4149960057..2d4702aa9a 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift @@ -5,6 +5,7 @@ import SwiftSignalKit import Display import TelegramPresentationData import MergeLists +import ChatPresentationInterfaceState enum ChatMediaInputGridEntryStableId: Equatable, Hashable { case search diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 5524919cbb..addfbec8d9 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -18,6 +18,7 @@ import GalleryUI import OverlayStatusController import PresentationDataUtils import ChatInterfaceState +import ChatPresentationInterfaceState struct PeerSpecificPackData { let peer: Peer diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index b7533b2157..1278c85d0f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -26,6 +26,7 @@ import WallpaperBackgroundNode import LocalMediaResources import AppBundle import LottieMeshSwift +import ChatPresentationInterfaceState private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 6fb49a5451..5a5b806925 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -22,6 +22,7 @@ import AppBundle import Markdown import WallpaperBackgroundNode import SwiftSignalKit +import ChatPresentationInterfaceState enum InternalBubbleTapAction { case action(() -> Void) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 8d4d30b2c4..1fd6b23263 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -17,6 +17,7 @@ import CheckNode import MusicAlbumArtResources import AudioBlob import ContextUI +import ChatPresentationInterfaceState private struct FetchControls { let fetch: () -> Void diff --git a/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift index 8cc77a91bb..2b182d3853 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift @@ -8,6 +8,7 @@ import SwiftSignalKit import TelegramPresentationData import AccountContext import AppBundle +import ChatPresentationInterfaceState final class ChatMessageReportInputPanelNode: ChatInputPanelNode { private let reportButton: HighlightableButtonNode diff --git a/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift index 4d04335615..9ff0db5f13 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift @@ -8,6 +8,7 @@ import SwiftSignalKit import TelegramPresentationData import AccountContext import AppBundle +import ChatPresentationInterfaceState final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { private let deleteButton: HighlightableButtonNode diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 7677776378..d190cc399a 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -17,6 +17,7 @@ import ContextUI import RadialStatusNode import InvisibleInkDustNode import TextFormat +import ChatPresentationInterfaceState private enum PinnedMessageAnimation { case slideToTop diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index b05e92e7ac..309fc63e88 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -9,6 +9,7 @@ import TelegramBaseController import AccountContext import AlertUI import PresentationDataUtils +import ChatPresentationInterfaceState final class ChatRecentActionsController: TelegramBaseController { private var controllerNode: ChatRecentActionsControllerNode { diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index 37e9f2ff92..3ee367b69f 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -11,6 +11,7 @@ import AppBundle import ContextUI import AnimationUI import ManagedAnimationNode +import ChatPresentationInterfaceState extension AudioWaveformNode: CustomMediaPlayerScrubbingForegroundNode { diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index e616a208fb..428e779c1d 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -9,6 +9,7 @@ import LocalizedPeerData import TelegramStringFormatting import TextFormat import Markdown +import ChatPresentationInterfaceState private enum ChatReportPeerTitleButton: Equatable { case block diff --git a/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift index f7e0421690..3c99188165 100644 --- a/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift @@ -3,6 +3,7 @@ import UIKit import Display import AsyncDisplayKit import TelegramPresentationData +import ChatPresentationInterfaceState final class ChatRequestInProgressTitlePanelNode: ChatTitleAccessoryPanelNode { private let separatorNode: ASDisplayNode diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index ba31bbed28..2139713830 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -6,6 +6,7 @@ import TelegramCore import Postbox import SwiftSignalKit import TelegramStringFormatting +import ChatPresentationInterfaceState final class ChatRestrictedInputPanelNode: ChatInputPanelNode { private let textNode: ImmediateTextNode diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index 172a134d7a..ed4e27c6b8 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -8,6 +8,7 @@ import SwiftSignalKit import TelegramNotices import TelegramPresentationData import ActivityIndicator +import ChatPresentationInterfaceState private let labelFont = Font.regular(15.0) diff --git a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift index 4c2aaf455e..62d0acc049 100644 --- a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift @@ -9,6 +9,7 @@ import SearchBarNode import LocalizedPeerData import SwiftSignalKit import AccountContext +import ChatPresentationInterfaceState private let searchBarFont = Font.regular(17.0) diff --git a/submodules/TelegramUI/Sources/ChatSendButtonRadialStatusNode.swift b/submodules/TelegramUI/Sources/ChatSendButtonRadialStatusNode.swift index fcc19de66b..fd56578084 100644 --- a/submodules/TelegramUI/Sources/ChatSendButtonRadialStatusNode.swift +++ b/submodules/TelegramUI/Sources/ChatSendButtonRadialStatusNode.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit import Display import SwiftSignalKit import LegacyComponents +import ChatPresentationInterfaceState private final class ChatSendButtonRadialStatusNodeParameters: NSObject { let color: UIColor diff --git a/submodules/TelegramUI/Sources/ChatSlowmodeHintController.swift b/submodules/TelegramUI/Sources/ChatSlowmodeHintController.swift index aa2490db6d..f86f337f9f 100644 --- a/submodules/TelegramUI/Sources/ChatSlowmodeHintController.swift +++ b/submodules/TelegramUI/Sources/ChatSlowmodeHintController.swift @@ -3,6 +3,7 @@ import Display import TelegramPresentationData import SwiftSignalKit import TelegramStringFormatting +import ChatPresentationInterfaceState private func timeoutValue(strings: PresentationStrings, slowmodeState: ChatSlowmodeState) -> String { switch slowmodeState.variant { diff --git a/submodules/TelegramUI/Sources/ChatTextInputAccessoryItem.swift b/submodules/TelegramUI/Sources/ChatTextInputAccessoryItem.swift deleted file mode 100644 index 6b08831f42..0000000000 --- a/submodules/TelegramUI/Sources/ChatTextInputAccessoryItem.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -enum ChatTextInputAccessoryItem: Equatable { - case keyboard - case stickers(Bool) - case inputButtons - case commands - case silentPost(Bool) - case messageAutoremoveTimeout(Int32?) - case scheduledMessages -} diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index 1509557639..7dce2b95dc 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -5,6 +5,7 @@ import Display import TelegramCore import TelegramPresentationData import ContextUI +import ChatPresentationInterfaceState final class ChatTextInputActionButtonsNode: ASDisplayNode { private let presentationContext: ChatPresentationContext? diff --git a/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift b/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift index c4a015cc72..8640bf0e1f 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift @@ -5,6 +5,7 @@ import Display import SwiftSignalKit import TelegramPresentationData import AccountContext +import ChatPresentationInterfaceState private final class ChatTextInputAudioRecordingTimeNodeParameters: NSObject { let timestamp: Double diff --git a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift index 56868447d4..0c72039bfb 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift @@ -9,6 +9,7 @@ import LegacyComponents import AccountContext import ChatInterfaceState import AudioBlob +import ChatPresentationInterfaceState private let offsetThreshold: CGFloat = 10.0 private let dismissOffsetThreshold: CGFloat = 70.0 diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 2abce06ba4..0892e0dcde 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -18,6 +18,9 @@ import ObjCRuntimeUtils import AvatarNode import ContextUI import InvisibleInkDustNode +import TextInputMenu +import Pasteboard +import ChatPresentationInterfaceState private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -296,7 +299,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { var isMediaDeleted: Bool = false - private let inputMenu: ChatTextInputMenu + private let inputMenu: TextInputMenu private var theme: PresentationTheme? private var strings: PresentationStrings? @@ -452,7 +455,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { if presentationInterfaceState.chatLocation.peerId.namespace == Namespaces.Peer.SecretChat { hasSpoilers = false } - self.inputMenu = ChatTextInputMenu(hasSpoilers: hasSpoilers) + self.inputMenu = TextInputMenu(hasSpoilers: hasSpoilers) self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true diff --git a/submodules/TelegramUI/Sources/ChatTextInputSlowmodePlaceholderNode.swift b/submodules/TelegramUI/Sources/ChatTextInputSlowmodePlaceholderNode.swift index 61ecdd6962..09a9c63300 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputSlowmodePlaceholderNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputSlowmodePlaceholderNode.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import TelegramPresentationData import TelegramStringFormatting import AppBundle +import ChatPresentationInterfaceState final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode { private var theme: PresentationTheme diff --git a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift index 1f26975cf2..5a07973883 100644 --- a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit +import ChatPresentationInterfaceState class ChatTitleAccessoryPanelNode: ASDisplayNode { struct LayoutResult { diff --git a/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift b/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift index a29e529ead..8e42a432af 100644 --- a/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit +import ChatPresentationInterfaceState final class ChatToastAlertPanelNode: ChatTitleAccessoryPanelNode { private let separatorNode: ASDisplayNode diff --git a/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift index 74d782ef12..7d6c6313ec 100644 --- a/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift @@ -6,6 +6,7 @@ import TelegramCore import Postbox import SwiftSignalKit import TelegramPresentationData +import ChatPresentationInterfaceState final class ChatUnblockInputPanelNode: ChatInputPanelNode { private let button: HighlightableButtonNode diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index a1bedd5f47..a3aa3b2f4e 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -8,6 +8,7 @@ import TelegramPresentationData import TelegramUIPreferences import MergeLists import AccountContext +import ChatPresentationInterfaceState private struct CommandChatInputContextPanelEntryStableId: Hashable { let command: PeerCommand diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift index f656b43d2b..c3d69c08db 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import TelegramUIPreferences import MergeLists import AccountContext +import ChatPresentationInterfaceState private struct CommandMenuChatInputContextPanelEntryStableId: Hashable { let command: PeerCommand diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 1a5dc443c6..f54ba80199 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -10,8 +10,9 @@ import ProgressNavigationButtonNode import AccountContext import ContactListUI import SearchUI +import AttachmentUI -class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController { +class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController, AttachmentContainable { private let context: AccountContext private let autoDismiss: Bool @@ -69,6 +70,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } } + var requestAttachmentMenuExpansion: () -> Void = {} + init(_ params: ContactSelectionControllerParams) { self.context = params.context self.autoDismiss = params.autoDismiss diff --git a/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift b/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift index e12d8f3e53..23340b9c86 100644 --- a/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift @@ -5,6 +5,7 @@ import Display import TelegramCore import Postbox import SwiftSignalKit +import ChatPresentationInterfaceState final class DeleteChatInputPanelNode: ChatInputPanelNode { private let button: HighlightableButtonNode diff --git a/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift index da52a17ac4..c5e77562fe 100644 --- a/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift @@ -7,6 +7,7 @@ import TelegramPresentationData import TelegramStringFormatting import TelegramUIPreferences import AccountContext +import ChatPresentationInterfaceState final class DisabledContextResultsChatInputContextPanelNode: ChatInputContextPanelNode { private let containerNode: ASDisplayNode diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 004e239043..66cc0c9175 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -16,6 +16,7 @@ import SearchBarNode import UndoUI import SegmentedControlNode import LegacyComponents +import ChatPresentationInterfaceState private enum DrawingPaneType { case stickers diff --git a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift index 6100fa6f54..3a83ffbe7a 100644 --- a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift @@ -12,6 +12,7 @@ import AccountContext import RadialStatusNode import PhotoResources import TelegramStringFormatting +import ChatPresentationInterfaceState final class EditAccessoryPanelNode: AccessoryPanelNode { let dateTimeFormat: PresentationDateTimeFormat diff --git a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift index 7f0117a7da..f9b39020be 100644 --- a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift @@ -9,6 +9,7 @@ import TelegramUIPreferences import MergeLists import AccountContext import Emoji +import ChatPresentationInterfaceState private struct EmojisChatInputContextPanelEntryStableId: Hashable, Equatable { let symbol: String diff --git a/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift index 11c2158590..fae0dc6a57 100644 --- a/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift @@ -14,6 +14,7 @@ import PresentationDataUtils import TextFormat import Markdown import TelegramNotices +import ChatPresentationInterfaceState func textStringForForwardedMessage(_ message: Message, strings: PresentationStrings) -> (String, Bool) { for media in message.media { diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index 39595ecfd5..93a48f9171 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -10,6 +10,7 @@ import MergeLists import AccountContext import AccountContext import ItemListUI +import ChatPresentationInterfaceState private struct HashtagChatInputContextPanelEntryStableId: Hashable { let text: String diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index 169d75d239..90d7be3e55 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -11,6 +11,7 @@ import MergeLists import AccountContext import StickerPackPreviewUI import ContextUI +import ChatPresentationInterfaceState private struct ChatContextResultStableId: Hashable { let result: ChatContextResult diff --git a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift index 1cfa240838..ed1d249c0a 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift @@ -11,6 +11,7 @@ import MergeLists import AccountContext import StickerPackPreviewUI import ContextUI +import ChatPresentationInterfaceState final class HorizontalStickersChatContextPanelInteraction { var previewedStickerItem: StickerPackItem? diff --git a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift index 5600370547..f0854ae2cc 100644 --- a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift +++ b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import AccountContext import StickerPackPreviewUI import ContextUI +import ChatPresentationInterfaceState private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollViewDelegate { private final class DisplayItem { diff --git a/submodules/TelegramUI/Sources/LegacyCamera.swift b/submodules/TelegramUI/Sources/LegacyCamera.swift index 5bce5a0ba2..56fac51e0f 100644 --- a/submodules/TelegramUI/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Sources/LegacyCamera.swift @@ -23,10 +23,10 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch let controller: TGCameraController if let cameraView = cameraView, let previewView = cameraView.previewView() { controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat, camera: previewView.camera, previewView: previewView, intent: photoOnly ? TGCameraControllerGenericPhotoOnlyIntent : TGCameraControllerGenericIntent) - controller.inhibitMultipleCapture = editingMedia } else { controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat) } + controller.inhibitMultipleCapture = editingMedia controller.presentScheduleController = { done in presentSchedulePicker { time in diff --git a/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift b/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift index 7018299088..6ae0117529 100644 --- a/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift +++ b/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift @@ -14,16 +14,7 @@ import ImageCompression import LocalMediaResources import AppBundle import LegacyMediaPickerUI - -final class InstantVideoControllerRecordingStatus { - let micLevel: Signal - let duration: Signal - - init(micLevel: Signal, duration: Signal) { - self.micLevel = micLevel - self.duration = duration - } -} +import ChatPresentationInterfaceState final class InstantVideoController: LegacyController, StandalonePresentableController { private var captureController: TGVideoMessageCaptureController? diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index 62c84ba93a..31124eb2be 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -7,6 +7,7 @@ import TelegramAudio import UniversalMediaPlayer import AccountContext import OpusBinding +import ChatPresentationInterfaceState private let kOutputBus: UInt32 = 0 private let kInputBus: UInt32 = 1 diff --git a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift index 2b32115f6d..8705221cdc 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift @@ -10,6 +10,7 @@ import TextFormat import AccountContext import LocalizedPeerData import ItemListUI +import ChatPresentationInterfaceState private struct MentionChatInputContextPanelEntry: Comparable, Identifiable { let index: Int diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 0ab5f8ba22..f997a38598 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -10,6 +10,7 @@ import InstantPageUI import ChatListUI import PeerAvatarGalleryUI import SettingsUI +import ChatPresentationInterfaceState public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) { var found = false diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 63c8440de7..f34f83fb66 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import DirectionalPanGesture +import ChatPresentationInterfaceState final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestureRecognizerDelegate { let ready = Promise() diff --git a/submodules/TelegramUI/Sources/PaneSearchContainerNode.swift b/submodules/TelegramUI/Sources/PaneSearchContainerNode.swift index db73a8b356..d00c0d977c 100644 --- a/submodules/TelegramUI/Sources/PaneSearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/PaneSearchContainerNode.swift @@ -7,6 +7,7 @@ import Postbox import TelegramCore import TelegramPresentationData import AccountContext +import ChatPresentationInterfaceState private let searchBarHeight: CGFloat = 52.0 diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index ff916e8672..799abd9e13 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -14,6 +14,7 @@ import TelegramBaseController import OverlayStatusController import ListMessageItem import UndoUI +import ChatPresentationInterfaceState final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 3b2ec15c7e..3250e2cd0f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -64,6 +64,7 @@ import CalendarMessageScreen import TooltipUI import QrCodeUI import Translate +import ChatPresentationInterfaceState protocol PeerInfoScreenItem: AnyObject { var id: AnyHashable { get } diff --git a/submodules/TelegramUI/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Sources/PeerSelectionController.swift index d139ce1f97..a4ab1eab5b 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionController.swift @@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private var customTitle: String? public var peerSelected: ((Peer) -> Void)? - public var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode, ChatInterfaceForwardOptionsState?) -> Void)? + public var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? private let filter: ChatListNodePeersFilter private let attemptSelection: ((Peer) -> Void)? diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index c8b51ace9f..3068098ce6 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -12,6 +12,10 @@ import SearchUI import ContactListUI import ChatListUI import SegmentedControlNode +import AttachmentTextInputPanelNode +import ChatPresentationInterfaceState +import ChatSendMessageActionUI +import ChatTextLinkEditUI final class PeerSelectionControllerNode: ASDisplayNode { private let context: AccountContext @@ -37,7 +41,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let toolbarSeparatorNode: ASDisplayNode? private let segmentedControlNode: SegmentedControlNode? - private var textInputPanelNode: PeerSelectionTextInputPanelNode? + private var textInputPanelNode: AttachmentTextInputPanelNode? private var forwardAccessoryPanelNode: ForwardAccessoryPanelNode? var contactListNode: ContactListNode? @@ -58,7 +62,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { var requestOpenDisabledPeer: ((Peer) -> Void)? var requestOpenPeerFromSearch: ((Peer) -> Void)? var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)? - var requestSend: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode, ChatInterfaceForwardOptionsState?) -> Void)? + var requestSend: (([Peer], [PeerId: Peer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? private var presentationData: PresentationData { didSet { @@ -378,7 +382,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.addSubnode(forwardAccessoryPanelNode) self.forwardAccessoryPanelNode = forwardAccessoryPanelNode - let textInputPanelNode = PeerSelectionTextInputPanelNode(presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }) + let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }) textInputPanelNode.interfaceInteraction = self.interfaceInteraction textInputPanelNode.sendMessage = { [weak self] mode in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index 3a5ca4785a..4b060af65b 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -13,6 +13,7 @@ import PhotoResources import TelegramStringFormatting import InvisibleInkDustNode import TextFormat +import ChatPresentationInterfaceState final class ReplyAccessoryPanelNode: AccessoryPanelNode { private let messageDisposable = MetaDisposable() diff --git a/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift b/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift index 7f17fdb540..71a718e90d 100644 --- a/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift @@ -6,6 +6,7 @@ import TelegramCore import Postbox import SwiftSignalKit import LocalizedPeerData +import ChatPresentationInterfaceState final class SecretChatHandshakeStatusInputPanelNode: ChatInputPanelNode { private let button: HighlightableButtonNode diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift index 23aec1d1db..a0055a4545 100644 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift +++ b/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift @@ -8,6 +8,7 @@ import Postbox import TelegramPresentationData import StickerResources import AccountContext +import ChatPresentationInterfaceState final class StickersChatInputContextPanelItem: ListViewItem { let account: Account diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift index 41489ecbcd..7289fdf7da 100644 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift @@ -11,6 +11,7 @@ import MergeLists import AccountContext import StickerPackPreviewUI import ContextUI +import ChatPresentationInterfaceState private struct StickersChatInputContextPanelEntryStableId: Hashable { let ids: [MediaId] diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift index e3a3c8d718..4909d96e51 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift @@ -9,6 +9,7 @@ import TelegramUIPreferences import MergeLists import AccountContext import SwiftSignalKit +import ChatPresentationInterfaceState private enum VerticalChatContextResultsEntryStableId: Hashable { case action diff --git a/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift index 3ea6d8a12c..24ee0cb0af 100644 --- a/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift @@ -8,6 +8,7 @@ import Display import TelegramPresentationData import AccountContext import TelegramStringFormatting +import ChatPresentationInterfaceState final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode { private let webpageDisposable = MetaDisposable() diff --git a/submodules/TextInputMenu/BUILD b/submodules/TextInputMenu/BUILD new file mode 100644 index 0000000000..6ff751ff57 --- /dev/null +++ b/submodules/TextInputMenu/BUILD @@ -0,0 +1,18 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "TextInputMenu", + module_name = "TextInputMenu", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatTextInputMenu.swift b/submodules/TextInputMenu/Sources/TextInputMenu.swift similarity index 88% rename from submodules/TelegramUI/Sources/ChatTextInputMenu.swift rename to submodules/TextInputMenu/Sources/TextInputMenu.swift index 402145b38e..9e13cccfe9 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMenu.swift +++ b/submodules/TextInputMenu/Sources/TextInputMenu.swift @@ -2,13 +2,13 @@ import Foundation import UIKit import TelegramPresentationData -enum ChatTextInputMenuState { - case inactive - case general - case format -} - -final class ChatTextInputMenu { +public final class TextInputMenu { + public enum State { + case inactive + case general + case format + } + private var stringBold: String = "Bold" private var stringItalic: String = "Italic" private var stringMonospace: String = "Monospace" @@ -19,7 +19,7 @@ final class ChatTextInputMenu { private let hasSpoilers: Bool - private(set) var state: ChatTextInputMenuState = .inactive { + public private(set) var state: State = .inactive { didSet { if self.state != oldValue { switch self.state { @@ -48,7 +48,7 @@ final class ChatTextInputMenu { private var observer: NSObjectProtocol? - init(hasSpoilers: Bool = false) { + public init(hasSpoilers: Bool = false) { self.hasSpoilers = hasSpoilers self.observer = NotificationCenter.default.addObserver(forName: UIMenuController.didHideMenuNotification, object: nil, queue: nil, using: { [weak self] _ in self?.back() @@ -61,7 +61,7 @@ final class ChatTextInputMenu { } } - func updateStrings(_ strings: PresentationStrings) { + public func updateStrings(_ strings: PresentationStrings) { self.stringBold = strings.TextFormat_Bold self.stringItalic = strings.TextFormat_Italic self.stringMonospace = strings.TextFormat_Monospace @@ -71,17 +71,17 @@ final class ChatTextInputMenu { self.stringSpoiler = strings.TextFormat_Spoiler } - func activate() { + public func activate() { if self.state == .inactive { self.state = .general } } - func deactivate() { + public func deactivate() { self.state = .inactive } - func format(view: UIView, rect: CGRect) { + public func format(view: UIView, rect: CGRect) { if self.state == .general { self.state = .format if #available(iOS 13.0, *) { @@ -93,13 +93,13 @@ final class ChatTextInputMenu { } } - func back() { + public func back() { if self.state == .format { self.state = .general } } - func hide() { + public func hide() { self.back() if #available(iOS 13.0, *) { UIMenuController.shared.hideMenu()