diff --git a/submodules/DeviceAccess/Sources/DeviceAccess.swift b/submodules/DeviceAccess/Sources/DeviceAccess.swift index 631b0e8712..fa9b2e1647 100644 --- a/submodules/DeviceAccess/Sources/DeviceAccess.swift +++ b/submodules/DeviceAccess/Sources/DeviceAccess.swift @@ -87,6 +87,10 @@ public final class DeviceAccess { return AVAudioSession.sharedInstance().recordPermission == .granted } + public static func isCameraAccessAuthorized() -> Bool { + return PGCamera.cameraAuthorizationStatus() == PGCameraAuthorizationStatusAuthorized + } + public static func authorizationStatus(applicationInForeground: Signal? = nil, siriAuthorization: (() -> AccessType)? = nil, subject: DeviceAccessSubject) -> Signal { switch subject { case .notifications: diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index 93b5834dd2..c8022e37be 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -6359,6 +6359,20 @@ public extension Api { return result }) } + + public static func checkShortName(shortName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(676017721) + serializeString(shortName, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.checkShortName", parameters: [("shortName", shortName)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } } public struct account { public static func registerDevice(flags: Int32, tokenType: Int32, token: String, appSandbox: Api.Bool, secret: Buffer, otherUids: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramUI/Sources/ApplicationShortcutItem.swift b/submodules/TelegramUI/Sources/ApplicationShortcutItem.swift index ad690d29d0..6579fe691b 100644 --- a/submodules/TelegramUI/Sources/ApplicationShortcutItem.swift +++ b/submodules/TelegramUI/Sources/ApplicationShortcutItem.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import TelegramPresentationData +import DeviceAccess enum ApplicationShortcutItemType: String { case search @@ -44,12 +45,18 @@ func applicationShortcutItems(strings: PresentationStrings, otherAccountName: St ApplicationShortcutItem(type: .savedMessages, title: strings.Conversation_SavedMessages, subtitle: nil), ApplicationShortcutItem(type: .account, title: strings.Shortcut_SwitchAccount, subtitle: otherAccountName) ] - } else { + } else if DeviceAccess.isCameraAccessAuthorized() { return [ ApplicationShortcutItem(type: .search, title: strings.Common_Search, subtitle: nil), ApplicationShortcutItem(type: .compose, title: strings.Compose_NewMessage, subtitle: nil), ApplicationShortcutItem(type: .camera, title: strings.Camera_Title, subtitle: nil), ApplicationShortcutItem(type: .savedMessages, title: strings.Conversation_SavedMessages, subtitle: nil) ] + } else { + return [ + ApplicationShortcutItem(type: .search, title: strings.Common_Search, subtitle: nil), + ApplicationShortcutItem(type: .compose, title: strings.Compose_NewMessage, subtitle: nil), + ApplicationShortcutItem(type: .savedMessages, title: strings.Conversation_SavedMessages, subtitle: nil) + ] } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d4b59b1bf7..578c98f75d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4728,6 +4728,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) donateSendMessageIntent(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, intentContext: .chat, peerIds: [peerId]) + + strongSelf.updateChatPresentationInterfaceState(interactive: true, { $0.updatedShowCommands(false) }) } } @@ -9607,6 +9609,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) donateSendMessageIntent(account: self.context.account, sharedContext: self.context.sharedContext, intentContext: .chat, peerIds: [peerId]) + + self.updateChatPresentationInterfaceState(interactive: true, { $0.updatedShowCommands(false) }) } else { self.presentScheduleTimePicker(dismissByTapOutside: false, completion: { [weak self] time in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index 46ecf9361b..5be394c3cd 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -224,7 +224,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { transition.updateFrame(node: self.durationLabel, frame: CGRect(origin: CGPoint(x: width - rightInset - 90.0 - 4.0, y: 15.0), size: CGSize(width: 35.0, height: 20.0))) prevInputPanelNode?.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight)) - if let prevTextInputPanelNode = prevInputPanelNode as? ChatTextInputPanelNode { + if let prevTextInputPanelNode = self.prevInputPanelNode as? ChatTextInputPanelNode { self.prevInputPanelNode = nil if let audioRecordingDotNode = prevTextInputPanelNode.audioRecordingDotNode { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index da823e0d4e..1e119314c2 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1028,14 +1028,19 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.accessoryItemButtons = updatedButtons } + let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState + var hasMenuButton = false var menuButtonExpanded = false if let peer = interfaceState.renderedPeer?.peer as? TelegramUser, let _ = peer.botInfo, interfaceState.hasBotCommands { hasMenuButton = true - if case .none = interfaceState.inputMode { + if [.none, .inputButtons].contains(interfaceState.inputMode) { menuButtonExpanded = true } } + if mediaRecordingState != nil { + hasMenuButton = false + } let leftMenuInset: CGFloat let menuCollapsedButtonWidth: CGFloat = 38.0 @@ -1063,15 +1068,16 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { transition.updateFrame(node: self.menuButtonTextNode, frame: CGRect(origin: CGPoint(x: 16.0, y: 7.0 - UIScreenPixel), size: menuTextSize)) transition.updateAlpha(node: self.menuButtonTextNode, alpha: menuButtonExpanded ? 1.0 : 0.0) transition.updateFrame(node: self.menuButtonIconNode, frame: CGRect(x: 4.0 + UIScreenPixel, y: 1.0 + UIScreenPixel, width: 30.0, height: 30.0)) - transition.updateTransformScale(node: self.menuButton, scale: hasMenuButton ? 1.0 : 0.001) - transition.updateAlpha(node: self.menuButton, alpha: hasMenuButton ? 1.0 : 0.0) + + let showMenuButton = hasMenuButton && interfaceState.recordedMediaPreview == nil + transition.updateTransformScale(node: self.menuButton, scale: showMenuButton ? 1.0 : 0.001) + transition.updateAlpha(node: self.menuButton, alpha: showMenuButton ? 1.0 : 0.0) self.menuButton.isUserInteractionEnabled = hasMenuButton self.actionButtons.micButton.updateMode(mode: interfaceState.interfaceState.mediaRecordingMode, animated: transition.isAnimated) var hideMicButton = false var audioRecordingItemsAlpha: CGFloat = 1 - let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState if mediaRecordingState != nil || interfaceState.recordedMediaPreview != nil { audioRecordingItemsAlpha = 0 @@ -1098,7 +1104,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } - var animateCancelSlideIn = false let audioRecordingCancelIndicator: ChatTextInputAudioRecordingCancelIndicator if let currentAudioRecordingCancelIndicator = self.audioRecordingCancelIndicator { @@ -1218,7 +1223,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.audioRecordingDotNode?.removeFromSupernode() audioRecordingDotNode = AnimationNode(animation: "BinRed") self.audioRecordingDotNode = audioRecordingDotNode - self.addSubnode(audioRecordingDotNode) + self.insertSubnode(audioRecordingDotNode, belowSubnode: self.menuButton) self.animatingBinNode?.removeFromSupernode() self.animatingBinNode = nil } @@ -1256,6 +1261,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { audioRecordingCancelIndicator.layer.animateAlpha(from: CGFloat(audioRecordingCancelIndicator.layer.presentation()?.opacity ?? 1), to: 0, duration: 0.15, delay: 0, removeOnCompletion: false) } } else { + let update = self.actionButtons.micButton.audioRecorder != nil self.actionButtons.micButton.audioRecorder = nil self.actionButtons.micButton.videoRecordingStatus = nil transition.updateAlpha(layer: self.textInputBackgroundNode.layer, alpha: 1.0) @@ -1274,7 +1280,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } if let audioRecordingDotNode = self.audioRecordingDotNode { - let dismissDotNode = { [weak audioRecordingDotNode, weak attachmentButton, weak self] in + let dismissDotNode = { [weak audioRecordingDotNode, weak self] in guard let audioRecordingDotNode = audioRecordingDotNode, audioRecordingDotNode === self?.audioRecordingDotNode else { return } self?.audioRecordingDotNode = nil @@ -1284,11 +1290,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { audioRecordingDotNode?.removeFromSupernode() } - attachmentButton?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) - attachmentButton?.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + self?.attachmentButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + self?.attachmentButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) } - audioRecordingDotNode.layer.removeAllAnimations() + if update { + audioRecordingDotNode.layer.removeAllAnimations() + } if self.isMediaDeleted { if self.prevInputPanelNode is ChatRecordingPreviewInputPanelNode { @@ -1301,6 +1309,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { dismissDotNode() } + + if update { + self.audioRecordingDotNode?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: leftMenuInset, y: 0.0), duration: 0.15, removeOnCompletion: false, additive: true) + } } if let audioRecordingTimeNode = self.audioRecordingTimeNode { @@ -1530,7 +1542,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.updateActionButtons(hasText: hasText, hideMicButton: hideMicButton, animated: transition.isAnimated) - if let prevInputPanelNode = prevInputPanelNode { + if let prevInputPanelNode = self.prevInputPanelNode { prevInputPanelNode.frame = CGRect(origin: .zero, size: prevInputPanelNode.frame.size) } if let prevPreviewInputPanelNode = self.prevInputPanelNode as? ChatRecordingPreviewInputPanelNode { @@ -1543,7 +1555,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { func animatePosition(for previewSubnode: ASDisplayNode) { previewSubnode.layer.animatePosition( from: previewSubnode.position, - to: CGPoint(x: previewSubnode.position.x - 20, y: previewSubnode.position.y), + to: CGPoint(x: leftMenuInset.isZero ? previewSubnode.position.x - 20 : leftMenuInset + previewSubnode.frame.width / 2.0, y: previewSubnode.position.y), duration: 0.15 ) } @@ -1591,12 +1603,21 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { dismissBin() } + + prevPreviewInputPanelNode.deleteButton.layer.animatePosition(from: CGPoint(), to: CGPoint(x: leftMenuInset, y: 0.0), duration: 0.15, removeOnCompletion: false, additive: true) prevPreviewInputPanelNode.sendButton.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false) prevPreviewInputPanelNode.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) - actionButtons.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) - actionButtons.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + self.actionButtons.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + self.actionButtons.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + + if hasMenuButton { + self.menuButton.alpha = 1.0 + self.menuButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + self.menuButton.transform = CATransform3DIdentity + self.menuButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + } prevPreviewInputPanelNode.sendButton.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false) prevPreviewInputPanelNode.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift index 8fa0d0f5a9..60942fd489 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift @@ -137,6 +137,10 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { let selectionPosition = range.lowerBound + (replacementText as NSString).length + return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode) + } else { + let inputText = NSMutableAttributedString(string: "/" + command.command.text + " ") + let selectionPosition = (inputText.string as NSString).length + 1 return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode) } return (textInputState, inputMode) @@ -197,7 +201,7 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { } private func topInsetForLayout(size: CGSize) -> CGFloat { - let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 4.5) + let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 4.7) return max(size.height - minimumItemHeights, 0.0) } diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift index 866dad5b7f..6f7c52ec01 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift @@ -118,7 +118,7 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode { private let shadowNode: ASImageNode private let backgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode - + init() { self.textNode = TextNode() self.commandNode = TextNode() @@ -151,7 +151,26 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode { self.backgroundNode.addSubnode(self.separatorNode) } - override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + override func didLoad() { + super.didLoad() + + let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressed(_:))) + gestureRecognizer.minimumPressDuration = 0.3 + self.view.addGestureRecognizer(gestureRecognizer) + } + + @objc private func longPressed(_ gestureRecognizer: UILongPressGestureRecognizer) { + switch gestureRecognizer.state { + case .began: + if let item = self.item { + item.commandSelected(item.command, false) + } + default: + break + } + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { if let item = item as? CommandMenuChatInputPanelItem { let doLayout = self.asyncLayout() let merged = (top: previousItem != nil, bottom: nextItem != nil)