mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Message effects improvements
This commit is contained in:
parent
3beaf8d580
commit
8736981248
@ -123,7 +123,7 @@ public final class ViewController: UIViewController {
|
|||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
self.test = ReferenceCompareTest(view: self.view)
|
self.test = ReferenceCompareTest(view: self.view)
|
||||||
}
|
}
|
||||||
} else if "".isEmpty {
|
} else if !"".isEmpty {
|
||||||
let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
|
let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
|
||||||
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!
|
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!
|
||||||
|
|
||||||
|
@ -1068,6 +1068,29 @@ public protocol AccountGroupCallContext: AnyObject {
|
|||||||
public protocol AccountGroupCallContextCache: AnyObject {
|
public protocol AccountGroupCallContextCache: AnyObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class ChatSendMessageActionSheetControllerMessageEffect {
|
||||||
|
public let id: Int64
|
||||||
|
|
||||||
|
public init(id: Int64) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ChatSendMessageActionSheetControllerSendMode {
|
||||||
|
case generic
|
||||||
|
case silently
|
||||||
|
case whenOnline
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ChatSendMessageActionSheetControllerSourceSendButtonNode: ASDisplayNode {
|
||||||
|
func makeCustomContents() -> UIView?
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ChatSendMessageActionSheetController: ViewController {
|
||||||
|
typealias SendMode = ChatSendMessageActionSheetControllerSendMode
|
||||||
|
typealias MessageEffect = ChatSendMessageActionSheetControllerMessageEffect
|
||||||
|
}
|
||||||
|
|
||||||
public protocol AccountContext: AnyObject {
|
public protocol AccountContext: AnyObject {
|
||||||
var sharedContext: SharedAccountContext { get }
|
var sharedContext: SharedAccountContext { get }
|
||||||
var account: Account { get }
|
var account: Account { get }
|
||||||
|
@ -147,7 +147,7 @@ public enum PeerSelectionControllerContext {
|
|||||||
|
|
||||||
public protocol PeerSelectionController: ViewController {
|
public protocol PeerSelectionController: ViewController {
|
||||||
var peerSelected: ((EnginePeer, Int64?) -> Void)? { get set }
|
var peerSelected: ((EnginePeer, Int64?) -> Void)? { get set }
|
||||||
var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? { get set }
|
var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)? { get set }
|
||||||
var inProgress: Bool { get set }
|
var inProgress: Bool { get set }
|
||||||
var customDismiss: (() -> Void)? { get set }
|
var customDismiss: (() -> Void)? { get set }
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ import TelegramCore
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
|
import ComponentFlow
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
let sendContainerNode: ASDisplayNode
|
let sendContainerNode: ASDisplayNode
|
||||||
@ -16,6 +18,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
|||||||
var sendButtonHasApplyIcon = false
|
var sendButtonHasApplyIcon = false
|
||||||
var animatingSendButton = false
|
var animatingSendButton = false
|
||||||
let textNode: ImmediateTextNode
|
let textNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private var theme: PresentationTheme
|
||||||
|
|
||||||
var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
|
var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
|
||||||
|
|
||||||
@ -31,9 +35,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
|||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) {
|
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) {
|
||||||
let theme = presentationInterfaceState.theme
|
self.theme = presentationInterfaceState.theme
|
||||||
let strings = presentationInterfaceState.strings
|
self.strings = presentationInterfaceState.strings
|
||||||
self.strings = strings
|
|
||||||
|
|
||||||
self.sendContainerNode = ASDisplayNode()
|
self.sendContainerNode = ASDisplayNode()
|
||||||
self.sendContainerNode.layer.allowsGroupOpacity = true
|
self.sendContainerNode.layer.allowsGroupOpacity = true
|
||||||
@ -64,9 +67,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if highlighted {
|
if highlighted {
|
||||||
strongSelf.sendContainerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
|
let transition: Transition = .easeInOut(duration: 0.4)
|
||||||
} else if let presentationLayer = strongSelf.sendButton.layer.presentation() {
|
transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 0.75)
|
||||||
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)
|
} else {
|
||||||
|
let transition: Transition = .easeInOut(duration: 0.25)
|
||||||
|
transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +95,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !strongSelf.sendButtonHasApplyIcon {
|
if !strongSelf.sendButtonHasApplyIcon {
|
||||||
strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer)
|
strongSelf.sendButtonLongPressed?(strongSelf, recognizer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +117,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
|||||||
self.validLayout = size
|
self.validLayout = size
|
||||||
|
|
||||||
let width: CGFloat
|
let width: CGFloat
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: size.height))
|
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
if minimized {
|
if minimized {
|
||||||
width = 44.0
|
width = 44.0
|
||||||
} else {
|
} else {
|
||||||
@ -140,4 +145,16 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
|||||||
self.accessibilityLabel = self.strings.MediaPicker_Send
|
self.accessibilityLabel = self.strings.MediaPicker_Send
|
||||||
self.accessibilityHint = nil
|
self.accessibilityHint = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeCustomContents() -> UIView? {
|
||||||
|
if !self.textNode.alpha.isZero {
|
||||||
|
let textView = ImmediateTextView()
|
||||||
|
textView.attributedText = NSAttributedString(string: self.strings.MediaPicker_Send, font: Font.semibold(17.0), textColor: self.theme.chat.inputPanel.actionControlForegroundColor)
|
||||||
|
let textSize = textView.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
|
let _ = textSize
|
||||||
|
textView.frame = self.textNode.frame
|
||||||
|
return textView
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
|
|
||||||
private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)?
|
private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)?
|
||||||
|
|
||||||
public var sendMessage: (AttachmentTextInputPanelSendMode) -> Void = { _ in }
|
public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
|
||||||
public var updateHeight: (Bool) -> Void = { _ in }
|
public var updateHeight: (Bool) -> Void = { _ in }
|
||||||
|
|
||||||
private var updatingInputState = false
|
private var updatingInputState = false
|
||||||
@ -1843,7 +1843,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
sendPressed(effectiveInputText)
|
sendPressed(effectiveInputText)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.sendMessage(.generic)
|
self.sendMessage(.generic, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) {
|
@objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
@ -14,6 +14,7 @@ import MediaResources
|
|||||||
import LegacyMessageInputPanel
|
import LegacyMessageInputPanel
|
||||||
import LegacyMessageInputPanelInputView
|
import LegacyMessageInputPanelInputView
|
||||||
import AttachmentTextInputPanelNode
|
import AttachmentTextInputPanelNode
|
||||||
|
import ChatSendMessageActionUI
|
||||||
|
|
||||||
public enum AttachmentButtonType: Equatable {
|
public enum AttachmentButtonType: Equatable {
|
||||||
case gallery
|
case gallery
|
||||||
@ -97,6 +98,7 @@ public protocol AttachmentContainable: ViewController {
|
|||||||
var isContainerExpanded: () -> Bool { get set }
|
var isContainerExpanded: () -> Bool { get set }
|
||||||
var isPanGestureEnabled: (() -> Bool)? { get }
|
var isPanGestureEnabled: (() -> Bool)? { get }
|
||||||
var mediaPickerContext: AttachmentMediaPickerContext? { get }
|
var mediaPickerContext: AttachmentMediaPickerContext? { get }
|
||||||
|
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get }
|
||||||
|
|
||||||
func isContainerPanningUpdated(_ panning: Bool)
|
func isContainerPanningUpdated(_ panning: Bool)
|
||||||
|
|
||||||
@ -131,6 +133,10 @@ public extension AttachmentContainable {
|
|||||||
var isPanGestureEnabled: (() -> Bool)? {
|
var isPanGestureEnabled: (() -> Bool)? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AttachmentMediaPickerSendMode {
|
public enum AttachmentMediaPickerSendMode {
|
||||||
@ -154,8 +160,8 @@ public protocol AttachmentMediaPickerContext {
|
|||||||
func mainButtonAction()
|
func mainButtonAction()
|
||||||
|
|
||||||
func setCaption(_ caption: NSAttributedString)
|
func setCaption(_ caption: NSAttributedString)
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode)
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
|
||||||
func schedule()
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateShadowImage() -> UIImage? {
|
private func generateShadowImage() -> UIImage? {
|
||||||
@ -417,17 +423,17 @@ public class AttachmentController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.panel.sendMessagePressed = { [weak self] mode in
|
self.panel.sendMessagePressed = { [weak self] mode, messageEffect in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
switch mode {
|
switch mode {
|
||||||
case .generic:
|
case .generic:
|
||||||
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media)
|
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media, messageEffect: messageEffect)
|
||||||
case .silent:
|
case .silent:
|
||||||
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media)
|
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media, messageEffect: messageEffect)
|
||||||
case .schedule:
|
case .schedule:
|
||||||
strongSelf.mediaPickerContext?.schedule()
|
strongSelf.mediaPickerContext?.schedule(messageEffect: messageEffect)
|
||||||
case .whenOnline:
|
case .whenOnline:
|
||||||
strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media)
|
strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media, messageEffect: messageEffect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -455,6 +461,14 @@ public class AttachmentController: ViewController {
|
|||||||
strongSelf.controller?.presentInGlobalOverlay(c, with: nil)
|
strongSelf.controller?.presentInGlobalOverlay(c, with: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.panel.getCurrentSendMessageContextMediaPreview = { [weak self] in
|
||||||
|
guard let self, let currentController = self.currentControllers.last else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentController.getCurrentSendMessageContextMediaPreview?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -728,11 +728,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
var beganTextEditing: () -> Void = {}
|
var beganTextEditing: () -> Void = {}
|
||||||
var textUpdated: (NSAttributedString) -> Void = { _ in }
|
var textUpdated: (NSAttributedString) -> Void = { _ in }
|
||||||
var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in }
|
var sendMessagePressed: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
|
||||||
var requestLayout: () -> Void = {}
|
var requestLayout: () -> Void = {}
|
||||||
var present: (ViewController) -> Void = { _ in }
|
var present: (ViewController) -> Void = { _ in }
|
||||||
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
|
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
|
||||||
|
|
||||||
|
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)?
|
||||||
|
|
||||||
var mainButtonPressed: () -> Void = { }
|
var mainButtonPressed: () -> Void = { }
|
||||||
|
|
||||||
init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
|
init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
|
||||||
@ -967,20 +969,56 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
sendWhenOnlineAvailable = false
|
sendWhenOnlineAvailable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = makeChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, attachment: true, canSendWhenOnline: sendWhenOnlineAvailable, completion: {
|
let mediaPreview = strongSelf.getCurrentSendMessageContextMediaPreview?()
|
||||||
}, sendMessage: { [weak textInputPanelNode] mode, _ in
|
let isReady: Signal<Bool, NoError>
|
||||||
switch mode {
|
if let mediaPreview {
|
||||||
case .generic:
|
isReady = mediaPreview.isReady
|
||||||
textInputPanelNode?.sendMessage(.generic)
|
|> filter { $0 }
|
||||||
case .silently:
|
|> take(1)
|
||||||
textInputPanelNode?.sendMessage(.silent)
|
|> timeout(0.5, queue: .mainQueue(), alternate: .single(true))
|
||||||
case .whenOnline:
|
} else {
|
||||||
textInputPanelNode?.sendMessage(.whenOnline)
|
isReady = .single(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (isReady
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak strongSelf] _ in
|
||||||
|
guard let strongSelf else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}, schedule: { [weak textInputPanelNode] _ in
|
|
||||||
textInputPanelNode?.sendMessage(.schedule)
|
let controller = makeChatSendMessageActionSheetController(
|
||||||
}, reactionItems: effectItems, availableMessageEffects: availableMessageEffects, isPremium: hasPremium)
|
context: strongSelf.context,
|
||||||
strongSelf.presentInGlobalOverlay(controller)
|
peerId: strongSelf.presentationInterfaceState.chatLocation.peerId,
|
||||||
|
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds,
|
||||||
|
hasEntityKeyboard: hasEntityKeyboard,
|
||||||
|
gesture: gesture,
|
||||||
|
sourceSendButton: node,
|
||||||
|
textInputView: textInputNode.textView,
|
||||||
|
mediaPreview: mediaPreview,
|
||||||
|
emojiViewProvider: textInputPanelNode.emojiViewProvider,
|
||||||
|
attachment: true,
|
||||||
|
canSendWhenOnline: sendWhenOnlineAvailable,
|
||||||
|
completion: {
|
||||||
|
},
|
||||||
|
sendMessage: { [weak textInputPanelNode] mode, messageEffect in
|
||||||
|
switch mode {
|
||||||
|
case .generic:
|
||||||
|
textInputPanelNode?.sendMessage(.generic, messageEffect)
|
||||||
|
case .silently:
|
||||||
|
textInputPanelNode?.sendMessage(.silent, messageEffect)
|
||||||
|
case .whenOnline:
|
||||||
|
textInputPanelNode?.sendMessage(.whenOnline, messageEffect)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
schedule: { [weak textInputPanelNode] messageEffect in
|
||||||
|
textInputPanelNode?.sendMessage(.schedule, messageEffect)
|
||||||
|
},
|
||||||
|
reactionItems: effectItems,
|
||||||
|
availableMessageEffects: availableMessageEffects,
|
||||||
|
isPremium: hasPremium
|
||||||
|
)
|
||||||
|
strongSelf.presentInGlobalOverlay(controller)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}, openScheduledMessages: {
|
}, openScheduledMessages: {
|
||||||
}, openPeersNearby: {
|
}, openPeersNearby: {
|
||||||
@ -1249,9 +1287,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}, makeEntityInputView: self.makeEntityInputView)
|
}, makeEntityInputView: self.makeEntityInputView)
|
||||||
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
||||||
textInputPanelNode.sendMessage = { [weak self] mode in
|
textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.sendMessagePressed(mode)
|
strongSelf.sendMessagePressed(mode, messageEffect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textInputPanelNode.focusUpdated = { [weak self] focus in
|
textInputPanelNode.focusUpdated = { [weak self] focus in
|
||||||
|
@ -1340,7 +1340,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: messages.values.filter { messageIds.contains($0.id) })
|
self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: messages.values.filter { messageIds.contains($0.id) })
|
||||||
|
|
||||||
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true, selectForumThreads: true))
|
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true, selectForumThreads: true))
|
||||||
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
|
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in
|
||||||
guard let strongSelf = self, let strongController = peerSelectionController else {
|
guard let strongSelf = self, let strongController = peerSelectionController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -11,25 +11,6 @@ import TextFormat
|
|||||||
import ReactionSelectionNode
|
import ReactionSelectionNode
|
||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
|
|
||||||
public enum ChatSendMessageActionSheetControllerSendMode {
|
|
||||||
case generic
|
|
||||||
case silently
|
|
||||||
case whenOnline
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class ChatSendMessageActionSheetControllerMessageEffect {
|
|
||||||
public let id: Int64
|
|
||||||
|
|
||||||
public init(id: Int64) {
|
|
||||||
self.id = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public protocol ChatSendMessageActionSheetController: ViewController {
|
|
||||||
typealias SendMode = ChatSendMessageActionSheetControllerSendMode
|
|
||||||
typealias MessageEffect = ChatSendMessageActionSheetControllerMessageEffect
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ChatSendMessageActionSheetControllerImpl: ViewController, ChatSendMessageActionSheetController {
|
private final class ChatSendMessageActionSheetControllerImpl: ViewController, ChatSendMessageActionSheetController {
|
||||||
private var controllerNode: ChatSendMessageActionSheetControllerNode {
|
private var controllerNode: ChatSendMessageActionSheetControllerNode {
|
||||||
return self.displayNode as! ChatSendMessageActionSheetControllerNode
|
return self.displayNode as! ChatSendMessageActionSheetControllerNode
|
||||||
@ -203,6 +184,7 @@ public func makeChatSendMessageActionSheetController(
|
|||||||
gesture: ContextGesture,
|
gesture: ContextGesture,
|
||||||
sourceSendButton: ASDisplayNode,
|
sourceSendButton: ASDisplayNode,
|
||||||
textInputView: UITextView,
|
textInputView: UITextView,
|
||||||
|
mediaPreview: ChatSendMessageContextScreenMediaPreview? = nil,
|
||||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||||
wallpaperBackgroundNode: WallpaperBackgroundNode? = nil,
|
wallpaperBackgroundNode: WallpaperBackgroundNode? = nil,
|
||||||
attachment: Bool = false,
|
attachment: Bool = false,
|
||||||
@ -214,7 +196,7 @@ public func makeChatSendMessageActionSheetController(
|
|||||||
availableMessageEffects: AvailableMessageEffects? = nil,
|
availableMessageEffects: AvailableMessageEffects? = nil,
|
||||||
isPremium: Bool = false
|
isPremium: Bool = false
|
||||||
) -> ChatSendMessageActionSheetController {
|
) -> ChatSendMessageActionSheetController {
|
||||||
if textInputView.text.isEmpty {
|
if textInputView.text.isEmpty && !"".isEmpty {
|
||||||
return ChatSendMessageActionSheetControllerImpl(
|
return ChatSendMessageActionSheetControllerImpl(
|
||||||
context: context,
|
context: context,
|
||||||
updatedPresentationData: updatedPresentationData,
|
updatedPresentationData: updatedPresentationData,
|
||||||
@ -245,6 +227,7 @@ public func makeChatSendMessageActionSheetController(
|
|||||||
gesture: gesture,
|
gesture: gesture,
|
||||||
sourceSendButton: sourceSendButton,
|
sourceSendButton: sourceSendButton,
|
||||||
textInputView: textInputView,
|
textInputView: textInputView,
|
||||||
|
mediaPreview: mediaPreview,
|
||||||
emojiViewProvider: emojiViewProvider,
|
emojiViewProvider: emojiViewProvider,
|
||||||
wallpaperBackgroundNode: wallpaperBackgroundNode,
|
wallpaperBackgroundNode: wallpaperBackgroundNode,
|
||||||
attachment: attachment,
|
attachment: attachment,
|
||||||
|
@ -32,10 +32,21 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) ->
|
|||||||
return targetWindowFrame
|
return targetWindowFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public protocol ChatSendMessageContextScreenMediaPreview: AnyObject {
|
||||||
|
var isReady: Signal<Bool, NoError> { get }
|
||||||
|
var view: UIView { get }
|
||||||
|
var globalClippingRect: CGRect? { get }
|
||||||
|
|
||||||
|
func animateIn(transition: Transition)
|
||||||
|
func animateOut(transition: Transition)
|
||||||
|
func update(containerSize: CGSize, transition: Transition) -> CGSize
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatSendMessageContextScreenComponent: Component {
|
final class ChatSendMessageContextScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
|
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||||
let peerId: EnginePeer.Id?
|
let peerId: EnginePeer.Id?
|
||||||
let isScheduledMessages: Bool
|
let isScheduledMessages: Bool
|
||||||
let forwardMessageIds: [EngineMessage.Id]?
|
let forwardMessageIds: [EngineMessage.Id]?
|
||||||
@ -43,6 +54,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
let gesture: ContextGesture
|
let gesture: ContextGesture
|
||||||
let sourceSendButton: ASDisplayNode
|
let sourceSendButton: ASDisplayNode
|
||||||
let textInputView: UITextView
|
let textInputView: UITextView
|
||||||
|
let mediaPreview: ChatSendMessageContextScreenMediaPreview?
|
||||||
let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
||||||
let wallpaperBackgroundNode: WallpaperBackgroundNode?
|
let wallpaperBackgroundNode: WallpaperBackgroundNode?
|
||||||
let attachment: Bool
|
let attachment: Bool
|
||||||
@ -56,6 +68,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
|
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||||
peerId: EnginePeer.Id?,
|
peerId: EnginePeer.Id?,
|
||||||
isScheduledMessages: Bool,
|
isScheduledMessages: Bool,
|
||||||
forwardMessageIds: [EngineMessage.Id]?,
|
forwardMessageIds: [EngineMessage.Id]?,
|
||||||
@ -63,6 +76,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
gesture: ContextGesture,
|
gesture: ContextGesture,
|
||||||
sourceSendButton: ASDisplayNode,
|
sourceSendButton: ASDisplayNode,
|
||||||
textInputView: UITextView,
|
textInputView: UITextView,
|
||||||
|
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
|
||||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||||
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
||||||
attachment: Bool,
|
attachment: Bool,
|
||||||
@ -75,6 +89,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
isPremium: Bool
|
isPremium: Bool
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.isScheduledMessages = isScheduledMessages
|
self.isScheduledMessages = isScheduledMessages
|
||||||
self.forwardMessageIds = forwardMessageIds
|
self.forwardMessageIds = forwardMessageIds
|
||||||
@ -82,6 +97,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
self.gesture = gesture
|
self.gesture = gesture
|
||||||
self.sourceSendButton = sourceSendButton
|
self.sourceSendButton = sourceSendButton
|
||||||
self.textInputView = textInputView
|
self.textInputView = textInputView
|
||||||
|
self.mediaPreview = mediaPreview
|
||||||
self.emojiViewProvider = emojiViewProvider
|
self.emojiViewProvider = emojiViewProvider
|
||||||
self.wallpaperBackgroundNode = wallpaperBackgroundNode
|
self.wallpaperBackgroundNode = wallpaperBackgroundNode
|
||||||
self.attachment = attachment
|
self.attachment = attachment
|
||||||
@ -141,6 +157,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
private var standaloneReactionAnimation: AnimatedStickerNode?
|
private var standaloneReactionAnimation: AnimatedStickerNode?
|
||||||
|
|
||||||
private var isLoadingEffectAnimation: Bool = false
|
private var isLoadingEffectAnimation: Bool = false
|
||||||
|
private var isLoadingEffectAnimationTimerDisposable: Disposable?
|
||||||
private var loadEffectAnimationDisposable: Disposable?
|
private var loadEffectAnimationDisposable: Disposable?
|
||||||
|
|
||||||
private var presentationAnimationState: PresentationAnimationState = .initial
|
private var presentationAnimationState: PresentationAnimationState = .initial
|
||||||
@ -177,6 +194,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
deinit {
|
deinit {
|
||||||
self.messageEffectDisposable.dispose()
|
self.messageEffectDisposable.dispose()
|
||||||
self.loadEffectAnimationDisposable?.dispose()
|
self.loadEffectAnimationDisposable?.dispose()
|
||||||
|
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func onBackgroundTap(_ recognizer: UITapGestureRecognizer) {
|
@objc private func onBackgroundTap(_ recognizer: UITapGestureRecognizer) {
|
||||||
@ -196,6 +214,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
if case .initial = self.presentationAnimationState {
|
if case .initial = self.presentationAnimationState {
|
||||||
|
HapticFeedback().impact()
|
||||||
|
|
||||||
self.presentationAnimationState = .animatedIn
|
self.presentationAnimationState = .animatedIn
|
||||||
self.state?.updated(transition: .spring(duration: 0.42))
|
self.state?.updated(transition: .spring(duration: 0.42))
|
||||||
}
|
}
|
||||||
@ -270,7 +290,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
self.environment = environment
|
self.environment = environment
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
self.backgroundView.updateColor(
|
self.backgroundView.updateColor(
|
||||||
@ -298,15 +318,6 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
|
|
||||||
let sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
let sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
||||||
|
|
||||||
sendButton.update(
|
|
||||||
context: component.context,
|
|
||||||
presentationData: presentationData,
|
|
||||||
backgroundNode: component.wallpaperBackgroundNode,
|
|
||||||
isLoadingEffectAnimation: self.isLoadingEffectAnimation,
|
|
||||||
size: sourceSendButtonFrame.size,
|
|
||||||
transition: transition
|
|
||||||
)
|
|
||||||
|
|
||||||
let sendButtonScale: CGFloat
|
let sendButtonScale: CGFloat
|
||||||
switch self.presentationAnimationState {
|
switch self.presentationAnimationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
@ -389,7 +400,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.schedule(nil)
|
self.animateOutToEmpty = true
|
||||||
|
component.schedule(self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) }))
|
||||||
self.environment?.controller()?.dismiss()
|
self.environment?.controller()?.dismiss()
|
||||||
}
|
}
|
||||||
)))
|
)))
|
||||||
@ -476,10 +488,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
backgroundNode: component.wallpaperBackgroundNode,
|
backgroundNode: component.wallpaperBackgroundNode,
|
||||||
textString: textString,
|
textString: textString,
|
||||||
sourceTextInputView: component.textInputView as? ChatInputTextView,
|
sourceTextInputView: component.textInputView as? ChatInputTextView,
|
||||||
|
emojiViewProvider: component.emojiViewProvider,
|
||||||
|
sourceMediaPreview: component.mediaPreview,
|
||||||
textInsets: messageTextInsets,
|
textInsets: messageTextInsets,
|
||||||
explicitBackgroundSize: explicitMessageBackgroundSize,
|
explicitBackgroundSize: explicitMessageBackgroundSize,
|
||||||
maxTextWidth: localSourceTextInputViewFrame.width,
|
maxTextWidth: localSourceTextInputViewFrame.width,
|
||||||
maxTextHeight: maxTextHeight,
|
maxTextHeight: maxTextHeight,
|
||||||
|
containerSize: CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height),
|
||||||
effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil,
|
effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil,
|
||||||
transition: transition
|
transition: transition
|
||||||
)
|
)
|
||||||
@ -586,6 +601,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
self.selectedMessageEffect = nil
|
self.selectedMessageEffect = nil
|
||||||
reactionContextNode.selectedItems = Set([])
|
reactionContextNode.selectedItems = Set([])
|
||||||
self.loadEffectAnimationDisposable?.dispose()
|
self.loadEffectAnimationDisposable?.dispose()
|
||||||
|
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||||
|
self.isLoadingEffectAnimationTimerDisposable = nil
|
||||||
self.isLoadingEffectAnimation = false
|
self.isLoadingEffectAnimation = false
|
||||||
|
|
||||||
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
|
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
|
||||||
@ -619,10 +636,16 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.loadEffectAnimationDisposable?.dispose()
|
self.loadEffectAnimationDisposable?.dispose()
|
||||||
self.isLoadingEffectAnimation = true
|
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||||
if !self.isUpdating {
|
self.isLoadingEffectAnimationTimerDisposable = (Signal<Never, NoError>.complete() |> delay(0.2, queue: .mainQueue()) |> deliverOnMainQueue).startStrict(completed: { [weak self] in
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
guard let self else {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
self.isLoadingEffectAnimation = true
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
|
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
|
||||||
self.standaloneReactionAnimation = nil
|
self.standaloneReactionAnimation = nil
|
||||||
@ -666,9 +689,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
dataDisposabke.dispose()
|
dataDisposabke.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if DEBUG
|
/*#if DEBUG
|
||||||
loadEffectAnimationSignal = loadEffectAnimationSignal |> delay(1.0, queue: .mainQueue())
|
loadEffectAnimationSignal = loadEffectAnimationSignal |> delay(1.0, queue: .mainQueue())
|
||||||
#endif
|
#endif*/
|
||||||
|
|
||||||
self.loadEffectAnimationDisposable = (loadEffectAnimationSignal
|
self.loadEffectAnimationDisposable = (loadEffectAnimationSignal
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
@ -676,6 +699,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||||
|
self.isLoadingEffectAnimationTimerDisposable = nil
|
||||||
self.isLoadingEffectAnimation = false
|
self.isLoadingEffectAnimation = false
|
||||||
|
|
||||||
guard let targetView = self.messageItemView?.effectIconView else {
|
guard let targetView = self.messageItemView?.effectIconView else {
|
||||||
@ -689,7 +714,11 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
standaloneReactionAnimation = DirectAnimatedStickerNode()
|
standaloneReactionAnimation = DirectAnimatedStickerNode()
|
||||||
#else
|
#else
|
||||||
standaloneReactionAnimation = LottieMetalAnimatedStickerNode()
|
if "".isEmpty {
|
||||||
|
standaloneReactionAnimation = DirectAnimatedStickerNode()
|
||||||
|
} else {
|
||||||
|
standaloneReactionAnimation = LottieMetalAnimatedStickerNode()
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
standaloneReactionAnimation.isUserInteractionEnabled = false
|
standaloneReactionAnimation.isUserInteractionEnabled = false
|
||||||
@ -728,7 +757,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
self.environment?.controller()?.present(UndoOverlayController(
|
self.environment?.controller()?.present(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .premiumPaywall(
|
content: .premiumPaywall(
|
||||||
@ -764,9 +793,11 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sourceActionsStackFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 44.0), height: sourceSendButtonFrame.height)
|
||||||
|
var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize)
|
||||||
|
|
||||||
|
let sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
||||||
|
|
||||||
var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.minX, y: sourceSendButtonFrame.minY), size: sourceSendButtonFrame.size)
|
|
||||||
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize)
|
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize)
|
||||||
var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
||||||
|
|
||||||
@ -789,8 +820,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
let sendButtonFrame: CGRect
|
let sendButtonFrame: CGRect
|
||||||
switch self.presentationAnimationState {
|
switch self.presentationAnimationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
messageItemFrame = sourceMessageItemFrame
|
if component.mediaPreview != nil {
|
||||||
actionsStackFrame = sourceActionsStackFrame
|
messageItemFrame = readyMessageItemFrame
|
||||||
|
actionsStackFrame = readyActionsStackFrame
|
||||||
|
} else {
|
||||||
|
messageItemFrame = sourceMessageItemFrame
|
||||||
|
actionsStackFrame = sourceActionsStackFrame
|
||||||
|
}
|
||||||
sendButtonFrame = sourceSendButtonFrame
|
sendButtonFrame = sourceSendButtonFrame
|
||||||
case .animatedOut:
|
case .animatedOut:
|
||||||
if self.animateOutToEmpty {
|
if self.animateOutToEmpty {
|
||||||
@ -798,8 +834,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
actionsStackFrame = readyActionsStackFrame
|
actionsStackFrame = readyActionsStackFrame
|
||||||
sendButtonFrame = readySendButtonFrame
|
sendButtonFrame = readySendButtonFrame
|
||||||
} else {
|
} else {
|
||||||
messageItemFrame = sourceMessageItemFrame
|
if component.mediaPreview != nil {
|
||||||
actionsStackFrame = sourceActionsStackFrame
|
messageItemFrame = readyMessageItemFrame
|
||||||
|
actionsStackFrame = readyActionsStackFrame
|
||||||
|
} else {
|
||||||
|
messageItemFrame = sourceMessageItemFrame
|
||||||
|
actionsStackFrame = sourceActionsStackFrame
|
||||||
|
}
|
||||||
sendButtonFrame = sourceSendButtonFrame
|
sendButtonFrame = sourceSendButtonFrame
|
||||||
}
|
}
|
||||||
case .animatedIn:
|
case .animatedIn:
|
||||||
@ -809,6 +850,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: messageItemView, frame: messageItemFrame)
|
transition.setFrame(view: messageItemView, frame: messageItemFrame)
|
||||||
|
messageItemView.updateClippingRect(
|
||||||
|
sourceMediaPreview: component.mediaPreview,
|
||||||
|
isAnimatedIn: self.presentationAnimationState.key == .animatedIn,
|
||||||
|
localFrame: messageItemFrame,
|
||||||
|
containerSize: availableSize,
|
||||||
|
transition: transition
|
||||||
|
)
|
||||||
|
|
||||||
transition.setPosition(view: actionsStackNode.view, position: CGPoint(x: actionsStackFrame.maxX, y: actionsStackFrame.minY))
|
transition.setPosition(view: actionsStackNode.view, position: CGPoint(x: actionsStackFrame.maxX, y: actionsStackFrame.minY))
|
||||||
transition.setBounds(view: actionsStackNode.view, bounds: CGRect(origin: CGPoint(), size: actionsStackFrame.size))
|
transition.setBounds(view: actionsStackNode.view, bounds: CGRect(origin: CGPoint(), size: actionsStackFrame.size))
|
||||||
@ -820,9 +868,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
transition.setAlpha(view: actionsStackNode.view, alpha: 1.0)
|
transition.setAlpha(view: actionsStackNode.view, alpha: 1.0)
|
||||||
Transition.immediate.setScale(view: actionsStackNode.view, scale: 1.0)
|
Transition.immediate.setScale(view: actionsStackNode.view, scale: 1.0)
|
||||||
actionsStackNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.42, damping: 104.0)
|
actionsStackNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.42, damping: 104.0)
|
||||||
|
|
||||||
|
messageItemView.animateIn(transition: transition)
|
||||||
case .animatedOut:
|
case .animatedOut:
|
||||||
transition.setAlpha(view: actionsStackNode.view, alpha: 0.0)
|
transition.setAlpha(view: actionsStackNode.view, alpha: 0.0)
|
||||||
transition.setScale(view: actionsStackNode.view, scale: 0.001)
|
transition.setScale(view: actionsStackNode.view, scale: 0.001)
|
||||||
|
|
||||||
|
messageItemView.animateOut(transition: transition)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch self.presentationAnimationState {
|
switch self.presentationAnimationState {
|
||||||
@ -837,7 +889,11 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
|
|
||||||
if let reactionContextNode = self.reactionContextNode {
|
if let reactionContextNode = self.reactionContextNode {
|
||||||
let size = availableSize
|
let size = availableSize
|
||||||
let reactionsAnchorRect = messageItemFrame
|
var reactionsAnchorRect = messageItemFrame
|
||||||
|
if component.mediaPreview != nil {
|
||||||
|
reactionsAnchorRect.size.width += 100.0
|
||||||
|
reactionsAnchorRect.origin.x -= 4.0
|
||||||
|
}
|
||||||
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||||
reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: false, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
|
reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: false, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
|
||||||
reactionContextNode.updateIsIntersectingContent(isIntersectingContent: false, transition: .immediate)
|
reactionContextNode.updateIsIntersectingContent(isIntersectingContent: false, transition: .immediate)
|
||||||
@ -856,6 +912,16 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendButton.update(
|
||||||
|
context: component.context,
|
||||||
|
presentationData: presentationData,
|
||||||
|
backgroundNode: component.wallpaperBackgroundNode,
|
||||||
|
sourceSendButton: component.sourceSendButton,
|
||||||
|
isAnimatedIn: self.presentationAnimationState.key == .animatedIn,
|
||||||
|
isLoadingEffectAnimation: self.isLoadingEffectAnimation,
|
||||||
|
size: sendButtonFrame.size,
|
||||||
|
transition: transition
|
||||||
|
)
|
||||||
transition.setPosition(view: sendButton, position: sendButtonFrame.center)
|
transition.setPosition(view: sendButton, position: sendButtonFrame.center)
|
||||||
transition.setBounds(view: sendButton, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size))
|
transition.setBounds(view: sendButton, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size))
|
||||||
transition.setScale(view: sendButton, scale: sendButtonScale)
|
transition.setScale(view: sendButton, scale: sendButtonScale)
|
||||||
@ -866,21 +932,32 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
let backgroundAlpha: CGFloat
|
let backgroundAlpha: CGFloat
|
||||||
switch self.presentationAnimationState {
|
switch self.presentationAnimationState {
|
||||||
case .animatedIn:
|
case .animatedIn:
|
||||||
if previousAnimationState.key == .initial && self.initializationDisplayLink == nil {
|
if previousAnimationState.key == .initial {
|
||||||
self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in
|
if environment.inputHeight != 0.0 {
|
||||||
guard let self else {
|
if self.initializationDisplayLink == nil {
|
||||||
return
|
self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.initializationDisplayLink?.invalidate()
|
||||||
|
self.initializationDisplayLink = nil
|
||||||
|
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if component.mediaPreview == nil {
|
||||||
|
component.textInputView.isHidden = true
|
||||||
|
}
|
||||||
|
component.sourceSendButton.isHidden = true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
self.initializationDisplayLink?.invalidate()
|
if component.mediaPreview == nil {
|
||||||
self.initializationDisplayLink = nil
|
component.textInputView.isHidden = true
|
||||||
|
|
||||||
guard let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
component.textInputView.isHidden = true
|
|
||||||
component.sourceSendButton.isHidden = true
|
component.sourceSendButton.isHidden = true
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundAlpha = 1.0
|
backgroundAlpha = 1.0
|
||||||
@ -888,7 +965,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
backgroundAlpha = 0.0
|
backgroundAlpha = 0.0
|
||||||
|
|
||||||
if self.animateOutToEmpty {
|
if self.animateOutToEmpty {
|
||||||
component.textInputView.isHidden = false
|
if component.mediaPreview == nil {
|
||||||
|
component.textInputView.isHidden = false
|
||||||
|
}
|
||||||
component.sourceSendButton.isHidden = false
|
component.sourceSendButton.isHidden = false
|
||||||
|
|
||||||
transition.setAlpha(view: sendButton, alpha: 0.0)
|
transition.setAlpha(view: sendButton, alpha: 0.0)
|
||||||
@ -906,7 +985,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
if case let .animatedOut(completion) = self.presentationAnimationState {
|
if case let .animatedOut(completion) = self.presentationAnimationState {
|
||||||
if let component = self.component, !self.animateOutToEmpty {
|
if let component = self.component, !self.animateOutToEmpty {
|
||||||
component.textInputView.isHidden = false
|
if component.mediaPreview == nil {
|
||||||
|
component.textInputView.isHidden = false
|
||||||
|
}
|
||||||
component.sourceSendButton.isHidden = false
|
component.sourceSendButton.isHidden = false
|
||||||
}
|
}
|
||||||
completion()
|
completion()
|
||||||
@ -950,6 +1031,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
|||||||
gesture: ContextGesture,
|
gesture: ContextGesture,
|
||||||
sourceSendButton: ASDisplayNode,
|
sourceSendButton: ASDisplayNode,
|
||||||
textInputView: UITextView,
|
textInputView: UITextView,
|
||||||
|
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
|
||||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||||
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
||||||
attachment: Bool,
|
attachment: Bool,
|
||||||
@ -967,6 +1049,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
|||||||
context: context,
|
context: context,
|
||||||
component: ChatSendMessageContextScreenComponent(
|
component: ChatSendMessageContextScreenComponent(
|
||||||
context: context,
|
context: context,
|
||||||
|
updatedPresentationData: updatedPresentationData,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
isScheduledMessages: isScheduledMessages,
|
isScheduledMessages: isScheduledMessages,
|
||||||
forwardMessageIds: forwardMessageIds,
|
forwardMessageIds: forwardMessageIds,
|
||||||
@ -974,6 +1057,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
|||||||
gesture: gesture,
|
gesture: gesture,
|
||||||
sourceSendButton: sourceSendButton,
|
sourceSendButton: sourceSendButton,
|
||||||
textInputView: textInputView,
|
textInputView: textInputView,
|
||||||
|
mediaPreview: mediaPreview,
|
||||||
emojiViewProvider: emojiViewProvider,
|
emojiViewProvider: emojiViewProvider,
|
||||||
wallpaperBackgroundNode: wallpaperBackgroundNode,
|
wallpaperBackgroundNode: wallpaperBackgroundNode,
|
||||||
attachment: attachment,
|
attachment: attachment,
|
||||||
@ -987,18 +1071,12 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
|||||||
),
|
),
|
||||||
navigationBarAppearance: .none,
|
navigationBarAppearance: .none,
|
||||||
statusBarStyle: .none,
|
statusBarStyle: .none,
|
||||||
presentationMode: .default
|
presentationMode: .default,
|
||||||
|
updatedPresentationData: updatedPresentationData
|
||||||
)
|
)
|
||||||
|
|
||||||
self.lockOrientation = true
|
self.lockOrientation = true
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
|
||||||
/*gesture.externalEnded = { [weak self] _ in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.dismiss()
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
@ -97,10 +97,12 @@ private final class EffectIcon: Component {
|
|||||||
textView = ComponentView()
|
textView = ComponentView()
|
||||||
self.textView = textView
|
self.textView = textView
|
||||||
}
|
}
|
||||||
|
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0)
|
||||||
let textSize = textView.update(
|
let textSize = textView.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: text, font: Font.regular(10.0), textColor: .black))
|
text: .plain(NSAttributedString(string: text, font: Font.regular(10.0), textColor: .black)),
|
||||||
|
insets: textInsets
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
@ -138,12 +140,19 @@ final class MessageItemView: UIView {
|
|||||||
|
|
||||||
private let textClippingContainer: UIView
|
private let textClippingContainer: UIView
|
||||||
private var textNode: ChatInputTextNode?
|
private var textNode: ChatInputTextNode?
|
||||||
|
private var customEmojiContainerView: CustomEmojiContainerView?
|
||||||
|
private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
||||||
|
|
||||||
|
private var mediaPreviewClippingView: UIView?
|
||||||
|
private var mediaPreview: ChatSendMessageContextScreenMediaPreview?
|
||||||
|
|
||||||
private var effectIcon: ComponentView<Empty>?
|
private var effectIcon: ComponentView<Empty>?
|
||||||
var effectIconView: UIView? {
|
var effectIconView: UIView? {
|
||||||
return self.effectIcon?.view
|
return self.effectIcon?.view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var effectIconBackgroundView: UIImageView?
|
||||||
|
|
||||||
private var chatTheme: ChatPresentationThemeData?
|
private var chatTheme: ChatPresentationThemeData?
|
||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
|
|
||||||
@ -167,19 +176,36 @@ final class MessageItemView: UIView {
|
|||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateIn(transition: Transition) {
|
||||||
|
if let mediaPreview = self.mediaPreview {
|
||||||
|
mediaPreview.animateIn(transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(transition: Transition) {
|
||||||
|
if let mediaPreview = self.mediaPreview {
|
||||||
|
mediaPreview.animateOut(transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(
|
func update(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
presentationData: PresentationData,
|
presentationData: PresentationData,
|
||||||
backgroundNode: WallpaperBackgroundNode?,
|
backgroundNode: WallpaperBackgroundNode?,
|
||||||
textString: NSAttributedString,
|
textString: NSAttributedString,
|
||||||
sourceTextInputView: ChatInputTextView?,
|
sourceTextInputView: ChatInputTextView?,
|
||||||
|
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||||
|
sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?,
|
||||||
textInsets: UIEdgeInsets,
|
textInsets: UIEdgeInsets,
|
||||||
explicitBackgroundSize: CGSize?,
|
explicitBackgroundSize: CGSize?,
|
||||||
maxTextWidth: CGFloat,
|
maxTextWidth: CGFloat,
|
||||||
maxTextHeight: CGFloat,
|
maxTextHeight: CGFloat,
|
||||||
|
containerSize: CGSize,
|
||||||
effect: AvailableMessageEffects.MessageEffect?,
|
effect: AvailableMessageEffects.MessageEffect?,
|
||||||
transition: Transition
|
transition: Transition
|
||||||
) -> CGSize {
|
) -> CGSize {
|
||||||
|
self.emojiViewProvider = emojiViewProvider
|
||||||
|
|
||||||
var effectIconSize: CGSize?
|
var effectIconSize: CGSize?
|
||||||
if let effect {
|
if let effect {
|
||||||
let effectIcon: ComponentView<Empty>
|
let effectIcon: ComponentView<Empty>
|
||||||
@ -206,89 +232,6 @@ final class MessageItemView: UIView {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var textCutout: TextNodeCutout?
|
|
||||||
if let effectIconSize {
|
|
||||||
textCutout = TextNodeCutout(bottomRight: CGSize(width: effectIconSize.width + 4.0, height: effectIconSize.height))
|
|
||||||
}
|
|
||||||
let _ = textCutout
|
|
||||||
|
|
||||||
let textNode: ChatInputTextNode
|
|
||||||
if let current = self.textNode {
|
|
||||||
textNode = current
|
|
||||||
} else {
|
|
||||||
textNode = ChatInputTextNode(disableTiling: true)
|
|
||||||
textNode.textView.isScrollEnabled = false
|
|
||||||
textNode.isUserInteractionEnabled = false
|
|
||||||
self.textNode = textNode
|
|
||||||
self.textClippingContainer.addSubview(textNode.view)
|
|
||||||
|
|
||||||
if let sourceTextInputView {
|
|
||||||
textNode.textView.defaultTextContainerInset = sourceTextInputView.defaultTextContainerInset
|
|
||||||
}
|
|
||||||
|
|
||||||
let messageAttributedText = NSMutableAttributedString(attributedString: textString)
|
|
||||||
messageAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (messageAttributedText.string as NSString).length))
|
|
||||||
textNode.attributedText = messageAttributedText
|
|
||||||
}
|
|
||||||
|
|
||||||
let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor
|
|
||||||
let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle
|
|
||||||
if let sourceTextInputView, let textTheme = sourceTextInputView.theme {
|
|
||||||
switch textTheme.quote.lineStyle {
|
|
||||||
case .solid:
|
|
||||||
mappedLineStyle = .solid(color: mainColor)
|
|
||||||
case .doubleDashed:
|
|
||||||
mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear)
|
|
||||||
case .tripleDashed:
|
|
||||||
mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mappedLineStyle = .solid(color: mainColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
textNode.textView.theme = ChatInputTextView.Theme(
|
|
||||||
quote: ChatInputTextView.Theme.Quote(
|
|
||||||
background: mainColor.withMultipliedAlpha(0.1),
|
|
||||||
foreground: mainColor,
|
|
||||||
lineStyle: mappedLineStyle,
|
|
||||||
codeBackground: mainColor.withMultipliedAlpha(0.1),
|
|
||||||
codeForeground: mainColor
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0)
|
|
||||||
|
|
||||||
var currentRightInset: CGFloat = 0.0
|
|
||||||
if let sourceTextInputView {
|
|
||||||
currentRightInset = sourceTextInputView.currentRightInset
|
|
||||||
}
|
|
||||||
let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset)
|
|
||||||
textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight))
|
|
||||||
|
|
||||||
let textBoundingRect = textNode.textView.currentTextBoundingRect().integral
|
|
||||||
let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral
|
|
||||||
|
|
||||||
let textWidth = textBoundingRect.width
|
|
||||||
let textSize = CGSize(width: textWidth, height: textHeight)
|
|
||||||
|
|
||||||
var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom)
|
|
||||||
|
|
||||||
let effectInset: CGFloat = 12.0
|
|
||||||
if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset {
|
|
||||||
if lastLineBoundingRect != textBoundingRect {
|
|
||||||
positionedTextSize.height += 11.0
|
|
||||||
} else {
|
|
||||||
positionedTextSize.width += effectInset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom)
|
|
||||||
|
|
||||||
positionedTextSize.height = min(positionedTextSize.height, maxTextHeight)
|
|
||||||
|
|
||||||
let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom)
|
|
||||||
|
|
||||||
let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize)
|
|
||||||
|
|
||||||
let chatTheme: ChatPresentationThemeData
|
let chatTheme: ChatPresentationThemeData
|
||||||
if let current = self.chatTheme, current.theme === presentationData.theme {
|
if let current = self.chatTheme, current.theme === presentationData.theme {
|
||||||
chatTheme = current
|
chatTheme = current
|
||||||
@ -305,89 +248,329 @@ final class MessageItemView: UIView {
|
|||||||
maskMode: true,
|
maskMode: true,
|
||||||
backgroundNode: backgroundNode
|
backgroundNode: backgroundNode
|
||||||
)
|
)
|
||||||
self.backgroundNode.setType(
|
|
||||||
type: .outgoing(.None),
|
|
||||||
highlighted: false,
|
|
||||||
graphics: themeGraphics,
|
|
||||||
maskMode: true,
|
|
||||||
hasWallpaper: true,
|
|
||||||
transition: transition.containedViewLayoutTransition,
|
|
||||||
backgroundNode: backgroundNode
|
|
||||||
)
|
|
||||||
|
|
||||||
let backgroundSize = explicitBackgroundSize ?? size
|
if let sourceMediaPreview {
|
||||||
|
let mediaPreviewClippingView: UIView
|
||||||
let previousSize = self.currentSize
|
if let current = self.mediaPreviewClippingView {
|
||||||
self.currentSize = backgroundSize
|
mediaPreviewClippingView = current
|
||||||
|
} else {
|
||||||
let textClippingContainerFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: backgroundSize.width - 1.0 - 7.0, height: backgroundSize.height - 1.0 - 1.0))
|
mediaPreviewClippingView = UIView()
|
||||||
|
mediaPreviewClippingView.layer.anchorPoint = CGPoint()
|
||||||
var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size)
|
mediaPreviewClippingView.clipsToBounds = true
|
||||||
if explicitBackgroundSize != nil, let sourceTextInputView {
|
mediaPreviewClippingView.isUserInteractionEnabled = false
|
||||||
textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y
|
self.mediaPreviewClippingView = mediaPreviewClippingView
|
||||||
} else {
|
self.addSubview(mediaPreviewClippingView)
|
||||||
textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0
|
|
||||||
textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y)
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.center)
|
|
||||||
transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds)
|
|
||||||
|
|
||||||
textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight))
|
|
||||||
|
|
||||||
if let effectIcon = self.effectIcon, let effectIconSize {
|
|
||||||
if let effectIconView = effectIcon.view {
|
|
||||||
var animateIn = false
|
|
||||||
if effectIconView.superview == nil {
|
|
||||||
animateIn = true
|
|
||||||
self.addSubview(effectIconView)
|
|
||||||
}
|
|
||||||
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right + 2.0 - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
|
|
||||||
if animateIn {
|
|
||||||
if let previousSize {
|
|
||||||
let previousEffectIconFrame = CGRect(origin: CGPoint(x: previousSize.width - textInsets.right + 2.0 - effectIconSize.width, y: previousSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
|
|
||||||
effectIconView.frame = previousEffectIconFrame
|
|
||||||
} else {
|
|
||||||
effectIconView.frame = effectIconFrame
|
|
||||||
}
|
|
||||||
transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0)
|
|
||||||
if !transition.animation.isImmediate {
|
|
||||||
effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.setFrame(view: effectIconView, frame: effectIconFrame)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if let effectIcon = self.effectIcon {
|
if self.mediaPreview !== sourceMediaPreview {
|
||||||
self.effectIcon = nil
|
self.mediaPreview?.view.removeFromSuperview()
|
||||||
|
self.mediaPreview = nil
|
||||||
|
|
||||||
|
self.mediaPreview = sourceMediaPreview
|
||||||
|
if let mediaPreview = self.mediaPreview {
|
||||||
|
mediaPreviewClippingView.addSubview(mediaPreview.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaPreviewSize = sourceMediaPreview.update(containerSize: containerSize, transition: transition)
|
||||||
|
|
||||||
|
let backgroundSize = CGSize(width: mediaPreviewSize.width + 7.0, height: mediaPreviewSize.height)
|
||||||
|
|
||||||
|
let mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize)
|
||||||
|
transition.setFrame(view: sourceMediaPreview.view, frame: mediaPreviewFrame)
|
||||||
|
|
||||||
|
if let effectIcon = self.effectIcon, let effectIconSize {
|
||||||
if let effectIconView = effectIcon.view {
|
if let effectIconView = effectIcon.view {
|
||||||
let effectIconSize = effectIconView.bounds.size
|
var animateIn = false
|
||||||
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - effectIconSize.height), size: effectIconSize)
|
if effectIconView.superview == nil {
|
||||||
|
animateIn = true
|
||||||
|
self.addSubview(effectIconView)
|
||||||
|
}
|
||||||
|
|
||||||
|
let effectIconBackgroundView: UIImageView
|
||||||
|
if let current = self.effectIconBackgroundView {
|
||||||
|
effectIconBackgroundView = current
|
||||||
|
} else {
|
||||||
|
effectIconBackgroundView = UIImageView()
|
||||||
|
self.effectIconBackgroundView = effectIconBackgroundView
|
||||||
|
self.insertSubview(effectIconBackgroundView, belowSubview: effectIconView)
|
||||||
|
}
|
||||||
|
effectIconBackgroundView.backgroundColor = presentationData.theme.chat.message.mediaDateAndStatusFillColor
|
||||||
|
|
||||||
|
let effectIconBackgroundSize = CGSize(width: effectIconSize.width + 8.0 * 2.0, height: 18.0)
|
||||||
|
let effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 6.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 6.0), size: effectIconBackgroundSize)
|
||||||
|
|
||||||
|
let effectIconFrame = CGRect(origin: CGPoint(x: effectIconBackgroundFrame.minX + floor((effectIconBackgroundFrame.width - effectIconSize.width) * 0.5), y: effectIconBackgroundFrame.minY + floor((effectIconBackgroundFrame.height - effectIconSize.height) * 0.5)), size: effectIconSize)
|
||||||
|
|
||||||
|
if animateIn {
|
||||||
|
effectIconView.frame = effectIconFrame
|
||||||
|
|
||||||
|
effectIconBackgroundView.frame = effectIconBackgroundFrame
|
||||||
|
effectIconBackgroundView.layer.cornerRadius = effectIconBackgroundFrame.height * 0.5
|
||||||
|
|
||||||
|
transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0)
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.animateAlpha(view: effectIconBackgroundView, from: 0.0, to: 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
transition.setFrame(view: effectIconView, frame: effectIconFrame)
|
transition.setFrame(view: effectIconView, frame: effectIconFrame)
|
||||||
transition.setScale(view: effectIconView, scale: 0.001)
|
|
||||||
transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in
|
transition.setFrame(view: effectIconBackgroundView, frame: effectIconBackgroundFrame)
|
||||||
effectIconView?.removeFromSuperview()
|
transition.setCornerRadius(layer: effectIconBackgroundView.layer, cornerRadius: effectIconBackgroundFrame.height * 0.5)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let effectIcon = self.effectIcon {
|
||||||
|
self.effectIcon = nil
|
||||||
|
|
||||||
|
if let effectIconView = effectIcon.view {
|
||||||
|
transition.setScale(view: effectIconView, scale: 0.001)
|
||||||
|
transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in
|
||||||
|
effectIconView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let effectIconBackgroundView = self.effectIconBackgroundView {
|
||||||
|
self.effectIconBackgroundView = nil
|
||||||
|
transition.setAlpha(view: effectIconBackgroundView, alpha: 0.0, completion: { [weak effectIconBackgroundView] _ in
|
||||||
|
effectIconBackgroundView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return backgroundSize
|
||||||
let backgroundAlpha: CGFloat
|
|
||||||
if explicitBackgroundSize != nil {
|
|
||||||
backgroundAlpha = 0.0
|
|
||||||
} else {
|
} else {
|
||||||
backgroundAlpha = 1.0
|
let textNode: ChatInputTextNode
|
||||||
|
if let current = self.textNode {
|
||||||
|
textNode = current
|
||||||
|
} else {
|
||||||
|
textNode = ChatInputTextNode(disableTiling: true)
|
||||||
|
textNode.textView.isScrollEnabled = false
|
||||||
|
textNode.isUserInteractionEnabled = false
|
||||||
|
self.textNode = textNode
|
||||||
|
self.textClippingContainer.addSubview(textNode.view)
|
||||||
|
|
||||||
|
if let sourceTextInputView {
|
||||||
|
textNode.textView.defaultTextContainerInset = sourceTextInputView.defaultTextContainerInset
|
||||||
|
}
|
||||||
|
|
||||||
|
let messageAttributedText = NSMutableAttributedString(attributedString: textString)
|
||||||
|
//messageAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (messageAttributedText.string as NSString).length))
|
||||||
|
textNode.attributedText = messageAttributedText
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor
|
||||||
|
let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle
|
||||||
|
if let sourceTextInputView, let textTheme = sourceTextInputView.theme {
|
||||||
|
switch textTheme.quote.lineStyle {
|
||||||
|
case .solid:
|
||||||
|
mappedLineStyle = .solid(color: mainColor)
|
||||||
|
case .doubleDashed:
|
||||||
|
mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear)
|
||||||
|
case .tripleDashed:
|
||||||
|
mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mappedLineStyle = .solid(color: mainColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
textNode.textView.theme = ChatInputTextView.Theme(
|
||||||
|
quote: ChatInputTextView.Theme.Quote(
|
||||||
|
background: mainColor.withMultipliedAlpha(0.1),
|
||||||
|
foreground: mainColor,
|
||||||
|
lineStyle: mappedLineStyle,
|
||||||
|
codeBackground: mainColor.withMultipliedAlpha(0.1),
|
||||||
|
codeForeground: mainColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0)
|
||||||
|
|
||||||
|
var currentRightInset: CGFloat = 0.0
|
||||||
|
if let sourceTextInputView {
|
||||||
|
currentRightInset = sourceTextInputView.currentRightInset
|
||||||
|
}
|
||||||
|
let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset)
|
||||||
|
textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight))
|
||||||
|
|
||||||
|
let textBoundingRect = textNode.textView.currentTextBoundingRect().integral
|
||||||
|
let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral
|
||||||
|
|
||||||
|
let textWidth = textBoundingRect.width
|
||||||
|
let textSize = CGSize(width: textWidth, height: textHeight)
|
||||||
|
|
||||||
|
var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom)
|
||||||
|
|
||||||
|
let effectInset: CGFloat = 12.0
|
||||||
|
if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset {
|
||||||
|
if lastLineBoundingRect != textBoundingRect {
|
||||||
|
positionedTextSize.height += 11.0
|
||||||
|
} else {
|
||||||
|
positionedTextSize.width += effectInset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom)
|
||||||
|
|
||||||
|
positionedTextSize.height = min(positionedTextSize.height, maxTextHeight)
|
||||||
|
|
||||||
|
let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom)
|
||||||
|
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize)
|
||||||
|
|
||||||
|
self.backgroundNode.setType(
|
||||||
|
type: .outgoing(.None),
|
||||||
|
highlighted: false,
|
||||||
|
graphics: themeGraphics,
|
||||||
|
maskMode: true,
|
||||||
|
hasWallpaper: true,
|
||||||
|
transition: transition.containedViewLayoutTransition,
|
||||||
|
backgroundNode: backgroundNode
|
||||||
|
)
|
||||||
|
|
||||||
|
let backgroundSize = explicitBackgroundSize ?? size
|
||||||
|
|
||||||
|
let previousSize = self.currentSize
|
||||||
|
self.currentSize = backgroundSize
|
||||||
|
|
||||||
|
let textClippingContainerFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: backgroundSize.width - 1.0 - 7.0, height: backgroundSize.height - 1.0 - 1.0))
|
||||||
|
|
||||||
|
var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size)
|
||||||
|
if explicitBackgroundSize != nil, let sourceTextInputView {
|
||||||
|
textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y
|
||||||
|
} else {
|
||||||
|
textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0
|
||||||
|
textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.center)
|
||||||
|
transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds)
|
||||||
|
|
||||||
|
textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight))
|
||||||
|
self.updateTextContents()
|
||||||
|
|
||||||
|
if let effectIcon = self.effectIcon, let effectIconSize {
|
||||||
|
if let effectIconView = effectIcon.view {
|
||||||
|
var animateIn = false
|
||||||
|
if effectIconView.superview == nil {
|
||||||
|
animateIn = true
|
||||||
|
self.addSubview(effectIconView)
|
||||||
|
}
|
||||||
|
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right + 2.0 - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
|
||||||
|
if animateIn {
|
||||||
|
if let previousSize {
|
||||||
|
let previousEffectIconFrame = CGRect(origin: CGPoint(x: previousSize.width - textInsets.right + 2.0 - effectIconSize.width, y: previousSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
|
||||||
|
effectIconView.frame = previousEffectIconFrame
|
||||||
|
} else {
|
||||||
|
effectIconView.frame = effectIconFrame
|
||||||
|
}
|
||||||
|
transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0)
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: effectIconView, frame: effectIconFrame)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let effectIcon = self.effectIcon {
|
||||||
|
self.effectIcon = nil
|
||||||
|
|
||||||
|
if let effectIconView = effectIcon.view {
|
||||||
|
let effectIconSize = effectIconView.bounds.size
|
||||||
|
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - effectIconSize.height), size: effectIconSize)
|
||||||
|
transition.setFrame(view: effectIconView, frame: effectIconFrame)
|
||||||
|
transition.setScale(view: effectIconView, scale: 0.001)
|
||||||
|
transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in
|
||||||
|
effectIconView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundAlpha: CGFloat
|
||||||
|
if explicitBackgroundSize != nil {
|
||||||
|
backgroundAlpha = 0.0
|
||||||
|
} else {
|
||||||
|
backgroundAlpha = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
|
||||||
|
transition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha)
|
||||||
|
self.backgroundWallpaperNode.updateFrame(CGRect(origin: CGPoint(), size: backgroundSize), transition: transition.containedViewLayoutTransition)
|
||||||
|
transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
|
||||||
|
transition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha)
|
||||||
|
self.backgroundNode.updateLayout(size: backgroundSize, transition: transition.containedViewLayoutTransition)
|
||||||
|
|
||||||
|
return backgroundSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateClippingRect(
|
||||||
|
sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?,
|
||||||
|
isAnimatedIn: Bool,
|
||||||
|
localFrame: CGRect,
|
||||||
|
containerSize: CGSize,
|
||||||
|
transition: Transition
|
||||||
|
) {
|
||||||
|
if let mediaPreviewClippingView = self.mediaPreviewClippingView, let sourceMediaPreview {
|
||||||
|
let clippingFrame: CGRect
|
||||||
|
if !isAnimatedIn, let globalClippingRect = sourceMediaPreview.globalClippingRect {
|
||||||
|
clippingFrame = self.convert(globalClippingRect, from: nil)
|
||||||
|
} else {
|
||||||
|
clippingFrame = CGRect(origin: CGPoint(x: -localFrame.minX, y: -localFrame.minY), size: containerSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setPosition(view: mediaPreviewClippingView, position: clippingFrame.origin)
|
||||||
|
transition.setBounds(view: mediaPreviewClippingView, bounds: CGRect(origin: CGPoint(x: clippingFrame.minX, y: clippingFrame.minY), size: clippingFrame.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateTextContents() {
|
||||||
|
guard let textInputNode = self.textNode else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
|
var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
|
||||||
transition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha)
|
|
||||||
self.backgroundWallpaperNode.updateFrame(CGRect(origin: CGPoint(), size: backgroundSize), transition: transition.containedViewLayoutTransition)
|
|
||||||
transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
|
|
||||||
transition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha)
|
|
||||||
self.backgroundNode.updateLayout(size: backgroundSize, transition: transition.containedViewLayoutTransition)
|
|
||||||
|
|
||||||
return backgroundSize
|
if let attributedText = textInputNode.attributedText {
|
||||||
|
let beginning = textInputNode.textView.beginningOfDocument
|
||||||
|
attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in
|
||||||
|
if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
|
||||||
|
if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) {
|
||||||
|
let textRects = textInputNode.textView.selectionRects(for: textRange)
|
||||||
|
for textRect in textRects {
|
||||||
|
customEmojiRects.append((textRect.rect, value))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !customEmojiRects.isEmpty {
|
||||||
|
let customEmojiContainerView: CustomEmojiContainerView
|
||||||
|
if let current = self.customEmojiContainerView {
|
||||||
|
customEmojiContainerView = current
|
||||||
|
} else {
|
||||||
|
customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in
|
||||||
|
guard let self, let emojiViewProvider = self.emojiViewProvider else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return emojiViewProvider(emoji)
|
||||||
|
})
|
||||||
|
customEmojiContainerView.isUserInteractionEnabled = false
|
||||||
|
textInputNode.textView.addSubview(customEmojiContainerView)
|
||||||
|
self.customEmojiContainerView = customEmojiContainerView
|
||||||
|
}
|
||||||
|
|
||||||
|
customEmojiContainerView.update(emojiRects: customEmojiRects)
|
||||||
|
} else {
|
||||||
|
if let customEmojiContainerView = self.customEmojiContainerView {
|
||||||
|
customEmojiContainerView.removeFromSuperview()
|
||||||
|
self.customEmojiContainerView = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,9 @@ final class SendButton: HighlightTrackingButton {
|
|||||||
private let iconView: UIImageView
|
private let iconView: UIImageView
|
||||||
private var activityIndicator: ActivityIndicator?
|
private var activityIndicator: ActivityIndicator?
|
||||||
|
|
||||||
|
private var didProcessSourceCustomContent: Bool = false
|
||||||
|
private var sourceCustomContentView: UIView?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.containerView = UIView()
|
self.containerView = UIView()
|
||||||
self.containerView.isUserInteractionEnabled = false
|
self.containerView.isUserInteractionEnabled = false
|
||||||
@ -50,12 +53,14 @@ final class SendButton: HighlightTrackingButton {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
presentationData: PresentationData,
|
presentationData: PresentationData,
|
||||||
backgroundNode: WallpaperBackgroundNode?,
|
backgroundNode: WallpaperBackgroundNode?,
|
||||||
|
sourceSendButton: ASDisplayNode,
|
||||||
|
isAnimatedIn: Bool,
|
||||||
isLoadingEffectAnimation: Bool,
|
isLoadingEffectAnimation: Bool,
|
||||||
size: CGSize,
|
size: CGSize,
|
||||||
transition: Transition
|
transition: Transition
|
||||||
) {
|
) {
|
||||||
let innerSize = CGSize(width: 33.0, height: 33.0)
|
let innerSize = CGSize(width: size.width - 5.5 * 2.0, height: 33.0)
|
||||||
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: floor((size.width - innerSize.width) * 0.5), y: floor((size.height - innerSize.height) * 0.5)), size: innerSize))
|
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - innerSize.width) * 0.5), y: floorToScreenPixels((size.height - innerSize.height) * 0.5)), size: innerSize))
|
||||||
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: innerSize.height * 0.5)
|
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: innerSize.height * 0.5)
|
||||||
|
|
||||||
if self.window != nil {
|
if self.window != nil {
|
||||||
@ -71,7 +76,7 @@ final class SendButton: HighlightTrackingButton {
|
|||||||
transition.setFrame(view: backgroundContent.view, frame: CGRect(origin: CGPoint(), size: innerSize))
|
transition.setFrame(view: backgroundContent.view, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
if [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor {
|
if backgroundNode != nil && [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor {
|
||||||
self.backgroundContent?.isHidden = false
|
self.backgroundContent?.isHidden = false
|
||||||
self.backgroundLayer.isHidden = true
|
self.backgroundLayer.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
@ -79,18 +84,44 @@ final class SendButton: HighlightTrackingButton {
|
|||||||
self.backgroundLayer.isHidden = false
|
self.backgroundLayer.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundLayer.backgroundColor = presentationData.theme.list.itemAccentColor.cgColor
|
self.backgroundLayer.backgroundColor = presentationData.theme.chat.inputPanel.actionControlFillColor.cgColor
|
||||||
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: innerSize))
|
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
|
|
||||||
|
if !self.didProcessSourceCustomContent {
|
||||||
|
self.didProcessSourceCustomContent = true
|
||||||
|
|
||||||
|
if let sourceSendButton = sourceSendButton as? ChatSendMessageActionSheetControllerSourceSendButtonNode {
|
||||||
|
if let sourceCustomContentView = sourceSendButton.makeCustomContents() {
|
||||||
|
self.sourceCustomContentView = sourceCustomContentView
|
||||||
|
self.iconView.superview?.insertSubview(sourceCustomContentView, belowSubview: self.iconView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.iconView.image == nil {
|
if self.iconView.image == nil {
|
||||||
self.iconView.image = PresentationResourcesChat.chatInputPanelSendIconImage(presentationData.theme)
|
self.iconView.image = PresentationResourcesChat.chatInputPanelSendIconImage(presentationData.theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let sourceCustomContentView = self.sourceCustomContentView {
|
||||||
|
let sourceCustomContentSize = sourceCustomContentView.bounds.size
|
||||||
|
let sourceCustomContentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - sourceCustomContentSize.width) * 0.5) + UIScreenPixel, y: floorToScreenPixels((innerSize.height - sourceCustomContentSize.height) * 0.5)), size: sourceCustomContentSize)
|
||||||
|
transition.setPosition(view: sourceCustomContentView, position: sourceCustomContentFrame.center)
|
||||||
|
transition.setBounds(view: sourceCustomContentView, bounds: CGRect(origin: CGPoint(), size: sourceCustomContentFrame.size))
|
||||||
|
transition.setAlpha(view: sourceCustomContentView, alpha: isAnimatedIn ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let icon = self.iconView.image {
|
if let icon = self.iconView.image {
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: floor((innerSize.width - icon.size.width) * 0.5), y: floor((innerSize.height - icon.size.height) * 0.5)), size: icon.size)
|
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - icon.size.width) * 0.5) - UIScreenPixel, y: floorToScreenPixels((innerSize.height - icon.size.height) * 0.5)), size: icon.size)
|
||||||
transition.setPosition(view: self.iconView, position: iconFrame.center)
|
transition.setPosition(view: self.iconView, position: iconFrame.center)
|
||||||
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||||
transition.setAlpha(view: self.iconView, alpha: isLoadingEffectAnimation ? 0.0 : 1.0)
|
|
||||||
|
let iconViewAlpha: CGFloat
|
||||||
|
if (self.sourceCustomContentView != nil && !isAnimatedIn) || isLoadingEffectAnimation {
|
||||||
|
iconViewAlpha = 0.0
|
||||||
|
} else {
|
||||||
|
iconViewAlpha = 1.0
|
||||||
|
}
|
||||||
|
transition.setAlpha(view: self.iconView, alpha: iconViewAlpha)
|
||||||
transition.setScale(view: self.iconView, scale: isLoadingEffectAnimation ? 0.001 : 1.0)
|
transition.setScale(view: self.iconView, scale: isLoadingEffectAnimation ? 0.001 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,10 +549,10 @@ private final class CreatePollContext: AttachmentMediaPickerContext {
|
|||||||
func setCaption(_ caption: NSAttributedString) {
|
func setCaption(_ caption: NSAttributedString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
|
@ -404,10 +404,10 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
|
|||||||
func setCaption(_ caption: NSAttributedString) {
|
func setCaption(_ caption: NSAttributedString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
|
@ -47,6 +47,9 @@ swift_library(
|
|||||||
"//submodules/RadialStatusNode",
|
"//submodules/RadialStatusNode",
|
||||||
"//submodules/Camera",
|
"//submodules/Camera",
|
||||||
"//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation",
|
"//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation",
|
||||||
|
"//submodules/ChatSendMessageActionUI",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -25,6 +25,7 @@ import Camera
|
|||||||
import CameraScreen
|
import CameraScreen
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
import ImageObjectSeparation
|
import ImageObjectSeparation
|
||||||
|
import ChatSendMessageActionUI
|
||||||
|
|
||||||
final class MediaPickerInteraction {
|
final class MediaPickerInteraction {
|
||||||
let downloadManager: AssetDownloadManager
|
let downloadManager: AssetDownloadManager
|
||||||
@ -32,14 +33,14 @@ final class MediaPickerInteraction {
|
|||||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||||
let openDraft: (MediaEditorDraft, UIImage?) -> Void
|
let openDraft: (MediaEditorDraft, UIImage?) -> Void
|
||||||
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool
|
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool
|
||||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void
|
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void
|
||||||
let schedule: () -> Void
|
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
|
||||||
let dismissInput: () -> Void
|
let dismissInput: () -> Void
|
||||||
let selectionState: TGMediaSelectionContext?
|
let selectionState: TGMediaSelectionContext?
|
||||||
let editingState: TGMediaEditingContext
|
let editingState: TGMediaEditingContext
|
||||||
var hiddenMediaId: String?
|
var hiddenMediaId: String?
|
||||||
|
|
||||||
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||||
self.downloadManager = downloadManager
|
self.downloadManager = downloadManager
|
||||||
self.openMedia = openMedia
|
self.openMedia = openMedia
|
||||||
self.openSelectedMedia = openSelectedMedia
|
self.openSelectedMedia = openSelectedMedia
|
||||||
@ -199,7 +200,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
public var presentFilePicker: () -> Void = {}
|
public var presentFilePicker: () -> Void = {}
|
||||||
|
|
||||||
private var completed = false
|
private var completed = false
|
||||||
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _ in }
|
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _ in }
|
||||||
|
|
||||||
public var requestAttachmentMenuExpansion: () -> Void = { }
|
public var requestAttachmentMenuExpansion: () -> Void = { }
|
||||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||||
@ -212,6 +213,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
public var isContainerPanning: () -> Bool = { return false }
|
public var isContainerPanning: () -> Bool = { return false }
|
||||||
public var isContainerExpanded: () -> Bool = { return false }
|
public var isContainerExpanded: () -> Bool = { return false }
|
||||||
|
|
||||||
|
public var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? = nil
|
||||||
|
|
||||||
private let selectedCollection = Promise<PHAssetCollection?>(nil)
|
private let selectedCollection = Promise<PHAssetCollection?>(nil)
|
||||||
|
|
||||||
var dismissAll: () -> Void = { }
|
var dismissAll: () -> Void = { }
|
||||||
@ -433,6 +436,49 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controller.getCurrentSendMessageContextMediaPreview = { [weak self] () -> ChatSendMessageContextScreenMediaPreview? in
|
||||||
|
guard let self else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let controller = self.controller else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let (layout, navigationHeight) = self.validLayout else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var persistentItems = false
|
||||||
|
if case .media = controller.subject {
|
||||||
|
persistentItems = true
|
||||||
|
}
|
||||||
|
let previewNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: true)
|
||||||
|
let clippingRect = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - navigationHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom - 1.0)))
|
||||||
|
previewNode.globalClippingRect = self.view.convert(clippingRect, to: nil)
|
||||||
|
previewNode.interaction = self.controller?.interaction
|
||||||
|
previewNode.getTransitionView = { [weak self] identifier in
|
||||||
|
guard let self else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var node: MediaPickerGridItemNode?
|
||||||
|
self.gridNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.identifier == identifier {
|
||||||
|
node = itemNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let node = node {
|
||||||
|
return (node.view, node.spoilerNode?.dustNode, { [weak node] animateCheckNode in
|
||||||
|
node?.animateFadeIn(animateCheckNode: animateCheckNode, animateSpoilerNode: false)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
|
||||||
|
previewNode.updateLayout(size: layout.size, insets: UIEdgeInsets(), items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: .immediate)
|
||||||
|
|
||||||
|
return previewNode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -945,7 +991,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
persistentItems = true
|
persistentItems = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems)
|
let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: false)
|
||||||
selectionNode.alpha = animated ? 0.0 : 1.0
|
selectionNode.alpha = animated ? 0.0 : 1.0
|
||||||
selectionNode.layer.allowsGroupOpacity = true
|
selectionNode.layer.allowsGroupOpacity = true
|
||||||
selectionNode.isUserInteractionEnabled = false
|
selectionNode.isUserInteractionEnabled = false
|
||||||
@ -992,11 +1038,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
if animated {
|
if animated {
|
||||||
switch displayMode {
|
switch displayMode {
|
||||||
case .selected:
|
case .selected:
|
||||||
self.selectionNode?.animateIn(initiated: { [weak self] in
|
self.selectionNode?.animateIn(transition: .animated(duration: 0.25, curve: .easeInOut), initiated: { [weak self] in
|
||||||
self?.updateNavigation(transition: .immediate)
|
self?.updateNavigation(transition: .immediate)
|
||||||
}, completion: completion)
|
}, completion: completion)
|
||||||
case .all:
|
case .all:
|
||||||
self.selectionNode?.animateOut(completion: completion)
|
self.selectionNode?.animateOut(transition: .animated(duration: 0.25, curve: .easeInOut), completion: completion)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.updateNavigation(transition: .immediate)
|
self.updateNavigation(transition: .immediate)
|
||||||
@ -1099,7 +1145,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
return self?.transitionView(for: identifier)
|
return self?.transitionView(for: identifier)
|
||||||
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
|
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion)
|
||||||
}
|
}
|
||||||
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||||
self?.currentGalleryParentController = c
|
self?.currentGalleryParentController = c
|
||||||
@ -1138,7 +1184,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
return self?.transitionView(for: identifier)
|
return self?.transitionView(for: identifier)
|
||||||
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
|
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion)
|
||||||
}
|
}
|
||||||
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||||
self?.currentGalleryParentController = c
|
self?.currentGalleryParentController = c
|
||||||
@ -1172,7 +1218,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, completion: @escaping () -> Void) {
|
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, messageEffect: ChatSendMessageActionSheetController.MessageEffect?, completion: @escaping () -> Void) {
|
||||||
guard let controller = self.controller, !controller.completed else {
|
guard let controller = self.controller, !controller.completed else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1200,7 +1246,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.completed = true
|
controller.completed = true
|
||||||
controller.legacyCompletion(signals, silently, scheduleTime, { [weak self] identifier in
|
controller.legacyCompletion(signals, silently, scheduleTime, messageEffect, { [weak self] identifier in
|
||||||
return !asFile ? self?.getItemSnapshot(identifier) : nil
|
return !asFile ? self?.getItemSnapshot(identifier) : nil
|
||||||
}, { [weak self] in
|
}, { [weak self] in
|
||||||
completion()
|
completion()
|
||||||
@ -1913,18 +1959,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in
|
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, messageEffect, completion in
|
||||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing {
|
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing {
|
||||||
strongSelf.isDismissing = true
|
strongSelf.isDismissing = true
|
||||||
if let currentItem = currentItem {
|
if let currentItem = currentItem {
|
||||||
selectionState.setItem(currentItem, selected: true)
|
selectionState.setItem(currentItem, selected: true)
|
||||||
}
|
}
|
||||||
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, completion: completion)
|
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, messageEffect: messageEffect, completion: completion)
|
||||||
}
|
}
|
||||||
}, schedule: { [weak self] in
|
}, schedule: { [weak self] messageEffect in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
||||||
self?.interaction?.sendSelected(nil, false, time, true, {})
|
self?.interaction?.sendSelected(nil, false, time, true, messageEffect, {})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, dismissInput: { [weak self] in
|
}, dismissInput: { [weak self] in
|
||||||
@ -2384,7 +2430,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, completion: {})
|
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, messageEffect: nil, completion: {})
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
if selectionCount > 1 {
|
if selectionCount > 1 {
|
||||||
@ -2517,12 +2563,12 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||||||
self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
|
self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {})
|
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, messageEffect, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
self.controller?.interaction?.schedule()
|
self.controller?.interaction?.schedule(messageEffect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
|
@ -13,11 +13,15 @@ import MosaicLayout
|
|||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import ChatMessageBackground
|
import ChatMessageBackground
|
||||||
|
import ChatSendMessageActionUI
|
||||||
|
import ComponentFlow
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
|
||||||
private class MediaPickerSelectedItemNode: ASDisplayNode {
|
private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||||
let asset: TGMediaEditableItem
|
let asset: TGMediaEditableItem
|
||||||
private let interaction: MediaPickerInteraction?
|
private let interaction: MediaPickerInteraction?
|
||||||
private let enableAnimations: Bool
|
private let enableAnimations: Bool
|
||||||
|
private let isExternalPreview: Bool
|
||||||
|
|
||||||
private let imageNode: ImageNode
|
private let imageNode: ImageNode
|
||||||
private var checkNode: InteractiveCheckNode?
|
private var checkNode: InteractiveCheckNode?
|
||||||
@ -57,10 +61,11 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var videoDuration: Double?
|
private var videoDuration: Double?
|
||||||
|
|
||||||
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool) {
|
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool, isExternalPreview: Bool) {
|
||||||
self.asset = asset
|
self.asset = asset
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
self.enableAnimations = enableAnimations
|
self.enableAnimations = enableAnimations
|
||||||
|
self.isExternalPreview = isExternalPreview
|
||||||
|
|
||||||
self.imageNode = ImageNode()
|
self.imageNode = ImageNode()
|
||||||
self.imageNode.contentMode = .scaleAspectFill
|
self.imageNode.contentMode = .scaleAspectFill
|
||||||
@ -279,7 +284,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let checkNode = self.checkNode {
|
if let checkNode = self.checkNode {
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
transition.updateAlpha(node: checkNode, alpha: selectionState.count() < 2 ? 0.0 : 1.0)
|
transition.updateAlpha(node: checkNode, alpha: (self.isExternalPreview || selectionState.count() < 2) ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -382,7 +387,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateFrom(_ view: UIView) {
|
func animateFrom(_ view: UIView, transition: ContainedViewLayoutTransition) {
|
||||||
view.alpha = 0.0
|
view.alpha = 0.0
|
||||||
|
|
||||||
let frame = view.convert(view.bounds, to: self.supernode?.view)
|
let frame = view.convert(view.bounds, to: self.supernode?.view)
|
||||||
@ -392,13 +397,19 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||||||
self.durationBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.durationBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
self.updateLayout(size: frame.size, transition: .immediate)
|
self.updateLayout(size: frame.size, transition: .immediate)
|
||||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
self.updateLayout(size: targetFrame.size, transition: transition)
|
||||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in
|
transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, completion: { [weak self, weak view] _ in
|
||||||
view?.alpha = 1.0
|
guard let self else {
|
||||||
|
view?.alpha = 1.0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.isExternalPreview {
|
||||||
|
view?.alpha = 1.0
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateTo(_ view: UIView, dustNode: ASDisplayNode?, completion: @escaping (Bool) -> Void) {
|
func animateTo(_ view: UIView, dustNode: ASDisplayNode?, transition: ContainedViewLayoutTransition, completion: @escaping (Bool) -> Void) {
|
||||||
view.alpha = 0.0
|
view.alpha = 0.0
|
||||||
|
|
||||||
let frame = self.frame
|
let frame = self.frame
|
||||||
@ -418,14 +429,13 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||||||
self.addSubnode(dustNode)
|
self.addSubnode(dustNode)
|
||||||
dustNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
dustNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
dustNode.layer.animatePosition(from: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
transition.animatePositionAdditive(layer: dustNode.layer, offset: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position)
|
||||||
|
|
||||||
self.spoilerNode?.dustNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
self.spoilerNode?.dustNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.corners = []
|
self.corners = []
|
||||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
self.updateLayout(size: targetFrame.size, transition: transition)
|
||||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in
|
transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, removeOnCompletion: false, completion: { [weak view, weak self] _ in
|
||||||
view?.alpha = 1.0
|
view?.alpha = 1.0
|
||||||
|
|
||||||
self?.durationTextNode?.layer.removeAllAnimations()
|
self?.durationTextNode?.layer.removeAllAnimations()
|
||||||
@ -471,7 +481,7 @@ private class MessageBackgroundNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var absoluteRect: (CGRect, CGSize)?
|
private var absoluteRect: (CGRect, CGSize)?
|
||||||
|
|
||||||
func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode?, transition: ContainedViewLayoutTransition) {
|
||||||
self.backgroundNode.setType(type: .outgoing(.Extracted), highlighted: false, graphics: graphics, maskMode: false, hasWallpaper: wallpaper.hasWallpaper, transition: transition, backgroundNode: wallpaperBackgroundNode)
|
self.backgroundNode.setType(type: .outgoing(.Extracted), highlighted: false, graphics: graphics, maskMode: false, hasWallpaper: wallpaper.hasWallpaper, transition: transition, backgroundNode: wallpaperBackgroundNode)
|
||||||
self.backgroundWallpaperNode.setType(type: .outgoing(.Extracted), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: wallpaperBackgroundNode)
|
self.backgroundWallpaperNode.setType(type: .outgoing(.Extracted), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: wallpaperBackgroundNode)
|
||||||
self.shadowNode.setType(type: .outgoing(.Extracted), hasWallpaper: wallpaper.hasWallpaper, graphics: graphics)
|
self.shadowNode.setType(type: .outgoing(.Extracted), hasWallpaper: wallpaper.hasWallpaper, graphics: graphics)
|
||||||
@ -496,11 +506,13 @@ private class MessageBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
|
final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate, ChatSendMessageContextScreenMediaPreview {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let persistentItems: Bool
|
private let persistentItems: Bool
|
||||||
|
private let isExternalPreview: Bool
|
||||||
|
var globalClippingRect: CGRect?
|
||||||
|
|
||||||
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
|
fileprivate var wallpaperBackgroundNode: WallpaperBackgroundNode?
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
private var backgroundNodes: [Int: MessageBackgroundNode] = [:]
|
private var backgroundNodes: [Int: MessageBackgroundNode] = [:]
|
||||||
private var itemNodes: [String: MediaPickerSelectedItemNode] = [:]
|
private var itemNodes: [String: MediaPickerSelectedItemNode] = [:]
|
||||||
@ -518,17 +530,28 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
private var didSetReady = false
|
private var didSetReady = false
|
||||||
private var ready = Promise<Bool>()
|
private var ready = Promise<Bool>()
|
||||||
|
|
||||||
init(context: AccountContext, persistentItems: Bool) {
|
private var contentSize: CGSize = CGSize()
|
||||||
|
|
||||||
|
var isReady: Signal<Bool, NoError> {
|
||||||
|
return self.ready.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(context: AccountContext, persistentItems: Bool, isExternalPreview: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.persistentItems = persistentItems
|
self.persistentItems = persistentItems
|
||||||
|
self.isExternalPreview = isExternalPreview
|
||||||
|
|
||||||
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false)
|
|
||||||
self.wallpaperBackgroundNode.backgroundColor = .black
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
|
self.scrollNode.clipsToBounds = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.wallpaperBackgroundNode)
|
if !self.isExternalPreview {
|
||||||
|
let wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false)
|
||||||
|
wallpaperBackgroundNode.backgroundColor = .black
|
||||||
|
self.wallpaperBackgroundNode = wallpaperBackgroundNode
|
||||||
|
self.addSubnode(wallpaperBackgroundNode)
|
||||||
|
}
|
||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,7 +601,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
|
|
||||||
var getTransitionView: (String) -> (UIView, ASDisplayNode?, (Bool) -> Void)? = { _ in return nil }
|
var getTransitionView: (String) -> (UIView, ASDisplayNode?, (Bool) -> Void)? = { _ in return nil }
|
||||||
|
|
||||||
func animateIn(initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) {
|
func animateIn(transition: ContainedViewLayoutTransition, initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) {
|
||||||
let _ = (self.ready.get()
|
let _ = (self.ready.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
@ -589,79 +612,105 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
strongSelf.alpha = 1.0
|
strongSelf.alpha = 1.0
|
||||||
initiated()
|
initiated()
|
||||||
|
|
||||||
strongSelf.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
if let wallpaperBackgroundNode = strongSelf.wallpaperBackgroundNode {
|
||||||
|
wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
} else {
|
||||||
completion()
|
completion()
|
||||||
})
|
}
|
||||||
strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
|
|
||||||
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
||||||
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||||
|
if strongSelf.isExternalPreview {
|
||||||
|
Transition.immediate.setScale(layer: backgroundNode.layer, scale: 0.001)
|
||||||
|
transition.updateTransformScale(layer: backgroundNode.layer, scale: 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (identifier, itemNode) in strongSelf.itemNodes {
|
for (identifier, itemNode) in strongSelf.itemNodes {
|
||||||
if let (transitionView, _, _) = strongSelf.getTransitionView(identifier) {
|
if let (transitionView, _, _) = strongSelf.getTransitionView(identifier) {
|
||||||
itemNode.animateFrom(transitionView)
|
itemNode.animateFrom(transitionView, transition: transition)
|
||||||
} else {
|
} else {
|
||||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
if strongSelf.isExternalPreview {
|
||||||
|
itemNode.alpha = 0.0
|
||||||
|
transition.animateTransformScale(node: itemNode, from: 0.001)
|
||||||
|
transition.updateAlpha(node: itemNode, alpha: 1.0)
|
||||||
|
} else {
|
||||||
|
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let topNode = strongSelf.messageNodes?.first, !topNode.alpha.isZero {
|
if let topNode = strongSelf.messageNodes?.first, !topNode.alpha.isZero {
|
||||||
topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||||
topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(x: 0.0, y: -30.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let bottomNode = strongSelf.messageNodes?.last, !bottomNode.alpha.isZero {
|
if let bottomNode = strongSelf.messageNodes?.last, !bottomNode.alpha.isZero {
|
||||||
bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||||
bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(x: 0.0, y: 30.0))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void = {}) {
|
func animateOut(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) {
|
||||||
self.wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
if let wallpaperBackgroundNode = self.wallpaperBackgroundNode{
|
||||||
completion()
|
wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
|
completion()
|
||||||
if let strongSelf = self {
|
|
||||||
Queue.mainQueue().after(0.01) {
|
if let strongSelf = self {
|
||||||
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
Queue.mainQueue().after(0.01) {
|
||||||
backgroundNode.layer.removeAllAnimations()
|
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
||||||
|
backgroundNode.layer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, itemNode) in strongSelf.itemNodes {
|
||||||
|
itemNode.layer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.messageNodes?.first?.layer.removeAllAnimations()
|
||||||
|
strongSelf.messageNodes?.last?.layer.removeAllAnimations()
|
||||||
|
|
||||||
|
strongSelf.wallpaperBackgroundNode?.layer.removeAllAnimations()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, itemNode) in strongSelf.itemNodes {
|
|
||||||
itemNode.layer.removeAllAnimations()
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.messageNodes?.first?.layer.removeAllAnimations()
|
|
||||||
strongSelf.messageNodes?.last?.layer.removeAllAnimations()
|
|
||||||
|
|
||||||
strongSelf.wallpaperBackgroundNode.layer.removeAllAnimations()
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
|
} else {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
|
||||||
for (_, backgroundNode) in self.backgroundNodes {
|
for (_, backgroundNode) in self.backgroundNodes {
|
||||||
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||||
|
if self.isExternalPreview {
|
||||||
|
transition.updateTransformScale(layer: backgroundNode.layer, scale: 0.001)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (identifier, itemNode) in self.itemNodes {
|
for (identifier, itemNode) in self.itemNodes {
|
||||||
if let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) {
|
if let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) {
|
||||||
itemNode.animateTo(transitionView, dustNode: maybeDustNode, completion: completion)
|
itemNode.animateTo(transitionView, dustNode: maybeDustNode, transition: transition, completion: completion)
|
||||||
} else {
|
} else {
|
||||||
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
if self.isExternalPreview {
|
||||||
|
transition.updateTransformScale(node: itemNode, scale: 0.001)
|
||||||
|
transition.updateAlpha(node: itemNode, alpha: 0.0)
|
||||||
|
} else {
|
||||||
|
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let topNode = self.messageNodes?.first {
|
if let topNode = self.messageNodes?.first {
|
||||||
topNode.layer.animateAlpha(from: topNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
topNode.layer.animateAlpha(from: topNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
topNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let bottomNode = self.messageNodes?.last {
|
if let bottomNode = self.messageNodes?.last {
|
||||||
bottomNode.layer.animateAlpha(from: bottomNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
bottomNode.layer.animateAlpha(from: bottomNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
bottomNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -782,7 +831,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
if let current = self.itemNodes[identifier] {
|
if let current = self.itemNodes[identifier] {
|
||||||
itemNode = current
|
itemNode = current
|
||||||
} else {
|
} else {
|
||||||
itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
|
itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency, isExternalPreview: self.isExternalPreview)
|
||||||
self.itemNodes[identifier] = itemNode
|
self.itemNodes[identifier] = itemNode
|
||||||
self.scrollNode.addSubnode(itemNode)
|
self.scrollNode.addSubnode(itemNode)
|
||||||
|
|
||||||
@ -852,55 +901,61 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
|
if !self.isExternalPreview {
|
||||||
var peers = SimpleDictionary<PeerId, Peer>()
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
|
||||||
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)
|
var peers = SimpleDictionary<PeerId, Peer>()
|
||||||
|
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)
|
||||||
let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview
|
|
||||||
|
let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview
|
||||||
let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
|
||||||
let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
|
let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||||
|
let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
|
||||||
let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
|
||||||
let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
|
let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||||
|
let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
|
||||||
let headerItems: [ListViewItem] = [previewItem, dragItem]
|
|
||||||
|
let headerItems: [ListViewItem] = [previewItem, dragItem]
|
||||||
let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height)
|
|
||||||
if let messageNodes = self.messageNodes {
|
let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height)
|
||||||
for i in 0 ..< headerItems.count {
|
if let messageNodes = self.messageNodes {
|
||||||
let itemNode = messageNodes[i]
|
for i in 0 ..< headerItems.count {
|
||||||
headerItems[i].updateNode(async: { $0() }, node: {
|
let itemNode = messageNodes[i]
|
||||||
return itemNode
|
headerItems[i].updateNode(async: { $0() }, node: {
|
||||||
}, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in
|
return itemNode
|
||||||
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height))
|
}, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in
|
||||||
|
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height))
|
||||||
itemNode.contentSize = layout.contentSize
|
|
||||||
itemNode.insets = layout.insets
|
itemNode.contentSize = layout.contentSize
|
||||||
itemNode.frame = nodeFrame
|
itemNode.insets = layout.insets
|
||||||
itemNode.isUserInteractionEnabled = false
|
itemNode.frame = nodeFrame
|
||||||
|
itemNode.isUserInteractionEnabled = false
|
||||||
apply(ListViewItemApply(isOnScreen: true))
|
|
||||||
})
|
apply(ListViewItemApply(isOnScreen: true))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var messageNodes: [ListViewItemNode] = []
|
||||||
|
for i in 0 ..< headerItems.count {
|
||||||
|
var itemNode: ListViewItemNode?
|
||||||
|
headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in
|
||||||
|
itemNode = node
|
||||||
|
apply().1(ListViewItemApply(isOnScreen: true))
|
||||||
|
})
|
||||||
|
itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||||
|
itemNode!.isUserInteractionEnabled = false
|
||||||
|
messageNodes.append(itemNode!)
|
||||||
|
self.scrollNode.addSubnode(itemNode!)
|
||||||
|
}
|
||||||
|
self.messageNodes = messageNodes
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
var messageNodes: [ListViewItemNode] = []
|
|
||||||
for i in 0 ..< headerItems.count {
|
|
||||||
var itemNode: ListViewItemNode?
|
|
||||||
headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in
|
|
||||||
itemNode = node
|
|
||||||
apply().1(ListViewItemApply(isOnScreen: true))
|
|
||||||
})
|
|
||||||
itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
|
||||||
itemNode!.isUserInteractionEnabled = false
|
|
||||||
messageNodes.append(itemNode!)
|
|
||||||
self.scrollNode.addSubnode(itemNode!)
|
|
||||||
}
|
|
||||||
self.messageNodes = messageNodes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let spacing: CGFloat = 8.0
|
let spacing: CGFloat = 8.0
|
||||||
var contentHeight: CGFloat = 60.0
|
var contentHeight: CGFloat = 0.0
|
||||||
|
var contentWidth: CGFloat = 0.0
|
||||||
|
if !self.isExternalPreview {
|
||||||
|
contentHeight += 60.0
|
||||||
|
}
|
||||||
|
|
||||||
if let previewNode = self.messageNodes?.first {
|
if let previewNode = self.messageNodes?.first {
|
||||||
transition.updateFrame(node: previewNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top + 28.0), size: previewNode.frame.size))
|
transition.updateFrame(node: previewNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top + 28.0), size: previewNode.frame.size))
|
||||||
@ -915,26 +970,35 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
|
|
||||||
var groupIndex = 0
|
var groupIndex = 0
|
||||||
for (items, groupSize) in groupLayouts {
|
for (items, groupSize) in groupLayouts {
|
||||||
let groupRect = CGRect(origin: CGPoint(x: insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0), y: insets.top + contentHeight), size: groupSize)
|
var groupRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top + contentHeight), size: groupSize)
|
||||||
|
if !self.isExternalPreview {
|
||||||
let groupBackgroundNode: MessageBackgroundNode
|
groupRect.origin.x = insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0)
|
||||||
if let current = self.backgroundNodes[groupIndex] {
|
|
||||||
groupBackgroundNode = current
|
|
||||||
} else {
|
|
||||||
groupBackgroundNode = MessageBackgroundNode()
|
|
||||||
groupBackgroundNode.displaysAsynchronously = false
|
|
||||||
self.backgroundNodes[groupIndex] = groupBackgroundNode
|
|
||||||
self.scrollNode.insertSubnode(groupBackgroundNode, at: 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemTransition = transition
|
var itemTransition = transition
|
||||||
if groupBackgroundNode.frame.width.isZero {
|
|
||||||
itemTransition = .immediate
|
|
||||||
}
|
|
||||||
|
|
||||||
itemTransition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0))
|
|
||||||
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition)
|
|
||||||
|
|
||||||
|
if !self.isExternalPreview {
|
||||||
|
let groupBackgroundNode: MessageBackgroundNode
|
||||||
|
if let current = self.backgroundNodes[groupIndex] {
|
||||||
|
groupBackgroundNode = current
|
||||||
|
} else {
|
||||||
|
groupBackgroundNode = MessageBackgroundNode()
|
||||||
|
groupBackgroundNode.displaysAsynchronously = false
|
||||||
|
self.backgroundNodes[groupIndex] = groupBackgroundNode
|
||||||
|
self.scrollNode.insertSubnode(groupBackgroundNode, at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupBackgroundNode.frame.width.isZero {
|
||||||
|
itemTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupBackgroundFrame = groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0)
|
||||||
|
itemTransition.updatePosition(node: groupBackgroundNode, position: groupBackgroundFrame.center)
|
||||||
|
itemTransition.updateBounds(node: groupBackgroundNode, bounds: CGRect(origin: CGPoint(), size: groupBackgroundFrame.size))
|
||||||
|
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFirstGroup = true
|
||||||
for (item, itemRect, itemPosition) in items {
|
for (item, itemRect, itemPosition) in items {
|
||||||
if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] {
|
if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] {
|
||||||
var corners: CACornerMask = []
|
var corners: CACornerMask = []
|
||||||
@ -968,7 +1032,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contentHeight += groupSize.height + spacing
|
if isFirstGroup {
|
||||||
|
isFirstGroup = false
|
||||||
|
} else {
|
||||||
|
contentHeight += spacing
|
||||||
|
}
|
||||||
|
contentHeight += groupSize.height
|
||||||
|
contentWidth = max(contentWidth, groupSize.width)
|
||||||
groupIndex += 1
|
groupIndex += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1032,7 +1102,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
|
|
||||||
self.updateAbsoluteRects()
|
self.updateAbsoluteRects()
|
||||||
|
|
||||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
if self.isExternalPreview {
|
||||||
|
self.scrollNode.view.contentSize = CGSize(width: contentWidth, height: contentHeight)
|
||||||
|
} else {
|
||||||
|
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectionState() {
|
func updateSelectionState() {
|
||||||
@ -1047,6 +1123,25 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateIn(transition: Transition) {
|
||||||
|
self.animateIn(transition: transition.containedViewLayoutTransition, initiated: {}, completion: {})
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(transition: Transition) {
|
||||||
|
self.animateOut(transition: transition.containedViewLayoutTransition, completion: {})
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(containerSize: CGSize, transition: Transition) -> CGSize {
|
||||||
|
if var validLayout = self.validLayout {
|
||||||
|
validLayout.size = containerSize
|
||||||
|
self.validLayout = validLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateItems(transition: transition.containedViewLayoutTransition)
|
||||||
|
|
||||||
|
return self.contentSize
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, items: [TGMediaSelectableItem], grouped: Bool, theme: PresentationTheme, wallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, items: [TGMediaSelectableItem], grouped: Bool, theme: PresentationTheme, wallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, transition: ContainedViewLayoutTransition) {
|
||||||
let previous = self.validLayout
|
let previous = self.validLayout
|
||||||
self.validLayout = (size, insets, items, grouped, theme, wallpaper, bubbleCorners)
|
self.validLayout = (size, insets, items, grouped, theme, wallpaper, bubbleCorners)
|
||||||
@ -1068,10 +1163,12 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
}
|
}
|
||||||
|
|
||||||
let inset: CGFloat = insets.left == 70 ? insets.left : 0.0
|
let inset: CGFloat = insets.left == 70 ? insets.left : 0.0
|
||||||
self.wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false)
|
if let wallpaperBackgroundNode = self.wallpaperBackgroundNode {
|
||||||
self.wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners)
|
wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false)
|
||||||
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height)))
|
wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners)
|
||||||
self.wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition)
|
transition.updateFrame(node: wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height)))
|
||||||
|
wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
self.updateItems(transition: itemsTransition)
|
self.updateItems(transition: itemsTransition)
|
||||||
|
|
||||||
|
@ -1134,7 +1134,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
|
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
|
||||||
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) }
|
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) }
|
||||||
dict[-196020837] = { return Api.auth.SentCodeType.parse_sentCodeTypeEmailCode($0) }
|
dict[-196020837] = { return Api.auth.SentCodeType.parse_sentCodeTypeEmailCode($0) }
|
||||||
dict[-444918734] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) }
|
dict[331943703] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) }
|
||||||
dict[-1425815847] = { return Api.auth.SentCodeType.parse_sentCodeTypeFlashCall($0) }
|
dict[-1425815847] = { return Api.auth.SentCodeType.parse_sentCodeTypeFlashCall($0) }
|
||||||
dict[-648651719] = { return Api.auth.SentCodeType.parse_sentCodeTypeFragmentSms($0) }
|
dict[-648651719] = { return Api.auth.SentCodeType.parse_sentCodeTypeFragmentSms($0) }
|
||||||
dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) }
|
dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) }
|
||||||
@ -1365,7 +1365,7 @@ public extension Api {
|
|||||||
return parser(reader)
|
return parser(reader)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
|
telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,7 +589,7 @@ public extension Api.auth {
|
|||||||
case sentCodeTypeApp(length: Int32)
|
case sentCodeTypeApp(length: Int32)
|
||||||
case sentCodeTypeCall(length: Int32)
|
case sentCodeTypeCall(length: Int32)
|
||||||
case sentCodeTypeEmailCode(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?)
|
case sentCodeTypeEmailCode(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?)
|
||||||
case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32)
|
case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, playIntegrityNonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32)
|
||||||
case sentCodeTypeFlashCall(pattern: String)
|
case sentCodeTypeFlashCall(pattern: String)
|
||||||
case sentCodeTypeFragmentSms(url: String, length: Int32)
|
case sentCodeTypeFragmentSms(url: String, length: Int32)
|
||||||
case sentCodeTypeMissedCall(prefix: String, length: Int32)
|
case sentCodeTypeMissedCall(prefix: String, length: Int32)
|
||||||
@ -622,12 +622,13 @@ public extension Api.auth {
|
|||||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(resetAvailablePeriod!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(resetAvailablePeriod!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(resetPendingDate!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(resetPendingDate!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length):
|
case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-444918734)
|
buffer.appendInt32(331943703)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeBytes(nonce!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeBytes(nonce!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {serializeBytes(playIntegrityNonce!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(receipt!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 1) != 0 {serializeString(receipt!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(pushTimeout!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(pushTimeout!, buffer: buffer, boxed: false)}
|
||||||
serializeInt32(length, buffer: buffer, boxed: false)
|
serializeInt32(length, buffer: buffer, boxed: false)
|
||||||
@ -689,8 +690,8 @@ public extension Api.auth {
|
|||||||
return ("sentCodeTypeCall", [("length", length as Any)])
|
return ("sentCodeTypeCall", [("length", length as Any)])
|
||||||
case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate):
|
case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate):
|
||||||
return ("sentCodeTypeEmailCode", [("flags", flags as Any), ("emailPattern", emailPattern as Any), ("length", length as Any), ("resetAvailablePeriod", resetAvailablePeriod as Any), ("resetPendingDate", resetPendingDate as Any)])
|
return ("sentCodeTypeEmailCode", [("flags", flags as Any), ("emailPattern", emailPattern as Any), ("length", length as Any), ("resetAvailablePeriod", resetAvailablePeriod as Any), ("resetPendingDate", resetPendingDate as Any)])
|
||||||
case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length):
|
case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length):
|
||||||
return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)])
|
return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("playIntegrityNonce", playIntegrityNonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)])
|
||||||
case .sentCodeTypeFlashCall(let pattern):
|
case .sentCodeTypeFlashCall(let pattern):
|
||||||
return ("sentCodeTypeFlashCall", [("pattern", pattern as Any)])
|
return ("sentCodeTypeFlashCall", [("pattern", pattern as Any)])
|
||||||
case .sentCodeTypeFragmentSms(let url, let length):
|
case .sentCodeTypeFragmentSms(let url, let length):
|
||||||
@ -758,19 +759,22 @@ public extension Api.auth {
|
|||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
var _2: Buffer?
|
var _2: Buffer?
|
||||||
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) }
|
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) }
|
||||||
var _3: String?
|
var _3: Buffer?
|
||||||
if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) }
|
if Int(_1!) & Int(1 << 2) != 0 {_3 = parseBytes(reader) }
|
||||||
var _4: Int32?
|
var _4: String?
|
||||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
|
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) }
|
||||||
var _5: Int32?
|
var _5: Int32?
|
||||||
_5 = reader.readInt32()
|
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||||
|
var _6: Int32?
|
||||||
|
_6 = reader.readInt32()
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
|
||||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||||
let _c5 = _5 != nil
|
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
let _c6 = _6 != nil
|
||||||
return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, receipt: _3, pushTimeout: _4, length: _5!)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||||
|
return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityNonce: _3, receipt: _4, pushTimeout: _5, length: _6!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -2061,15 +2061,16 @@ public extension Api.functions.auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.auth {
|
public extension Api.functions.auth {
|
||||||
static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, safetyNetToken: String?, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(1940588736)
|
buffer.appendInt32(-1908857314)
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(phoneNumber, buffer: buffer, boxed: false)
|
serializeString(phoneNumber, buffer: buffer, boxed: false)
|
||||||
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
|
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(playIntegrityToken!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeString(safetyNetToken!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {serializeString(playIntegrityToken!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(iosPushSecret!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 1) != 0 {serializeString(iosPushSecret!, buffer: buffer, boxed: false)}
|
||||||
return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("playIntegrityToken", String(describing: playIntegrityToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("safetyNetToken", String(describing: safetyNetToken)), ("playIntegrityToken", String(describing: playIntegrityToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.Bool?
|
var result: Api.Bool?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
@ -2095,12 +2096,14 @@ public extension Api.functions.auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.auth {
|
public extension Api.functions.auth {
|
||||||
static func resendCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
|
static func resendCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, reason: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(1056025023)
|
buffer.appendInt32(-890997469)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(phoneNumber, buffer: buffer, boxed: false)
|
serializeString(phoneNumber, buffer: buffer, boxed: false)
|
||||||
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
|
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
|
||||||
return (FunctionDescription(name: "auth.resendCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
|
if Int(flags) & Int(1 << 0) != 0 {serializeString(reason!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "auth.resendCode", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("reason", String(describing: reason))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.auth.SentCode?
|
var result: Api.auth.SentCode?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
|
@ -133,7 +133,7 @@ private func sendFirebaseAuthorizationCode(accountManager: AccountManager<Telegr
|
|||||||
//auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool;
|
//auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool;
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
flags |= 1 << 1
|
flags |= 1 << 1
|
||||||
return account.network.request(Api.functions.auth.requestFirebaseSms(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, playIntegrityToken: nil, iosPushSecret: firebaseSecret))
|
return account.network.request(Api.functions.auth.requestFirebaseSms(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, safetyNetToken: nil, playIntegrityToken: nil, iosPushSecret: firebaseSecret))
|
||||||
|> mapError { _ -> SendFirebaseAuthorizationCodeError in
|
|> mapError { _ -> SendFirebaseAuthorizationCodeError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
@ -275,7 +275,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
|||||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
|
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type {
|
||||||
return firebaseSecretStream
|
return firebaseSecretStream
|
||||||
|> map { mapping -> String? in
|
|> map { mapping -> String? in
|
||||||
guard let receipt = receipt else {
|
guard let receipt = receipt else {
|
||||||
@ -395,7 +395,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func internalResendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> {
|
private func internalResendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> {
|
||||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: number, phoneCodeHash: hash, reason: nil), automaticFloodWait: false)
|
||||||
|> mapError { error -> AuthorizationCodeRequestError in
|
|> mapError { error -> AuthorizationCodeRequestError in
|
||||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||||
return .limitExceeded
|
return .limitExceeded
|
||||||
@ -421,7 +421,7 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
|
|||||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
|
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type {
|
||||||
return firebaseSecretStream
|
return firebaseSecretStream
|
||||||
|> map { mapping -> String? in
|
|> map { mapping -> String? in
|
||||||
guard let receipt = receipt else {
|
guard let receipt = receipt else {
|
||||||
@ -520,7 +520,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|
|||||||
switch state.contents {
|
switch state.contents {
|
||||||
case let .confirmationCodeEntry(number, type, hash, _, nextType, syncContacts, previousCodeEntryValue, _):
|
case let .confirmationCodeEntry(number, type, hash, _, nextType, syncContacts, previousCodeEntryValue, _):
|
||||||
if nextType != nil {
|
if nextType != nil {
|
||||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: number, phoneCodeHash: hash, reason: nil), automaticFloodWait: false)
|
||||||
|> mapError { error -> AuthorizationCodeRequestError in
|
|> mapError { error -> AuthorizationCodeRequestError in
|
||||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||||
return .limitExceeded
|
return .limitExceeded
|
||||||
@ -563,7 +563,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|
|||||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = newType {
|
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = newType {
|
||||||
return firebaseSecretStream
|
return firebaseSecretStream
|
||||||
|> map { mapping -> String? in
|
|> map { mapping -> String? in
|
||||||
guard let receipt = receipt else {
|
guard let receipt = receipt else {
|
||||||
|
@ -36,7 +36,7 @@ extension SentAuthorizationCodeType {
|
|||||||
self = .emailSetupRequired(appleSignInAllowed: (flags & (1 << 0)) != 0)
|
self = .emailSetupRequired(appleSignInAllowed: (flags & (1 << 0)) != 0)
|
||||||
case let .sentCodeTypeFragmentSms(url, length):
|
case let .sentCodeTypeFragmentSms(url, length):
|
||||||
self = .fragment(url: url, length: length)
|
self = .fragment(url: url, length: length)
|
||||||
case let .sentCodeTypeFirebaseSms(_, _, _, pushTimeout, length):
|
case let .sentCodeTypeFirebaseSms(_, _, _, _, pushTimeout, length):
|
||||||
self = .firebase(pushTimeout: pushTimeout, length: length)
|
self = .firebase(pushTimeout: pushTimeout, length: length)
|
||||||
case let .sentCodeTypeSmsWord(_, beginning):
|
case let .sentCodeTypeSmsWord(_, beginning):
|
||||||
self = .word(startsWith: beginning)
|
self = .word(startsWith: beginning)
|
||||||
|
@ -3,10 +3,6 @@ import TelegramApi
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
/*
|
|
||||||
availableEffect flags:# premium_required:flags.3?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:flags.1?long effect_animation_id:flags.2?long = AvailableEffect;
|
|
||||||
*/
|
|
||||||
|
|
||||||
public final class AvailableMessageEffects: Equatable, Codable {
|
public final class AvailableMessageEffects: Equatable, Codable {
|
||||||
public final class MessageEffect: Equatable, Codable {
|
public final class MessageEffect: Equatable, Codable {
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
@ -79,7 +79,7 @@ func _internal_requestChangeAccountPhoneNumberVerification(account: Account, pho
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|
return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false)
|
||||||
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
|
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
|
||||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||||
return .limitExceeded
|
return .limitExceeded
|
||||||
|
@ -41,7 +41,7 @@ func _internal_requestCancelAccountResetData(network: Network, hash: String) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||||
return network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|
return network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false)
|
||||||
|> mapError { error -> RequestCancelAccountResetDataError in
|
|> mapError { error -> RequestCancelAccountResetDataError in
|
||||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||||
return .limitExceeded
|
return .limitExceeded
|
||||||
|
@ -229,6 +229,15 @@ public:
|
|||||||
CGRect bounds;
|
CGRect bounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RenderTreeNodeContentItem {
|
||||||
|
public:
|
||||||
|
RenderTreeNodeContentItem() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNodeContentItem>> subItems;
|
||||||
|
};
|
||||||
|
|
||||||
class RenderTreeNodeContent {
|
class RenderTreeNodeContent {
|
||||||
public:
|
public:
|
||||||
enum class ShadingType {
|
enum class ShadingType {
|
||||||
|
@ -1036,7 +1036,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void renderChildren(AnimationFrameTime frameTime, std::optional<TrimParams> parentTrim) {
|
void updateChildren(AnimationFrameTime frameTime, std::optional<TrimParams> parentTrim) {
|
||||||
CATransform3D containerTransform = CATransform3D::identity();
|
CATransform3D containerTransform = CATransform3D::identity();
|
||||||
double containerOpacity = 1.0;
|
double containerOpacity = 1.0;
|
||||||
if (transform) {
|
if (transform) {
|
||||||
@ -1111,7 +1111,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subItems[i]->renderChildren(frameTime, childTrim);
|
subItems[i]->updateChildren(frameTime, childTrim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1284,14 +1284,14 @@ CompositionLayer(solidLayer, Vector2D::Zero()) {
|
|||||||
void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) {
|
void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) {
|
||||||
_frameTime = frame;
|
_frameTime = frame;
|
||||||
_frameTimeInitialized = true;
|
_frameTimeInitialized = true;
|
||||||
_contentTree->itemTree->renderChildren(_frameTime, std::nullopt);
|
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode() {
|
std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode() {
|
||||||
if (!_frameTimeInitialized) {
|
if (!_frameTimeInitialized) {
|
||||||
_frameTime = 0.0;
|
_frameTime = 0.0;
|
||||||
_frameTimeInitialized = true;
|
_frameTimeInitialized = true;
|
||||||
_contentTree->itemTree->renderChildren(_frameTime, std::nullopt);
|
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_renderTreeNode) {
|
if (!_renderTreeNode) {
|
||||||
|
@ -19,7 +19,6 @@ std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
|
|||||||
std::vector<std::shared_ptr<CompositionLayer>> compositionLayers;
|
std::vector<std::shared_ptr<CompositionLayer>> compositionLayers;
|
||||||
std::map<int, std::shared_ptr<CompositionLayer>> layerMap;
|
std::map<int, std::shared_ptr<CompositionLayer>> layerMap;
|
||||||
|
|
||||||
/// Organize the assets into a dictionary of [ID : ImageAsset]
|
|
||||||
std::vector<std::shared_ptr<LayerModel>> childLayers;
|
std::vector<std::shared_ptr<LayerModel>> childLayers;
|
||||||
|
|
||||||
for (const auto &layer : layers) {
|
for (const auto &layer : layers) {
|
||||||
|
@ -10212,7 +10212,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
func forwardMessages(messageIds: Set<MessageId>?) {
|
func forwardMessages(messageIds: Set<MessageId>?) {
|
||||||
if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty {
|
if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty {
|
||||||
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true))
|
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true))
|
||||||
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
|
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in
|
||||||
guard let strongSelf = self, let strongController = peerSelectionController else {
|
guard let strongSelf = self, let strongController = peerSelectionController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
private var customTitle: String?
|
private var customTitle: String?
|
||||||
|
|
||||||
public var peerSelected: ((EnginePeer, Int64?) -> Void)?
|
public var peerSelected: ((EnginePeer, Int64?) -> Void)?
|
||||||
public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
|
public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)?
|
||||||
private let filter: ChatListNodePeersFilter
|
private let filter: ChatListNodePeersFilter
|
||||||
private let forumPeerId: EnginePeer.Id?
|
private let forumPeerId: EnginePeer.Id?
|
||||||
private let selectForumThreads: Bool
|
private let selectForumThreads: Bool
|
||||||
@ -246,8 +246,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
|
|
||||||
self.peerSelectionNode.navigationBar = self.navigationBar
|
self.peerSelectionNode.navigationBar = self.navigationBar
|
||||||
|
|
||||||
self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState in
|
self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState, messageEffect in
|
||||||
self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState)
|
self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState, messageEffect)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.peerSelectionNode.requestDeactivateSearch = { [weak self] in
|
self.peerSelectionNode.requestDeactivateSearch = { [weak self] in
|
||||||
|
@ -83,7 +83,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
var requestOpenDisabledPeer: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
|
var requestOpenDisabledPeer: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
|
||||||
var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> Void)?
|
var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> Void)?
|
||||||
var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id) -> Void)?
|
var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id) -> Void)?
|
||||||
var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
|
var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)?
|
||||||
|
|
||||||
private var presentationData: PresentationData {
|
private var presentationData: PresentationData {
|
||||||
didSet {
|
didSet {
|
||||||
@ -529,7 +529,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) }
|
forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) }
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) })
|
strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) })
|
||||||
}
|
}
|
||||||
strongSelf.textInputPanelNode?.sendMessage(.generic)
|
strongSelf.textInputPanelNode?.sendMessage(.generic, nil)
|
||||||
|
|
||||||
f(.default)
|
f(.default)
|
||||||
})))
|
})))
|
||||||
@ -692,17 +692,17 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controller = makeChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, canSendWhenOnline: false, completion: {
|
let controller = makeChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, canSendWhenOnline: false, completion: {
|
||||||
}, sendMessage: { [weak textInputPanelNode] mode, _ in
|
}, sendMessage: { [weak textInputPanelNode] mode, messageEffect in
|
||||||
switch mode {
|
switch mode {
|
||||||
case .generic:
|
case .generic:
|
||||||
textInputPanelNode?.sendMessage(.generic)
|
textInputPanelNode?.sendMessage(.generic, messageEffect)
|
||||||
case .silently:
|
case .silently:
|
||||||
textInputPanelNode?.sendMessage(.silent)
|
textInputPanelNode?.sendMessage(.silent, messageEffect)
|
||||||
case .whenOnline:
|
case .whenOnline:
|
||||||
textInputPanelNode?.sendMessage(.whenOnline)
|
textInputPanelNode?.sendMessage(.whenOnline, messageEffect)
|
||||||
}
|
}
|
||||||
}, schedule: { [weak textInputPanelNode] _ in
|
}, schedule: { [weak textInputPanelNode] messageEffect in
|
||||||
textInputPanelNode?.sendMessage(.schedule)
|
textInputPanelNode?.sendMessage(.schedule, messageEffect)
|
||||||
})
|
})
|
||||||
strongSelf.presentInGlobalOverlay(controller, nil)
|
strongSelf.presentInGlobalOverlay(controller, nil)
|
||||||
}, openScheduledMessages: {
|
}, openScheduledMessages: {
|
||||||
@ -825,7 +825,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
||||||
textInputPanelNode.sendMessage = { [weak self] mode in
|
textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -835,7 +835,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers
|
let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers
|
||||||
if !selectedPeers.isEmpty {
|
if !selectedPeers.isEmpty {
|
||||||
strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState)
|
strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.addSubnode(textInputPanelNode)
|
self.addSubnode(textInputPanelNode)
|
||||||
|
@ -49,10 +49,10 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext {
|
|||||||
func setCaption(_ caption: NSAttributedString) {
|
func setCaption(_ caption: NSAttributedString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
|
@ -377,10 +377,10 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext {
|
|||||||
func setCaption(_ caption: NSAttributedString) {
|
func setCaption(_ caption: NSAttributedString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
|
@ -1556,14 +1556,14 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
completion(controller, mediaPickerContext)
|
completion(controller, mediaPickerContext)
|
||||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||||
attachmentController?.mediaPickerContext = mediaPickerContext
|
attachmentController?.mediaPickerContext = mediaPickerContext
|
||||||
}, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
|
}, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
|
||||||
guard let self, let view else {
|
guard let self, let view else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !inputText.string.isEmpty {
|
if !inputText.string.isEmpty {
|
||||||
self.clearInputText(view: view)
|
self.clearInputText(view: view)
|
||||||
}
|
}
|
||||||
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
case .file:
|
case .file:
|
||||||
@ -1875,7 +1875,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
bannedSendVideos: (Int32, Bool)?,
|
bannedSendVideos: (Int32, Bool)?,
|
||||||
present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void,
|
present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void,
|
||||||
updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void,
|
updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void,
|
||||||
completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void
|
completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void
|
||||||
) {
|
) {
|
||||||
guard let component = view.component else {
|
guard let component = view.component else {
|
||||||
return
|
return
|
||||||
@ -1936,8 +1936,8 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
return self.getCaptionPanelView(view: view, peer: peer, mediaPicker: controller)
|
return self.getCaptionPanelView(view: view, peer: peer, mediaPicker: controller)
|
||||||
}
|
}
|
||||||
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
|
controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in
|
||||||
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
|
completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion)
|
||||||
}
|
}
|
||||||
present(controller, mediaPickerContext)
|
present(controller, mediaPickerContext)
|
||||||
}
|
}
|
||||||
@ -2240,14 +2240,14 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
present(controller, mediaPickerContext)
|
present(controller, mediaPickerContext)
|
||||||
},
|
},
|
||||||
updateMediaPickerContext: { _ in },
|
updateMediaPickerContext: { _ in },
|
||||||
completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
|
completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
|
||||||
guard let self, let view else {
|
guard let self, let view else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !inputText.string.isEmpty {
|
if !inputText.string.isEmpty {
|
||||||
self.clearInputText(view: view)
|
self.clearInputText(view: view)
|
||||||
}
|
}
|
||||||
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2569,7 +2569,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
|
private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
|
||||||
guard let component = view.component else {
|
guard let component = view.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2620,6 +2620,13 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let messageEffect {
|
||||||
|
message = message.withUpdatedAttributes { attributes in
|
||||||
|
var attributes = attributes
|
||||||
|
attributes.append(EffectMessageAttribute(id: messageEffect.id))
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
mappedMessages.append(message)
|
mappedMessages.append(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,10 +183,10 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext {
|
|||||||
func setCaption(_ caption: NSAttributedString) {
|
func setCaption(_ caption: NSAttributedString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
|
@ -27,8 +27,8 @@ extension ChatControllerImpl {
|
|||||||
subjects: subjects,
|
subjects: subjects,
|
||||||
presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in
|
presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
|
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
|
||||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -9058,7 +9058,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
|
func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
|
||||||
self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!)
|
self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] items in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] items in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -9112,6 +9112,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
usedCorrelationId = correlationId
|
usedCorrelationId = correlationId
|
||||||
completionImpl = nil
|
completionImpl = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let messageEffect {
|
||||||
|
message = message.withUpdatedAttributes { attributes in
|
||||||
|
var attributes = attributes
|
||||||
|
attributes.append(EffectMessageAttribute(id: messageEffect.id))
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mappedMessages.append(message)
|
mappedMessages.append(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ extension ChatControllerImpl {
|
|||||||
}), in: .current)
|
}), in: .current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions in
|
controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in
|
||||||
guard let strongSelf = self, let strongController = controller else {
|
guard let strongSelf = self, let strongController = controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -308,11 +308,11 @@ extension ChatControllerImpl {
|
|||||||
completion(controller, mediaPickerContext)
|
completion(controller, mediaPickerContext)
|
||||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||||
attachmentController?.mediaPickerContext = mediaPickerContext
|
attachmentController?.mediaPickerContext = mediaPickerContext
|
||||||
}, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
|
}, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
|
||||||
if !inputText.string.isEmpty {
|
if !inputText.string.isEmpty {
|
||||||
self?.clearInputText()
|
self?.clearInputText()
|
||||||
}
|
}
|
||||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||||
})
|
})
|
||||||
case .file:
|
case .file:
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
@ -1136,7 +1136,7 @@ extension ChatControllerImpl {
|
|||||||
self.present(actionSheet, in: .window(.root))
|
self.present(actionSheet, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
|
func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
|
||||||
var isScheduledMessages = false
|
var isScheduledMessages = false
|
||||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||||
isScheduledMessages = true
|
isScheduledMessages = true
|
||||||
@ -1210,8 +1210,8 @@ extension ChatControllerImpl {
|
|||||||
controller.getCaptionPanelView = { [weak self] in
|
controller.getCaptionPanelView = { [weak self] in
|
||||||
return self?.getCaptionPanelView(isFile: false)
|
return self?.getCaptionPanelView(isFile: false)
|
||||||
}
|
}
|
||||||
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
|
controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in
|
||||||
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
|
completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion)
|
||||||
}
|
}
|
||||||
present(controller, mediaPickerContext)
|
present(controller, mediaPickerContext)
|
||||||
}
|
}
|
||||||
|
@ -451,11 +451,11 @@ final class ContactsPickerContext: AttachmentMediaPickerContext {
|
|||||||
self.controller?.caption = caption
|
self.controller?.caption = caption
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil)
|
self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
self.controller?.presentScheduleTimePicker ({ time in
|
self.controller?.presentScheduleTimePicker ({ time in
|
||||||
self.controller?.contactsNode.requestMultipleAction?(false, time)
|
self.controller?.contactsNode.requestMultipleAction?(false, time)
|
||||||
})
|
})
|
||||||
|
@ -35,14 +35,14 @@ final class WebSearchControllerInteraction {
|
|||||||
let setSearchQuery: (String) -> Void
|
let setSearchQuery: (String) -> Void
|
||||||
let deleteRecentQuery: (String) -> Void
|
let deleteRecentQuery: (String) -> Void
|
||||||
let toggleSelection: (ChatContextResult, Bool) -> Bool
|
let toggleSelection: (ChatContextResult, Bool) -> Bool
|
||||||
let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void
|
let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void
|
||||||
let schedule: () -> Void
|
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
|
||||||
let avatarCompleted: (UIImage) -> Void
|
let avatarCompleted: (UIImage) -> Void
|
||||||
let selectionState: TGMediaSelectionContext?
|
let selectionState: TGMediaSelectionContext?
|
||||||
let editingState: TGMediaEditingContext
|
let editingState: TGMediaEditingContext
|
||||||
var hiddenMediaId: String?
|
var hiddenMediaId: String?
|
||||||
|
|
||||||
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?) -> Void, schedule: @escaping () -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||||
self.openResult = openResult
|
self.openResult = openResult
|
||||||
self.setSearchQuery = setSearchQuery
|
self.setSearchQuery = setSearchQuery
|
||||||
self.deleteRecentQuery = deleteRecentQuery
|
self.deleteRecentQuery = deleteRecentQuery
|
||||||
@ -254,7 +254,7 @@ public final class WebSearchController: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}, sendSelected: { [weak self] current, silently, scheduleTime in
|
}, sendSelected: { [weak self] current, silently, scheduleTime, messageEffect in
|
||||||
if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults {
|
if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults {
|
||||||
if let current = current {
|
if let current = current {
|
||||||
let currentItem = LegacyWebSearchItem(result: current)
|
let currentItem = LegacyWebSearchItem(result: current)
|
||||||
@ -264,10 +264,10 @@ public final class WebSearchController: ViewController {
|
|||||||
sendSelected(results, selectionState, editingState, false)
|
sendSelected(results, selectionState, editingState, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, schedule: { [weak self] in
|
}, schedule: { [weak self] messageEffect in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
||||||
self?.controllerInteraction?.sendSelected(nil, false, time)
|
self?.controllerInteraction?.sendSelected(nil, false, time, nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, avatarCompleted: { result in
|
}, avatarCompleted: { result in
|
||||||
@ -606,12 +606,12 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext {
|
|||||||
self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
|
self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
self.interaction?.sendSelected(nil, mode == .silently, nil)
|
self.interaction?.sendSelected(nil, mode == .silently, nil, messageEffect)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func schedule() {
|
public func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
self.interaction?.schedule()
|
self.interaction?.schedule(messageEffect)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func mainButtonAction() {
|
public func mainButtonAction() {
|
||||||
|
@ -717,7 +717,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func sendPressed() {
|
@objc private func sendPressed() {
|
||||||
self.controllerInteraction.sendSelected(nil, false, nil)
|
self.controllerInteraction.sendSelected(nil, false, nil, nil)
|
||||||
|
|
||||||
self.cancel?()
|
self.cancel?()
|
||||||
}
|
}
|
||||||
@ -740,7 +740,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
return self?.transitionNode(for: result)?.transitionView()
|
return self?.transitionNode(for: result)?.transitionView()
|
||||||
}, completed: { [weak self] result in
|
}, completed: { [weak self] result in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.controllerInteraction.sendSelected(result, false, nil)
|
strongSelf.controllerInteraction.sendSelected(result, false, nil, nil)
|
||||||
strongSelf.cancel?()
|
strongSelf.cancel?()
|
||||||
}
|
}
|
||||||
}, getCaptionPanelView: self.getCaptionPanelView, present: present)
|
}, getCaptionPanelView: self.getCaptionPanelView, present: present)
|
||||||
@ -760,7 +760,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
}, baseNavigationController: nil, sendCurrent: { [weak self] result in
|
}, baseNavigationController: nil, sendCurrent: { [weak self] result in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.controllerInteraction.sendSelected(result, false, nil)
|
strongSelf.controllerInteraction.sendSelected(result, false, nil, nil)
|
||||||
strongSelf.cancel?()
|
strongSelf.cancel?()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2092,10 +2092,10 @@ final class WebAppPickerContext: AttachmentMediaPickerContext {
|
|||||||
func setCaption(_ caption: NSAttributedString) {
|
func setCaption(_ caption: NSAttributedString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user