diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6e084c4d66..858c36322d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7256,3 +7256,13 @@ Sorry for the inconvenience."; "SharedMedia.CommonGroupCount_1" = "%@ group in common"; "SharedMedia.CommonGroupCount_any" = "%@ groups in common"; + +"Attachment.Camera" = "Camera"; +"Attachment.Gallery" = "Gallery"; +"Attachment.File" = "File"; +"Attachment.Location" = "Location"; +"Attachment.Contact" = "Contact"; +"Attachment.Poll" = "Poll"; + +"Attachment.SelectFromGallery" = "Select from Gallery"; +"Attachment.SelectFromFiles" = "Select from Files"; 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 new file mode 100644 index 0000000000..38ab706a33 --- /dev/null +++ b/submodules/AttachmentUI/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AttachmentUI", + module_name = "AttachmentUI", + 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/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/AccountContext:AccountContext", + "//submodules/LegacyComponents:LegacyComponents", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/AppBundle:AppBundle", + "//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 new file mode 100644 index 0000000000..9e63e249f1 --- /dev/null +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -0,0 +1,511 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit +import UIKitRuntimeUtils +import Display +import DirectionalPanGesture +import TelegramPresentationData + +final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { + let wrappingNode: ASDisplayNode + let clipNode: ASDisplayNode + let container: NavigationContainer + + private(set) var isReady: Bool = false + private(set) var dismissProgress: CGFloat = 0.0 + var isReadyUpdated: (() -> Void)? + var updateDismissProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)? + var interactivelyDismissed: (() -> Void)? + + var updateModalProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)? + + private var isUpdatingState = false + private var isDismissed = false + private var isInteractiveDimissEnabled = true + + public private(set) var isExpanded = false + + private var validLayout: (layout: ContainerViewLayout, controllers: [AttachmentContainable], coveredByModalTransition: CGFloat)? + + var keyboardViewManager: KeyboardViewManager? { + didSet { + if self.keyboardViewManager !== oldValue { + self.container.keyboardViewManager = self.keyboardViewManager + } + } + } + + var canHaveKeyboardFocus: Bool = false { + didSet { + self.container.canHaveKeyboardFocus = self.canHaveKeyboardFocus + } + } + + private var panGestureRecognizer: UIPanGestureRecognizer? + + init(presentationData: PresentationData) { + self.wrappingNode = ASDisplayNode() + self.clipNode = ASDisplayNode() + self.clipNode.backgroundColor = presentationData.theme.list.plainBackgroundColor + + self.container = NavigationContainer(controllerRemoved: { _ in }) + self.container.clipsToBounds = true + + super.init() + + 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 + guard let strongSelf = self else { + return + } + if !strongSelf.isReady { + strongSelf.isReady = true + if !strongSelf.isUpdatingState { + strongSelf.isReadyUpdated?() + } + } + } + + applySmoothRoundedCorners(self.container.layer) + } + + override func didLoad() { + super.didLoad() + + let panRecognizer = DirectionalPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.panGestureRecognizer = panRecognizer + self.wrappingNode.view.addGestureRecognizer(panRecognizer) + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { + return true + } + return false + } + + private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? + + let defaultTopInset: CGFloat = 210.0 + @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { + guard let (layout, controllers, coveredByModalTransition) = self.validLayout else { + return + } + + let isLandscape = layout.orientation == .landscape + let edgeTopInset = isLandscape ? 0.0 : defaultTopInset + + 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 + } else { + topInset = edgeTopInset + } + + self.panGestureArguments = (topInset, 0.0, scrollView, listNode) + case .changed: + guard let (topInset, panOffset, scrollView, listNode) = self.panGestureArguments else { + return + } + let visibleContentOffset = listNode?.visibleContentOffset() + let contentOffset = scrollView?.contentOffset.y ?? 0.0 + + var translation = recognizer.translation(in: self.view).y + + 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) + + + if !self.isExpanded { + if currentOffset > 0.0, let scrollView = scrollView { + scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) + } + + var bounds = self.bounds + bounds.origin.y = -translation + bounds.origin.y = min(0.0, bounds.origin.y) + self.bounds = bounds + } + + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .immediate) + case .ended: + guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else { + return + } + let visibleContentOffset = listNode?.visibleContentOffset() + let contentOffset = scrollView?.contentOffset.y ?? 0.0 + + let translation = recognizer.translation(in: self.view).y + var velocity = recognizer.velocity(in: self.view) + + if case let .known(value) = visibleContentOffset, value > 0.0 { + velocity = CGPoint() + } else if case .unknown = visibleContentOffset { + velocity = CGPoint() + } else if contentOffset > 0.0 { + velocity = CGPoint() + } + + 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 { + self.panGestureArguments = nil + if velocity.y > 300.0 || offset > topInset / 2.0 { + self.isExpanded = false + if let listNode = listNode { + listNode.scroller.setContentOffset(CGPoint(), animated: false) + } else if let scrollView = scrollView { + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } + + let distance = topInset - offset + let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance) + let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: transition) + } else { + self.isExpanded = true + + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } else { + self.panGestureArguments = nil + + var dismissing = false + if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) { + self.interactivelyDismissed?() + dismissing = true + } else if (velocity.y < -300.0 || offset < topInset / 2.0) { + if velocity.y > -2200.0, let listNode = listNode { + DispatchQueue.main.async { + listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + } + + let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset) + let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) + self.isExpanded = true + + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: transition) + } else { + if let listNode = listNode { + listNode.scroller.setContentOffset(CGPoint(), animated: false) + } else if let scrollView = scrollView { + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } + + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + + if !dismissing { + var bounds = self.bounds + let previousBounds = bounds + bounds.origin.y = 0.0 + self.bounds = bounds + self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) + } + } + case .cancelled: + self.panGestureArguments = nil + + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .animated(duration: 0.3, curve: .easeInOut)) + default: + break + } + } + + private func checkInteractiveDismissWithControllers() -> Bool { + if let controller = self.container.controllers.last { + if !controller.attemptNavigation({ + }) { + return false + } + } + 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) + } + + func update(layout: ContainerViewLayout, controllers: [AttachmentContainable], coveredByModalTransition: CGFloat, transition: ContainedViewLayoutTransition) { + if self.isDismissed { + return + } + self.isUpdatingState = true + + self.validLayout = (layout, controllers, coveredByModalTransition) + + self.panGestureRecognizer?.isEnabled = (layout.inputHeight == nil || layout.inputHeight == 0.0) +// self.scrollNode.view.isScrollEnabled = (layout.inputHeight == nil || layout.inputHeight == 0.0) && self.isInteractiveDimissEnabled + + let isLandscape = layout.orientation == .landscape + let edgeTopInset = isLandscape ? 0.0 : defaultTopInset + + let topInset: CGFloat + if let (panInitialTopInset, panOffset, _, _) = self.panGestureArguments { + if self.isExpanded { + topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset)) + } else { + topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) + } + } else { + topInset = self.isExpanded ? 0.0 : edgeTopInset + } + 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.clipNode.clipsToBounds = true + + if isLandscape { + self.clipNode.cornerRadius = 0.0 + } else { + self.clipNode.cornerRadius = 10.0 + } + + if #available(iOS 11.0, *) { + if layout.safeInsets.bottom.isZero { + self.wrappingNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + } else { + self.wrappingNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] + } + } + + var containerTopInset: CGFloat + if isLandscape { + containerTopInset = 0.0 + containerLayout = layout + + let unscaledFrame = CGRect(origin: CGPoint(), size: containerLayout.size) + containerScale = 1.0 + containerFrame = unscaledFrame + clipFrame = unscaledFrame + } else { + containerTopInset = 10.0 + if let statusBarHeight = layout.statusBarHeight { + containerTopInset += statusBarHeight + } + + let effectiveStatusBarHeight: CGFloat? = nil + + 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: -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.clipNode.clipsToBounds = true + self.clipNode.cornerRadius = 10.0 + if #available(iOS 11.0, *) { + self.clipNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] + } + + let verticalInset: CGFloat = 44.0 + + let maxSide = max(layout.size.width, layout.size.height) + let minSide = min(layout.size.width, layout.size.height) + 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 { + inputHeight = max(0.0, inputHeightValue - (layout.size.height - containerFrame.maxY)) + } + + let effectiveStatusBarHeight: CGFloat? = nil + + 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.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) + + self.isUpdatingState = false + } + + func dismiss(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) -> ContainedViewLayoutTransition { + for controller in self.container.controllers { + controller.viewWillDisappear(transition.isAnimated) + } + + if let firstController = self.container.controllers.first, case .standaloneModal = firstController.navigationPresentation { + for controller in self.container.controllers { + controller.setIgnoreAppearanceMethodInvocations(true) + controller.displayNode.removeFromSupernode() + controller.setIgnoreAppearanceMethodInvocations(false) + controller.viewDidDisappear(transition.isAnimated) + } + completion() + return transition + } else { + if transition.isAnimated { + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0 + self.bounds.height), beginWithCurrentState: true, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + for controller in strongSelf.container.controllers { + controller.viewDidDisappear(transition.isAnimated) + } + completion() + }) + return positionTransition + } else { + for controller in self.container.controllers { + controller.setIgnoreAppearanceMethodInvocations(true) + controller.displayNode.removeFromSupernode() + controller.setIgnoreAppearanceMethodInvocations(false) + controller.viewDidDisappear(transition.isAnimated) + } + completion() + return transition + } + } + } + + private func findScrollView(view: UIView?) -> (UIScrollView, ListView?)? { + if let view = view { + if let view = view as? UIScrollView { + return (view, nil) + } + if let node = view.asyncdisplaykit_node as? ListView { + return (node.scroller, node) + } + return findScrollView(view: view.superview) + } else { + return nil + } + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if !self.wrappingNode.frame.contains(point) { + return false + } + return super.point(inside: point, with: event) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let result = super.hitTest(point, with: event) else { + return nil + } +// var currentParent: UIView? = result +// var enableScrolling = true +// while true { +// if currentParent == nil { +// break +// } +// if currentParent is UIKeyInput { +// if currentParent?.disablesInteractiveModalDismiss == true { +// enableScrolling = false +// break +// } +// } else if let scrollView = currentParent as? UIScrollView { +// if scrollView === self.scrollNode.view { +// break +// } +// if scrollView.disablesInteractiveModalDismiss { +// enableScrolling = false +// break +// } else { +// if scrollView.isDecelerating && scrollView.contentOffset.y < -scrollView.contentInset.top { +// return self.scrollNode.view +// } +// } +// } else if let listView = currentParent as? ListViewBackingView, let listNode = listView.target { +// if listNode.view.disablesInteractiveModalDismiss { +// enableScrolling = false +// break +// } else if listNode.scroller.isDecelerating && listNode.scroller.contentOffset.y < listNode.scroller.contentInset.top { +// return self.scrollNode.view +// } +// } +// currentParent = currentParent?.superview +// } +// if let controller = self.container.controllers.last { +// if controller.view.disablesInteractiveModalDismiss { +// enableScrolling = false +// } +// } +// self.isInteractiveDimissEnabled = enableScrolling +// if let layout = self.validLayout { +// if layout.inputHeight != nil && layout.inputHeight != 0.0 { +// enableScrolling = false +// } +// } +// self.scrollNode.view.isScrollEnabled = enableScrolling + return result + } +} diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift new file mode 100644 index 0000000000..6ae9e738d6 --- /dev/null +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -0,0 +1,373 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import TelegramStringFormatting +import UIKitRuntimeUtils + +public enum AttachmentButtonType: Equatable { + case camera + case gallery + case file + case location + case contact + case poll + 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 + let panel: AttachmentPanel + + private var validLayout: ContainerViewLayout? + private var modalProgress: CGFloat = 0.0 + + private var currentType: AttachmentButtonType? + 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 + + self.dim = ASDisplayNode() + self.dim.alpha = 0.0 + self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) + + 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) + + strongSelf.modalProgress = progress + strongSelf.containerLayoutUpdated(layout, transition: transition) + } + } + self.container.isReadyUpdated = { [weak self] in + if let strongSelf = self, let layout = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + } + } + + 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.panel.beganTextEditing = { [weak self] in + if let strongSelf = self { + 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() { + super.didLoad() + + self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + + 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) + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let controller = self.controller, controller.isInteractionDisabled() { + return self.view + } else { + return super.hitTest(point, with: event) + } + } + + func dismiss(animated: Bool, completion: @escaping () -> Void = {}) { + if animated { + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0 + self.bounds.height), beginWithCurrentState: true, completion: { [weak self] _ in + let _ = self?.container.dismiss(transition: .immediate, completion: completion) + }) + 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) + } + } + + func switchToController(_ type: AttachmentButtonType, _ ascending: Bool) { + guard self.currentType != type else { + return + } + let previousType = self.currentType + self.currentType = type + 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) { + 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 + + transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size)) + + let containerTransition: ContainedViewLayoutTransition + if self.container.supernode == nil { + containerTransition = .immediate + } else { + containerTransition = transition + } + + if !self.isUpdatingContainer { + self.isUpdatingContainer = true + + 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: self.switchingController ? .immediate : transition) + + if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady { + self.addSubnode(self.container) + self.container.addSubnode(self.panel) + + self.animateIn(transition: transition) + } + + self.isUpdatingContainer = false + } + + if self.modalProgress < 0.5 { + self.isCollapsed = false + } else if self.modalProgress == 1.0 { + self.isCollapsed = true + } + + 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 (AttachmentContainable?, AttachmentMediaPickerContext?) -> Void) -> Void = { _, completion in + completion(nil, nil) + } + + 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) { + fatalError("init(coder:) has not been implemented") + } + + private var node: Node { + return self.displayNode as! Node + } + + open override func loadDisplayNode() { + self.displayNode = Node(controller: self) + self.displayNodeDidLoad() + } + + public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + self.view.endEditing(true) + if flag { + self.node.dismiss(animated: true, completion: { + super.dismiss(animated: flag, completion: {}) + completion?() + }) + } else { + super.dismiss(animated: false, completion: {}) + completion?() + } + } + + private func isInteractionDisabled() -> Bool { + return false + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.node.containerLayoutUpdated(layout, transition: transition) + } +} diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift new file mode 100644 index 0000000000..f3809c60ef --- /dev/null +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -0,0 +1,926 @@ +import Foundation +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: 60.0, height: 49.0) + +private let iconSize = CGSize(width: 54.0, height: 42.0) +private let normalSideInset: CGFloat = 3.0 +private let smallSideInset: CGFloat = 0.0 + +private enum AttachmentButtonTransition { + case transitionIn + case selection +} + +private func generateBackgroundImage(colors: [UIColor]) -> UIImage? { + return generateImage(iconSize, rotatedContext: { size, context in + var locations: [CGFloat] + if colors.count == 3 { + locations = [1.0, 0.5, 0.0] + } else { + locations = [1.0, 0.0] + } + let colors: [CGColor] = colors.map { $0.cgColor } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + if colors.count == 2 { + context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: .drawsAfterEndLocation) + } else if colors.count == 3 { + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: size.height), end: CGPoint(x: size.width, y: 0.0), options: .drawsAfterEndLocation) + } +// let center = CGPoint(x: 10.0, y: 10.0) +// context.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: size.width, options: .drawsAfterEndLocation) + }) +} + +private let buttonGlowImage: UIImage? = { + let inset: CGFloat = 6.0 + return generateImage(CGSize(width: iconSize.width + inset * 2.0, height: iconSize.height + inset * 2.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let rect = bounds.insetBy(dx: inset, dy: inset) + let path = UIBezierPath(roundedRect: rect, cornerRadius: 21.0).cgPath + context.addRect(bounds) + context.addPath(path) + context.clip(using: .evenOdd) + + context.addPath(path) + context.setShadow(offset: CGSize(), blur: 14.0, color: UIColor(rgb: 0xffffff, alpha: 0.8).cgColor) + context.setFillColor(UIColor.white.cgColor) + context.fillPath() + })?.withRenderingMode(.alwaysTemplate) +}() + +private let buttonSelectionMaskImage: UIImage? = { + let inset: CGFloat = 3.0 + return generateImage(CGSize(width: iconSize.width + inset * 2.0, height: iconSize.height + inset * 2.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let path = UIBezierPath(roundedRect: bounds, cornerRadius: 23.0).cgPath + context.addPath(path) + context.setFillColor(UIColor(rgb: 0xffffff).cgColor) + context.fillPath() + })?.withRenderingMode(.alwaysTemplate) +}() + +private final class AttachButtonComponent: CombinedComponent { + let context: AccountContext + let type: AttachmentButtonType + let isSelected: Bool + let isCollapsed: Bool + let transitionFraction: CGFloat + let strings: PresentationStrings + let theme: PresentationTheme + let action: () -> Void + + init( + context: AccountContext, + type: AttachmentButtonType, + isSelected: Bool, + isCollapsed: Bool, + transitionFraction: CGFloat, + strings: PresentationStrings, + theme: PresentationTheme, + action: @escaping () -> Void + ) { + self.context = context + self.type = type + self.isSelected = isSelected + self.isCollapsed = isCollapsed + self.transitionFraction = transitionFraction + self.strings = strings + self.theme = theme + self.action = action + } + + static func ==(lhs: AttachButtonComponent, rhs: AttachButtonComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.type != rhs.type { + return false + } + if lhs.isSelected != rhs.isSelected { + return false + } + if lhs.isCollapsed != rhs.isCollapsed { + return false + } + if lhs.transitionFraction != rhs.transitionFraction { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.theme !== rhs.theme { + return false + } + return true + } + + static var body: Body { + let icon = Child(AttachButtonIconComponent.self) + let title = Child(Text.self) + + return { context in + let name: String + let animationName: String? + let imageName: String? + let backgroundColors: [UIColor] + let foregroundColor: UIColor = .white + + let isCollapsed = context.component.isCollapsed + + switch context.component.type { + case .camera: + name = context.component.strings.Attachment_Camera + animationName = "anim_camera" + imageName = "Chat/Attach Menu/Camera" + backgroundColors = [UIColor(rgb: 0xba4aae), UIColor(rgb: 0xdd4e6f), UIColor(rgb: 0xf4b76c)] + case .gallery: + name = context.component.strings.Attachment_Gallery + animationName = "anim_gallery" + imageName = "Chat/Attach Menu/Gallery" + backgroundColors = [UIColor(rgb: 0x2071f1), UIColor(rgb: 0x1bc9fa)] + case .file: + name = context.component.strings.Attachment_File + animationName = "anim_file" + imageName = "Chat/Attach Menu/File" + backgroundColors = [UIColor(rgb: 0xed705d), UIColor(rgb: 0xffa14c)] + case .location: + name = context.component.strings.Attachment_Location + animationName = "anim_location" + imageName = "Chat/Attach Menu/Location" + backgroundColors = [UIColor(rgb: 0x5fb84f), UIColor(rgb: 0x99de6f)] + case .contact: + name = context.component.strings.Attachment_Contact + animationName = "anim_contact" + imageName = "Chat/Attach Menu/Contact" + backgroundColors = [UIColor(rgb: 0xaa47d6), UIColor(rgb: 0xd67cf4)] + case .poll: + name = context.component.strings.Attachment_Poll + animationName = "anim_poll" + imageName = "Chat/Attach Menu/Poll" + backgroundColors = [UIColor(rgb: 0xe9484f), UIColor(rgb: 0xee707e)] + case let .app(appName): + name = appName + animationName = nil + imageName = nil + backgroundColors = [UIColor(rgb: 0x000000), UIColor(rgb: 0x000000)] + } + + let icon = icon.update( + component: AttachButtonIconComponent( + animationName: animationName, + imageName: imageName, + isSelected: context.component.isSelected, + backgroundColors: backgroundColors, + foregroundColor: foregroundColor, + theme: context.component.theme, + context: context.component.context, + action: context.component.action + ), + availableSize: iconSize, + transition: context.transition + ) + + let title = title.update( + component: Text( + text: name, + font: Font.regular(11.0), + color: context.component.theme.actionSheet.primaryTextColor + ), + availableSize: context.availableSize, + transition: .immediate + ) + + let topInset: CGFloat = 8.0 + let spacing: CGFloat = 3.0 + UIScreenPixel + + let normalIconScale = isCollapsed ? 0.7 : 1.0 + let smallIconScale = isCollapsed ? 0.5 : 0.6 + + 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: 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 = floorToScreenPixels(iconFrame.midY - title.size.height / 2.0) + } + + context.add(title + .position(CGPoint(x: titleFrame.midX, y: titleFrame.midY)) + .opacity(isCollapsed ? 0.0 : 1.0 - abs(context.component.transitionFraction)) + ) + + context.add(icon + .position(CGPoint(x: iconFrame.midX, y: iconFrame.midY)) + .scale(iconScale) + ) + + return context.availableSize + } + } +} + +private final class AttachButtonIconComponent: Component { + let animationName: String? + let imageName: String? + let isSelected: Bool + let backgroundColors: [UIColor] + let foregroundColor: UIColor + let theme: PresentationTheme + let context: AccountContext + + let action: () -> Void + + init( + animationName: String?, + imageName: String?, + isSelected: Bool, + backgroundColors: [UIColor], + foregroundColor: UIColor, + theme: PresentationTheme, + context: AccountContext, + action: @escaping () -> Void + ) { + self.animationName = animationName + self.imageName = imageName + self.isSelected = isSelected + self.backgroundColors = backgroundColors + self.foregroundColor = foregroundColor + self.theme = theme + self.context = context + self.action = action + } + + static func ==(lhs: AttachButtonIconComponent, rhs: AttachButtonIconComponent) -> Bool { + if lhs.animationName != rhs.animationName { + return false + } + if lhs.imageName != rhs.imageName { + return false + } + if lhs.isSelected != rhs.isSelected { + return false + } + if lhs.backgroundColors != rhs.backgroundColors { + return false + } + if lhs.foregroundColor != rhs.foregroundColor { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.context !== rhs.context { + return false + } + return true + } + + 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 + + self.selectionView = UIImageView() + self.selectionView.image = buttonSelectionMaskImage + self.selectionView.isUserInteractionEnabled = false + + self.backgroundView = UIView() + self.backgroundView.clipsToBounds = true + self.backgroundView.isUserInteractionEnabled = false + self.backgroundView.layer.cornerRadius = 21.0 + + 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.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 = { [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) + } + } + } + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure() + } + + @objc private func pressed() { + self.action?() + } + + func update(component: AttachButtonIconComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.action = component.action + + if self.currentColors != component.backgroundColors { + self.currentColors = component.backgroundColors + self.backgroundView.layer.contents = generateBackgroundImage(colors: component.backgroundColors)?.cgImage + + if let color = component.backgroundColors.last { + self.glowView.tintColor = color + self.selectionView.tintColor = color.withAlphaComponent(0.2) + } + } + + if self.currentImageName != component.imageName { + self.currentImageName = component.imageName + if let imageName = component.imageName, let image = UIImage(bundleImageName: imageName) { + self.iconView.image = image + + let scale: CGFloat = 0.875 + let iconSize = CGSize(width: floorToScreenPixels(image.size.width * scale), height: floorToScreenPixels(image.size.height * scale)) + self.iconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - iconSize.height) / 2.0)), size: iconSize) + } + } + + if self.currentIsSelected != component.isSelected { + self.currentIsSelected = component.isSelected + + transition.setScale(view: self.selectionView, scale: component.isSelected ? 1.0 : 0.8) + } + + 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) + + self.selectionView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: contentFrame.width + 6.0, height: contentFrame.height + 6.0)) + self.selectionView.center = CGPoint(x: contentFrame.midX, y: contentFrame.midY) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + +final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { + private let context: AccountContext + private var presentationData: PresentationData + + 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(set) var isCollapsed: Bool = false + private(set) var isSelecting: Bool = false + + 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.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 + + self.scrollNode = ASScrollNode() + 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.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() { + super.didLoad() + if #available(iOS 13.0, *) { + self.containerNode.layer.cornerCurve = .continuous + } + + self.scrollNode.view.delegate = self + self.scrollNode.view.showsHorizontalScrollIndicator = false + self.scrollNode.view.showsVerticalScrollIndicator = false + + let effect: UIVisualEffect + switch self.presentationData.theme.actionSheet.backgroundType { + case .light: + effect = UIBlurEffect(style: .light) + case .dark: + effect = UIBlurEffect(style: .dark) + } + let effectView = UIVisualEffectView(effect: effect) + self.effectView = effectView + 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 layout = self.validLayout else { + return + } + + let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0) + let actualVisibleRect = self.scrollNode.bounds + 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: sideInset + buttonSize.width * CGFloat(i), y: 0.0), size: buttonSize) + if !visibleRect.intersects(buttonFrame) { + continue + } + validButtons.insert(i) + + let edge = buttonSize.width * 0.75 + let leftEdge = max(-edge, min(0.0, buttonFrame.minX - actualVisibleRect.minX)) / -edge + let rightEdge = min(edge, max(0.0, buttonFrame.maxX - actualVisibleRect.maxX)) / edge + + let transitionFraction: CGFloat + if leftEdge > rightEdge { + transitionFraction = leftEdge + } else { + transitionFraction = -rightEdge + } + + var buttonTransition = transition + let buttonView: ComponentHostView + if let current = self.buttonViews[i] { + buttonView = current + } else { + buttonTransition = .immediate + buttonView = ComponentHostView() + self.buttonViews[i] = buttonView + self.scrollNode.view.addSubview(buttonView) + } + + let type = self.buttons[i] + let _ = buttonView.update( + transition: buttonTransition, + component: AnyComponent(AttachButtonComponent( + context: self.context, + type: type, + isSelected: i == self.selectedIndex, + isCollapsed: self.isCollapsed, + transitionFraction: transitionFraction, + strings: self.presentationData.strings, + theme: self.presentationData.theme, + action: { [weak self] in + if let strongSelf = self { + let ascending = i > strongSelf.selectedIndex + strongSelf.selectedIndex = i + strongSelf.selectionChanged(type, ascending) + strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) + } + }) + ), + environment: {}, + containerSize: buttonSize + ) + buttonTransition.setFrame(view: buttonView, frame: buttonFrame) + } + } + + private func updateScrollLayoutIfNeeded(force: Bool, transition: ContainedViewLayoutTransition) -> Bool { + guard let layout = self.validLayout else { + return false + } + if self.scrollLayout?.width == layout.size.width && !force { + return false + } + + let buttonSize = self.isCollapsed ? smallPanelButtonSize : panelButtonSize + 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(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 + } + + 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 + + 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 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 || 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) + } + + 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) { + self.updateViews(transition: .immediate) + } +} 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/ComponentFlow/Source/Base/ChildComponentTransitions.swift b/submodules/ComponentFlow/Source/Base/ChildComponentTransitions.swift index 881c14d844..d5f40e0280 100644 --- a/submodules/ComponentFlow/Source/Base/ChildComponentTransitions.swift +++ b/submodules/ComponentFlow/Source/Base/ChildComponentTransitions.swift @@ -67,8 +67,14 @@ public extension Transition.DisappearWithGuide { public extension Transition.Update { static let `default` = Transition.Update { component, view, transition in let frame = component.size.centered(around: component._position ?? CGPoint()) - if view.frame != frame { - transition.setFrame(view: view, frame: frame) + if let scale = component._scale { + transition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: frame.size)) + transition.setPosition(view: view, position: frame.center) + transition.setScale(view: view, scale: scale) + } else { + if view.frame != frame { + transition.setFrame(view: view, frame: frame) + } } let opacity = component._opacity ?? 1.0 if view.alpha != opacity { diff --git a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift index a860ce7f95..a518cf5ee1 100644 --- a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift +++ b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift @@ -175,6 +175,7 @@ public final class _UpdatedChildComponent { var _removed: Bool = false var _position: CGPoint? + var _scale: CGFloat? var _opacity: CGFloat? var _cornerRadius: CGFloat? var _clipsToBounds: Bool? @@ -239,6 +240,11 @@ public final class _UpdatedChildComponent { return self } + @discardableResult public func scale(_ scale: CGFloat) -> _UpdatedChildComponent { + self._scale = scale + return self + } + @discardableResult public func opacity(_ opacity: CGFloat) -> _UpdatedChildComponent { self._opacity = opacity return self @@ -683,7 +689,13 @@ public extension CombinedComponent { view.insertSubview(updatedChild.view, at: index) - updatedChild.view.frame = updatedChild.size.centered(around: updatedChild._position ?? CGPoint()) + if let scale = updatedChild._scale { + updatedChild.view.bounds = CGRect(origin: CGPoint(), size: updatedChild.size) + updatedChild.view.center = updatedChild._position ?? CGPoint() + updatedChild.view.transform = CGAffineTransform(scaleX: scale, y: scale) + } else { + updatedChild.view.frame = updatedChild.size.centered(around: updatedChild._position ?? CGPoint()) + } updatedChild.view.alpha = updatedChild._opacity ?? 1.0 updatedChild.view.clipsToBounds = updatedChild._clipsToBounds ?? false updatedChild.view.layer.cornerRadius = updatedChild._cornerRadius ?? 0.0 diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index a69a7bf576..a6e832f3c9 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -176,6 +176,40 @@ public struct Transition { } } + public func setBounds(view: UIView, bounds: CGRect, completion: ((Bool) -> Void)? = nil) { + if view.bounds == bounds { + completion?(true) + return + } + switch self.animation { + case .none: + view.bounds = bounds + completion?(true) + case .curve: + let previousBounds = view.bounds + view.bounds = bounds + + self.animateBounds(view: view, from: previousBounds, to: view.bounds, completion: completion) + } + } + + public func setPosition(view: UIView, position: CGPoint, completion: ((Bool) -> Void)? = nil) { + if view.center == position { + completion?(true) + return + } + switch self.animation { + case .none: + view.center = position + completion?(true) + case .curve: + let previousPosition = view.center + view.center = position + + self.animatePosition(view: view, from: previousPosition, to: view.center, completion: completion) + } + } + public func setAlpha(view: UIView, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { if view.alpha == alpha { completion?(true) @@ -191,7 +225,35 @@ public struct Transition { self.animateAlpha(view: view, from: previousAlpha, to: alpha, completion: completion) } } - + + public func setScale(view: UIView, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = view.layer.presentation()?.transform ?? view.layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale == scale { + completion?(true) + return + } + switch self.animation { + case .none: + view.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + completion?(true) + case let .curve(duration, curve): + let previousScale = currentScale + view.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + view.layer.animate( + from: previousScale as NSNumber, + to: scale as NSNumber, + keyPath: "transform.scale", + duration: duration, + delay: 0.0, + curve: curve, + removeOnCompletion: true, + additive: false, + completion: completion + ) + } + } + public func setSublayerTransform(view: UIView, transform: CATransform3D, completion: ((Bool) -> Void)? = nil) { switch self.animation { case .none: 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..bff1e5489c 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.3) + case .click06: + impactGenerator.impactOccurred(intensity: 0.4) + case .veryLight: + impactGenerator.impactOccurred(intensity: 0.3) + default: + impactGenerator.impactOccurred() + } } else { impactGenerator.impactOccurred() } diff --git a/submodules/Display/Source/KeyboardManager.swift b/submodules/Display/Source/KeyboardManager.swift index 62cbd4b903..83fd35fb88 100644 --- a/submodules/Display/Source/KeyboardManager.swift +++ b/submodules/Display/Source/KeyboardManager.swift @@ -134,7 +134,7 @@ private func endAnimations(view: UIView) { } } -func viewTreeContainsFirstResponder(view: UIView) -> Bool { +public func viewTreeContainsFirstResponder(view: UIView) -> Bool { if view.isFirstResponder { return true } else { @@ -147,14 +147,14 @@ func viewTreeContainsFirstResponder(view: UIView) -> Bool { } } -final class KeyboardViewManager { +public final class KeyboardViewManager { private let host: StatusBarHost init(host: StatusBarHost) { self.host = host } - func dismissEditingWithoutAnimation(view: UIView) { + public func dismissEditingWithoutAnimation(view: UIView) { if viewTreeContainsFirstResponder(view: view) { view.endEditing(true) if let keyboardWindow = self.host.keyboardWindow { @@ -165,7 +165,7 @@ final class KeyboardViewManager { } } - func update(leftEdge: CGFloat, transition: ContainedViewLayoutTransition) { + public func update(leftEdge: CGFloat, transition: ContainedViewLayoutTransition) { guard let keyboardWindow = self.host.keyboardWindow else { return } diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index 6aa0a28880..92e8b3cf9f 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit -final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { +public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { private final class Child { let value: ViewController var layout: ContainerViewLayout @@ -73,19 +73,20 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { var pending: PendingChild? } - private(set) var controllers: [ViewController] = [] + public private(set) var controllers: [ViewController] = [] private var state: State = State(layout: nil, canBeClosed: nil, top: nil, transition: nil, pending: nil) private var ignoreInputHeight: Bool = false - private(set) var isReady: Bool = false - var isReadyUpdated: (() -> Void)? - var controllerRemoved: (ViewController) -> Void - var keyboardViewManager: KeyboardViewManager? { + public private(set) var isReady: Bool = false + public var isReadyUpdated: (() -> Void)? + public var controllerRemoved: (ViewController) -> Void + public var keyboardViewManager: KeyboardViewManager? { didSet { } } - var canHaveKeyboardFocus: Bool = false { + + public var canHaveKeyboardFocus: Bool = false { didSet { if self.canHaveKeyboardFocus != oldValue { if !self.canHaveKeyboardFocus { @@ -96,14 +97,15 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { } } - var isInFocus: Bool = false { + public var isInFocus: Bool = false { didSet { if self.isInFocus != oldValue { self.inFocusUpdated(isInFocus: self.isInFocus) } } } - func inFocusUpdated(isInFocus: Bool) { + + public func inFocusUpdated(isInFocus: Bool) { self.state.top?.value.isInFocus = isInFocus } @@ -113,13 +115,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { var statusBarStyle: StatusBarStyle = .Ignore var statusBarStyleUpdated: ((ContainedViewLayoutTransition) -> Void)? - init(controllerRemoved: @escaping (ViewController) -> Void) { + public init(controllerRemoved: @escaping (ViewController) -> Void) { self.controllerRemoved = controllerRemoved super.init() } - override func didLoad() { + public override func didLoad() { super.didLoad() let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in @@ -263,7 +265,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { } } - func update(layout: ContainerViewLayout, canBeClosed: Bool, controllers: [ViewController], transition: ContainedViewLayoutTransition) { + public func update(layout: ContainerViewLayout, canBeClosed: Bool, controllers: [ViewController], transition: ContainedViewLayoutTransition) { self.state.layout = layout self.state.canBeClosed = canBeClosed @@ -539,7 +541,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { } } - func updateAdditionalKeyboardLeftEdgeOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition) { + public func updateAdditionalKeyboardLeftEdgeOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition) { self.additionalKeyboardLeftEdgeOffset = offset self.syncKeyboard(leftEdge: self.currentKeyboardLeftEdge, transition: transition) } @@ -562,7 +564,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { return nil } diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 80de7e8eef..27dec081c0 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -533,8 +533,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { } navigationController.filterController(self, animated: animated) } else { - self.presentingViewController?.dismiss(animated: false, completion: nil) - assertionFailure() + self.presentingViewController?.dismiss(animated: flag, completion: nil) } } @@ -612,7 +611,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { if let navigationController = self.navigationController as? NavigationController { navigationController.filterController(self, animated: true) } else { - self.presentingViewController?.dismiss(animated: false, completion: nil) + self.presentingViewController?.dismiss(animated: true, completion: nil) } } diff --git a/submodules/GameUI/Sources/GameController.swift b/submodules/GameUI/Sources/GameController.swift index b3913f61de..2fa0e015ac 100644 --- a/submodules/GameUI/Sources/GameController.swift +++ b/submodules/GameUI/Sources/GameController.swift @@ -14,11 +14,11 @@ public final class GameController: ViewController { private let context: AccountContext private let url: String - private let message: EngineMessage + private let message: EngineMessage? private var presentationData: PresentationData - public init(context: AccountContext, url: String, message: EngineMessage) { + public init(context: AccountContext, url: String, message: EngineMessage?) { self.context = context self.url = url self.message = message @@ -29,26 +29,30 @@ public final class GameController: ViewController { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.sharePressed)) - - for media in message.media { - if let game = media as? TelegramMediaGame { - let titleView = GameControllerTitleView(theme: self.presentationData.theme) - - var botPeer: EnginePeer? - inner: for attribute in message.attributes { - if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId { - botPeer = message.peers[peerId].flatMap(EnginePeer.init) - break inner + if let message = message { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.sharePressed)) + + for media in message.media { + if let game = media as? TelegramMediaGame { + let titleView = GameControllerTitleView(theme: self.presentationData.theme) + + var botPeer: EnginePeer? + inner: for attribute in message.attributes { + if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId { + botPeer = message.peers[peerId].flatMap(EnginePeer.init) + break inner + } } + if botPeer == nil { + botPeer = message.author + } + + titleView.set(title: game.title, subtitle: "@\(botPeer?.addressName ?? "")") + self.navigationItem.titleView = titleView } - if botPeer == nil { - botPeer = message.author - } - - titleView.set(title: game.title, subtitle: "@\(botPeer?.addressName ?? "")") - self.navigationItem.titleView = titleView } + } else { + self.title = "App" } } diff --git a/submodules/GameUI/Sources/GameControllerNode.swift b/submodules/GameUI/Sources/GameControllerNode.swift index 6b22a0c21b..551c9fcd88 100644 --- a/submodules/GameUI/Sources/GameControllerNode.swift +++ b/submodules/GameUI/Sources/GameControllerNode.swift @@ -30,9 +30,9 @@ final class GameControllerNode: ViewControllerTracingNode { private let context: AccountContext var presentationData: PresentationData private let present: (ViewController, Any?) -> Void - private let message: EngineMessage + private let message: EngineMessage? - init(context: AccountContext, presentationData: PresentationData, url: String, present: @escaping (ViewController, Any?) -> Void, message: EngineMessage) { + init(context: AccountContext, presentationData: PresentationData, url: String, present: @escaping (ViewController, Any?) -> Void, message: EngineMessage?) { self.context = context self.presentationData = presentationData self.present = present @@ -107,18 +107,21 @@ final class GameControllerNode: ViewControllerTracingNode { } private func shareData() -> (EnginePeer, String)? { + guard let message = self.message else { + return nil + } var botPeer: EnginePeer? var gameName: String? - for media in self.message.media { + for media in message.media { if let game = media as? TelegramMediaGame { - inner: for attribute in self.message.attributes { + inner: for attribute in message.attributes { if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId { - botPeer = self.message.peers[peerId].flatMap(EnginePeer.init) + botPeer = message.peers[peerId].flatMap(EnginePeer.init) break inner } } if botPeer == nil { - botPeer = self.message.author + botPeer = message.author } gameName = game.name @@ -144,8 +147,8 @@ final class GameControllerNode: ViewControllerTracingNode { if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty { if eventName == "share_score" { self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, text, account, _ in - if let strongSelf = self { - let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: strongSelf.message.id, to: $0, as: nil) } + if let strongSelf = self, let message = strongSelf.message { + let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, as: nil) } return .single(.preparing) |> then( combineLatest(signals) diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index 3606de013b..1bb571e473 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -32,8 +32,6 @@ private struct StickerPackPreviewGridEntry: Comparable, Equatable, Identifiable func item(account: Account, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) -> StickerPackPreviewGridItem { return StickerPackPreviewGridItem(account: account, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isVerified: self.isVerified) } - - } private struct StickerPackPreviewGridTransaction { diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 194154f69a..3610c24fe3 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -761,6 +761,9 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { animationNode = current } else { animationNode = AnimatedStickerNode() + animationNode.started = { [weak self] in + self?.removePlaceholder(animated: false) + } strongSelf.animationNode = animationNode strongSelf.addSubnode(animationNode) 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 a46eaaf720..67fda6a816 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -211,6 +211,9 @@ if (strongController == nil) return; + if (strongController->_toolbarView.superview == nil) + return; + UIView *toolbarView = strongController->_toolbarView; if (enabled) { @@ -271,7 +274,11 @@ [groupsController setIsFirstInStack:true]; [pickerController setIsFirstInStack:false]; - [assetsController setViewControllers:@[ groupsController, pickerController ]]; + if (intent == TGMediaAssetsControllerSendMediaIntent) { + [assetsController setViewControllers:@[ pickerController ]]; + } else { + [assetsController setViewControllers:@[ groupsController, pickerController ]]; + } ((TGNavigationBar *)assetsController.navigationBar).navigationController = assetsController; assetsController.recipientName = recipientName; @@ -611,7 +618,8 @@ [strongSelf groupPhotosPressed]; }; } - [self.view addSubview:_toolbarView]; + if (_intent != TGMediaAssetsControllerSendMediaIntent) + [self.view addSubview:_toolbarView]; if (@available(iOS 14.0, *)) { if ([PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite] == PHAuthorizationStatusLimited) { @@ -1386,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 @@ -1412,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 1d1d74287c..55fb746f20 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -89,7 +89,11 @@ _assetGroup = assetGroup; _intent = intent; - [self setTitle:_assetGroup.title]; + if (_intent == TGMediaAssetsControllerSendMediaIntent) { + [self setTitle:TGLocalized(@"Attachment.Gallery")]; + } else { + [self setTitle:assetGroup.title]; + } _assetsDisposable = [[SMetaDisposable alloc] init]; } @@ -152,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) @@ -167,8 +174,11 @@ if (strongSelf->_assetGroup == nil) strongSelf->_assetGroup = assetGroup; - [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) { @@ -190,13 +200,16 @@ { 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->_collectionView setContentOffset:CGPointMake(0.0, -strongSelf->_collectionView.contentInset.top) animated:false]; + } else if (scrollToBottom) { [strongSelf->_collectionView layoutSubviews]; [strongSelf _adjustContentOffsetToBottom]; } @@ -219,6 +232,7 @@ [super viewWillAppear:animated]; [self setup3DTouch]; + [self setLeftBarButtonItem:[(TGMediaAssetsController *)self.navigationController leftBarButtonItem]]; [self setRightBarButtonItem:[(TGMediaAssetsController *)self.navigationController rightBarButtonItem]]; } @@ -369,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/TGNavigationBar.m b/submodules/LegacyComponents/Sources/TGNavigationBar.m index fb338ba368..7f765de697 100644 --- a/submodules/LegacyComponents/Sources/TGNavigationBar.m +++ b/submodules/LegacyComponents/Sources/TGNavigationBar.m @@ -326,7 +326,7 @@ static id _musicPlayerProvider; } [super setFrame:frame]; - + if (_statusBarBackgroundView != nil && _statusBarBackgroundView.superview != nil) { _statusBarBackgroundView.frame = CGRectMake(0, -self.frame.origin.y, self.frame.size.width, 20); 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 7d7570ee92..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 @@ -453,6 +458,10 @@ 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: CGSize(width: layout.size.width - self.parentInsets.left - self.parentInsets.right, height: layout.size.height)) + } + self.controllerNode.controllerView = self.legacyController.view if let legacyController = self.legacyController as? TGViewController { legacyController.ignoreAppearEvents = true @@ -554,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/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index 65c11c9f0d..57e5326505 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -831,7 +831,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } } - let topInset: CGFloat = floor((layout.size.height - navigationHeight) / 2.0 + navigationHeight) +// let topInset: CGFloat = floor((layout.size.height - navigationHeight) / 2.0 + navigationHeight) + let topInset: CGFloat = 240.0 let overlap: CGFloat = 6.0 let headerHeight: CGFloat if isPickingLocation, let actionHeight = actionHeight { 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/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 7263d527bf..66360a870a 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -65,6 +65,8 @@ public enum PresentationResourceKey: Int32 { case itemListBlockDestructiveIcon case itemListAddDeviceIcon case itemListResetIcon + case itemListImageIcon + case itemListCloudIcon case itemListVoiceCallIcon case itemListVideoCallIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 9c342181d0..361f10141e 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -222,6 +222,18 @@ public struct PresentationResourcesItemList { }) } + public static func imageIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.itemListImageIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Image"), color: theme.list.itemAccentColor) + }) + } + + public static func cloudIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.itemListCloudIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Cloud"), color: theme.list.itemAccentColor) + }) + } + public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? { if !top && !bottom { return nil diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index e70273fb5b..1cb00bb78c 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -257,6 +257,12 @@ swift_library( "//submodules/SoftwareVideo:SoftwareVideo", "//submodules/ManagedFile:ManagedFile", "//submodules/FetchManagerImpl:FetchManagerImpl", + "//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/Images.xcassets/Chat/Attach Menu/Camera.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Camera.imageset/Contents.json new file mode 100644 index 0000000000..806935b949 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Camera.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Property 1=Camera.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Camera.imageset/Property 1=Camera.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Camera.imageset/Property 1=Camera.pdf new file mode 100644 index 0000000000..429bee41ab --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Camera.imageset/Property 1=Camera.pdf @@ -0,0 +1,118 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 14.000000 17.000000 cm +1.000000 1.000000 1.000000 scn +0.000000 16.899923 m +0.000000 17.921806 0.000000 18.432747 0.062100 18.861015 c +0.443433 21.490837 2.509163 23.556566 5.138984 23.937901 c +5.567253 24.000000 6.078194 24.000000 7.100077 24.000000 c +7.197224 24.000000 l +7.389089 24.000000 7.485021 24.000000 7.575428 24.005465 c +8.449500 24.058290 9.256863 24.490376 9.785665 25.188347 c +9.840356 25.260532 9.893565 25.340347 9.999977 25.499966 c +10.000000 25.500000 l +10.000046 25.500069 l +10.000060 25.500093 l +10.106449 25.659672 10.159652 25.739475 10.214336 25.811653 c +10.743138 26.509623 11.550501 26.941710 12.424573 26.994535 c +12.514979 27.000000 12.610912 27.000000 12.802777 27.000000 c +19.197224 27.000000 l +19.389088 27.000000 19.485020 27.000000 19.575428 26.994535 c +20.449501 26.941710 21.256863 26.509623 21.785666 25.811653 c +21.840359 25.739460 21.893574 25.659641 22.000000 25.500000 c +22.106426 25.340359 22.159641 25.260540 22.214334 25.188347 c +22.743137 24.490377 23.550499 24.058289 24.424572 24.005465 c +24.514980 24.000000 24.610912 24.000000 24.802776 24.000000 c +24.899923 24.000000 l +25.921803 24.000000 26.432743 24.000000 26.861012 23.937901 c +29.490833 23.556566 31.556562 21.490837 31.937897 18.861015 c +31.999996 18.432747 31.999996 17.921806 31.999996 16.899923 c +31.999996 9.600000 l +31.999996 6.239685 31.999996 4.559526 31.346035 3.276056 c +30.770796 2.147085 29.852911 1.229200 28.723940 0.653961 c +27.440470 0.000000 25.760311 0.000000 22.399996 0.000000 c +9.599999 0.000000 l +6.239685 0.000000 4.559527 0.000000 3.276057 0.653961 c +2.147084 1.229200 1.229201 2.147085 0.653961 3.276056 c +0.000000 4.559526 0.000000 6.239685 0.000000 9.600000 c +0.000000 16.899923 l +h +16.000000 16.500000 m +13.514719 16.500000 11.500000 14.485281 11.500000 12.000000 c +11.500000 9.514719 13.514719 7.500000 16.000000 7.500000 c +18.485281 7.500000 20.500000 9.514719 20.500000 12.000000 c +20.500000 14.485281 18.485281 16.500000 16.000000 16.500000 c +h +8.500000 12.000000 m +8.500000 16.142136 11.857864 19.500000 16.000000 19.500000 c +20.142136 19.500000 23.500000 16.142136 23.500000 12.000000 c +23.500000 7.857864 20.142136 4.500000 16.000000 4.500000 c +11.857864 4.500000 8.500000 7.857864 8.500000 12.000000 c +h +26.000000 16.000000 m +27.104570 16.000000 28.000000 16.895432 28.000000 18.000000 c +28.000000 19.104568 27.104570 20.000000 26.000000 20.000000 c +24.895430 20.000000 24.000000 19.104568 24.000000 18.000000 c +24.000000 16.895432 24.895430 16.000000 26.000000 16.000000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2589 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 60.000000 60.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002679 00000 n +0000002702 00000 n +0000002875 00000 n +0000002949 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3008 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cloud.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cloud.imageset/Contents.json new file mode 100644 index 0000000000..8b6a066c0c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cloud.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cloud_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cloud.imageset/cloud_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cloud.imageset/cloud_30.pdf new file mode 100644 index 0000000000..3fb0c04ca6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cloud.imageset/cloud_30.pdf @@ -0,0 +1,213 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.500000 5.569031 cm +0.000000 0.000000 0.000000 scn +20.853670 11.223368 m +20.197720 11.332699 l +20.167723 11.152725 20.212988 10.968333 20.322916 10.822711 c +20.432844 10.677088 20.597776 10.583029 20.779081 10.562564 c +20.853670 11.223368 l +h +20.858553 11.252664 m +21.514503 11.143333 l +21.520527 11.179465 21.523552 11.216033 21.523552 11.252664 c +20.858553 11.252664 l +h +15.873398 17.663544 m +16.037714 18.307922 l +16.037458 18.307989 l +15.873398 17.663544 l +h +8.432284 14.406836 m +8.123861 13.817684 l +8.445732 13.649182 8.843261 13.770190 9.016658 14.089451 c +8.432284 14.406836 l +h +3.232294 11.457733 m +3.459931 10.832909 l +3.709621 10.923876 3.881181 11.154763 3.896227 11.420082 c +3.232294 11.457733 l +h +20.345879 1.926858 m +20.345879 1.261856 l +20.347536 1.261860 l +20.345879 1.926858 l +h +21.509621 11.114037 m +21.514503 11.143333 l +20.202602 11.361995 l +20.197720 11.332699 l +21.509621 11.114037 l +h +21.523552 11.252664 m +21.523552 14.584707 19.261030 17.485977 16.037714 18.307922 c +15.709081 17.019163 l +18.344908 16.347027 20.193552 13.975069 20.193552 11.252664 c +21.523552 11.252664 l +h +16.037458 18.307989 m +12.807217 19.130329 9.437991 17.651901 7.847911 14.724220 c +9.016658 14.089451 l +10.317089 16.483826 13.070669 17.690840 15.709337 17.019098 c +16.037458 18.307989 l +h +8.740708 14.995987 m +6.028514 16.415833 2.742277 14.562114 2.568361 11.495386 c +3.896227 11.420082 l +4.015268 13.519165 6.265923 14.790322 8.123861 13.817684 c +8.740708 14.995987 l +h +3.004657 12.082559 m +0.798734 11.278893 -0.665000 9.183575 -0.665000 6.843657 c +0.665000 6.843657 l +0.665000 8.624672 1.779289 10.220613 3.459931 10.832909 c +3.004657 12.082559 l +h +-0.665000 6.843657 m +-0.665000 3.761652 1.834792 1.261858 4.916798 1.261858 c +4.916798 2.591858 l +2.569331 2.591858 0.665000 4.496190 0.665000 6.843657 c +-0.665000 6.843657 l +h +4.916798 1.261858 m +20.345879 1.261858 l +20.345879 2.591858 l +4.916798 2.591858 l +4.916798 1.261858 l +h +20.347536 1.261860 m +27.062166 1.278605 27.596050 11.131536 20.928259 11.884171 c +20.779081 10.562564 l +25.790516 9.996892 25.377172 2.604407 20.344219 2.591856 c +20.347536 1.261860 l +h +f +n +Q + +endstream +endobj + +2 0 obj + 2195 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 30.000000 m +30.000000 30.000000 l +30.000000 0.000000 l +0.000000 0.000000 l +0.000000 30.000000 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 232 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Pages 9 0 R + /Type /Catalog + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000002453 00000 n +0000002476 00000 n +0000002956 00000 n +0000002978 00000 n +0000003276 00000 n +0000003378 00000 n +0000003399 00000 n +0000003572 00000 n +0000003646 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +3706 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contact.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contact.imageset/Contents.json new file mode 100644 index 0000000000..a406bd6cae --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contact.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Union-2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contact.imageset/Union-2.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contact.imageset/Union-2.pdf new file mode 100644 index 0000000000..707375b99e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contact.imageset/Union-2.pdf @@ -0,0 +1,79 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +1.000000 1.000000 1.000000 scn +12.473795 18.000000 m +15.787504 18.000000 18.473795 20.686291 18.473795 24.000000 c +18.473795 27.313709 15.787504 30.000000 12.473795 30.000000 c +9.160086 30.000000 6.473795 27.313709 6.473795 24.000000 c +6.473795 20.686291 9.160086 18.000000 12.473795 18.000000 c +h +12.473970 14.000000 m +17.620707 14.000000 22.118784 11.222771 24.551340 7.085182 c +25.277521 5.850006 24.987329 4.309250 23.837667 3.454098 c +21.863237 1.985462 18.145235 0.000000 12.473970 0.000000 c +6.802708 0.000000 3.084706 1.985462 1.110274 3.454098 c +-0.039390 4.309246 -0.329582 5.850006 0.396601 7.085182 c +2.829157 11.222771 7.327235 14.000000 12.473970 14.000000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 770 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.947937 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000860 00000 n +0000000882 00000 n +0000001055 00000 n +0000001129 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1188 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/File.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/File.imageset/Contents.json new file mode 100644 index 0000000000..3b0c6903e1 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/File.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Subtract.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/File.imageset/Subtract.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/File.imageset/Subtract.pdf new file mode 100644 index 0000000000..ac014ededf --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/File.imageset/Subtract.pdf @@ -0,0 +1,96 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +1.000000 1.000000 1.000000 scn +14.400000 0.000000 m +9.600000 0.000000 l +6.239685 0.000000 4.559527 0.000000 3.276057 0.653961 c +2.147084 1.229200 1.229201 2.147085 0.653961 3.276056 c +0.000000 4.559526 0.000000 6.239685 0.000000 9.600000 c +0.000000 20.400002 l +0.000000 23.760315 0.000000 25.440472 0.653961 26.723944 c +1.229201 27.852915 2.147084 28.770800 3.276057 29.346039 c +4.559527 30.000000 6.239685 30.000000 9.600000 30.000000 c +11.341846 30.000000 l +12.345142 30.000000 12.846791 30.000000 13.317464 29.884233 c +13.734708 29.781607 14.132692 29.612423 14.496114 29.383186 c +14.906072 29.124596 15.254162 28.763371 15.950342 28.040918 c +22.208498 21.546604 l +22.208509 21.546591 l +22.870174 20.859959 23.201010 20.516640 23.437363 20.119482 c +23.646940 19.767319 23.801153 19.385040 23.894608 18.986031 c +24.000000 18.536037 24.000000 18.059254 24.000000 17.105690 c +24.000000 9.600000 l +24.000000 6.239685 24.000000 4.559526 23.346039 3.276056 c +22.770800 2.147085 21.852915 1.229200 20.723944 0.653961 c +19.440474 0.000000 17.760315 0.000000 14.400000 0.000000 c +h +21.023287 16.875000 m +14.187500 16.875000 l +13.082931 16.875000 12.187500 17.770432 12.187500 18.875000 c +12.187500 25.710787 l +12.187500 26.601692 13.264641 27.047859 13.894606 26.417894 c +21.730392 18.582108 l +22.360359 17.952141 21.914192 16.875000 21.023287 16.875000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1447 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001537 00000 n +0000001560 00000 n +0000001733 00000 n +0000001807 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1866 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Gallery.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Gallery.imageset/Contents.json new file mode 100644 index 0000000000..4171612590 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Gallery.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Union.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Gallery.imageset/Union.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Gallery.imageset/Union.pdf new file mode 100644 index 0000000000..f4bcf9be8b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Gallery.imageset/Union.pdf @@ -0,0 +1,123 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 -0.000050 cm +1.000000 1.000000 1.000000 scn +11.100000 28.000050 m +11.035790 28.000050 l +9.410112 28.000065 8.098883 28.000076 7.037065 27.913322 c +5.943791 27.823997 4.983509 27.635279 4.095072 27.182598 c +2.683856 26.463547 1.536502 25.316193 0.817452 23.904978 c +0.364771 23.016541 0.176052 22.056259 0.086728 20.962984 c +-0.000027 19.901167 -0.000015 18.589939 0.000000 16.964260 c +0.000000 16.900049 l +0.000000 11.100050 l +0.000000 11.035839 l +-0.000015 9.410160 -0.000027 8.098932 0.086728 7.037113 c +0.176052 5.943840 0.364771 4.983559 0.817452 4.095121 c +1.536502 2.683905 2.683856 1.536551 4.095072 0.817501 c +4.983509 0.364819 5.943791 0.176100 7.037065 0.086777 c +8.098874 0.000023 9.410091 0.000034 11.035753 0.000050 c +11.100000 0.000050 l +16.900000 0.000050 l +16.964247 0.000050 l +18.589909 0.000034 19.901125 0.000023 20.962936 0.086777 c +22.056210 0.176100 23.016491 0.364819 23.904928 0.817501 c +25.316145 1.536551 26.463499 2.683905 27.182549 4.095121 c +27.635231 4.983559 27.823950 5.943840 27.913273 7.037113 c +28.000027 8.098925 28.000015 9.410141 28.000000 11.035803 c +28.000000 11.100050 l +28.000000 16.900049 l +28.000000 16.964296 l +28.000015 18.589958 28.000027 19.901175 27.913273 20.962984 c +27.823950 22.056259 27.635231 23.016541 27.182549 23.904978 c +26.463499 25.316193 25.316145 26.463547 23.904928 27.182598 c +23.016491 27.635279 22.056210 27.823997 20.962936 27.913322 c +19.901117 28.000076 18.589890 28.000065 16.964211 28.000050 c +16.900000 28.000050 l +11.100000 28.000050 l +h +3.093551 7.093601 m +3.170904 6.300156 3.303499 5.824047 3.490471 5.457092 c +3.921902 4.610363 4.610314 3.921951 5.457044 3.490520 c +5.852077 3.289240 6.373610 3.150980 7.281361 3.076813 c +8.206622 3.001217 9.395091 3.000050 11.100000 3.000050 c +16.900000 3.000050 l +18.604908 3.000050 19.793379 3.001217 20.718641 3.076813 c +21.626392 3.150980 22.147924 3.289240 22.542957 3.490520 c +23.389687 3.921951 24.078098 4.610363 24.509529 5.457092 c +24.710810 5.852125 24.849070 6.373657 24.923237 7.281408 c +24.963232 7.770926 24.982393 8.334116 24.991571 9.008478 c +19.060661 14.939390 l +18.474873 15.525175 17.525127 15.525177 16.939339 14.939390 c +11.000000 9.000050 l +9.060661 10.939388 l +8.474873 11.525177 7.525128 11.525177 6.939342 10.939392 c +3.093551 7.093601 l +h +9.000000 16.000050 m +10.656855 16.000050 12.000000 17.343195 12.000000 19.000050 c +12.000000 20.656902 10.656855 22.000050 9.000000 22.000050 c +7.343146 22.000050 6.000001 20.656902 6.000001 19.000050 c +6.000001 17.343195 7.343146 16.000050 9.000000 16.000050 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2627 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002717 00000 n +0000002740 00000 n +0000002913 00000 n +0000002987 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3046 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Image.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Image.imageset/Contents.json new file mode 100644 index 0000000000..e11ef0c09b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "image_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Image.imageset/image_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Image.imageset/image_30.pdf new file mode 100644 index 0000000000..5674e8680a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Image.imageset/image_30.pdf @@ -0,0 +1,160 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.335022 5.335083 cm +0.000000 0.000000 0.000000 scn +7.065000 19.329956 m +7.035460 19.329956 l +5.940372 19.329960 5.077796 19.329964 4.383656 19.273251 c +3.675523 19.215395 3.084329 19.095215 2.547134 18.821501 c +1.669358 18.374252 0.955704 17.660599 0.508455 16.782822 c +0.234740 16.245626 0.114562 15.654433 0.056705 14.946301 c +-0.000008 14.252161 -0.000005 13.389584 0.000000 12.294496 c +0.000000 12.264956 l +0.000000 7.064956 l +0.000000 7.035416 l +-0.000005 5.940329 -0.000008 5.077752 0.056705 4.383612 c +0.114562 3.675479 0.234740 3.084284 0.508455 2.547091 c +0.955704 1.669313 1.669358 0.955660 2.547134 0.508410 c +3.084329 0.234695 3.675523 0.114517 4.383656 0.056660 c +5.077746 -0.000051 5.940250 -0.000048 7.035228 -0.000044 c +7.035275 -0.000044 l +7.035323 -0.000044 l +7.035370 -0.000044 l +7.065000 -0.000044 l +12.265000 -0.000044 l +12.294630 -0.000044 l +12.294676 -0.000044 l +12.294722 -0.000044 l +12.294767 -0.000044 l +13.389750 -0.000048 14.252252 -0.000051 14.946344 0.056660 c +15.654477 0.114517 16.245672 0.234695 16.782866 0.508410 c +17.660643 0.955660 18.374296 1.669313 18.821547 2.547091 c +19.095261 3.084284 19.215439 3.675479 19.273296 4.383612 c +19.330008 5.077704 19.330004 5.940207 19.330000 7.035190 c +19.330000 7.035235 l +19.330000 7.035281 l +19.330000 7.035327 l +19.330000 7.064957 l +19.330000 12.264956 l +19.330000 12.294586 l +19.330000 12.294634 l +19.330000 12.294681 l +19.330000 12.294727 l +19.330004 13.389706 19.330008 14.252210 19.273296 14.946301 c +19.215439 15.654433 19.095261 16.245626 18.821547 16.782822 c +18.374296 17.660599 17.660643 18.374252 16.782866 18.821501 c +16.245672 19.095215 15.654477 19.215395 14.946344 19.273251 c +14.252204 19.329964 13.389627 19.329960 12.294539 19.329956 c +12.264999 19.329956 l +7.065000 19.329956 l +h +3.150942 17.636463 m +3.469394 17.798721 3.866076 17.896530 4.491960 17.947668 c +5.125607 17.999439 5.933921 17.999956 7.065000 17.999956 c +12.264999 17.999956 l +13.396078 17.999956 14.204392 17.999439 14.838039 17.947668 c +15.463923 17.896530 15.860606 17.798721 16.179058 17.636463 c +16.806580 17.316725 17.316771 16.806536 17.636507 16.179014 c +17.798767 15.860562 17.896576 15.463881 17.947712 14.837996 c +17.999483 14.204350 18.000000 13.396034 18.000000 12.264956 c +18.000000 7.064957 l +18.000000 6.309554 17.999769 5.698115 17.984194 5.186526 c +15.466902 7.867050 l +14.693245 8.690873 13.378143 8.669041 12.632261 7.819986 c +11.520865 6.554860 l +6.709533 11.633489 l +5.937637 12.448267 4.633699 12.427576 3.888045 11.588715 c +1.330000 8.710914 l +1.330000 12.264956 l +1.330000 13.396034 1.330517 14.204349 1.382288 14.837996 c +1.433425 15.463881 1.531234 15.860562 1.693493 16.179014 c +2.013231 16.806536 2.523421 17.316725 3.150942 17.636463 c +h +14.497394 6.956580 m +17.768957 3.472878 l +17.730608 3.355118 17.686632 3.249275 17.636507 3.150898 c +17.316771 2.523376 16.806580 2.013186 16.179058 1.693449 c +16.167240 1.687428 16.155315 1.681494 16.143274 1.675650 c +12.439418 5.585277 l +13.631460 6.942204 l +13.859314 7.201574 14.261054 7.208245 14.497394 6.956580 c +h +1.330082 6.709091 m +1.330711 5.761070 1.336117 5.057030 1.382288 4.491917 c +1.433425 3.866033 1.531234 3.469350 1.693493 3.150898 c +2.013231 2.523376 2.523421 2.013186 3.150942 1.693449 c +3.469394 1.531189 3.866076 1.433380 4.491960 1.382244 c +5.125607 1.330473 5.933922 1.329956 7.065000 1.329956 c +12.265000 1.329956 l +13.261761 1.329956 14.007863 1.330357 14.604662 1.365883 c +5.744016 10.718788 l +5.508215 10.967690 5.109884 10.961369 4.882100 10.705111 c +1.330082 6.709091 l +h +14.040000 12.164956 m +15.075534 12.164956 15.915000 13.004422 15.915000 14.039956 c +15.915000 15.075490 15.075534 15.914956 14.040000 15.914956 c +13.004466 15.914956 12.165000 15.075490 12.165000 14.039956 c +12.165000 13.004422 13.004466 12.164956 14.040000 12.164956 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3879 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003969 00000 n +0000003992 00000 n +0000004165 00000 n +0000004239 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4298 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Location.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Location.imageset/Contents.json new file mode 100644 index 0000000000..730276db9c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Location.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Subtract-2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Location.imageset/Subtract-2.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Location.imageset/Subtract-2.pdf new file mode 100644 index 0000000000..a72085cb96 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Location.imageset/Subtract-2.pdf @@ -0,0 +1,77 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +1.000000 1.000000 1.000000 scn +12.000000 0.000000 m +15.627419 0.000000 24.000000 11.372581 24.000000 20.000000 c +24.000000 26.627417 18.627419 32.000000 12.000000 32.000000 c +5.372583 32.000000 0.000000 26.627417 0.000000 20.000000 c +0.000000 11.372581 8.372583 0.000000 12.000000 0.000000 c +h +12.000000 15.000000 m +14.761425 15.000000 17.000000 17.238577 17.000000 20.000000 c +17.000000 22.761423 14.761425 25.000000 12.000000 25.000000 c +9.238577 25.000000 7.000000 22.761423 7.000000 20.000000 c +7.000000 17.238577 9.238577 15.000000 12.000000 15.000000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 656 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 32.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000746 00000 n +0000000768 00000 n +0000000941 00000 n +0000001015 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1074 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Poll.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Poll.imageset/Contents.json new file mode 100644 index 0000000000..1fab2f6057 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Poll.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Union.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Poll.imageset/Union.png b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Poll.imageset/Union.png new file mode 100644 index 0000000000..728dd1f6ac Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Poll.imageset/Union.png differ 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 new file mode 100644 index 0000000000..d5ce069db7 --- /dev/null +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -0,0 +1,144 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import LegacyComponents +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import AccountContext +import ItemListPeerActionItem +import AttachmentUI + +private final class AttachmentFileControllerArguments { + let openGallery: () -> Void + let openFiles: () -> Void + + init(openGallery: @escaping () -> Void, openFiles: @escaping () -> Void) { + self.openGallery = openGallery + self.openFiles = openFiles + } +} + +private enum AttachmentFileSection: Int32 { + case select + case recent +} + +private enum AttachmentFileEntry: ItemListNodeEntry { + case selectFromGallery(PresentationTheme, String) + case selectFromFiles(PresentationTheme, String) + + case recentHeader(PresentationTheme, String) + + var section: ItemListSectionId { + switch self { + case .selectFromGallery, .selectFromFiles: + return AttachmentFileSection.select.rawValue + case .recentHeader: + return AttachmentFileSection.recent.rawValue + } + } + + var stableId: Int32 { + switch self { + case .selectFromGallery: + return 0 + case .selectFromFiles: + return 1 + case .recentHeader: + return 2 + } + } + + static func ==(lhs: AttachmentFileEntry, rhs: AttachmentFileEntry) -> Bool { + switch lhs { + case let .selectFromGallery(lhsTheme, lhsText): + if case let .selectFromGallery(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .selectFromFiles(lhsTheme, lhsText): + if case let .selectFromFiles(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .recentHeader(lhsTheme, lhsText): + if case let .recentHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + } + } + + static func <(lhs: AttachmentFileEntry, rhs: AttachmentFileEntry) -> Bool { + return lhs.stableId < rhs.stableId + } + + func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { + let arguments = arguments as! AttachmentFileControllerArguments + switch self { + case let .selectFromGallery(_, text): + return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.imageIcon(presentationData.theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { + arguments.openGallery() + }) + case let .selectFromFiles(_, text): + return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.cloudIcon(presentationData.theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { + arguments.openFiles() + }) + case let .recentHeader(_, text): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) + } + } +} + +private func attachmentFileControllerEntries(presentationData: PresentationData) -> [AttachmentFileEntry] { + var entries: [AttachmentFileEntry] = [] + + 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())) + + return entries +} + +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)? + let arguments = AttachmentFileControllerArguments(openGallery: { + presentGallery() + }, openFiles: { + presentFiles() + }) + + let signal = context.sharedContext.presentationData + |> deliverOnMainQueue + |> map { presentationData -> (ItemListControllerState, (ItemListNodeState, Any)) in + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Attachment_File), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + dismissImpl?() + }), rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: attachmentFileControllerEntries(presentationData: presentationData), style: .blocks, emptyStateItem: nil, animateChanges: false) + + return (controllerState, (listState, arguments)) + } |> afterDisposed { + actionsDisposable.dispose() + } + + let controller = AttachmentFileControllerImpl(context: context, state: signal) + dismissImpl = { [weak controller] in + controller?.dismiss(animated: true) + } + return controller +} 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 5d36a6bc7e..375de93813 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -68,22 +68,17 @@ import CalendarMessageScreen 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) @@ -176,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, *) @@ -2988,7 +2978,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { - strongSelf.presentPollCreation(isQuiz: isQuiz) + if let controller = strongSelf.configurePollCreation(isQuiz: isQuiz) { + strongSelf.effectiveNavigationController?.pushViewController(controller) + } }) }, displayPollSolution: { [weak self] solution, sourceNode in self?.displayPollSolution(solution: solution, sourceNode: sourceNode, isAutomatic: false) @@ -3174,7 +3166,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) } else { - strongSelf.presentMediaPicker(fileMode: false, editingMedia: true, completion: { signals, _, _ in + strongSelf.presentMediaPicker(fileMode: false, editingMedia: true, present: { [weak self] c, _ in + self?.effectiveNavigationController?.pushViewController(c) + }, completion: { signals, _, _ in self?.interfaceInteraction?.setupEditMessage(messageId, { _ in }) self?.editMessageMediaWithLegacySignals(signals) }) @@ -5785,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) @@ -10210,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 @@ -10235,7 +10228,309 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return inputPanelNode } + private func openCamera() { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + + var photoOnly = false + if let callManager = self.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall { + photoOnly = true + } + + let storeEditedPhotos = false + 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: 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() + } + } + }, recognizedQRCode: { [weak self] code in + if let strongSelf = self { + if let (host, port, username, password, secret) = parseProxyUrl(code) { + strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil) + } + } + }, presentSchedulePicker: { [weak self] done in + if let strongSelf = self { + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + strongSelf.openScheduledMessages() + } + } + }) + } + }, presentTimerPicker: { [weak self] done in + if let strongSelf = self { + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) + }) + } + }, presentStickers: { [weak self] completion in + if let strongSelf = self { + let controller = DrawingStickersScreen(context: strongSelf.context, selectSticker: { fileReference, node, rect in + completion(fileReference.media, fileReference.media.isAnimatedSticker || fileReference.media.isVideoSticker, node.view, rect) + return true + }) + strongSelf.present(controller, in: .window(.root)) + return controller + } else { + return nil + } + }, getCaptionPanelView: { [weak self] in + return self?.getCaptionPanelView() + }) + } + private func presentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + self.chatDisplayNode.dismissInput() + + let currentLocationController = Atomic(value: nil) + + 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, nil) + attachmentController?.dismiss(animated: true) + strongSelf.openCamera() + strongSelf.controllerNavigationDisposable.set(nil) + case .gallery: + 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) + case .file: + let controller = attachmentFileController(context: strongSelf.context, presentGallery: { [weak self, weak attachmentController] in + attachmentController?.dismiss(animated: true) + self?.presentFileGallery() + }, presentFiles: { [weak self, weak attachmentController] in + attachmentController?.dismiss(animated: true) + self?.presentICloudFileGallery() + }) + completion(controller, nil) + strongSelf.controllerNavigationDisposable.set(nil) + case .location: + strongSelf.controllerNavigationDisposable.set(nil) + let existingController = currentLocationController.with { $0 } + if let controller = existingController { + completion(controller, nil) + return + } + let selfPeerId: PeerId + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + selfPeerId = peer.id + } else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) { + selfPeerId = peer.id + } else { + selfPeerId = strongSelf.context.account.peerId + } + let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(selfPeerId) + } + |> deliverOnMainQueue).start(next: { [weak self] selfPeer in + guard let strongSelf = self, let selfPeer = selfPeer else { + return + } + let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages + let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _ in + guard let strongSelf = self else { + return + } + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }, nil) + strongSelf.sendMessages([message]) + }) + 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, nil) + strongSelf.controllerNavigationDisposable.set((contactsController.result + |> deliverOnMainQueue).start(next: { [weak self] peers in + if let strongSelf = self, let (peers, _) = peers { + if peers.count > 1 { + var enqueueMessages: [EnqueueMessage] = [] + for peer in peers { + var media: TelegramMediaContact? + switch peer { + case let .peer(contact, _, _): + guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + continue + } + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) + case let .deviceContact(_, basicData): + guard !basicData.phoneNumbers.isEmpty else { + continue + } + let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) + } + + if let media = media { + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }, nil) + let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) + enqueueMessages.append(message) + } + } + strongSelf.sendMessages(enqueueMessages) + } else if let peer = peers.first { + let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> + switch peer { + case let .peer(contact, _, _): + guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + return + } + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + let context = strongSelf.context + dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:])) + |> take(1) + |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in + var stableId: String? + let queryPhoneNumber = formatPhoneNumber(phoneNumber) + outer: for (id, data) in basicData { + for phoneNumber in data.phoneNumbers { + if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber { + stableId = id + break outer + } + } + } + + if let stableId = stableId { + return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) + |> take(1) + |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + return (contact, extendedData) + } + } else { + return .single((contact, contactData)) + } + } + case let .deviceContact(id, _): + dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) + |> take(1) + |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + return (nil, extendedData) + } + } + strongSelf.controllerNavigationDisposable.set((dataSignal + |> deliverOnMainQueue).start(next: { peerAndContactData in + if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 { + if contactData.isPrimitive { + let phone = contactData.basicData.phoneNumbers[0].value + let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil) + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }, nil) + let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) + strongSelf.sendMessages([message]) + } else { + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { + return + } + let phone = contactData.basicData.phoneNumbers[0].value + if let vCardData = contactData.serializedVCard() { + let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData) + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }, nil) + let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) + strongSelf.sendMessages([message]) + } + }), completed: nil, cancelled: nil) + strongSelf.effectiveNavigationController?.pushViewController(contactController) + } + } + })) + } + } + })) + case .poll: + let controller = strongSelf.configurePollCreation() + completion(controller, nil) + strongSelf.controllerNavigationDisposable.set(nil) + case .app: + return + } + } + self.present(attachmentController, in: .window(.root)) + } + + private func oldPresentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) { let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) return entry ?? GeneratedMediaStoreSettings.defaultSettings @@ -10284,7 +10579,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if canSendPolls { items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.AttachmentMenu_Poll, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() - self?.presentPollCreation() + if let controller = self?.configurePollCreation() { + self?.effectiveNavigationController?.pushViewController(controller) + } })) } items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Contact, color: .accent, action: { [weak actionSheet] in @@ -10331,7 +10628,9 @@ 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, completion: { signals, silentPosting, scheduleTime 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 { strongSelf.clearInputText() } @@ -10406,7 +10705,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openContacts: { self?.presentContactPicker() }, openPoll: { - self?.presentPollCreation() + if let controller = self?.configurePollCreation() { + self?.effectiveNavigationController?.pushViewController(controller) + } }, presentSelectionLimitExceeded: { guard let strongSelf = self else { return @@ -10512,102 +10813,112 @@ 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?.effectiveNavigationController?.pushViewController(c) + }, completion: { [weak self] signals, silentPosting, scheduleTime in + if editingMessage { + self?.editMessageMediaWithLegacySignals(signals) + } else { + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) + } + }) + } + + private func presentICloudFileGallery(editingMessage: Bool = false) { + self.present(legacyICloudFilePicker(theme: self.presentationData.theme, completion: { [weak self] urls in + if let strongSelf = self, !urls.isEmpty { + var signals: [Signal] = [] + for url in urls { + signals.append(iCloudFileDescription(url)) + } + strongSelf.enqueueMediaMessageDisposable.set((combineLatest(signals) + |> deliverOnMainQueue).start(next: { results in + if let strongSelf = self { + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + + for item in results { + if let item = item, item.fileSize > 2000 * 1024 * 1024 { + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Conversation_UploadFileTooLarge, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + return + } + } + + var groupingKey: Int64? + var fileTypes: (music: Bool, other: Bool) = (false, false) + if results.count > 1 { + for item in results { + if let item = item { + let pathExtension = (item.fileName as NSString).pathExtension.lowercased() + if ["mp3", "m4a"].contains(pathExtension) { + fileTypes.music = true + } else { + fileTypes.other = true + } + } + } + } + if fileTypes.music != fileTypes.other { + groupingKey = Int64.random(in: Int64.min ... Int64.max) + } + + var messages: [EnqueueMessage] = [] + for item in results { + if let item = item { + let fileId = Int64.random(in: Int64.min ... Int64.max) + let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension) + var previewRepresentations: [TelegramMediaImageRepresentation] = [] + if mimeType.hasPrefix("image/") || mimeType == "application/pdf" { + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil)) + } + var attributes: [TelegramMediaFileAttribute] = [] + attributes.append(.FileName(fileName: item.fileName)) + if let audioMetadata = item.audioMetadata { + attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) + } + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: item.fileSize, attributes: attributes) + let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: replyMessageId, localGroupingKey: groupingKey, correlationId: nil) + messages.append(message) + } + if let _ = groupingKey, messages.count % 10 == 0 { + groupingKey = Int64.random(in: Int64.min ... Int64.max) + } + } + + if !messages.isEmpty { + if editingMessage { + strongSelf.editMessageMediaWithMessages(messages) + } else { + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }, nil) + strongSelf.sendMessages(messages) + } + } + } + })) + } + }), in: .window(.root)) + } + private func presentFileMediaPickerOptions(editingMessage: Bool) { let actionSheet = ActionSheetController(presentationData: self.presentationData) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: self.presentationData.strings.Conversation_FilePhotoOrVideo, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.presentMediaPicker(fileMode: true, editingMedia: editingMessage, completion: { signals, silentPosting, scheduleTime in - if editingMessage { - self?.editMessageMediaWithLegacySignals(signals) - } else { - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) - } - }) + strongSelf.presentFileGallery(editingMessage: editingMessage) } }), ActionSheetButtonItem(title: self.presentationData.strings.Conversation_FileICloudDrive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, completion: { urls in - if let strongSelf = self, !urls.isEmpty { - var signals: [Signal] = [] - for url in urls { - signals.append(iCloudFileDescription(url)) - } - strongSelf.enqueueMediaMessageDisposable.set((combineLatest(signals) - |> deliverOnMainQueue).start(next: { results in - if let strongSelf = self { - let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId - - for item in results { - if let item = item, item.fileSize > 2000 * 1024 * 1024 { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Conversation_UploadFileTooLarge, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - return - } - } - - var groupingKey: Int64? - var fileTypes: (music: Bool, other: Bool) = (false, false) - if results.count > 1 { - for item in results { - if let item = item { - let pathExtension = (item.fileName as NSString).pathExtension.lowercased() - if ["mp3", "m4a"].contains(pathExtension) { - fileTypes.music = true - } else { - fileTypes.other = true - } - } - } - } - if fileTypes.music != fileTypes.other { - groupingKey = Int64.random(in: Int64.min ... Int64.max) - } - - var messages: [EnqueueMessage] = [] - for item in results { - if let item = item { - let fileId = Int64.random(in: Int64.min ... Int64.max) - let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension) - var previewRepresentations: [TelegramMediaImageRepresentation] = [] - if mimeType.hasPrefix("image/") || mimeType == "application/pdf" { - previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil)) - } - var attributes: [TelegramMediaFileAttribute] = [] - attributes.append(.FileName(fileName: item.fileName)) - if let audioMetadata = item.audioMetadata { - attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) - } - - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: item.fileSize, attributes: attributes) - let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: replyMessageId, localGroupingKey: groupingKey, correlationId: nil) - messages.append(message) - } - if let _ = groupingKey, messages.count % 10 == 0 { - groupingKey = Int64.random(in: Int64.min ... Int64.max) - } - } - - if !messages.isEmpty { - if editingMessage { - strongSelf.editMessageMediaWithMessages(messages) - } else { - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } - }) - } - }, nil) - strongSelf.sendMessages(messages) - } - } - } - })) - } - }), in: .window(.root)) + strongSelf.presentICloudFileGallery(editingMessage: editingMessage) } }) ]), ActionSheetItemGroup(items: [ @@ -10619,7 +10930,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(fileMode: Bool, editingMedia: Bool, 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) @@ -10643,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 @@ -10651,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 @@ -10738,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() - strongSelf.effectiveNavigationController?.pushViewController(legacyController) + present(legacyController, LegacyAssetPickerContext(controller: controller)) } }) }) @@ -11266,41 +11578,42 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(tooltipScreen, in: .current) } - private func presentPollCreation(isQuiz: Bool? = nil) { - if let peer = self.presentationInterfaceState.renderedPeer?.peer { - self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), isQuiz: isQuiz, completion: { [weak self] poll in - guard let strongSelf = self else { - return - } - let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } - }) - } - }, nil) - let message: EnqueueMessage = .message( - text: "", - attributes: [], - mediaReference: .standalone(media: TelegramMediaPoll( - pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min ... Int64.max)), - publicity: poll.publicity, - kind: poll.kind, - text: poll.text, - options: poll.options, - correctAnswers: poll.correctAnswers, - results: poll.results, - isClosed: false, - deadlineTimeout: poll.deadlineTimeout - )), - replyToMessageId: nil, - localGroupingKey: nil, - correlationId: nil - ) - strongSelf.sendMessages([message.withUpdatedReplyToMessageId(replyMessageId)]) - })) + private func configurePollCreation(isQuiz: Bool? = nil) -> AttachmentContainable? { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return nil } + return createPollController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), isQuiz: isQuiz, completion: { [weak self] poll in + guard let strongSelf = self else { + return + } + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }, nil) + let message: EnqueueMessage = .message( + text: "", + attributes: [], + mediaReference: .standalone(media: TelegramMediaPoll( + pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min ... Int64.max)), + publicity: poll.publicity, + kind: poll.kind, + text: poll.text, + options: poll.options, + correctAnswers: poll.correctAnswers, + results: poll.results, + isClosed: false, + deadlineTimeout: poll.deadlineTimeout + )), + replyToMessageId: nil, + localGroupingKey: nil, + correlationId: nil + ) + strongSelf.sendMessages([message.withUpdatedReplyToMessageId(replyMessageId)]) + }) } func transformEnqueueMessages(_ messages: [EnqueueMessage]) -> [EnqueueMessage] { 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 2e733509e1..b4ef4f108d 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -27,6 +27,8 @@ import ReactionListContextMenuContent import TelegramUIPreferences import Translate import DebugSettingsUI +import ChatPresentationInterfaceState +import Pasteboard private struct MessageContextMenuData { let starStatus: Bool? @@ -291,17 +293,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 07b29069e3..b85904187c 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/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 761f4ab5e1..eafa243f3e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -292,6 +292,10 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } else if let type = webpage.type { switch type { + case "photo": + if webpage.displayUrl.hasPrefix("t.me/") { + actionTitle = item.presentationData.strings.Conversation_ViewMessage + } case "telegram_user": actionTitle = item.presentationData.strings.Conversation_UserSendMessage case "telegram_channel_request": diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 1ec99a8373..6232a0844f 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 42332314f1..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 @@ -91,6 +94,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.title = self.titleProducer(self.presentationData.strings) self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) self.scrollToTop = { [weak self] in if let strongSelf = self { @@ -115,10 +119,12 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } }) - self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in - self?.activateSearch() - }) - self.navigationBar?.setContentNode(self.searchContentNode, animated: false) + if !params.multipleSelection { + self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in + self?.activateSearch() + }) + self.navigationBar?.setContentNode(self.searchContentNode, animated: false) + } if params.multipleSelection { self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.beginSelection)) 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 1164f46a3a..ff30790408 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 ede0ddfc9f..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() + 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 8947d8c076..6e7f6fcca9 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 1f5db3883a..b1fc27e49a 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()