mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +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, *) {
|
||||
self.test = ReferenceCompareTest(view: self.view)
|
||||
}
|
||||
} else if "".isEmpty {
|
||||
} else if !"".isEmpty {
|
||||
let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
|
||||
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!
|
||||
|
||||
|
@ -1068,6 +1068,29 @@ public protocol AccountGroupCallContext: 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 {
|
||||
var sharedContext: SharedAccountContext { get }
|
||||
var account: Account { get }
|
||||
|
@ -147,7 +147,7 @@ public enum PeerSelectionControllerContext {
|
||||
|
||||
public protocol PeerSelectionController: ViewController {
|
||||
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 customDismiss: (() -> Void)? { get set }
|
||||
}
|
||||
|
@ -6,8 +6,10 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ContextUI
|
||||
import ChatPresentationInterfaceState
|
||||
import ComponentFlow
|
||||
import AccountContext
|
||||
|
||||
final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
||||
final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
|
||||
private let strings: PresentationStrings
|
||||
|
||||
let sendContainerNode: ASDisplayNode
|
||||
@ -17,6 +19,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
||||
var animatingSendButton = false
|
||||
let textNode: ImmediateTextNode
|
||||
|
||||
private var theme: PresentationTheme
|
||||
|
||||
var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
|
||||
|
||||
private var gestureRecognizer: ContextGesture?
|
||||
@ -31,9 +35,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
||||
private var validLayout: CGSize?
|
||||
|
||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) {
|
||||
let theme = presentationInterfaceState.theme
|
||||
let strings = presentationInterfaceState.strings
|
||||
self.strings = strings
|
||||
self.theme = presentationInterfaceState.theme
|
||||
self.strings = presentationInterfaceState.strings
|
||||
|
||||
self.sendContainerNode = ASDisplayNode()
|
||||
self.sendContainerNode.layer.allowsGroupOpacity = true
|
||||
@ -64,9 +67,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
||||
}
|
||||
} else {
|
||||
if highlighted {
|
||||
strongSelf.sendContainerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
|
||||
} else if let presentationLayer = strongSelf.sendButton.layer.presentation() {
|
||||
strongSelf.sendContainerNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
|
||||
let transition: Transition = .easeInOut(duration: 0.4)
|
||||
transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 0.75)
|
||||
} 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
|
||||
}
|
||||
if !strongSelf.sendButtonHasApplyIcon {
|
||||
strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer)
|
||||
strongSelf.sendButtonLongPressed?(strongSelf, recognizer)
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +117,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
||||
self.validLayout = size
|
||||
|
||||
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 {
|
||||
width = 44.0
|
||||
} else {
|
||||
@ -140,4 +145,16 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
||||
self.accessibilityLabel = self.strings.MediaPicker_Send
|
||||
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)?
|
||||
|
||||
public var sendMessage: (AttachmentTextInputPanelSendMode) -> Void = { _ in }
|
||||
public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
|
||||
public var updateHeight: (Bool) -> Void = { _ in }
|
||||
|
||||
private var updatingInputState = false
|
||||
@ -1843,7 +1843,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
sendPressed(effectiveInputText)
|
||||
return
|
||||
}
|
||||
self.sendMessage(.generic)
|
||||
self.sendMessage(.generic, nil)
|
||||
}
|
||||
|
||||
@objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) {
|
||||
|
@ -14,6 +14,7 @@ import MediaResources
|
||||
import LegacyMessageInputPanel
|
||||
import LegacyMessageInputPanelInputView
|
||||
import AttachmentTextInputPanelNode
|
||||
import ChatSendMessageActionUI
|
||||
|
||||
public enum AttachmentButtonType: Equatable {
|
||||
case gallery
|
||||
@ -97,6 +98,7 @@ public protocol AttachmentContainable: ViewController {
|
||||
var isContainerExpanded: () -> Bool { get set }
|
||||
var isPanGestureEnabled: (() -> Bool)? { get }
|
||||
var mediaPickerContext: AttachmentMediaPickerContext? { get }
|
||||
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get }
|
||||
|
||||
func isContainerPanningUpdated(_ panning: Bool)
|
||||
|
||||
@ -131,6 +133,10 @@ public extension AttachmentContainable {
|
||||
var isPanGestureEnabled: (() -> Bool)? {
|
||||
return nil
|
||||
}
|
||||
|
||||
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public enum AttachmentMediaPickerSendMode {
|
||||
@ -154,8 +160,8 @@ public protocol AttachmentMediaPickerContext {
|
||||
func mainButtonAction()
|
||||
|
||||
func setCaption(_ caption: NSAttributedString)
|
||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode)
|
||||
func schedule()
|
||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
|
||||
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
|
||||
}
|
||||
|
||||
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 {
|
||||
switch mode {
|
||||
case .generic:
|
||||
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media)
|
||||
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media, messageEffect: messageEffect)
|
||||
case .silent:
|
||||
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media)
|
||||
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media, messageEffect: messageEffect)
|
||||
case .schedule:
|
||||
strongSelf.mediaPickerContext?.schedule()
|
||||
strongSelf.mediaPickerContext?.schedule(messageEffect: messageEffect)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
self.panel.getCurrentSendMessageContextMediaPreview = { [weak self] in
|
||||
guard let self, let currentController = self.currentControllers.last else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return currentController.getCurrentSendMessageContextMediaPreview?()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -728,11 +728,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
var beganTextEditing: () -> Void = {}
|
||||
var textUpdated: (NSAttributedString) -> Void = { _ in }
|
||||
var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in }
|
||||
var sendMessagePressed: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
|
||||
var requestLayout: () -> Void = {}
|
||||
var present: (ViewController) -> Void = { _ in }
|
||||
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
|
||||
|
||||
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)?
|
||||
|
||||
var mainButtonPressed: () -> Void = { }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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: {
|
||||
}, sendMessage: { [weak textInputPanelNode] mode, _ in
|
||||
switch mode {
|
||||
case .generic:
|
||||
textInputPanelNode?.sendMessage(.generic)
|
||||
case .silently:
|
||||
textInputPanelNode?.sendMessage(.silent)
|
||||
case .whenOnline:
|
||||
textInputPanelNode?.sendMessage(.whenOnline)
|
||||
let mediaPreview = strongSelf.getCurrentSendMessageContextMediaPreview?()
|
||||
let isReady: Signal<Bool, NoError>
|
||||
if let mediaPreview {
|
||||
isReady = mediaPreview.isReady
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> timeout(0.5, queue: .mainQueue(), alternate: .single(true))
|
||||
} else {
|
||||
isReady = .single(true)
|
||||
}
|
||||
|
||||
let _ = (isReady
|
||||
|> deliverOnMainQueue).start(next: { [weak strongSelf] _ in
|
||||
guard let strongSelf else {
|
||||
return
|
||||
}
|
||||
}, schedule: { [weak textInputPanelNode] _ in
|
||||
textInputPanelNode?.sendMessage(.schedule)
|
||||
}, reactionItems: effectItems, availableMessageEffects: availableMessageEffects, isPremium: hasPremium)
|
||||
strongSelf.presentInGlobalOverlay(controller)
|
||||
|
||||
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,
|
||||
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: {
|
||||
}, openPeersNearby: {
|
||||
@ -1249,9 +1287,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
}, makeEntityInputView: self.makeEntityInputView)
|
||||
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
||||
textInputPanelNode.sendMessage = { [weak self] mode in
|
||||
textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in
|
||||
if let strongSelf = self {
|
||||
strongSelf.sendMessagePressed(mode)
|
||||
strongSelf.sendMessagePressed(mode, messageEffect)
|
||||
}
|
||||
}
|
||||
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) })
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
@ -11,25 +11,6 @@ import TextFormat
|
||||
import ReactionSelectionNode
|
||||
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 var controllerNode: ChatSendMessageActionSheetControllerNode {
|
||||
return self.displayNode as! ChatSendMessageActionSheetControllerNode
|
||||
@ -203,6 +184,7 @@ public func makeChatSendMessageActionSheetController(
|
||||
gesture: ContextGesture,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
textInputView: UITextView,
|
||||
mediaPreview: ChatSendMessageContextScreenMediaPreview? = nil,
|
||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||
wallpaperBackgroundNode: WallpaperBackgroundNode? = nil,
|
||||
attachment: Bool = false,
|
||||
@ -214,7 +196,7 @@ public func makeChatSendMessageActionSheetController(
|
||||
availableMessageEffects: AvailableMessageEffects? = nil,
|
||||
isPremium: Bool = false
|
||||
) -> ChatSendMessageActionSheetController {
|
||||
if textInputView.text.isEmpty {
|
||||
if textInputView.text.isEmpty && !"".isEmpty {
|
||||
return ChatSendMessageActionSheetControllerImpl(
|
||||
context: context,
|
||||
updatedPresentationData: updatedPresentationData,
|
||||
@ -245,6 +227,7 @@ public func makeChatSendMessageActionSheetController(
|
||||
gesture: gesture,
|
||||
sourceSendButton: sourceSendButton,
|
||||
textInputView: textInputView,
|
||||
mediaPreview: mediaPreview,
|
||||
emojiViewProvider: emojiViewProvider,
|
||||
wallpaperBackgroundNode: wallpaperBackgroundNode,
|
||||
attachment: attachment,
|
||||
|
@ -32,10 +32,21 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) ->
|
||||
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 {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
let peerId: EnginePeer.Id?
|
||||
let isScheduledMessages: Bool
|
||||
let forwardMessageIds: [EngineMessage.Id]?
|
||||
@ -43,6 +54,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
let gesture: ContextGesture
|
||||
let sourceSendButton: ASDisplayNode
|
||||
let textInputView: UITextView
|
||||
let mediaPreview: ChatSendMessageContextScreenMediaPreview?
|
||||
let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
||||
let wallpaperBackgroundNode: WallpaperBackgroundNode?
|
||||
let attachment: Bool
|
||||
@ -56,6 +68,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||
peerId: EnginePeer.Id?,
|
||||
isScheduledMessages: Bool,
|
||||
forwardMessageIds: [EngineMessage.Id]?,
|
||||
@ -63,6 +76,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
gesture: ContextGesture,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
textInputView: UITextView,
|
||||
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
|
||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
||||
attachment: Bool,
|
||||
@ -75,6 +89,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
isPremium: Bool
|
||||
) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.peerId = peerId
|
||||
self.isScheduledMessages = isScheduledMessages
|
||||
self.forwardMessageIds = forwardMessageIds
|
||||
@ -82,6 +97,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
self.gesture = gesture
|
||||
self.sourceSendButton = sourceSendButton
|
||||
self.textInputView = textInputView
|
||||
self.mediaPreview = mediaPreview
|
||||
self.emojiViewProvider = emojiViewProvider
|
||||
self.wallpaperBackgroundNode = wallpaperBackgroundNode
|
||||
self.attachment = attachment
|
||||
@ -141,6 +157,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
private var standaloneReactionAnimation: AnimatedStickerNode?
|
||||
|
||||
private var isLoadingEffectAnimation: Bool = false
|
||||
private var isLoadingEffectAnimationTimerDisposable: Disposable?
|
||||
private var loadEffectAnimationDisposable: Disposable?
|
||||
|
||||
private var presentationAnimationState: PresentationAnimationState = .initial
|
||||
@ -177,6 +194,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
deinit {
|
||||
self.messageEffectDisposable.dispose()
|
||||
self.loadEffectAnimationDisposable?.dispose()
|
||||
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func onBackgroundTap(_ recognizer: UITapGestureRecognizer) {
|
||||
@ -196,6 +214,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
|
||||
func animateIn() {
|
||||
if case .initial = self.presentationAnimationState {
|
||||
HapticFeedback().impact()
|
||||
|
||||
self.presentationAnimationState = .animatedIn
|
||||
self.state?.updated(transition: .spring(duration: 0.42))
|
||||
}
|
||||
@ -270,7 +290,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
self.environment = environment
|
||||
self.state = state
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundView.updateColor(
|
||||
@ -298,15 +318,6 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
|
||||
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
|
||||
switch self.presentationAnimationState {
|
||||
case .initial:
|
||||
@ -389,7 +400,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.schedule(nil)
|
||||
self.animateOutToEmpty = true
|
||||
component.schedule(self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) }))
|
||||
self.environment?.controller()?.dismiss()
|
||||
}
|
||||
)))
|
||||
@ -476,10 +488,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
backgroundNode: component.wallpaperBackgroundNode,
|
||||
textString: textString,
|
||||
sourceTextInputView: component.textInputView as? ChatInputTextView,
|
||||
emojiViewProvider: component.emojiViewProvider,
|
||||
sourceMediaPreview: component.mediaPreview,
|
||||
textInsets: messageTextInsets,
|
||||
explicitBackgroundSize: explicitMessageBackgroundSize,
|
||||
maxTextWidth: localSourceTextInputViewFrame.width,
|
||||
maxTextHeight: maxTextHeight,
|
||||
containerSize: CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height),
|
||||
effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil,
|
||||
transition: transition
|
||||
)
|
||||
@ -586,6 +601,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
self.selectedMessageEffect = nil
|
||||
reactionContextNode.selectedItems = Set([])
|
||||
self.loadEffectAnimationDisposable?.dispose()
|
||||
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||
self.isLoadingEffectAnimationTimerDisposable = nil
|
||||
self.isLoadingEffectAnimation = false
|
||||
|
||||
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
|
||||
@ -619,10 +636,16 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
}
|
||||
|
||||
self.loadEffectAnimationDisposable?.dispose()
|
||||
self.isLoadingEffectAnimation = true
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||
}
|
||||
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||
self.isLoadingEffectAnimationTimerDisposable = (Signal<Never, NoError>.complete() |> delay(0.2, queue: .mainQueue()) |> deliverOnMainQueue).startStrict(completed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isLoadingEffectAnimation = true
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||
}
|
||||
})
|
||||
|
||||
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
|
||||
self.standaloneReactionAnimation = nil
|
||||
@ -666,9 +689,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
dataDisposabke.dispose()
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
/*#if DEBUG
|
||||
loadEffectAnimationSignal = loadEffectAnimationSignal |> delay(1.0, queue: .mainQueue())
|
||||
#endif
|
||||
#endif*/
|
||||
|
||||
self.loadEffectAnimationDisposable = (loadEffectAnimationSignal
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
@ -676,6 +699,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
self.isLoadingEffectAnimationTimerDisposable?.dispose()
|
||||
self.isLoadingEffectAnimationTimerDisposable = nil
|
||||
self.isLoadingEffectAnimation = false
|
||||
|
||||
guard let targetView = self.messageItemView?.effectIconView else {
|
||||
@ -689,7 +714,11 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
#if targetEnvironment(simulator)
|
||||
standaloneReactionAnimation = DirectAnimatedStickerNode()
|
||||
#else
|
||||
standaloneReactionAnimation = LottieMetalAnimatedStickerNode()
|
||||
if "".isEmpty {
|
||||
standaloneReactionAnimation = DirectAnimatedStickerNode()
|
||||
} else {
|
||||
standaloneReactionAnimation = LottieMetalAnimatedStickerNode()
|
||||
}
|
||||
#endif
|
||||
|
||||
standaloneReactionAnimation.isUserInteractionEnabled = false
|
||||
@ -728,7 +757,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
//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(
|
||||
presentationData: presentationData,
|
||||
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 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
|
||||
switch self.presentationAnimationState {
|
||||
case .initial:
|
||||
messageItemFrame = sourceMessageItemFrame
|
||||
actionsStackFrame = sourceActionsStackFrame
|
||||
if component.mediaPreview != nil {
|
||||
messageItemFrame = readyMessageItemFrame
|
||||
actionsStackFrame = readyActionsStackFrame
|
||||
} else {
|
||||
messageItemFrame = sourceMessageItemFrame
|
||||
actionsStackFrame = sourceActionsStackFrame
|
||||
}
|
||||
sendButtonFrame = sourceSendButtonFrame
|
||||
case .animatedOut:
|
||||
if self.animateOutToEmpty {
|
||||
@ -798,8 +834,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
actionsStackFrame = readyActionsStackFrame
|
||||
sendButtonFrame = readySendButtonFrame
|
||||
} else {
|
||||
messageItemFrame = sourceMessageItemFrame
|
||||
actionsStackFrame = sourceActionsStackFrame
|
||||
if component.mediaPreview != nil {
|
||||
messageItemFrame = readyMessageItemFrame
|
||||
actionsStackFrame = readyActionsStackFrame
|
||||
} else {
|
||||
messageItemFrame = sourceMessageItemFrame
|
||||
actionsStackFrame = sourceActionsStackFrame
|
||||
}
|
||||
sendButtonFrame = sourceSendButtonFrame
|
||||
}
|
||||
case .animatedIn:
|
||||
@ -809,6 +850,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
}
|
||||
|
||||
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.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.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)
|
||||
|
||||
messageItemView.animateIn(transition: transition)
|
||||
case .animatedOut:
|
||||
transition.setAlpha(view: actionsStackNode.view, alpha: 0.0)
|
||||
transition.setScale(view: actionsStackNode.view, scale: 0.001)
|
||||
|
||||
messageItemView.animateOut(transition: transition)
|
||||
}
|
||||
} else {
|
||||
switch self.presentationAnimationState {
|
||||
@ -837,7 +889,11 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
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))
|
||||
reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: false, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
|
||||
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.setBounds(view: sendButton, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size))
|
||||
transition.setScale(view: sendButton, scale: sendButtonScale)
|
||||
@ -866,21 +932,32 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
let backgroundAlpha: CGFloat
|
||||
switch self.presentationAnimationState {
|
||||
case .animatedIn:
|
||||
if previousAnimationState.key == .initial && self.initializationDisplayLink == nil {
|
||||
self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if previousAnimationState.key == .initial {
|
||||
if environment.inputHeight != 0.0 {
|
||||
if self.initializationDisplayLink == nil {
|
||||
self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.initializationDisplayLink?.invalidate()
|
||||
self.initializationDisplayLink = nil
|
||||
self.initializationDisplayLink?.invalidate()
|
||||
self.initializationDisplayLink = nil
|
||||
|
||||
guard let component = self.component else {
|
||||
return
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if component.mediaPreview == nil {
|
||||
component.textInputView.isHidden = true
|
||||
}
|
||||
component.sourceSendButton.isHidden = true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if component.mediaPreview == nil {
|
||||
component.textInputView.isHidden = true
|
||||
}
|
||||
component.textInputView.isHidden = true
|
||||
component.sourceSendButton.isHidden = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
backgroundAlpha = 1.0
|
||||
@ -888,7 +965,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
backgroundAlpha = 0.0
|
||||
|
||||
if self.animateOutToEmpty {
|
||||
component.textInputView.isHidden = false
|
||||
if component.mediaPreview == nil {
|
||||
component.textInputView.isHidden = false
|
||||
}
|
||||
component.sourceSendButton.isHidden = false
|
||||
|
||||
transition.setAlpha(view: sendButton, alpha: 0.0)
|
||||
@ -906,7 +985,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
}
|
||||
if case let .animatedOut(completion) = self.presentationAnimationState {
|
||||
if let component = self.component, !self.animateOutToEmpty {
|
||||
component.textInputView.isHidden = false
|
||||
if component.mediaPreview == nil {
|
||||
component.textInputView.isHidden = false
|
||||
}
|
||||
component.sourceSendButton.isHidden = false
|
||||
}
|
||||
completion()
|
||||
@ -950,6 +1031,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
||||
gesture: ContextGesture,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
textInputView: UITextView,
|
||||
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
|
||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
||||
attachment: Bool,
|
||||
@ -967,6 +1049,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
||||
context: context,
|
||||
component: ChatSendMessageContextScreenComponent(
|
||||
context: context,
|
||||
updatedPresentationData: updatedPresentationData,
|
||||
peerId: peerId,
|
||||
isScheduledMessages: isScheduledMessages,
|
||||
forwardMessageIds: forwardMessageIds,
|
||||
@ -974,6 +1057,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
||||
gesture: gesture,
|
||||
sourceSendButton: sourceSendButton,
|
||||
textInputView: textInputView,
|
||||
mediaPreview: mediaPreview,
|
||||
emojiViewProvider: emojiViewProvider,
|
||||
wallpaperBackgroundNode: wallpaperBackgroundNode,
|
||||
attachment: attachment,
|
||||
@ -987,18 +1071,12 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .none,
|
||||
presentationMode: .default
|
||||
presentationMode: .default,
|
||||
updatedPresentationData: updatedPresentationData
|
||||
)
|
||||
|
||||
self.lockOrientation = true
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
|
||||
/*gesture.externalEnded = { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.dismiss()
|
||||
}*/
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -97,10 +97,12 @@ private final class EffectIcon: Component {
|
||||
textView = ComponentView()
|
||||
self.textView = textView
|
||||
}
|
||||
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0)
|
||||
let textSize = textView.update(
|
||||
transition: .immediate,
|
||||
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: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
@ -138,12 +140,19 @@ final class MessageItemView: UIView {
|
||||
|
||||
private let textClippingContainer: UIView
|
||||
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>?
|
||||
var effectIconView: UIView? {
|
||||
return self.effectIcon?.view
|
||||
}
|
||||
|
||||
private var effectIconBackgroundView: UIImageView?
|
||||
|
||||
private var chatTheme: ChatPresentationThemeData?
|
||||
private var currentSize: CGSize?
|
||||
|
||||
@ -167,19 +176,36 @@ final class MessageItemView: UIView {
|
||||
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(
|
||||
context: AccountContext,
|
||||
presentationData: PresentationData,
|
||||
backgroundNode: WallpaperBackgroundNode?,
|
||||
textString: NSAttributedString,
|
||||
sourceTextInputView: ChatInputTextView?,
|
||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||
sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?,
|
||||
textInsets: UIEdgeInsets,
|
||||
explicitBackgroundSize: CGSize?,
|
||||
maxTextWidth: CGFloat,
|
||||
maxTextHeight: CGFloat,
|
||||
containerSize: CGSize,
|
||||
effect: AvailableMessageEffects.MessageEffect?,
|
||||
transition: Transition
|
||||
) -> CGSize {
|
||||
self.emojiViewProvider = emojiViewProvider
|
||||
|
||||
var effectIconSize: CGSize?
|
||||
if let effect {
|
||||
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
|
||||
if let current = self.chatTheme, current.theme === presentationData.theme {
|
||||
chatTheme = current
|
||||
@ -305,89 +248,329 @@ final class MessageItemView: UIView {
|
||||
maskMode: true,
|
||||
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
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
if let sourceMediaPreview {
|
||||
let mediaPreviewClippingView: UIView
|
||||
if let current = self.mediaPreviewClippingView {
|
||||
mediaPreviewClippingView = current
|
||||
} else {
|
||||
mediaPreviewClippingView = UIView()
|
||||
mediaPreviewClippingView.layer.anchorPoint = CGPoint()
|
||||
mediaPreviewClippingView.clipsToBounds = true
|
||||
mediaPreviewClippingView.isUserInteractionEnabled = false
|
||||
self.mediaPreviewClippingView = mediaPreviewClippingView
|
||||
self.addSubview(mediaPreviewClippingView)
|
||||
}
|
||||
} else {
|
||||
if let effectIcon = self.effectIcon {
|
||||
self.effectIcon = nil
|
||||
|
||||
if self.mediaPreview !== sourceMediaPreview {
|
||||
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 {
|
||||
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)
|
||||
var animateIn = false
|
||||
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.setScale(view: effectIconView, scale: 0.001)
|
||||
transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in
|
||||
effectIconView?.removeFromSuperview()
|
||||
|
||||
transition.setFrame(view: effectIconBackgroundView, frame: effectIconBackgroundFrame)
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let backgroundAlpha: CGFloat
|
||||
if explicitBackgroundSize != nil {
|
||||
backgroundAlpha = 0.0
|
||||
return backgroundSize
|
||||
} 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))
|
||||
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)
|
||||
var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
|
||||
|
||||
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 var activityIndicator: ActivityIndicator?
|
||||
|
||||
private var didProcessSourceCustomContent: Bool = false
|
||||
private var sourceCustomContentView: UIView?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.containerView = UIView()
|
||||
self.containerView.isUserInteractionEnabled = false
|
||||
@ -50,12 +53,14 @@ final class SendButton: HighlightTrackingButton {
|
||||
context: AccountContext,
|
||||
presentationData: PresentationData,
|
||||
backgroundNode: WallpaperBackgroundNode?,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
isAnimatedIn: Bool,
|
||||
isLoadingEffectAnimation: Bool,
|
||||
size: CGSize,
|
||||
transition: Transition
|
||||
) {
|
||||
let innerSize = CGSize(width: 33.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))
|
||||
let innerSize = CGSize(width: size.width - 5.5 * 2.0, height: 33.0)
|
||||
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)
|
||||
|
||||
if self.window != nil {
|
||||
@ -71,7 +76,7 @@ final class SendButton: HighlightTrackingButton {
|
||||
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.backgroundLayer.isHidden = true
|
||||
} else {
|
||||
@ -79,18 +84,44 @@ final class SendButton: HighlightTrackingButton {
|
||||
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))
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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.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)
|
||||
}
|
||||
|
||||
|
@ -549,10 +549,10 @@ private final class CreatePollContext: AttachmentMediaPickerContext {
|
||||
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() {
|
||||
|
@ -404,10 +404,10 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
|
||||
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() {
|
||||
|
@ -47,6 +47,9 @@ swift_library(
|
||||
"//submodules/RadialStatusNode",
|
||||
"//submodules/Camera",
|
||||
"//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation",
|
||||
"//submodules/ChatSendMessageActionUI",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -25,6 +25,7 @@ import Camera
|
||||
import CameraScreen
|
||||
import MediaEditor
|
||||
import ImageObjectSeparation
|
||||
import ChatSendMessageActionUI
|
||||
|
||||
final class MediaPickerInteraction {
|
||||
let downloadManager: AssetDownloadManager
|
||||
@ -32,14 +33,14 @@ final class MediaPickerInteraction {
|
||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||
let openDraft: (MediaEditorDraft, UIImage?) -> Void
|
||||
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool
|
||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void
|
||||
let schedule: () -> Void
|
||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void
|
||||
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
|
||||
let dismissInput: () -> Void
|
||||
let selectionState: TGMediaSelectionContext?
|
||||
let editingState: TGMediaEditingContext
|
||||
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.openMedia = openMedia
|
||||
self.openSelectedMedia = openSelectedMedia
|
||||
@ -199,7 +200,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
public var presentFilePicker: () -> Void = {}
|
||||
|
||||
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 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 isContainerExpanded: () -> Bool = { return false }
|
||||
|
||||
public var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? = nil
|
||||
|
||||
private let selectedCollection = Promise<PHAssetCollection?>(nil)
|
||||
|
||||
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 {
|
||||
@ -945,7 +991,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
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.layer.allowsGroupOpacity = true
|
||||
selectionNode.isUserInteractionEnabled = false
|
||||
@ -992,11 +1038,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
if animated {
|
||||
switch displayMode {
|
||||
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)
|
||||
}, completion: completion)
|
||||
case .all:
|
||||
self.selectionNode?.animateOut(completion: completion)
|
||||
self.selectionNode?.animateOut(transition: .animated(duration: 0.25, curve: .easeInOut), completion: completion)
|
||||
}
|
||||
} else {
|
||||
self.updateNavigation(transition: .immediate)
|
||||
@ -1099,7 +1145,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
return self?.transitionView(for: identifier)
|
||||
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
||||
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
|
||||
self?.currentGalleryParentController = c
|
||||
@ -1138,7 +1184,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
return self?.transitionView(for: identifier)
|
||||
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
||||
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
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -1200,7 +1246,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
return
|
||||
}
|
||||
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
|
||||
}, { [weak self] in
|
||||
completion()
|
||||
@ -1913,18 +1959,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
} else {
|
||||
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 {
|
||||
strongSelf.isDismissing = true
|
||||
if let currentItem = currentItem {
|
||||
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 {
|
||||
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
|
||||
@ -2384,7 +2430,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}, action: { [weak self] _, f in
|
||||
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 {
|
||||
@ -2517,12 +2563,12 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
||||
self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
|
||||
}
|
||||
|
||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
||||
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {})
|
||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, messageEffect, {})
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
self.controller?.interaction?.schedule()
|
||||
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||
self.controller?.interaction?.schedule(messageEffect)
|
||||
}
|
||||
|
||||
func mainButtonAction() {
|
||||
|
@ -13,11 +13,15 @@ import MosaicLayout
|
||||
import WallpaperBackgroundNode
|
||||
import AccountContext
|
||||
import ChatMessageBackground
|
||||
import ChatSendMessageActionUI
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
let asset: TGMediaEditableItem
|
||||
private let interaction: MediaPickerInteraction?
|
||||
private let enableAnimations: Bool
|
||||
private let isExternalPreview: Bool
|
||||
|
||||
private let imageNode: ImageNode
|
||||
private var checkNode: InteractiveCheckNode?
|
||||
@ -57,10 +61,11 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
|
||||
private var videoDuration: Double?
|
||||
|
||||
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool) {
|
||||
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool, isExternalPreview: Bool) {
|
||||
self.asset = asset
|
||||
self.interaction = interaction
|
||||
self.enableAnimations = enableAnimations
|
||||
self.isExternalPreview = isExternalPreview
|
||||
|
||||
self.imageNode = ImageNode()
|
||||
self.imageNode.contentMode = .scaleAspectFill
|
||||
@ -279,7 +284,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
|
||||
if let checkNode = self.checkNode {
|
||||
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
|
||||
}
|
||||
|
||||
func animateFrom(_ view: UIView) {
|
||||
func animateFrom(_ view: UIView, transition: ContainedViewLayoutTransition) {
|
||||
view.alpha = 0.0
|
||||
|
||||
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.updateLayout(size: frame.size, transition: .immediate)
|
||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in
|
||||
view?.alpha = 1.0
|
||||
self.updateLayout(size: targetFrame.size, transition: transition)
|
||||
transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, completion: { [weak self, weak view] _ in
|
||||
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
|
||||
|
||||
let frame = self.frame
|
||||
@ -418,14 +429,13 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
self.addSubnode(dustNode)
|
||||
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.corners = []
|
||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in
|
||||
self.updateLayout(size: targetFrame.size, transition: transition)
|
||||
transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, removeOnCompletion: false, completion: { [weak view, weak self] _ in
|
||||
view?.alpha = 1.0
|
||||
|
||||
self?.durationTextNode?.layer.removeAllAnimations()
|
||||
@ -471,7 +481,7 @@ private class MessageBackgroundNode: ASDisplayNode {
|
||||
|
||||
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.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)
|
||||
@ -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 persistentItems: Bool
|
||||
private let isExternalPreview: Bool
|
||||
var globalClippingRect: CGRect?
|
||||
|
||||
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
|
||||
fileprivate var wallpaperBackgroundNode: WallpaperBackgroundNode?
|
||||
private let scrollNode: ASScrollNode
|
||||
private var backgroundNodes: [Int: MessageBackgroundNode] = [:]
|
||||
private var itemNodes: [String: MediaPickerSelectedItemNode] = [:]
|
||||
@ -518,17 +530,28 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
||||
private var didSetReady = false
|
||||
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.persistentItems = persistentItems
|
||||
self.isExternalPreview = isExternalPreview
|
||||
|
||||
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false)
|
||||
self.wallpaperBackgroundNode.backgroundColor = .black
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNode.clipsToBounds = false
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -578,7 +601,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
||||
|
||||
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()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
@ -589,79 +612,105 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
||||
strongSelf.alpha = 1.0
|
||||
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()
|
||||
})
|
||||
strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
||||
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 {
|
||||
if let (transitionView, _, _) = strongSelf.getTransitionView(identifier) {
|
||||
itemNode.animateFrom(transitionView)
|
||||
itemNode.animateFrom(transitionView, transition: transition)
|
||||
} 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 {
|
||||
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 {
|
||||
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 = {}) {
|
||||
self.wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
completion()
|
||||
func animateOut(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) {
|
||||
if let wallpaperBackgroundNode = self.wallpaperBackgroundNode{
|
||||
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) {
|
||||
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
||||
backgroundNode.layer.removeAllAnimations()
|
||||
if let strongSelf = self {
|
||||
Queue.mainQueue().after(0.01) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
|
||||
for (_, backgroundNode) in self.backgroundNodes {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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] {
|
||||
itemNode = current
|
||||
} 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.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))
|
||||
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)
|
||||
if !self.isExternalPreview {
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
|
||||
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 {
|
||||
for i in 0 ..< headerItems.count {
|
||||
let itemNode = messageNodes[i]
|
||||
headerItems[i].updateNode(async: { $0() }, node: {
|
||||
return itemNode
|
||||
}, 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))
|
||||
let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height)
|
||||
if let messageNodes = self.messageNodes {
|
||||
for i in 0 ..< headerItems.count {
|
||||
let itemNode = messageNodes[i]
|
||||
headerItems[i].updateNode(async: { $0() }, node: {
|
||||
return itemNode
|
||||
}, 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.frame = nodeFrame
|
||||
itemNode.isUserInteractionEnabled = false
|
||||
itemNode.contentSize = layout.contentSize
|
||||
itemNode.insets = layout.insets
|
||||
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
|
||||
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 {
|
||||
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
|
||||
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)
|
||||
|
||||
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)
|
||||
var groupRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top + contentHeight), size: groupSize)
|
||||
if !self.isExternalPreview {
|
||||
groupRect.origin.x = insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0)
|
||||
}
|
||||
|
||||
var itemTransition = transition
|
||||
if groupBackgroundNode.frame.width.isZero {
|
||||
itemTransition = .immediate
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
var isFirstGroup = true
|
||||
for (item, itemRect, itemPosition) in items {
|
||||
if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] {
|
||||
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
|
||||
}
|
||||
|
||||
@ -1032,7 +1102,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
||||
|
||||
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() {
|
||||
@ -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) {
|
||||
let previous = self.validLayout
|
||||
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
|
||||
self.wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false)
|
||||
self.wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners)
|
||||
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)))
|
||||
self.wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition)
|
||||
if let wallpaperBackgroundNode = self.wallpaperBackgroundNode {
|
||||
wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false)
|
||||
wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners)
|
||||
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)
|
||||
|
||||
|
@ -1134,7 +1134,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
|
||||
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($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[-648651719] = { return Api.auth.SentCodeType.parse_sentCodeTypeFragmentSms($0) }
|
||||
dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) }
|
||||
@ -1365,7 +1365,7 @@ public extension Api {
|
||||
return parser(reader)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -589,7 +589,7 @@ public extension Api.auth {
|
||||
case sentCodeTypeApp(length: Int32)
|
||||
case sentCodeTypeCall(length: 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 sentCodeTypeFragmentSms(url: 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 << 4) != 0 {serializeInt32(resetPendingDate!, buffer: buffer, boxed: false)}
|
||||
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 {
|
||||
buffer.appendInt32(-444918734)
|
||||
buffer.appendInt32(331943703)
|
||||
}
|
||||
serializeInt32(flags, 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 {serializeInt32(pushTimeout!, buffer: buffer, boxed: false)}
|
||||
serializeInt32(length, buffer: buffer, boxed: false)
|
||||
@ -689,8 +690,8 @@ public extension Api.auth {
|
||||
return ("sentCodeTypeCall", [("length", length as Any)])
|
||||
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)])
|
||||
case .sentCodeTypeFirebaseSms(let flags, let nonce, 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)])
|
||||
case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length):
|
||||
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):
|
||||
return ("sentCodeTypeFlashCall", [("pattern", pattern as Any)])
|
||||
case .sentCodeTypeFragmentSms(let url, let length):
|
||||
@ -758,19 +759,22 @@ public extension Api.auth {
|
||||
_1 = reader.readInt32()
|
||||
var _2: Buffer?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) }
|
||||
var _3: String?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) }
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
|
||||
var _3: Buffer?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {_3 = parseBytes(reader) }
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) }
|
||||
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 _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 _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, receipt: _3, pushTimeout: _4, length: _5!)
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
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 {
|
||||
return nil
|
||||
|
@ -2061,15 +2061,16 @@ 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()
|
||||
buffer.appendInt32(1940588736)
|
||||
buffer.appendInt32(-1908857314)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(phoneNumber, 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)}
|
||||
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)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -2095,12 +2096,14 @@ 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()
|
||||
buffer.appendInt32(1056025023)
|
||||
buffer.appendInt32(-890997469)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(phoneNumber, 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)
|
||||
var result: Api.auth.SentCode?
|
||||
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;
|
||||
var flags: Int32 = 0
|
||||
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
|
||||
return .generic
|
||||
}
|
||||
@ -275,7 +275,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
|
||||
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
|
||||
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type {
|
||||
return firebaseSecretStream
|
||||
|> map { mapping -> String? in
|
||||
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> {
|
||||
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
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
@ -421,7 +421,7 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
|
||||
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
|
||||
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type {
|
||||
return firebaseSecretStream
|
||||
|> map { mapping -> String? in
|
||||
guard let receipt = receipt else {
|
||||
@ -520,7 +520,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(number, type, hash, _, nextType, syncContacts, previousCodeEntryValue, _):
|
||||
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
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
@ -563,7 +563,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
|
||||
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = newType {
|
||||
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = newType {
|
||||
return firebaseSecretStream
|
||||
|> map { mapping -> String? in
|
||||
guard let receipt = receipt else {
|
||||
|
@ -36,7 +36,7 @@ extension SentAuthorizationCodeType {
|
||||
self = .emailSetupRequired(appleSignInAllowed: (flags & (1 << 0)) != 0)
|
||||
case let .sentCodeTypeFragmentSms(url, length):
|
||||
self = .fragment(url: url, length: length)
|
||||
case let .sentCodeTypeFirebaseSms(_, _, _, pushTimeout, length):
|
||||
case let .sentCodeTypeFirebaseSms(_, _, _, _, pushTimeout, length):
|
||||
self = .firebase(pushTimeout: pushTimeout, length: length)
|
||||
case let .sentCodeTypeSmsWord(_, beginning):
|
||||
self = .word(startsWith: beginning)
|
||||
|
@ -3,10 +3,6 @@ import TelegramApi
|
||||
import Postbox
|
||||
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 MessageEffect: Equatable, Codable {
|
||||
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> {
|
||||
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
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
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> {
|
||||
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
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
|
@ -229,6 +229,15 @@ public:
|
||||
CGRect bounds;
|
||||
};
|
||||
|
||||
class RenderTreeNodeContentItem {
|
||||
public:
|
||||
RenderTreeNodeContentItem() {
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<RenderTreeNodeContentItem>> subItems;
|
||||
};
|
||||
|
||||
class RenderTreeNodeContent {
|
||||
public:
|
||||
enum class ShadingType {
|
||||
|
@ -1036,7 +1036,7 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
void renderChildren(AnimationFrameTime frameTime, std::optional<TrimParams> parentTrim) {
|
||||
void updateChildren(AnimationFrameTime frameTime, std::optional<TrimParams> parentTrim) {
|
||||
CATransform3D containerTransform = CATransform3D::identity();
|
||||
double containerOpacity = 1.0;
|
||||
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) {
|
||||
_frameTime = frame;
|
||||
_frameTimeInitialized = true;
|
||||
_contentTree->itemTree->renderChildren(_frameTime, std::nullopt);
|
||||
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
|
||||
}
|
||||
|
||||
std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode() {
|
||||
if (!_frameTimeInitialized) {
|
||||
_frameTime = 0.0;
|
||||
_frameTimeInitialized = true;
|
||||
_contentTree->itemTree->renderChildren(_frameTime, std::nullopt);
|
||||
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
|
||||
}
|
||||
|
||||
if (!_renderTreeNode) {
|
||||
|
@ -19,7 +19,6 @@ std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
|
||||
std::vector<std::shared_ptr<CompositionLayer>> compositionLayers;
|
||||
std::map<int, std::shared_ptr<CompositionLayer>> layerMap;
|
||||
|
||||
/// Organize the assets into a dictionary of [ID : ImageAsset]
|
||||
std::vector<std::shared_ptr<LayerModel>> childLayers;
|
||||
|
||||
for (const auto &layer : layers) {
|
||||
|
@ -10212,7 +10212,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
func forwardMessages(messageIds: Set<MessageId>?) {
|
||||
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))
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||
private var customTitle: String?
|
||||
|
||||
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 forumPeerId: EnginePeer.Id?
|
||||
private let selectForumThreads: Bool
|
||||
@ -246,8 +246,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||
|
||||
self.peerSelectionNode.navigationBar = self.navigationBar
|
||||
|
||||
self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState in
|
||||
self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState)
|
||||
self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState, messageEffect in
|
||||
self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState, messageEffect)
|
||||
}
|
||||
|
||||
self.peerSelectionNode.requestDeactivateSearch = { [weak self] in
|
||||
|
@ -83,7 +83,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
var requestOpenDisabledPeer: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
|
||||
var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> 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 {
|
||||
didSet {
|
||||
@ -529,7 +529,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) }
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) })
|
||||
}
|
||||
strongSelf.textInputPanelNode?.sendMessage(.generic)
|
||||
strongSelf.textInputPanelNode?.sendMessage(.generic, nil)
|
||||
|
||||
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: {
|
||||
}, sendMessage: { [weak textInputPanelNode] mode, _ in
|
||||
}, sendMessage: { [weak textInputPanelNode] mode, messageEffect in
|
||||
switch mode {
|
||||
case .generic:
|
||||
textInputPanelNode?.sendMessage(.generic)
|
||||
textInputPanelNode?.sendMessage(.generic, messageEffect)
|
||||
case .silently:
|
||||
textInputPanelNode?.sendMessage(.silent)
|
||||
textInputPanelNode?.sendMessage(.silent, messageEffect)
|
||||
case .whenOnline:
|
||||
textInputPanelNode?.sendMessage(.whenOnline)
|
||||
textInputPanelNode?.sendMessage(.whenOnline, messageEffect)
|
||||
}
|
||||
}, schedule: { [weak textInputPanelNode] _ in
|
||||
textInputPanelNode?.sendMessage(.schedule)
|
||||
}, schedule: { [weak textInputPanelNode] messageEffect in
|
||||
textInputPanelNode?.sendMessage(.schedule, messageEffect)
|
||||
})
|
||||
strongSelf.presentInGlobalOverlay(controller, nil)
|
||||
}, openScheduledMessages: {
|
||||
@ -825,7 +825,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
return nil
|
||||
})
|
||||
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
||||
textInputPanelNode.sendMessage = { [weak self] mode in
|
||||
textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -835,7 +835,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
|
||||
let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers
|
||||
if !selectedPeers.isEmpty {
|
||||
strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState)
|
||||
strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect)
|
||||
}
|
||||
}
|
||||
self.addSubnode(textInputPanelNode)
|
||||
|
@ -49,10 +49,10 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext {
|
||||
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() {
|
||||
|
@ -377,10 +377,10 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext {
|
||||
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() {
|
||||
|
@ -1556,14 +1556,14 @@ final class StoryItemSetContainerSendMessage {
|
||||
completion(controller, mediaPickerContext)
|
||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if !inputText.string.isEmpty {
|
||||
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:
|
||||
@ -1875,7 +1875,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
bannedSendVideos: (Int32, Bool)?,
|
||||
present: @escaping (MediaPickerScreen, 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 {
|
||||
return
|
||||
@ -1936,8 +1936,8 @@ final class StoryItemSetContainerSendMessage {
|
||||
}
|
||||
return self.getCaptionPanelView(view: view, peer: peer, mediaPicker: controller)
|
||||
}
|
||||
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
|
||||
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
|
||||
controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in
|
||||
completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion)
|
||||
}
|
||||
present(controller, mediaPickerContext)
|
||||
}
|
||||
@ -2240,14 +2240,14 @@ final class StoryItemSetContainerSendMessage {
|
||||
present(controller, mediaPickerContext)
|
||||
},
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if !inputText.string.isEmpty {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -183,10 +183,10 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext {
|
||||
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() {
|
||||
|
@ -27,8 +27,8 @@ extension ChatControllerImpl {
|
||||
subjects: subjects,
|
||||
presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in
|
||||
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
|
||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||
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, 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!)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] items in
|
||||
if let strongSelf = self {
|
||||
@ -9112,6 +9112,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
usedCorrelationId = correlationId
|
||||
completionImpl = nil
|
||||
}
|
||||
|
||||
if let messageEffect {
|
||||
message = message.withUpdatedAttributes { attributes in
|
||||
var attributes = attributes
|
||||
attributes.append(EffectMessageAttribute(id: messageEffect.id))
|
||||
return attributes
|
||||
}
|
||||
}
|
||||
|
||||
mappedMessages.append(message)
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ extension ChatControllerImpl {
|
||||
}), 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 {
|
||||
return
|
||||
}
|
||||
|
@ -308,11 +308,11 @@ extension ChatControllerImpl {
|
||||
completion(controller, mediaPickerContext)
|
||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||
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 {
|
||||
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:
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
@ -1136,7 +1136,7 @@ extension ChatControllerImpl {
|
||||
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
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
@ -1210,8 +1210,8 @@ extension ChatControllerImpl {
|
||||
controller.getCaptionPanelView = { [weak self] in
|
||||
return self?.getCaptionPanelView(isFile: false)
|
||||
}
|
||||
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
|
||||
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
|
||||
controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in
|
||||
completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion)
|
||||
}
|
||||
present(controller, mediaPickerContext)
|
||||
}
|
||||
|
@ -451,11 +451,11 @@ final class ContactsPickerContext: AttachmentMediaPickerContext {
|
||||
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)
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||
self.controller?.presentScheduleTimePicker ({ time in
|
||||
self.controller?.contactsNode.requestMultipleAction?(false, time)
|
||||
})
|
||||
|
@ -35,14 +35,14 @@ final class WebSearchControllerInteraction {
|
||||
let setSearchQuery: (String) -> Void
|
||||
let deleteRecentQuery: (String) -> Void
|
||||
let toggleSelection: (ChatContextResult, Bool) -> Bool
|
||||
let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void
|
||||
let schedule: () -> Void
|
||||
let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void
|
||||
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
|
||||
let avatarCompleted: (UIImage) -> Void
|
||||
let selectionState: TGMediaSelectionContext?
|
||||
let editingState: TGMediaEditingContext
|
||||
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.setSearchQuery = setSearchQuery
|
||||
self.deleteRecentQuery = deleteRecentQuery
|
||||
@ -254,7 +254,7 @@ public final class WebSearchController: ViewController {
|
||||
} else {
|
||||
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 current = current {
|
||||
let currentItem = LegacyWebSearchItem(result: current)
|
||||
@ -264,10 +264,10 @@ public final class WebSearchController: ViewController {
|
||||
sendSelected(results, selectionState, editingState, false)
|
||||
}
|
||||
}
|
||||
}, schedule: { [weak self] in
|
||||
}, schedule: { [weak self] messageEffect in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
||||
self?.controllerInteraction?.sendSelected(nil, false, time)
|
||||
self?.controllerInteraction?.sendSelected(nil, false, time, nil)
|
||||
})
|
||||
}
|
||||
}, avatarCompleted: { result in
|
||||
@ -606,12 +606,12 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext {
|
||||
self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
|
||||
}
|
||||
|
||||
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
||||
self.interaction?.sendSelected(nil, mode == .silently, nil)
|
||||
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||
self.interaction?.sendSelected(nil, mode == .silently, nil, messageEffect)
|
||||
}
|
||||
|
||||
public func schedule() {
|
||||
self.interaction?.schedule()
|
||||
public func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
|
||||
self.interaction?.schedule(messageEffect)
|
||||
}
|
||||
|
||||
public func mainButtonAction() {
|
||||
|
@ -717,7 +717,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func sendPressed() {
|
||||
self.controllerInteraction.sendSelected(nil, false, nil)
|
||||
self.controllerInteraction.sendSelected(nil, false, nil, nil)
|
||||
|
||||
self.cancel?()
|
||||
}
|
||||
@ -740,7 +740,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
return self?.transitionNode(for: result)?.transitionView()
|
||||
}, completed: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction.sendSelected(result, false, nil)
|
||||
strongSelf.controllerInteraction.sendSelected(result, false, nil, nil)
|
||||
strongSelf.cancel?()
|
||||
}
|
||||
}, getCaptionPanelView: self.getCaptionPanelView, present: present)
|
||||
@ -760,7 +760,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
|
||||
}, baseNavigationController: nil, sendCurrent: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction.sendSelected(result, false, nil)
|
||||
strongSelf.controllerInteraction.sendSelected(result, false, nil, nil)
|
||||
strongSelf.cancel?()
|
||||
}
|
||||
})
|
||||
|
@ -2092,10 +2092,10 @@ final class WebAppPickerContext: AttachmentMediaPickerContext {
|
||||
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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user