Glass
This commit is contained in:
parent
8e7e5fefb1
commit
4337026fba
37 changed files with 2883 additions and 786 deletions
|
|
@ -445,7 +445,7 @@ public class AnimatedCountLabelView: UIView {
|
||||||
effectiveSegmentWidth = max(effectiveSegmentWidth, 4.0)
|
effectiveSegmentWidth = max(effectiveSegmentWidth, 4.0)
|
||||||
}
|
}
|
||||||
calculatedSegments[segment.key] = (layout, effectiveSegmentWidth, apply)
|
calculatedSegments[segment.key] = (layout, effectiveSegmentWidth, apply)
|
||||||
contentSize.width += effectiveSegmentWidth
|
contentSize.width += floor(effectiveSegmentWidth * 0.9)
|
||||||
contentSize.height = max(contentSize.height, layout.size.height)
|
contentSize.height = max(contentSize.height, layout.size.height)
|
||||||
remainingSize.width = max(0.0, remainingSize.width - layout.size.width)
|
remainingSize.width = max(0.0, remainingSize.width - layout.size.width)
|
||||||
if layout.truncated {
|
if layout.truncated {
|
||||||
|
|
|
||||||
|
|
@ -1273,6 +1273,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
}, editTodoMessage: { _, _, _ in
|
}, editTodoMessage: { _, _, _ in
|
||||||
|
}, dismissUrlPreview: {
|
||||||
|
}, dismissForwardMessages: {
|
||||||
|
}, dismissSuggestPost: {
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,9 @@ public final class ChatPanelInterfaceInteraction {
|
||||||
public let forwardCurrentForwardMessages: () -> Void
|
public let forwardCurrentForwardMessages: () -> Void
|
||||||
public let forwardMessages: ([Message]) -> Void
|
public let forwardMessages: ([Message]) -> Void
|
||||||
public let updateForwardOptionsState: ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void
|
public let updateForwardOptionsState: ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void
|
||||||
public let presentForwardOptions: (ASDisplayNode) -> Void
|
public let presentForwardOptions: (UIView) -> Void
|
||||||
public let presentReplyOptions: (ASDisplayNode) -> Void
|
public let presentReplyOptions: (UIView) -> Void
|
||||||
public let presentLinkOptions: (ASDisplayNode) -> Void
|
public let presentLinkOptions: (UIView) -> Void
|
||||||
public let presentSuggestPostOptions: () -> Void
|
public let presentSuggestPostOptions: () -> Void
|
||||||
public let shareSelectedMessages: () -> Void
|
public let shareSelectedMessages: () -> Void
|
||||||
public let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void
|
public let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void
|
||||||
|
|
@ -187,6 +187,9 @@ public final class ChatPanelInterfaceInteraction {
|
||||||
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
|
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
|
||||||
public let dismissAllTooltips: () -> Void
|
public let dismissAllTooltips: () -> Void
|
||||||
public let editTodoMessage: (MessageId, Int32?, Bool) -> Void
|
public let editTodoMessage: (MessageId, Int32?, Bool) -> Void
|
||||||
|
public let dismissUrlPreview: () -> Void
|
||||||
|
public let dismissForwardMessages: () -> Void
|
||||||
|
public let dismissSuggestPost: () -> Void
|
||||||
public let requestLayout: (ContainedViewLayoutTransition) -> Void
|
public let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||||
public let chatController: () -> ViewController?
|
public let chatController: () -> ViewController?
|
||||||
public let statuses: ChatPanelInterfaceInteractionStatuses?
|
public let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||||
|
|
@ -205,9 +208,9 @@ public final class ChatPanelInterfaceInteraction {
|
||||||
forwardCurrentForwardMessages: @escaping () -> Void,
|
forwardCurrentForwardMessages: @escaping () -> Void,
|
||||||
forwardMessages: @escaping ([Message]) -> Void,
|
forwardMessages: @escaping ([Message]) -> Void,
|
||||||
updateForwardOptionsState: @escaping ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void,
|
updateForwardOptionsState: @escaping ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void,
|
||||||
presentForwardOptions: @escaping (ASDisplayNode) -> Void,
|
presentForwardOptions: @escaping (UIView) -> Void,
|
||||||
presentReplyOptions: @escaping (ASDisplayNode) -> Void,
|
presentReplyOptions: @escaping (UIView) -> Void,
|
||||||
presentLinkOptions: @escaping (ASDisplayNode) -> Void,
|
presentLinkOptions: @escaping (UIView) -> Void,
|
||||||
presentSuggestPostOptions: @escaping () -> Void,
|
presentSuggestPostOptions: @escaping () -> Void,
|
||||||
shareSelectedMessages: @escaping () -> Void,
|
shareSelectedMessages: @escaping () -> Void,
|
||||||
updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void,
|
updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void,
|
||||||
|
|
@ -306,6 +309,9 @@ public final class ChatPanelInterfaceInteraction {
|
||||||
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
|
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
|
||||||
dismissAllTooltips: @escaping () -> Void,
|
dismissAllTooltips: @escaping () -> Void,
|
||||||
editTodoMessage: @escaping (MessageId, Int32?, Bool) -> Void,
|
editTodoMessage: @escaping (MessageId, Int32?, Bool) -> Void,
|
||||||
|
dismissUrlPreview: @escaping () -> Void,
|
||||||
|
dismissForwardMessages: @escaping () -> Void,
|
||||||
|
dismissSuggestPost: @escaping () -> Void,
|
||||||
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
|
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
|
||||||
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
|
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
|
||||||
toggleChatSidebarMode: @escaping () -> Void,
|
toggleChatSidebarMode: @escaping () -> Void,
|
||||||
|
|
@ -428,6 +434,9 @@ public final class ChatPanelInterfaceInteraction {
|
||||||
self.updateRecordingTrimRange = updateRecordingTrimRange
|
self.updateRecordingTrimRange = updateRecordingTrimRange
|
||||||
self.dismissAllTooltips = dismissAllTooltips
|
self.dismissAllTooltips = dismissAllTooltips
|
||||||
self.editTodoMessage = editTodoMessage
|
self.editTodoMessage = editTodoMessage
|
||||||
|
self.dismissUrlPreview = dismissUrlPreview
|
||||||
|
self.dismissForwardMessages = dismissForwardMessages
|
||||||
|
self.dismissSuggestPost = dismissSuggestPost
|
||||||
self.updateHistoryFilter = updateHistoryFilter
|
self.updateHistoryFilter = updateHistoryFilter
|
||||||
self.updateChatLocationThread = updateChatLocationThread
|
self.updateChatLocationThread = updateChatLocationThread
|
||||||
self.toggleChatSidebarMode = toggleChatSidebarMode
|
self.toggleChatSidebarMode = toggleChatSidebarMode
|
||||||
|
|
@ -559,6 +568,9 @@ public final class ChatPanelInterfaceInteraction {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
}, editTodoMessage: { _, _, _ in
|
}, editTodoMessage: { _, _, _ in
|
||||||
|
}, dismissUrlPreview: {
|
||||||
|
}, dismissForwardMessages: {
|
||||||
|
}, dismissSuggestPost: {
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
|
|
||||||
|
|
@ -1304,4 +1304,20 @@ public struct ComponentTransition {
|
||||||
completion: completion
|
completion: completion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func animateBlur(layer: CALayer, fromRadius: CGFloat, toRadius: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||||
|
if let blurFilter = CALayer.blur() {
|
||||||
|
blurFilter.setValue(toRadius as NSNumber, forKey: "inputRadius")
|
||||||
|
layer.filters = [blurFilter]
|
||||||
|
layer.animate(from: fromRadius as NSNumber, to: toRadius as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.3, removeOnCompletion: removeOnCompletion, completion: { [weak layer] flag in
|
||||||
|
if let layer {
|
||||||
|
if toRadius <= 0.0 {
|
||||||
|
layer.filters = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completion?(flag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ public final class RoundedRectangle: Component {
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: imageSize).insetBy(dx: stroke, dy: stroke))
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: imageSize).insetBy(dx: stroke, dy: stroke))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.image = UIGraphicsGetImageFromCurrentImageContext()?.stretchableImage(withLeftCapWidth: Int(cornerRadius), topCapHeight: Int(cornerRadius))
|
self.image = UIGraphicsGetImageFromCurrentImageContext()?.stretchableImage(withLeftCapWidth: Int(cornerRadius), topCapHeight: Int(cornerRadius))
|
||||||
UIGraphicsEndImageContext()
|
UIGraphicsEndImageContext()
|
||||||
} else if component.colors.count > 1 {
|
} else if component.colors.count > 1 {
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ public final class NavigationBackgroundNode: ASDisplayNode {
|
||||||
|
|
||||||
private var enableBlur: Bool
|
private var enableBlur: Bool
|
||||||
private var enableSaturation: Bool
|
private var enableSaturation: Bool
|
||||||
|
private var customBlurRadius: CGFloat?
|
||||||
|
|
||||||
public var effectView: UIVisualEffectView?
|
public var effectView: UIVisualEffectView?
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
|
|
@ -164,10 +165,11 @@ public final class NavigationBackgroundNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true) {
|
public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true, customBlurRadius: CGFloat? = nil) {
|
||||||
self._color = .clear
|
self._color = .clear
|
||||||
self.enableBlur = enableBlur
|
self.enableBlur = enableBlur
|
||||||
self.enableSaturation = enableSaturation
|
self.enableSaturation = enableSaturation
|
||||||
|
self.customBlurRadius = customBlurRadius
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
|
||||||
|
|
@ -222,6 +224,9 @@ public final class NavigationBackgroundNode: ASDisplayNode {
|
||||||
if !allowedKeys.contains(filterName) {
|
if !allowedKeys.contains(filterName) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" {
|
||||||
|
filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius")
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 213
|
return 214
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
|
|
||||||
|
|
@ -948,9 +948,9 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
|
||||||
panelControlDestructiveColor: UIColor(rgb: 0xff3b30),
|
panelControlDestructiveColor: UIColor(rgb: 0xff3b30),
|
||||||
inputBackgroundColor: UIColor(rgb: 0xffffff),
|
inputBackgroundColor: UIColor(rgb: 0xffffff),
|
||||||
inputStrokeColor: UIColor(rgb: 0x000000, alpha: 0.1),
|
inputStrokeColor: UIColor(rgb: 0x000000, alpha: 0.1),
|
||||||
inputPlaceholderColor: UIColor(rgb: 0xbebec0),
|
inputPlaceholderColor: UIColor(rgb: 0x909090, alpha: 0.7),
|
||||||
inputTextColor: UIColor(rgb: 0x000000),
|
inputTextColor: UIColor(rgb: 0x000000),
|
||||||
inputControlColor: UIColor(rgb: 0x868D98),
|
inputControlColor: UIColor(rgb: 0x202020, alpha: 0.6),
|
||||||
actionControlFillColor: defaultDayAccentColor,
|
actionControlFillColor: defaultDayAccentColor,
|
||||||
actionControlForegroundColor: UIColor(rgb: 0xffffff),
|
actionControlForegroundColor: UIColor(rgb: 0xffffff),
|
||||||
primaryTextColor: UIColor(rgb: 0x000000),
|
primaryTextColor: UIColor(rgb: 0x000000),
|
||||||
|
|
|
||||||
|
|
@ -504,19 +504,19 @@ public struct PresentationResourcesChat {
|
||||||
|
|
||||||
public static func chatInputPanelAttachmentButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputPanelAttachmentButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputPanelAttachmentButtonImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputPanelAttachmentButtonImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconAttachment"), color: theme.chat.inputPanel.panelControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconAttachment"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputPanelEditAttachmentButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputPanelEditAttachmentButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputPanelEditAttachmentButtonImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputPanelEditAttachmentButtonImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/Replace"), color: theme.chat.inputPanel.panelControlAccentColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/Replace"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputPanelExpandButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputPanelExpandButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputPanelExpandButtonImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputPanelExpandButtonImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconExpandInput"), color: theme.chat.inputPanel.panelControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconExpandInput"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -534,53 +534,53 @@ public struct PresentationResourcesChat {
|
||||||
|
|
||||||
public static func chatInputTextFieldStickersImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldStickersImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldStickersImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldStickersImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconStickers"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconStickers"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldInputButtonsImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldInputButtonsImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldInputButtonsImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldInputButtonsImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconInputButtons"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconInputButtons"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldCommandsImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldCommandsImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldCommandsImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldCommandsImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconCommands"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconCommands"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldSilentPostOnImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldSilentPostOnImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldSilentPostOnImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldSilentPostOnImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSilentPostOn"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSilentPostOn"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldSilentPostOffImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldSilentPostOffImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldSilentPostOffImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldSilentPostOffImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSilentPostOff"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSilentPostOff"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldSuggestPostImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldSuggestPostImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldSuggestPostImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldSuggestPostImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSuggestPost"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSuggestPost"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldKeyboardImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldKeyboardImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldKeyboardImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldKeyboardImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconKeyboard"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconKeyboard"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldTimerImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldTimerImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldTimerImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldTimerImage.rawValue, { theme in
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconTimer"), color: theme.chat.inputPanel.inputControlColor) {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconTimer"), color: .white) {
|
||||||
return generateImage(CGSize(width: image.size.width, height: image.size.height + 1.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: image.size.width, height: image.size.height + 1.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: image.size))
|
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: image.size))
|
||||||
})
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -589,13 +589,13 @@ public struct PresentationResourcesChat {
|
||||||
|
|
||||||
public static func chatInputTextFieldScheduleImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldScheduleImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldScheduleImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldScheduleImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSchedule"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSchedule"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func chatInputTextFieldGiftImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatInputTextFieldGiftImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputTextFieldGiftImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputTextFieldGiftImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconGift"), color: theme.chat.inputPanel.inputControlColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconGift"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -616,7 +616,7 @@ public struct PresentationResourcesChat {
|
||||||
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor)
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
context.setLineWidth(1.5)
|
context.setLineWidth(1.5)
|
||||||
|
|
||||||
let position = CGPoint(x: 9.0 - 0.5, y: 23.0)
|
let position = CGPoint(x: 9.0 - 0.5, y: 23.0)
|
||||||
|
|
@ -624,7 +624,7 @@ public struct PresentationResourcesChat {
|
||||||
context.addLine(to: CGPoint(x: position.x + 10.0, y: position.y - 10.0))
|
context.addLine(to: CGPoint(x: position.x + 10.0, y: position.y - 10.0))
|
||||||
context.addLine(to: CGPoint(x: position.x + 19.0, y: position.y - 1.0))
|
context.addLine(to: CGPoint(x: position.x + 19.0, y: position.y - 1.0))
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
})
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -633,7 +633,7 @@ public struct PresentationResourcesChat {
|
||||||
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor)
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
context.setLineWidth(1.5)
|
context.setLineWidth(1.5)
|
||||||
|
|
||||||
context.translateBy(x: size.width * 0.5, y: size.height * 0.5)
|
context.translateBy(x: size.width * 0.5, y: size.height * 0.5)
|
||||||
|
|
@ -644,7 +644,7 @@ public struct PresentationResourcesChat {
|
||||||
context.addLine(to: CGPoint(x: position.x + 10.0, y: position.y - 10.0))
|
context.addLine(to: CGPoint(x: position.x + 10.0, y: position.y - 10.0))
|
||||||
context.addLine(to: CGPoint(x: position.x + 19.0, y: position.y - 1.0))
|
context.addLine(to: CGPoint(x: position.x + 19.0, y: position.y - 1.0))
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
})
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -653,10 +653,10 @@ public struct PresentationResourcesChat {
|
||||||
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigateToMentions"), color: theme.rootController.navigationBar.accentTextColor), let cgImage = image.cgImage {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigateToMentions"), color: UIColor.white), let cgImage = image.cgImage {
|
||||||
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
||||||
}
|
}
|
||||||
})
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -665,10 +665,10 @@ public struct PresentationResourcesChat {
|
||||||
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.rootController.navigationBar.accentTextColor), let cgImage = image.cgImage {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: UIColor.white), let cgImage = image.cgImage {
|
||||||
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
||||||
}
|
}
|
||||||
})
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,9 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/FaceScanScreen",
|
"//submodules/TelegramUI/Components/FaceScanScreen",
|
||||||
"//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist",
|
"//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist",
|
||||||
"//submodules/ContactsHelper",
|
"//submodules/ContactsHelper",
|
||||||
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatInputAccessoryPanel",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
"//build-system:ios_sim_arm64": [],
|
"//build-system:ios_sim_arm64": [],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ChatInputAccessoryPanel",
|
||||||
|
module_name = "ChatInputAccessoryPanel",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/TelegramUIPreferences",
|
||||||
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import TelegramUIPreferences
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
|
||||||
|
public final class ChatInputAccessoryPanelEnvironment: Equatable {
|
||||||
|
public let theme: PresentationTheme
|
||||||
|
public let strings: PresentationStrings
|
||||||
|
public let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
|
public let dateTimeFormat: PresentationDateTimeFormat
|
||||||
|
|
||||||
|
public init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
strings: PresentationStrings,
|
||||||
|
nameDisplayOrder: PresentationPersonNameOrder,
|
||||||
|
dateTimeFormat: PresentationDateTimeFormat
|
||||||
|
) {
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
|
self.dateTimeFormat = dateTimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: ChatInputAccessoryPanelEnvironment, rhs: ChatInputAccessoryPanelEnvironment) -> Bool {
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.dateTimeFormat != rhs.dateTimeFormat {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ChatInputAccessoryPanelView: UIView {
|
||||||
|
var contentTintView: UIView { get }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ChatInputMessageAccessoryPanel",
|
||||||
|
module_name = "ChatInputMessageAccessoryPanel",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatInputAccessoryPanel",
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||||
|
"//submodules/TelegramStringFormatting",
|
||||||
|
"//submodules/PhotoResources",
|
||||||
|
"//submodules/TextFormat",
|
||||||
|
"//submodules/TelegramUI/Components/CompositeTextNode",
|
||||||
|
"//submodules/ChatInterfaceState",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,911 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ChatInputAccessoryPanel
|
||||||
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
|
import SwiftSignalKit
|
||||||
|
import ComponentFlow
|
||||||
|
import Display
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import MultilineTextWithEntitiesComponent
|
||||||
|
import TelegramStringFormatting
|
||||||
|
import PhotoResources
|
||||||
|
import TextFormat
|
||||||
|
import CompositeTextNode
|
||||||
|
import ChatInterfaceState
|
||||||
|
|
||||||
|
private func generateCloseIcon() -> UIImage {
|
||||||
|
return generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
|
context.setLineWidth(2.0)
|
||||||
|
context.setLineCap(.round)
|
||||||
|
context.move(to: CGPoint(x: 1.0, y: 1.0))
|
||||||
|
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
|
||||||
|
context.strokePath()
|
||||||
|
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
|
||||||
|
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
|
||||||
|
context.strokePath()
|
||||||
|
})!.withRenderingMode(.alwaysTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func textStringForForwardedMessage(_ message: EngineMessage, strings: PresentationStrings) -> (text: String, entities: [MessageTextEntity], isMedia: Bool) {
|
||||||
|
for media in message.media {
|
||||||
|
switch media {
|
||||||
|
case _ as TelegramMediaImage:
|
||||||
|
return (strings.Message_Photo, [], true)
|
||||||
|
case let file as TelegramMediaFile:
|
||||||
|
if file.isVideoSticker || file.isAnimatedSticker {
|
||||||
|
return (strings.Message_Sticker, [], true)
|
||||||
|
}
|
||||||
|
var fileName: String = strings.Message_File
|
||||||
|
for attribute in file.attributes {
|
||||||
|
switch attribute {
|
||||||
|
case .Sticker:
|
||||||
|
return (strings.Message_Sticker, [], true)
|
||||||
|
case let .FileName(name):
|
||||||
|
fileName = name
|
||||||
|
case let .Audio(isVoice, _, title, performer, _):
|
||||||
|
if isVoice {
|
||||||
|
return (strings.Message_Audio, [], true)
|
||||||
|
} else {
|
||||||
|
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
|
||||||
|
return (title + " — " + performer, [], true)
|
||||||
|
} else if let title = title, !title.isEmpty {
|
||||||
|
return (title, [], true)
|
||||||
|
} else if let performer = performer, !performer.isEmpty {
|
||||||
|
return (performer, [], true)
|
||||||
|
} else {
|
||||||
|
return (strings.Message_Audio, [], true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .Video:
|
||||||
|
if file.isAnimated {
|
||||||
|
return (strings.Message_Animation, [], true)
|
||||||
|
} else {
|
||||||
|
return (strings.Message_Video, [], true)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (fileName, [], true)
|
||||||
|
case _ as TelegramMediaContact:
|
||||||
|
return (strings.Message_Contact, [], true)
|
||||||
|
case let game as TelegramMediaGame:
|
||||||
|
return (game.title, [], true)
|
||||||
|
case _ as TelegramMediaMap:
|
||||||
|
return (strings.Message_Location, [], true)
|
||||||
|
case _ as TelegramMediaAction:
|
||||||
|
return ("", [], true)
|
||||||
|
case _ as TelegramMediaPoll:
|
||||||
|
return (strings.ForwardedPolls(1), [], true)
|
||||||
|
case let todo as TelegramMediaTodo:
|
||||||
|
return (todo.text, [], true)
|
||||||
|
case let dice as TelegramMediaDice:
|
||||||
|
return (dice.emoji, [], true)
|
||||||
|
case let invoice as TelegramMediaInvoice:
|
||||||
|
return (invoice.title, [], true)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (message.text, message._asMessage().textEntitiesAttribute?.entities ?? [], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ChatInputMessageAccessoryPanel: Component {
|
||||||
|
public typealias EnvironmentType = ChatInputAccessoryPanelEnvironment
|
||||||
|
|
||||||
|
public enum Contents: Equatable {
|
||||||
|
public final class Reply: Equatable {
|
||||||
|
public let id: EngineMessage.Id
|
||||||
|
public let quote: EngineMessageReplyQuote?
|
||||||
|
public let todoItemId: Int32?
|
||||||
|
public let message: EngineMessage?
|
||||||
|
|
||||||
|
public init(id: EngineMessage.Id, quote: EngineMessageReplyQuote?, todoItemId: Int32?, message: EngineMessage?) {
|
||||||
|
self.id = id
|
||||||
|
self.quote = quote
|
||||||
|
self.todoItemId = todoItemId
|
||||||
|
self.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Reply, rhs: Reply) -> Bool {
|
||||||
|
if lhs.id != rhs.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.quote != rhs.quote {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.todoItemId != rhs.todoItemId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.message?.id != rhs.message?.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.message?.stableVersion != rhs.message?.stableVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class Edit: Equatable {
|
||||||
|
public let id: EngineMessage.Id
|
||||||
|
public let message: EngineMessage?
|
||||||
|
|
||||||
|
public init(id: EngineMessage.Id, message: EngineMessage?) {
|
||||||
|
self.id = id
|
||||||
|
self.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Edit, rhs: Edit) -> Bool {
|
||||||
|
if lhs.id != rhs.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.message?.id != rhs.message?.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.message?.stableVersion != rhs.message?.stableVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class Forward: Equatable {
|
||||||
|
public let messageIds: [EngineMessage.Id]
|
||||||
|
public let forwardOptionsState: ChatInterfaceForwardOptionsState?
|
||||||
|
|
||||||
|
public init(messageIds: [EngineMessage.Id], forwardOptionsState: ChatInterfaceForwardOptionsState?) {
|
||||||
|
self.messageIds = messageIds
|
||||||
|
self.forwardOptionsState = forwardOptionsState
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Forward, rhs: Forward) -> Bool {
|
||||||
|
if lhs.messageIds != rhs.messageIds {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.forwardOptionsState != rhs.forwardOptionsState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class LinkPreview: Equatable {
|
||||||
|
public let url: String
|
||||||
|
public let webpage: TelegramMediaWebpage
|
||||||
|
|
||||||
|
public init(url: String, webpage: TelegramMediaWebpage) {
|
||||||
|
self.url = url
|
||||||
|
self.webpage = webpage
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: LinkPreview, rhs: LinkPreview) -> Bool {
|
||||||
|
if lhs.url != rhs.url {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.webpage != rhs.webpage {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class SuggestPost: Equatable {
|
||||||
|
public let state: ChatInterfaceState.PostSuggestionState
|
||||||
|
|
||||||
|
public init(state: ChatInterfaceState.PostSuggestionState) {
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: SuggestPost, rhs: SuggestPost) -> Bool {
|
||||||
|
if lhs.state != rhs.state {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reply(Reply)
|
||||||
|
case edit(Edit)
|
||||||
|
case forward(Forward)
|
||||||
|
case linkPreview(LinkPreview)
|
||||||
|
case suggestPost(SuggestPost)
|
||||||
|
}
|
||||||
|
|
||||||
|
let context: AccountContext
|
||||||
|
let contents: Contents
|
||||||
|
let chatPeerId: EnginePeer.Id?
|
||||||
|
let action: ((UIView) -> Void)?
|
||||||
|
let dismiss: (UIView) -> Void
|
||||||
|
|
||||||
|
public init(
|
||||||
|
context: AccountContext,
|
||||||
|
contents: Contents,
|
||||||
|
chatPeerId: EnginePeer.Id?,
|
||||||
|
action: ((UIView) -> Void)?,
|
||||||
|
dismiss: @escaping (UIView) -> Void
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.contents = contents
|
||||||
|
self.chatPeerId = chatPeerId
|
||||||
|
self.action = action
|
||||||
|
self.dismiss = dismiss
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: ChatInputMessageAccessoryPanel, rhs: ChatInputMessageAccessoryPanel) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.contents != rhs.contents {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.chatPeerId != rhs.chatPeerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (lhs.action == nil) != (rhs.action == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView, ChatInputAccessoryPanelView {
|
||||||
|
private let closeButton: HighlightTrackingButton
|
||||||
|
private let closeButtonIcon: GlassBackgroundView.ContentImageView
|
||||||
|
|
||||||
|
private let lineView: UIImageView
|
||||||
|
private let titleNode: CompositeTextNode
|
||||||
|
private let text = ComponentView<Empty>()
|
||||||
|
private let tintText = ComponentView<Empty>()
|
||||||
|
|
||||||
|
public let contentTintView: UIView
|
||||||
|
|
||||||
|
private var isUpdating: Bool = false
|
||||||
|
private var component: ChatInputMessageAccessoryPanel?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
private var environment: EnvironmentType?
|
||||||
|
|
||||||
|
private var messages: [EngineMessage] = []
|
||||||
|
private var contentDisposable: Disposable?
|
||||||
|
|
||||||
|
private var inlineTextStarImage: UIImage?
|
||||||
|
private var inlineTextTonImage: (UIImage, UIColor)?
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.contentTintView = UIView()
|
||||||
|
|
||||||
|
self.closeButton = HighlightTrackingButton()
|
||||||
|
self.closeButtonIcon = GlassBackgroundView.ContentImageView()
|
||||||
|
|
||||||
|
self.lineView = UIImageView()
|
||||||
|
self.titleNode = CompositeTextNode()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.lineView)
|
||||||
|
self.addSubview(self.titleNode.view)
|
||||||
|
|
||||||
|
self.addSubview(self.closeButtonIcon)
|
||||||
|
self.contentTintView.addSubview(self.closeButtonIcon.tintMask)
|
||||||
|
self.addSubview(self.closeButton)
|
||||||
|
|
||||||
|
self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), for: .touchUpInside)
|
||||||
|
|
||||||
|
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.contentDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
component.action?(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func closeButtonPressed() {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.dismiss(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(component: ChatInputMessageAccessoryPanel, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.isUpdating = true
|
||||||
|
defer {
|
||||||
|
self.isUpdating = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let environment = environment[EnvironmentType.self].value
|
||||||
|
|
||||||
|
if self.component == nil {
|
||||||
|
let messageIds: [EngineMessage.Id]
|
||||||
|
switch component.contents {
|
||||||
|
case let .edit(edit):
|
||||||
|
messageIds = [edit.id]
|
||||||
|
case let .reply(reply):
|
||||||
|
messageIds = [reply.id]
|
||||||
|
case let .forward(forward):
|
||||||
|
messageIds = forward.messageIds
|
||||||
|
case .linkPreview, .suggestPost:
|
||||||
|
messageIds = []
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contentDisposable?.dispose()
|
||||||
|
if !messageIds.isEmpty {
|
||||||
|
self.contentDisposable = (component.context.engine.data.subscribe(
|
||||||
|
EngineDataList(messageIds.map { id in
|
||||||
|
return TelegramEngine.EngineData.Item.Messages.Message(id: id)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] messages in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.messages = messages.compactMap { $0 }
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .immediate, isLocal: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
self.environment = environment
|
||||||
|
|
||||||
|
if self.closeButtonIcon.image == nil {
|
||||||
|
self.closeButtonIcon.image = generateCloseIcon()
|
||||||
|
}
|
||||||
|
if self.lineView.image == nil {
|
||||||
|
self.lineView.image = generateImage(CGSize(width: 2.0, height: 3.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 1.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
})?.withRenderingMode(.alwaysTemplate).stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = CGSize(width: availableSize.width, height: 52.0)
|
||||||
|
|
||||||
|
let containerInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 6.0, right: 0.0)
|
||||||
|
|
||||||
|
let lineSize = CGSize(width: 2.0, height: size.height - containerInsets.top - containerInsets.bottom)
|
||||||
|
let lineFrame = CGRect(origin: CGPoint(x: containerInsets.left, y: containerInsets.top), size: lineSize)
|
||||||
|
transition.setFrame(view: self.lineView, frame: lineFrame)
|
||||||
|
self.lineView.tintColor = environment.theme.chat.inputPanel.panelControlAccentColor
|
||||||
|
|
||||||
|
let closeButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||||
|
let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - closeButtonSize.width, y: floor((size.height - closeButtonSize.height) * 0.5)), size: closeButtonSize)
|
||||||
|
transition.setFrame(view: self.closeButton, frame: closeButtonFrame)
|
||||||
|
|
||||||
|
if let image = self.closeButtonIcon.image {
|
||||||
|
let closeButtonIconFrame = image.size.centered(in: closeButtonFrame)
|
||||||
|
transition.setFrame(view: self.closeButtonIcon, frame: closeButtonIconFrame)
|
||||||
|
}
|
||||||
|
self.closeButtonIcon.tintColor = environment.theme.chat.inputPanel.inputControlColor
|
||||||
|
|
||||||
|
let secondaryTextColor = environment.theme.chat.inputPanel.inputControlColor.withMultipliedBrightnessBy(0.5)
|
||||||
|
|
||||||
|
var textString: NSAttributedString
|
||||||
|
var isPhoto = false
|
||||||
|
if self.messages.count == 1, let message = self.messages.first {
|
||||||
|
var text = ""
|
||||||
|
let effectiveMessage = message
|
||||||
|
//TODO:release media
|
||||||
|
/*if let currentEditMediaReference = self.currentEditMediaReference {
|
||||||
|
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
|
||||||
|
}*/
|
||||||
|
let (attributedText, _, _) = descriptionStringForMessage(
|
||||||
|
contentSettings: component.context.currentContentSettings.with { $0 },
|
||||||
|
message: effectiveMessage,
|
||||||
|
strings: environment.strings,
|
||||||
|
nameDisplayOrder: environment.nameDisplayOrder,
|
||||||
|
dateTimeFormat: environment.dateTimeFormat,
|
||||||
|
accountPeerId: component.context.account.peerId
|
||||||
|
)
|
||||||
|
text = attributedText.string
|
||||||
|
|
||||||
|
var updatedMediaReference: AnyMediaReference?
|
||||||
|
var imageDimensions: CGSize?
|
||||||
|
if !message._asMessage().containsSecretMedia {
|
||||||
|
var candidateMediaReference: AnyMediaReference?
|
||||||
|
for media in message.media {
|
||||||
|
if media is TelegramMediaImage || media is TelegramMediaFile {
|
||||||
|
candidateMediaReference = .message(message: MessageReference(message._asMessage()), media: media)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let imageReference = candidateMediaReference?.concrete(TelegramMediaImage.self) {
|
||||||
|
updatedMediaReference = imageReference.abstract
|
||||||
|
if let representation = largestRepresentationForPhoto(imageReference.media) {
|
||||||
|
imageDimensions = representation.dimensions.cgSize
|
||||||
|
}
|
||||||
|
} else if let fileReference = candidateMediaReference?.concrete(TelegramMediaFile.self) {
|
||||||
|
updatedMediaReference = fileReference.abstract
|
||||||
|
if !fileReference.media.isInstantVideo, let representation = largestImageRepresentation(fileReference.media.previewRepresentations), !fileReference.media.isSticker {
|
||||||
|
imageDimensions = representation.dimensions.cgSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*let imageNodeLayout = self.imageNode.asyncLayout()
|
||||||
|
var applyImage: (() -> Void)?
|
||||||
|
if let imageDimensions = imageDimensions {
|
||||||
|
let boundingSize = CGSize(width: 35.0, height: 35.0)
|
||||||
|
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
|
||||||
|
}
|
||||||
|
|
||||||
|
var mediaUpdated = false
|
||||||
|
if let updatedMediaReference = updatedMediaReference, let previousMediaReference = self.previousMediaReference {
|
||||||
|
mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media)
|
||||||
|
} else if (updatedMediaReference != nil) != (self.previousMediaReference != nil) {
|
||||||
|
mediaUpdated = true
|
||||||
|
}
|
||||||
|
self.previousMediaReference = updatedMediaReference*/
|
||||||
|
|
||||||
|
let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute })
|
||||||
|
|
||||||
|
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||||
|
let _ = updateImageSignal
|
||||||
|
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
|
||||||
|
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
|
||||||
|
updateImageSignal = chatMessagePhotoThumbnail(account: component.context.account, userLocation: MediaResourceUserLocation.peer(message.id.peerId), photoReference: imageReference, blurred: hasSpoiler)
|
||||||
|
isPhoto = true
|
||||||
|
} else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
|
||||||
|
if fileReference.media.isVideo {
|
||||||
|
updateImageSignal = chatMessageVideoThumbnail(account: component.context.account, userLocation: MediaResourceUserLocation.peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler)
|
||||||
|
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
|
||||||
|
updateImageSignal = chatWebpageSnippetFile(account: component.context.account, userLocation: MediaResourceUserLocation.peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateImageSignal = .single({ _ in return nil })
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMedia: Bool
|
||||||
|
let isText: Bool
|
||||||
|
/*if let currentEditMediaReference = self.currentEditMediaReference {
|
||||||
|
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
|
||||||
|
}*/
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
switch messageContentKind(contentSettings: component.context.currentContentSettings.with { $0 }, message: effectiveMessage, strings: environment.strings, nameDisplayOrder: environment.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: component.context.account.peerId) {
|
||||||
|
case .text:
|
||||||
|
isMedia = false
|
||||||
|
isText = true
|
||||||
|
default:
|
||||||
|
isMedia = true
|
||||||
|
isText = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFont = Font.regular(14.0)
|
||||||
|
let messageText: NSAttributedString
|
||||||
|
if isText {
|
||||||
|
let entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
|
||||||
|
switch entity.type {
|
||||||
|
case .Spoiler, .CustomEmoji:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let textColor = environment.theme.chat.inputPanel.primaryTextColor
|
||||||
|
if entities.count > 0 {
|
||||||
|
messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
|
||||||
|
} else {
|
||||||
|
messageText = NSAttributedString(string: text, font: textFont, textColor: isMedia ? secondaryTextColor : environment.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messageText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: isMedia ? secondaryTextColor : environment.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
}
|
||||||
|
textString = messageText
|
||||||
|
} else {
|
||||||
|
textString = NSAttributedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
var titleText: [CompositeTextNode.Component] = []
|
||||||
|
switch component.contents {
|
||||||
|
case .edit:
|
||||||
|
let canEditMedia: Bool
|
||||||
|
//TODO:release
|
||||||
|
/*if let message = self.message, !messageMediaEditingOptions(message: message).isEmpty {
|
||||||
|
canEditMedia = true
|
||||||
|
} else {
|
||||||
|
canEditMedia = false
|
||||||
|
}*/
|
||||||
|
canEditMedia = !"".isEmpty
|
||||||
|
|
||||||
|
let titleStringValue: String
|
||||||
|
if let message = self.messages.first, message.id.namespace == Namespaces.Message.QuickReplyCloud {
|
||||||
|
titleStringValue = environment.strings.Conversation_EditingQuickReplyPanelTitle
|
||||||
|
} else if canEditMedia {
|
||||||
|
titleStringValue = isPhoto ? environment.strings.Conversation_EditingPhotoPanelTitle : environment.strings.Conversation_EditingCaptionPanelTitle
|
||||||
|
} else {
|
||||||
|
titleStringValue = environment.strings.Conversation_EditingMessagePanelTitle
|
||||||
|
}
|
||||||
|
titleText = [.text(NSAttributedString(string: titleStringValue, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
|
||||||
|
case let .reply(reply):
|
||||||
|
if let peer = self.messages.first?.peers[reply.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
let icon: UIImage?
|
||||||
|
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon"), color: environment.theme.chat.inputPanel.panelControlAccentColor)
|
||||||
|
|
||||||
|
if let icon {
|
||||||
|
let rawString: PresentationStrings.FormattedString
|
||||||
|
if reply.quote != nil {
|
||||||
|
rawString = environment.strings.Chat_ReplyPanel_ReplyToQuoteBy(peer.debugDisplayTitle)
|
||||||
|
} else {
|
||||||
|
rawString = environment.strings.Chat_ReplyPanel_ReplyTo(peer.debugDisplayTitle)
|
||||||
|
}
|
||||||
|
if let nameRange = rawString.ranges.first {
|
||||||
|
titleText = []
|
||||||
|
|
||||||
|
let rawNsString = rawString.string as NSString
|
||||||
|
if nameRange.range.lowerBound != 0 {
|
||||||
|
titleText.append(.text(NSAttributedString(string: rawNsString.substring(with: NSRange(location: 0, length: nameRange.range.lowerBound)), font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
|
||||||
|
}
|
||||||
|
titleText.append(.icon(icon))
|
||||||
|
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
|
||||||
|
|
||||||
|
if nameRange.range.upperBound != rawNsString.length {
|
||||||
|
titleText.append(.text(NSAttributedString(string: rawNsString.substring(with: NSRange(location: nameRange.range.upperBound, length: rawNsString.length - nameRange.range.upperBound)), font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
titleText.append(.text(NSAttributedString(string: rawString.string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var authorName = ""
|
||||||
|
if let forwardInfo = self.messages.first?._asMessage().forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||||
|
if let author = forwardInfo.author {
|
||||||
|
authorName = EnginePeer(author).displayTitle(strings: environment.strings, displayOrder: environment.nameDisplayOrder)
|
||||||
|
} else if let authorSignature = forwardInfo.authorSignature {
|
||||||
|
authorName = authorSignature
|
||||||
|
}
|
||||||
|
} else if let author = self.messages.first?._asMessage().effectiveAuthor {
|
||||||
|
authorName = EnginePeer(author).displayTitle(strings: environment.strings, displayOrder: environment.nameDisplayOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = reply.todoItemId {
|
||||||
|
let string = environment.strings.Chat_ReplyPanel_ReplyToTodoItem
|
||||||
|
titleText = [.text(NSAttributedString(string: string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
|
||||||
|
} else if let _ = reply.quote {
|
||||||
|
let string = environment.strings.Chat_ReplyPanel_ReplyToQuoteBy(authorName).string
|
||||||
|
titleText = [.text(NSAttributedString(string: string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
|
||||||
|
} else {
|
||||||
|
let string = environment.strings.Conversation_ReplyMessagePanelTitle(authorName).string
|
||||||
|
titleText = [.text(NSAttributedString(string: string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply.id.peerId != component.chatPeerId {
|
||||||
|
if let peer = self.messages.first?.peers[reply.id.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
|
||||||
|
let icon: UIImage?
|
||||||
|
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
|
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon")
|
||||||
|
} else {
|
||||||
|
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextGroupIcon")
|
||||||
|
}
|
||||||
|
if let iconImage = generateTintedImage(image: icon, color: environment.theme.chat.inputPanel.panelControlAccentColor) {
|
||||||
|
titleText.append(.icon(iconImage))
|
||||||
|
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let message = self.messages.first {
|
||||||
|
let textFont = Font.regular(14.0)
|
||||||
|
if let quote = reply.quote {
|
||||||
|
let textColor = environment.theme.chat.inputPanel.primaryTextColor
|
||||||
|
textString = stringWithAppliedEntities(trimToLineCount(quote.text, lineCount: 1), entities: quote.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
|
||||||
|
} else if let todoItemId = reply.todoItemId, let todo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo, let todoItem = todo.items.first(where: { $0.id == todoItemId }) {
|
||||||
|
let textColor = environment.theme.chat.inputPanel.primaryTextColor
|
||||||
|
textString = stringWithAppliedEntities(trimToLineCount(todoItem.text, lineCount: 1), entities: todoItem.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .forward(forward):
|
||||||
|
var title = ""
|
||||||
|
var authors = ""
|
||||||
|
var uniquePeerIds = Set<EnginePeer.Id>()
|
||||||
|
var text = NSMutableAttributedString(string: "")
|
||||||
|
|
||||||
|
for message in self.messages {
|
||||||
|
if let author = message.forwardInfo?.author ?? message._asMessage().effectiveAuthor, !uniquePeerIds.contains(author.id) {
|
||||||
|
uniquePeerIds.insert(author.id)
|
||||||
|
if !authors.isEmpty {
|
||||||
|
authors.append(", ")
|
||||||
|
}
|
||||||
|
if author.id == component.context.account.peerId {
|
||||||
|
authors.append(environment.strings.DialogList_You)
|
||||||
|
} else {
|
||||||
|
authors.append(EnginePeer(author).compactDisplayTitle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.messages.count == 1 {
|
||||||
|
title = environment.strings.Conversation_ForwardOptions_ForwardTitleSingle
|
||||||
|
let (string, entities, _) = textStringForForwardedMessage(messages[0], strings: environment.strings)
|
||||||
|
|
||||||
|
text = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(authors): ", font: Font.regular(14.0), textColor: secondaryTextColor))
|
||||||
|
|
||||||
|
let additionalText = NSMutableAttributedString(attributedString: NSAttributedString(string: string, font: Font.regular(14.0), textColor: secondaryTextColor))
|
||||||
|
for entity in entities {
|
||||||
|
switch entity.type {
|
||||||
|
case let .CustomEmoji(_, fileId):
|
||||||
|
let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
|
||||||
|
if range.lowerBound >= 0 && range.upperBound <= additionalText.length {
|
||||||
|
additionalText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: messages[0].associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: range)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append(additionalText)
|
||||||
|
} else {
|
||||||
|
title = environment.strings.Conversation_ForwardOptions_ForwardTitle(Int32(messages.count))
|
||||||
|
text = NSMutableAttributedString(attributedString: NSAttributedString(string: environment.strings.Conversation_ForwardFrom(authors).string, font: Font.regular(14.0), textColor: secondaryTextColor))
|
||||||
|
}
|
||||||
|
|
||||||
|
if forward.forwardOptionsState?.hideNames == true {
|
||||||
|
text = NSMutableAttributedString(attributedString: NSAttributedString(string: environment.strings.Conversation_ForwardOptions_SenderNamesRemoved, font: Font.regular(14.0), textColor: secondaryTextColor))
|
||||||
|
}
|
||||||
|
|
||||||
|
titleText = [.text(NSAttributedString(string: title, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
|
||||||
|
textString = text
|
||||||
|
case let .linkPreview(linkPreview):
|
||||||
|
var authorName = ""
|
||||||
|
var text = ""
|
||||||
|
switch linkPreview.webpage.content {
|
||||||
|
case .Pending:
|
||||||
|
authorName = environment.strings.Channel_NotificationLoading
|
||||||
|
text = linkPreview.url
|
||||||
|
case let .Loaded(content):
|
||||||
|
if let contentText = content.text {
|
||||||
|
text = contentText
|
||||||
|
} else {
|
||||||
|
if let file = content.file, let mediaKind = mediaContentKind(EngineMedia(file)) {
|
||||||
|
if content.type == "telegram_background" {
|
||||||
|
text = environment.strings.Message_Wallpaper
|
||||||
|
} else if content.type == "telegram_theme" {
|
||||||
|
text = environment.strings.Message_Theme
|
||||||
|
} else {
|
||||||
|
text = stringForMediaKind(mediaKind, strings: environment.strings).0.string
|
||||||
|
}
|
||||||
|
} else if content.type == "telegram_theme" {
|
||||||
|
text = environment.strings.Message_Theme
|
||||||
|
} else if content.type == "video" {
|
||||||
|
text = stringForMediaKind(.video, strings: environment.strings).0.string
|
||||||
|
} else if content.type == "telegram_story" {
|
||||||
|
text = stringForMediaKind(.story, strings: environment.strings).0.string
|
||||||
|
} else if let _ = content.image {
|
||||||
|
text = stringForMediaKind(.image, strings: environment.strings).0.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let title = content.title {
|
||||||
|
authorName = title
|
||||||
|
} else if let websiteName = content.websiteName {
|
||||||
|
authorName = websiteName
|
||||||
|
} else {
|
||||||
|
authorName = content.displayUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
titleText = [.text(NSAttributedString(string: authorName, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
|
||||||
|
textString = NSAttributedString(string: text, font: Font.regular(14.0), textColor: environment.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
case let .suggestPost(suggestPost):
|
||||||
|
if suggestPost.state.editingOriginalMessageId != nil {
|
||||||
|
titleText.append(.text(NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputEditTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
|
||||||
|
} else {
|
||||||
|
titleText.append(.text(NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFont = Font.regular(14.0)
|
||||||
|
|
||||||
|
if let price = suggestPost.state.price, price.amount != .zero {
|
||||||
|
let currencySymbol: String
|
||||||
|
let amountString: String
|
||||||
|
switch price.currency {
|
||||||
|
case .stars:
|
||||||
|
currencySymbol = "#"
|
||||||
|
amountString = "\(price.amount)"
|
||||||
|
case .ton:
|
||||||
|
currencySymbol = "$"
|
||||||
|
amountString = formatTonAmountText(price.amount.value, dateTimeFormat: environment.dateTimeFormat)
|
||||||
|
}
|
||||||
|
if let timestamp = suggestPost.state.timestamp {
|
||||||
|
let timeString = humanReadableStringForTimestamp(strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, timestamp: timestamp, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat(
|
||||||
|
dateFormatString: { value in
|
||||||
|
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_Date(value).string, ranges: [])
|
||||||
|
},
|
||||||
|
tomorrowFormatString: { value in
|
||||||
|
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: [])
|
||||||
|
},
|
||||||
|
todayFormatString: { value in
|
||||||
|
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: [])
|
||||||
|
},
|
||||||
|
yesterdayFormatString: { value in
|
||||||
|
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: [])
|
||||||
|
}
|
||||||
|
)).string
|
||||||
|
textString = NSAttributedString(string: "\(currencySymbol)\(amountString) 📅 \(timeString)", font: textFont, textColor: environment.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
} else {
|
||||||
|
textString = NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputSubtitleAnytime("\(currencySymbol)\(amountString)").string, font: textFont, textColor: environment.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textString = NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputSubtitleEmpty, font: textFont, textColor: environment.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mutableTextString = NSMutableAttributedString(attributedString: textString)
|
||||||
|
for currency in [.stars, .ton] as [CurrencyAmount.Currency] {
|
||||||
|
var inlineTextStarImage: UIImage?
|
||||||
|
if let current = self.inlineTextStarImage {
|
||||||
|
inlineTextStarImage = current
|
||||||
|
} else {
|
||||||
|
if let image = UIImage(bundleImageName: "Premium/Stars/StarSmall") {
|
||||||
|
let starInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||||
|
inlineTextStarImage = generateImage(CGSize(width: starInsets.left + image.size.width + starInsets.right, height: image.size.height), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
defer {
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
image.draw(at: CGPoint(x: starInsets.left, y: starInsets.top))
|
||||||
|
})?.withRenderingMode(.alwaysOriginal)
|
||||||
|
self.inlineTextStarImage = inlineTextStarImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inlineTextTonImage: UIImage?
|
||||||
|
if let current = self.inlineTextTonImage, current.1 == environment.theme.list.itemAccentColor {
|
||||||
|
inlineTextTonImage = current.0
|
||||||
|
} else {
|
||||||
|
if let image = UIImage(bundleImageName: "Ads/TonMedium") {
|
||||||
|
let tonInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||||
|
let inlineTextTonImageValue = generateTintedImage(image: generateImage(CGSize(width: tonInsets.left + image.size.width + tonInsets.right, height: image.size.height), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
defer {
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
image.draw(at: CGPoint(x: tonInsets.left, y: tonInsets.top))
|
||||||
|
}), color: environment.theme.list.itemAccentColor)!.withRenderingMode(.alwaysOriginal)
|
||||||
|
inlineTextTonImage = inlineTextTonImageValue
|
||||||
|
self.inlineTextTonImage = (inlineTextTonImageValue, environment.theme.list.itemAccentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currencySymbol: String
|
||||||
|
let currencyImage: UIImage?
|
||||||
|
switch currency {
|
||||||
|
case .stars:
|
||||||
|
currencySymbol = "#"
|
||||||
|
currencyImage = inlineTextStarImage
|
||||||
|
case .ton:
|
||||||
|
currencySymbol = "$"
|
||||||
|
currencyImage = inlineTextTonImage
|
||||||
|
}
|
||||||
|
|
||||||
|
if let range = mutableTextString.string.range(of: currencySymbol), let currencyImage {
|
||||||
|
final class RunDelegateData {
|
||||||
|
let ascent: CGFloat
|
||||||
|
let descent: CGFloat
|
||||||
|
let width: CGFloat
|
||||||
|
|
||||||
|
init(ascent: CGFloat, descent: CGFloat, width: CGFloat) {
|
||||||
|
self.ascent = ascent
|
||||||
|
self.descent = descent
|
||||||
|
self.width = width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let runDelegateData = RunDelegateData(
|
||||||
|
ascent: Font.regular(14.0).ascender,
|
||||||
|
descent: Font.regular(14.0).descender,
|
||||||
|
width: currencyImage.size.width + 2.0
|
||||||
|
)
|
||||||
|
var callbacks = CTRunDelegateCallbacks(
|
||||||
|
version: kCTRunDelegateCurrentVersion,
|
||||||
|
dealloc: { dataRef in
|
||||||
|
Unmanaged<RunDelegateData>.fromOpaque(dataRef).release()
|
||||||
|
},
|
||||||
|
getAscent: { dataRef in
|
||||||
|
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||||
|
return data.takeUnretainedValue().ascent
|
||||||
|
},
|
||||||
|
getDescent: { dataRef in
|
||||||
|
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||||
|
return data.takeUnretainedValue().descent
|
||||||
|
},
|
||||||
|
getWidth: { dataRef in
|
||||||
|
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||||
|
return data.takeUnretainedValue().width
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) {
|
||||||
|
mutableTextString.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: NSRange(range, in: mutableTextString.string))
|
||||||
|
}
|
||||||
|
mutableTextString.addAttribute(.attachment, value: currencyImage, range: NSRange(range, in: mutableTextString.string))
|
||||||
|
mutableTextString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: mutableTextString.string))
|
||||||
|
mutableTextString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: mutableTextString.string))
|
||||||
|
}
|
||||||
|
|
||||||
|
textString = mutableTextString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let textInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 0.0, right: 44.0)
|
||||||
|
|
||||||
|
self.titleNode.components = titleText
|
||||||
|
let titleSize = self.titleNode.update(constrainedSize: CGSize(width: availableSize.width - lineFrame.maxX - textInsets.left - textInsets.right, height: 100.0))
|
||||||
|
|
||||||
|
let textSize = self.text.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(textString),
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - lineFrame.maxX - textInsets.left - textInsets.right, height: 100.0)
|
||||||
|
)
|
||||||
|
let tintTextString = NSMutableAttributedString(attributedString: textString)
|
||||||
|
tintTextString.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(location: 0, length: tintTextString.length))
|
||||||
|
let _ = self.tintText.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(tintTextString),
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - lineFrame.maxX - textInsets.left - textInsets.right, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let titleTextSpacing: CGFloat = 1.0
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: lineFrame.maxX + textInsets.left, y: textInsets.top), size: titleSize)
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: lineFrame.maxX + textInsets.left, y: titleFrame.maxY + titleTextSpacing), size: textSize)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.titleNode.view, frame: titleFrame)
|
||||||
|
|
||||||
|
if let textView = self.text.view, let tintTextView = self.tintText.view {
|
||||||
|
if textView.superview == nil {
|
||||||
|
textView.layer.anchorPoint = CGPoint()
|
||||||
|
self.addSubview(textView)
|
||||||
|
|
||||||
|
tintTextView.layer.anchorPoint = CGPoint()
|
||||||
|
self.contentTintView.addSubview(tintTextView)
|
||||||
|
}
|
||||||
|
transition.setPosition(view: textView, position: textFrame.origin)
|
||||||
|
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||||
|
|
||||||
|
transition.setPosition(view: tintTextView, position: textFrame.origin)
|
||||||
|
tintTextView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ swift_library(
|
||||||
"//submodules/TelegramCore",
|
"//submodules/TelegramCore",
|
||||||
"//submodules/AccountContext",
|
"//submodules/AccountContext",
|
||||||
"//submodules/ChatPresentationInterfaceState",
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
|
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
|
import ChatControllerInteraction
|
||||||
|
|
||||||
public protocol ChatInputPanelViewForOverlayContent: UIView {
|
public protocol ChatInputPanelViewForOverlayContent: UIView {
|
||||||
func maybeDismissContent(point: CGPoint)
|
func maybeDismissContent(point: CGPoint)
|
||||||
|
|
@ -13,6 +14,7 @@ public protocol ChatInputPanelViewForOverlayContent: UIView {
|
||||||
|
|
||||||
open class ChatInputPanelNode: ASDisplayNode {
|
open class ChatInputPanelNode: ASDisplayNode {
|
||||||
open var context: AccountContext?
|
open var context: AccountContext?
|
||||||
|
open var chatControllerInteraction: ChatControllerInteraction?
|
||||||
open var interfaceInteraction: ChatPanelInterfaceInteraction?
|
open var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||||
open var prevInputPanelNode: ChatInputPanelNode?
|
open var prevInputPanelNode: ChatInputPanelNode?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,9 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
}, editTodoMessage: { _, _, _ in
|
}, editTodoMessage: { _, _, _ in
|
||||||
|
}, dismissUrlPreview: {
|
||||||
|
}, dismissForwardMessages: {
|
||||||
|
}, dismissSuggestPost: {
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
|
|
||||||
|
|
@ -388,7 +388,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||||
|
|
||||||
let alertController = richTextAlertController(context: self.context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: self.strings.Conversation_ForwardOptions_ShowOptions, action: { [weak self] in
|
let alertController = richTextAlertController(context: self.context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: self.strings.Conversation_ForwardOptions_ShowOptions, action: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.interfaceInteraction?.presentForwardOptions(strongSelf)
|
strongSelf.interfaceInteraction?.presentForwardOptions(strongSelf.view)
|
||||||
Queue.mainQueue().after(0.5) {
|
Queue.mainQueue().after(0.5) {
|
||||||
strongSelf.updateThemeAndStrings(theme: strongSelf.theme, strings: strongSelf.strings, forwardOptionsState: strongSelf.forwardOptionsState, force: true)
|
strongSelf.updateThemeAndStrings(theme: strongSelf.theme, strings: strongSelf.strings, forwardOptionsState: strongSelf.forwardOptionsState, force: true)
|
||||||
}
|
}
|
||||||
|
|
@ -409,7 +409,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
self.previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
self.interfaceInteraction?.presentForwardOptions(self)
|
self.interfaceInteraction?.presentForwardOptions(self.view)
|
||||||
Queue.mainQueue().after(1.5) {
|
Queue.mainQueue().after(1.5) {
|
||||||
self.updateThemeAndStrings(theme: self.theme, strings: self.strings, forwardOptionsState: self.forwardOptionsState, force: true)
|
self.updateThemeAndStrings(theme: self.theme, strings: self.strings, forwardOptionsState: self.forwardOptionsState, force: true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
self.previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
self.interfaceInteraction?.presentReplyOptions(self)
|
self.interfaceInteraction?.presentReplyOptions(self.view)
|
||||||
Queue.mainQueue().after(1.5) {
|
Queue.mainQueue().after(1.5) {
|
||||||
self.updateThemeAndStrings(theme: self.theme, strings: self.strings, force: true)
|
self.updateThemeAndStrings(theme: self.theme, strings: self.strings, force: true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var clipContentToTopPanel: Bool = false
|
public var clipContentToTopPanel: Bool = false
|
||||||
|
|
||||||
public var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
|
public var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
|
||||||
public override var externalTopPanelContainer: UIView? {
|
public override var externalTopPanelContainer: UIView? {
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,13 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
|
||||||
private var modeTimeoutTimer: SwiftSignalKit.Timer?
|
private var modeTimeoutTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
private let animationView: ComponentView<Empty>
|
private let animationView: ComponentView<Empty>
|
||||||
|
public var animationOutput: UIImageView? {
|
||||||
|
didSet {
|
||||||
|
if let view = self.animationView.view as? LottieComponent.View {
|
||||||
|
view.output = self.animationOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var recordingOverlay: ChatTextInputAudioRecordingOverlay?
|
private var recordingOverlay: ChatTextInputAudioRecordingOverlay?
|
||||||
private var startTouchLocation: CGPoint?
|
private var startTouchLocation: CGPoint?
|
||||||
|
|
@ -415,7 +422,7 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(LottieComponent(
|
component: AnyComponent(LottieComponent(
|
||||||
content: LottieComponent.AppBundleContent(name: animationName),
|
content: LottieComponent.AppBundleContent(name: animationName),
|
||||||
color: self.useDarkTheme ? .white : self.theme.chat.inputPanel.panelControlColor.blitOver(self.theme.chat.inputPanel.inputBackgroundColor, alpha: 1.0)
|
color: self.useDarkTheme ? .white : self.theme.chat.inputPanel.inputControlColor
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: animationFrame.size
|
containerSize: animationFrame.size
|
||||||
|
|
@ -425,6 +432,7 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
|
||||||
view.isUserInteractionEnabled = false
|
view.isUserInteractionEnabled = false
|
||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
self.insertSubview(view, at: 0)
|
self.insertSubview(view, at: 0)
|
||||||
|
view.output = self.animationOutput
|
||||||
self.updateShadow()
|
self.updateShadow()
|
||||||
}
|
}
|
||||||
view.frame = animationFrame
|
view.frame = animationFrame
|
||||||
|
|
@ -433,6 +441,10 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
|
||||||
view.playOnce()
|
view.playOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let animationOutput = self.animationOutput {
|
||||||
|
animationOutput.frame = animationFrame
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateTheme(theme: PresentationTheme) {
|
public func updateTheme(theme: PresentationTheme) {
|
||||||
|
|
@ -553,6 +565,11 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
|
||||||
if let layer = self.animationView.view?.layer {
|
if let layer = self.animationView.view?.layer {
|
||||||
transition.updateAlpha(layer: layer, alpha: 0.0)
|
transition.updateAlpha(layer: layer, alpha: 0.0)
|
||||||
transition.updateTransformScale(layer: layer, scale: 0.3)
|
transition.updateTransformScale(layer: layer, scale: 0.3)
|
||||||
|
|
||||||
|
if let animationOutput = self.animationOutput {
|
||||||
|
transition.updateAlpha(layer: animationOutput.layer, alpha: 0.0)
|
||||||
|
transition.updateTransformScale(layer: animationOutput.layer, scale: 0.3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -569,6 +586,11 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
|
||||||
if let layer = self.animationView.view?.layer {
|
if let layer = self.animationView.view?.layer {
|
||||||
transition.updateAlpha(layer: layer, alpha: 1.0)
|
transition.updateAlpha(layer: layer, alpha: 1.0)
|
||||||
transition.updateTransformScale(layer: layer, scale: 1.0)
|
transition.updateTransformScale(layer: layer, scale: 1.0)
|
||||||
|
|
||||||
|
if let animationOutput = self.animationOutput {
|
||||||
|
transition.updateAlpha(layer: animationOutput.layer, alpha: 1.0)
|
||||||
|
transition.updateTransformScale(layer: animationOutput.layer, scale: 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -582,6 +604,11 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
|
||||||
let iconSize = view.bounds.size
|
let iconSize = view.bounds.size
|
||||||
view.bounds = CGRect(origin: .zero, size: iconSize)
|
view.bounds = CGRect(origin: .zero, size: iconSize)
|
||||||
view.center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
view.center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
|
||||||
|
if let animationOutput = self.animationOutput {
|
||||||
|
animationOutput.bounds = view.bounds
|
||||||
|
animationOutput.center = view.center
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -732,6 +732,7 @@ public final class EntityKeyboardComponent: Component {
|
||||||
topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent(
|
topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent(
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
overflowHeight: component.hiddenInputHeight,
|
overflowHeight: component.hiddenInputHeight,
|
||||||
|
topInset: 12.0,
|
||||||
displayBackground: component.externalTopPanelContainer != nil ? .none : component.displayTopPanelBackground
|
displayBackground: component.externalTopPanelContainer != nil ? .none : component.displayTopPanelBackground
|
||||||
)),
|
)),
|
||||||
externalTopPanelContainer: component.externalTopPanelContainer,
|
externalTopPanelContainer: component.externalTopPanelContainer,
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,18 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||||
|
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let overflowHeight: CGFloat
|
let overflowHeight: CGFloat
|
||||||
|
let topInset: CGFloat
|
||||||
let displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
|
let displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
overflowHeight: CGFloat,
|
overflowHeight: CGFloat,
|
||||||
|
topInset: CGFloat,
|
||||||
displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
|
displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.overflowHeight = overflowHeight
|
self.overflowHeight = overflowHeight
|
||||||
|
self.topInset = topInset
|
||||||
self.displayBackground = displayBackground
|
self.displayBackground = displayBackground
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,6 +60,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||||
if lhs.overflowHeight != rhs.overflowHeight {
|
if lhs.overflowHeight != rhs.overflowHeight {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.topInset != rhs.topInset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.displayBackground != rhs.displayBackground {
|
if lhs.displayBackground != rhs.displayBackground {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +101,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||||
|
|
||||||
func update(component: EntityKeyboardTopContainerPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
func update(component: EntityKeyboardTopContainerPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
let intrinsicHeight: CGFloat = 34.0
|
let intrinsicHeight: CGFloat = 34.0
|
||||||
let height = intrinsicHeight
|
let height = intrinsicHeight + component.topInset
|
||||||
|
|
||||||
let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value
|
let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value
|
||||||
|
|
||||||
|
|
@ -122,7 +128,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||||
let panel = panelEnvironment.contentTopPanels[index]
|
let panel = panelEnvironment.contentTopPanels[index]
|
||||||
let indexOffset = index - centralIndex
|
let indexOffset = index - centralIndex
|
||||||
|
|
||||||
let panelFrame = CGRect(origin: CGPoint(x: CGFloat(indexOffset) * availableSize.width, y: -component.overflowHeight), size: CGSize(width: availableSize.width, height: intrinsicHeight + component.overflowHeight))
|
let panelFrame = CGRect(origin: CGPoint(x: CGFloat(indexOffset) * availableSize.width, y: component.topInset - component.overflowHeight), size: CGSize(width: availableSize.width, height: intrinsicHeight + component.overflowHeight))
|
||||||
|
|
||||||
let isInBounds = visibleBounds.intersects(panelFrame)
|
let isInBounds = visibleBounds.intersects(panelFrame)
|
||||||
let isPartOfTransition: Bool
|
let isPartOfTransition: Bool
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "GlassBackgroundComponent",
|
||||||
|
module_name = "GlassBackgroundComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,517 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
|
||||||
|
private func generateForegroundImage(size: CGSize, isDark: Bool, fillColor: UIColor) -> UIImage {
|
||||||
|
var size = size
|
||||||
|
if size == .zero {
|
||||||
|
size = CGSize(width: 1.0, height: 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateImage(size, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let maxColor = UIColor(white: 1.0, alpha: isDark ? 0.67 : 0.9)
|
||||||
|
let minColor = UIColor(white: 1.0, alpha: 0.0)
|
||||||
|
|
||||||
|
context.setFillColor(fillColor.cgColor)
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let lineWidth: CGFloat = isDark ? 0.66 : 0.66
|
||||||
|
|
||||||
|
context.saveGState()
|
||||||
|
|
||||||
|
let darkShadeColor = UIColor(white: isDark ? 1.0 : 0.0, alpha: 0.035)
|
||||||
|
let lightShadeColor = UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.035)
|
||||||
|
let innerShadowBlur: CGFloat = 24.0
|
||||||
|
|
||||||
|
context.resetClip()
|
||||||
|
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
||||||
|
context.clip()
|
||||||
|
context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0))
|
||||||
|
context.addEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(UIColor.black.cgColor)
|
||||||
|
context.setShadow(offset: CGSize(width: 10.0, height: -10.0), blur: innerShadowBlur, color: darkShadeColor.cgColor)
|
||||||
|
context.fillPath(using: .evenOdd)
|
||||||
|
|
||||||
|
context.resetClip()
|
||||||
|
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
||||||
|
context.clip()
|
||||||
|
context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0))
|
||||||
|
context.addEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(UIColor.black.cgColor)
|
||||||
|
context.setShadow(offset: CGSize(width: -10.0, height: 10.0), blur: innerShadowBlur, color: lightShadeColor.cgColor)
|
||||||
|
context.fillPath(using: .evenOdd)
|
||||||
|
|
||||||
|
context.restoreGState()
|
||||||
|
|
||||||
|
context.setLineWidth(lineWidth)
|
||||||
|
|
||||||
|
context.addRect(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)))
|
||||||
|
context.clip()
|
||||||
|
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
||||||
|
context.replacePathWithStrokedPath()
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
do {
|
||||||
|
var locations: [CGFloat] = [0.0, 0.5, 0.5 + 0.2, 1.0 - 0.1, 1.0]
|
||||||
|
let colors: [CGColor] = [maxColor.cgColor, maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor]
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
context.resetClip()
|
||||||
|
context.addRect(CGRect(origin: CGPoint(x: size.width - size.width * 0.5, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)))
|
||||||
|
context.clip()
|
||||||
|
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
||||||
|
context.replacePathWithStrokedPath()
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
do {
|
||||||
|
var locations: [CGFloat] = [0.0, 0.1, 0.5 - 0.2, 0.5, 1.0]
|
||||||
|
let colors: [CGColor] = [maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor, maxColor.cgColor]
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||||
|
}
|
||||||
|
})!.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5))
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ContentContainer: UIView {
|
||||||
|
private let maskContentView: UIView
|
||||||
|
|
||||||
|
init(maskContentView: UIView) {
|
||||||
|
self.maskContentView = maskContentView
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
guard let result = super.hitTest(point, with: event) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if result === self {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didAddSubview(_ subview: UIView) {
|
||||||
|
super.didAddSubview(subview)
|
||||||
|
|
||||||
|
if let subview = subview as? GlassBackgroundView.ContentView {
|
||||||
|
self.maskContentView.addSubview(subview.tintMask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func willRemoveSubview(_ subview: UIView) {
|
||||||
|
super.willRemoveSubview(subview)
|
||||||
|
|
||||||
|
if let subview = subview as? GlassBackgroundView.ContentView {
|
||||||
|
subview.tintMask.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class GlassBackgroundView: UIView {
|
||||||
|
public protocol ContentView: UIView {
|
||||||
|
var tintMask: UIView { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ContentLayer: SimpleLayer {
|
||||||
|
public var targetLayer: CALayer?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var position: CGPoint {
|
||||||
|
get {
|
||||||
|
return super.position
|
||||||
|
} set(value) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.position = value
|
||||||
|
}
|
||||||
|
super.position = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var bounds: CGRect {
|
||||||
|
get {
|
||||||
|
return super.bounds
|
||||||
|
} set(value) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.bounds = value
|
||||||
|
}
|
||||||
|
super.bounds = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var anchorPoint: CGPoint {
|
||||||
|
get {
|
||||||
|
return super.anchorPoint
|
||||||
|
} set(value) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.anchorPoint = value
|
||||||
|
}
|
||||||
|
super.anchorPoint = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var anchorPointZ: CGFloat {
|
||||||
|
get {
|
||||||
|
return super.anchorPointZ
|
||||||
|
} set(value) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.anchorPointZ = value
|
||||||
|
}
|
||||||
|
super.anchorPointZ = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var opacity: Float {
|
||||||
|
get {
|
||||||
|
return super.opacity
|
||||||
|
} set(value) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.opacity = value
|
||||||
|
}
|
||||||
|
super.opacity = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var sublayerTransform: CATransform3D {
|
||||||
|
get {
|
||||||
|
return super.sublayerTransform
|
||||||
|
} set(value) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.sublayerTransform = value
|
||||||
|
}
|
||||||
|
super.sublayerTransform = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var transform: CATransform3D {
|
||||||
|
get {
|
||||||
|
return super.transform
|
||||||
|
} set(value) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.transform = value
|
||||||
|
}
|
||||||
|
super.transform = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func add(_ animation: CAAnimation, forKey key: String?) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func removeAllAnimations() {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func removeAnimation(forKey: String) {
|
||||||
|
if let targetLayer = self.targetLayer {
|
||||||
|
targetLayer.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ContentColorView: UIView, ContentView {
|
||||||
|
override public static var layerClass: AnyClass {
|
||||||
|
return ContentLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
public let tintMask: UIView
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.tintMask = UIView()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.tintMask.tintColor = .black
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ContentImageView: UIImageView, ContentView {
|
||||||
|
override public static var layerClass: AnyClass {
|
||||||
|
return ContentLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
private let tintImageView: UIImageView
|
||||||
|
public var tintMask: UIView {
|
||||||
|
return self.tintImageView
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var image: UIImage? {
|
||||||
|
didSet {
|
||||||
|
self.tintImageView.image = self.image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.tintImageView = UIImageView()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.tintImageView.tintColor = .black
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(image: UIImage?) {
|
||||||
|
self.tintImageView = UIImageView()
|
||||||
|
|
||||||
|
super.init(image: image)
|
||||||
|
|
||||||
|
self.tintImageView.image = image
|
||||||
|
self.tintImageView.tintColor = .black
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(image: UIImage?, highlightedImage: UIImage?) {
|
||||||
|
self.tintImageView = UIImageView()
|
||||||
|
|
||||||
|
super.init(image: image, highlightedImage: highlightedImage)
|
||||||
|
|
||||||
|
self.tintImageView.image = image
|
||||||
|
self.tintImageView.tintColor = .black
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Params: Equatable {
|
||||||
|
let cornerRadius: CGFloat
|
||||||
|
let isDark: Bool
|
||||||
|
let tintColor: UIColor
|
||||||
|
|
||||||
|
init(cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor) {
|
||||||
|
self.cornerRadius = cornerRadius
|
||||||
|
self.isDark = isDark
|
||||||
|
self.tintColor = tintColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let backgroundNode: NavigationBackgroundNode?
|
||||||
|
private let nativeView: UIVisualEffectView?
|
||||||
|
|
||||||
|
private let foregroundView: UIImageView?
|
||||||
|
|
||||||
|
public let maskContentView: UIView
|
||||||
|
private let contentContainer: ContentContainer
|
||||||
|
|
||||||
|
public var contentView: UIView {
|
||||||
|
return self.contentContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
private var params: Params?
|
||||||
|
|
||||||
|
public override init(frame: CGRect) {
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
self.backgroundNode = nil
|
||||||
|
let glassEffect = UIGlassEffect(style: .clear)
|
||||||
|
glassEffect.isInteractive = false
|
||||||
|
let nativeView = UIVisualEffectView(effect: glassEffect)
|
||||||
|
self.nativeView = nativeView
|
||||||
|
//nativeView.overrideUserInterfaceStyle = .light
|
||||||
|
//nativeView.traitOverrides.userInterfaceStyle = .light
|
||||||
|
self.foregroundView = UIImageView()
|
||||||
|
//self.foregroundView = nil
|
||||||
|
} else {
|
||||||
|
self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0)
|
||||||
|
self.nativeView = nil
|
||||||
|
self.foregroundView = UIImageView()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.maskContentView = UIView()
|
||||||
|
self.maskContentView.backgroundColor = .white
|
||||||
|
if let filter = CALayer.luminanceToAlpha() {
|
||||||
|
self.maskContentView.layer.filters = [filter]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contentContainer = ContentContainer(maskContentView: self.maskContentView)
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
if let nativeView = self.nativeView {
|
||||||
|
self.addSubview(nativeView)
|
||||||
|
}
|
||||||
|
if let backgroundNode = self.backgroundNode {
|
||||||
|
self.addSubview(backgroundNode.view)
|
||||||
|
}
|
||||||
|
if let foregroundView = self.foregroundView {
|
||||||
|
self.addSubview(foregroundView)
|
||||||
|
foregroundView.mask = self.maskContentView
|
||||||
|
}
|
||||||
|
self.addSubview(self.contentContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor, transition: ComponentTransition) {
|
||||||
|
if let nativeView = self.nativeView {
|
||||||
|
let previousFrame = nativeView.frame
|
||||||
|
|
||||||
|
transition.containedViewLayoutTransition.animateView {
|
||||||
|
nativeView.layer.cornerRadius = cornerRadius
|
||||||
|
nativeView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeView.layer.animateFrame(from: previousFrame, to: CGRect(origin: CGPoint(), size: size), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
}
|
||||||
|
if let backgroundNode = self.backgroundNode {
|
||||||
|
backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.alpha != 1.0, transition: transition.containedViewLayoutTransition)
|
||||||
|
backgroundNode.update(size: size, cornerRadius: cornerRadius, transition: transition.containedViewLayoutTransition)
|
||||||
|
transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = Params(cornerRadius: cornerRadius, isDark: isDark, tintColor: tintColor)
|
||||||
|
if self.params != params {
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
/*if let nativeView {
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
let glassEffect = UIGlassEffect(style: .regular)
|
||||||
|
glassEffect.tintColor = tintColor
|
||||||
|
glassEffect.isInteractive = false
|
||||||
|
|
||||||
|
nativeView.effect = glassEffect
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if let foregroundView = self.foregroundView {
|
||||||
|
foregroundView.image = generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
if let foregroundView {
|
||||||
|
transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class VariableBlurView: UIVisualEffectView {
|
||||||
|
public let maxBlurRadius: CGFloat
|
||||||
|
|
||||||
|
public var gradientMask: UIImage {
|
||||||
|
didSet {
|
||||||
|
if self.gradientMask !== oldValue {
|
||||||
|
self.resetEffect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(gradientMask: UIImage, maxBlurRadius: CGFloat = 20.0) {
|
||||||
|
self.gradientMask = gradientMask
|
||||||
|
self.maxBlurRadius = maxBlurRadius
|
||||||
|
|
||||||
|
super.init(effect: UIBlurEffect(style: .regular))
|
||||||
|
|
||||||
|
self.resetEffect()
|
||||||
|
|
||||||
|
if self.subviews.indices.contains(1) {
|
||||||
|
let tintOverlayView = subviews[1]
|
||||||
|
tintOverlayView.alpha = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
|
||||||
|
self.resetEffect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetEffect() {
|
||||||
|
let filterClassStringEncoded = "Q0FGaWx0ZXI="
|
||||||
|
let filterClassString: String = {
|
||||||
|
if
|
||||||
|
let data = Data(base64Encoded: filterClassStringEncoded),
|
||||||
|
let string = String(data: data, encoding: .utf8)
|
||||||
|
{
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}()
|
||||||
|
let filterWithTypeStringEncoded = "ZmlsdGVyV2l0aFR5cGU6"
|
||||||
|
let filterWithTypeString: String = {
|
||||||
|
if
|
||||||
|
let data = Data(base64Encoded: filterWithTypeStringEncoded),
|
||||||
|
let string = String(data: data, encoding: .utf8)
|
||||||
|
{
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}()
|
||||||
|
|
||||||
|
let filterWithTypeSelector = Selector(filterWithTypeString)
|
||||||
|
|
||||||
|
guard let filterClass = NSClassFromString(filterClassString) as AnyObject as? NSObjectProtocol else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard filterClass.responds(to: filterWithTypeSelector) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let variableBlur = filterClass.perform(filterWithTypeSelector, with: "variableBlur").takeUnretainedValue()
|
||||||
|
|
||||||
|
guard let variableBlur = variableBlur as? NSObject else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let gradientImageRef = self.gradientMask.cgImage else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
|
||||||
|
variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage")
|
||||||
|
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
|
||||||
|
variableBlur.setValue(UIScreenScale, forKey: "scale")
|
||||||
|
|
||||||
|
let backdropLayer = self.subviews.first?.layer
|
||||||
|
backdropLayer?.filters = [variableBlur]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -439,6 +439,9 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
}, editTodoMessage: { _, _, _ in
|
}, editTodoMessage: { _, _, _ in
|
||||||
|
}, dismissUrlPreview: {
|
||||||
|
}, dismissForwardMessages: {
|
||||||
|
}, dismissSuggestPost: {
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
|
|
||||||
|
|
@ -582,7 +582,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(content: .list($0)) })
|
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceView: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(content: .list($0)) })
|
||||||
contextController.dismissedForCancel = { [weak chatController] in
|
contextController.dismissedForCancel = { [weak chatController] in
|
||||||
if let selectedMessageIds = chatController?.selectedMessageIds {
|
if let selectedMessageIds = chatController?.selectedMessageIds {
|
||||||
var forwardMessageIds = strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
|
var forwardMessageIds = strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
|
||||||
|
|
@ -826,6 +826,9 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
}, editTodoMessage: { _, _, _ in
|
}, editTodoMessage: { _, _, _ in
|
||||||
|
}, dismissUrlPreview: {
|
||||||
|
}, dismissForwardMessages: {
|
||||||
|
}, dismissSuggestPost: {
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
|
@ -1819,7 +1822,7 @@ private func stringForRequestPeerType(strings: PresentationStrings, peerType: Re
|
||||||
|
|
||||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||||
let controller: ViewController
|
let controller: ViewController
|
||||||
weak var sourceNode: ASDisplayNode?
|
weak var sourceView: UIView?
|
||||||
let sourceRect: CGRect?
|
let sourceRect: CGRect?
|
||||||
|
|
||||||
let navigationController: NavigationController? = nil
|
let navigationController: NavigationController? = nil
|
||||||
|
|
@ -1828,17 +1831,24 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
||||||
|
|
||||||
init(controller: ViewController, sourceNode: ASDisplayNode?, sourceRect: CGRect? = nil, passthroughTouches: Bool) {
|
init(controller: ViewController, sourceNode: ASDisplayNode?, sourceRect: CGRect? = nil, passthroughTouches: Bool) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sourceNode = sourceNode
|
self.sourceView = sourceNode?.view
|
||||||
|
self.sourceRect = sourceRect
|
||||||
|
self.passthroughTouches = passthroughTouches
|
||||||
|
}
|
||||||
|
|
||||||
|
init(controller: ViewController, sourceView: UIView?, sourceRect: CGRect? = nil, passthroughTouches: Bool) {
|
||||||
|
self.controller = controller
|
||||||
|
self.sourceView = sourceView
|
||||||
self.sourceRect = sourceRect
|
self.sourceRect = sourceRect
|
||||||
self.passthroughTouches = passthroughTouches
|
self.passthroughTouches = passthroughTouches
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||||
let sourceNode = self.sourceNode
|
let sourceView = self.sourceView
|
||||||
let sourceRect = self.sourceRect
|
let sourceRect = self.sourceRect
|
||||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceView] in
|
||||||
if let sourceNode = sourceNode {
|
if let sourceView {
|
||||||
return (sourceNode.view, sourceRect ?? sourceNode.bounds)
|
return (sourceView, sourceRect ?? sourceView.bounds)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1982,21 +1982,21 @@ extension ChatControllerImpl {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardOptionsState(f($0.forwardOptionsState ?? ChatInterfaceForwardOptionsState(hideNames: false, hideCaptions: false, unhideNamesOnCaptionChange: false))) }) })
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardOptionsState(f($0.forwardOptionsState ?? ChatInterfaceForwardOptionsState(hideNames: false, hideCaptions: false, unhideNamesOnCaptionChange: false))) }) })
|
||||||
}
|
}
|
||||||
}, presentForwardOptions: { [weak self] sourceNode in
|
}, presentForwardOptions: { [weak self] sourceView in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
presentChatForwardOptions(selfController: self, sourceNode: sourceNode)
|
presentChatForwardOptions(selfController: self, sourceView: sourceView)
|
||||||
}, presentReplyOptions: { [weak self] sourceNode in
|
}, presentReplyOptions: { [weak self] sourceView in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
presentChatReplyOptions(selfController: self, sourceNode: sourceNode)
|
presentChatReplyOptions(selfController: self, sourceView: sourceView)
|
||||||
}, presentLinkOptions: { [weak self] sourceNode in
|
}, presentLinkOptions: { [weak self] sourceView in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
presentChatLinkOptions(selfController: self, sourceNode: sourceNode)
|
presentChatLinkOptions(selfController: self, sourceView: sourceView)
|
||||||
}, presentSuggestPostOptions: { [weak self] in
|
}, presentSuggestPostOptions: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|
@ -4340,6 +4340,30 @@ extension ChatControllerImpl {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.openTodoEditing(messageId: messageId, itemId: itemId, append: append)
|
self.openTodoEditing(messageId: messageId, itemId: itemId, append: append)
|
||||||
|
}, dismissUrlPreview: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.chatDisplayNode.dismissUrlPreview()
|
||||||
|
}, dismissForwardMessages: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.chatDisplayNode.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil) })
|
||||||
|
}, dismissSuggestPost: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.chatDisplayNode.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { state in
|
||||||
|
var state = state
|
||||||
|
if let postSuggestionState = state.postSuggestionState {
|
||||||
|
state = state.withUpdatedPostSuggestionState(nil)
|
||||||
|
if postSuggestionState.editingOriginalMessageId != nil {
|
||||||
|
state = state.withUpdatedEditMessage(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
}, updateHistoryFilter: { [weak self] update in
|
}, updateHistoryFilter: { [weak self] update in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -27,28 +27,28 @@ private enum OptionsId: Hashable {
|
||||||
case link
|
case link
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentChatInputOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, initialId: OptionsId) {
|
private func presentChatInputOptions(selfController: ChatControllerImpl, sourceView: UIView, initialId: OptionsId) {
|
||||||
var getContextController: (() -> ContextController?)?
|
var getContextController: (() -> ContextController?)?
|
||||||
|
|
||||||
var sources: [ContextController.Source] = []
|
var sources: [ContextController.Source] = []
|
||||||
|
|
||||||
let replySelectionState = Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>(ChatControllerSubject.MessageOptionsInfo.SelectionState(canQuote: false, quote: nil))
|
let replySelectionState = Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>(ChatControllerSubject.MessageOptionsInfo.SelectionState(canQuote: false, quote: nil))
|
||||||
|
|
||||||
if let source = chatReplyOptions(selfController: selfController, sourceNode: sourceNode, getContextController: {
|
if let source = chatReplyOptions(selfController: selfController, sourceView: sourceView, getContextController: {
|
||||||
return getContextController?()
|
return getContextController?()
|
||||||
}, selectionState: replySelectionState) {
|
}, selectionState: replySelectionState) {
|
||||||
sources.append(source)
|
sources.append(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
var forwardDismissedForCancel: (() -> Void)?
|
var forwardDismissedForCancel: (() -> Void)?
|
||||||
if let (source, dismissedForCancel) = chatForwardOptions(selfController: selfController, sourceNode: sourceNode, getContextController: {
|
if let (source, dismissedForCancel) = chatForwardOptions(selfController: selfController, sourceView: sourceView, getContextController: {
|
||||||
return getContextController?()
|
return getContextController?()
|
||||||
}) {
|
}) {
|
||||||
forwardDismissedForCancel = dismissedForCancel
|
forwardDismissedForCancel = dismissedForCancel
|
||||||
sources.append(source)
|
sources.append(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let source = chatLinkOptions(selfController: selfController, sourceNode: sourceNode, getContextController: {
|
if let source = chatLinkOptions(selfController: selfController, sourceView: sourceView, getContextController: {
|
||||||
return getContextController?()
|
return getContextController?()
|
||||||
}, replySelectionState: replySelectionState) {
|
}, replySelectionState: replySelectionState) {
|
||||||
sources.append(source)
|
sources.append(source)
|
||||||
|
|
@ -84,7 +84,7 @@ private func presentChatInputOptions(selfController: ChatControllerImpl, sourceN
|
||||||
selfController.presentInGlobalOverlay(contextController)
|
selfController.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?) -> (ContextController.Source, () -> Void)? {
|
private func chatForwardOptions(selfController: ChatControllerImpl, sourceView: UIView, getContextController: @escaping () -> ContextController?) -> (ContextController.Source, () -> Void)? {
|
||||||
guard let peerId = selfController.chatLocation.peerId else {
|
guard let peerId = selfController.chatLocation.peerId else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -270,13 +270,13 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode:
|
||||||
return (ContextController.Source(
|
return (ContextController.Source(
|
||||||
id: AnyHashable(OptionsId.forward),
|
id: AnyHashable(OptionsId.forward),
|
||||||
title: selfController.presentationData.strings.Conversation_MessageOptionsTabForward,
|
title: selfController.presentationData.strings.Conversation_MessageOptionsTabForward,
|
||||||
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)),
|
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceView: sourceView, passthroughTouches: true)),
|
||||||
items: items |> map { ContextController.Items(id: AnyHashable("forward"), content: .list($0)) }
|
items: items |> map { ContextController.Items(id: AnyHashable("forward"), content: .list($0)) }
|
||||||
), dismissedForCancel)
|
), dismissedForCancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) {
|
func presentChatForwardOptions(selfController: ChatControllerImpl, sourceView: UIView) {
|
||||||
presentChatInputOptions(selfController: selfController, sourceNode: sourceNode, initialId: .forward)
|
presentChatInputOptions(selfController: selfController, sourceView: sourceView, initialId: .forward)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateChatReplyOptionItems(selfController: ChatControllerImpl, chatController: ChatControllerImpl) -> Signal<ContextController.Items, NoError> {
|
private func generateChatReplyOptionItems(selfController: ChatControllerImpl, chatController: ChatControllerImpl) -> Signal<ContextController.Items, NoError> {
|
||||||
|
|
@ -498,7 +498,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
private func chatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?, selectionState: Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>) -> ContextController.Source? {
|
private func chatReplyOptions(selfController: ChatControllerImpl, sourceView: UIView, getContextController: @escaping () -> ContextController?, selectionState: Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>) -> ContextController.Source? {
|
||||||
guard let peerId = selfController.chatLocation.peerId else {
|
guard let peerId = selfController.chatLocation.peerId else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -543,13 +543,13 @@ private func chatReplyOptions(selfController: ChatControllerImpl, sourceNode: AS
|
||||||
return ContextController.Source(
|
return ContextController.Source(
|
||||||
id: AnyHashable(OptionsId.reply),
|
id: AnyHashable(OptionsId.reply),
|
||||||
title: selfController.presentationData.strings.Conversation_MessageOptionsTabReply,
|
title: selfController.presentationData.strings.Conversation_MessageOptionsTabReply,
|
||||||
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)),
|
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceView: sourceView, passthroughTouches: true)),
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentChatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) {
|
func presentChatReplyOptions(selfController: ChatControllerImpl, sourceView: UIView) {
|
||||||
presentChatInputOptions(selfController: selfController, sourceNode: sourceNode, initialId: .reply)
|
presentChatInputOptions(selfController: selfController, sourceView: sourceView, initialId: .reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubject: ChatInterfaceState.ReplyMessageSubject) {
|
func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubject: ChatInterfaceState.ReplyMessageSubject) {
|
||||||
|
|
@ -716,7 +716,7 @@ func moveReplyToChat(selfController: ChatControllerImpl, peerId: EnginePeer.Id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?, replySelectionState: Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>) -> ContextController.Source? {
|
private func chatLinkOptions(selfController: ChatControllerImpl, sourceView: UIView, getContextController: @escaping () -> ContextController?, replySelectionState: Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>) -> ContextController.Source? {
|
||||||
guard let peerId = selfController.chatLocation.peerId else {
|
guard let peerId = selfController.chatLocation.peerId else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -969,13 +969,13 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||||
return ContextController.Source(
|
return ContextController.Source(
|
||||||
id: AnyHashable(OptionsId.link),
|
id: AnyHashable(OptionsId.link),
|
||||||
title: selfController.presentationData.strings.Conversation_MessageOptionsTabLink,
|
title: selfController.presentationData.strings.Conversation_MessageOptionsTabLink,
|
||||||
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)),
|
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceView: sourceView, passthroughTouches: true)),
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentChatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) {
|
func presentChatLinkOptions(selfController: ChatControllerImpl, sourceView: UIView) {
|
||||||
presentChatInputOptions(selfController: selfController, sourceNode: sourceNode, initialId: .link)
|
presentChatInputOptions(selfController: selfController, sourceView: sourceView, initialId: .link)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ChatControllerImpl {
|
extension ChatControllerImpl {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import ComponentFlow
|
||||||
import ChatEmptyNode
|
import ChatEmptyNode
|
||||||
import SpaceWarpView
|
import SpaceWarpView
|
||||||
import ChatSideTopicsPanel
|
import ChatSideTopicsPanel
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
|
||||||
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
||||||
let itemNode: OverlayMediaItemNode
|
let itemNode: OverlayMediaItemNode
|
||||||
|
|
@ -214,12 +215,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
let inputPanelBackgroundNode: NavigationBackgroundNode
|
let inputPanelBackgroundNode: NavigationBackgroundNode
|
||||||
|
|
||||||
private var navigationBarBackgroundContent: WallpaperBubbleBackgroundNode?
|
private var navigationBarBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||||
private var inputPanelBackgroundContent: WallpaperBubbleBackgroundNode?
|
|
||||||
|
|
||||||
private var intrinsicInputPanelBackgroundNodeSize: CGSize?
|
private var intrinsicInputPanelBackgroundNodeSize: CGSize?
|
||||||
private let inputPanelBackgroundSeparatorNode: ASDisplayNode
|
|
||||||
private var inputPanelBottomBackgroundSeparatorBaseOffset: CGFloat = 0.0
|
private var inputPanelBottomBackgroundSeparatorBaseOffset: CGFloat = 0.0
|
||||||
private let inputPanelBottomBackgroundSeparatorNode: ASDisplayNode
|
|
||||||
private var plainInputSeparatorAlpha: CGFloat?
|
private var plainInputSeparatorAlpha: CGFloat?
|
||||||
private var usePlainInputSeparator: Bool
|
private var usePlainInputSeparator: Bool
|
||||||
|
|
||||||
|
|
@ -237,6 +235,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
private var leftPanelContainer: ChatControllerTitlePanelNodeContainer
|
private var leftPanelContainer: ChatControllerTitlePanelNodeContainer
|
||||||
private(set) var leftPanel: (component: AnyComponentWithIdentity<ChatSidePanelEnvironment>, view: ComponentView<ChatSidePanelEnvironment>)?
|
private(set) var leftPanel: (component: AnyComponentWithIdentity<ChatSidePanelEnvironment>, view: ComponentView<ChatSidePanelEnvironment>)?
|
||||||
|
|
||||||
|
private var inputPanelBackgroundBlurMask: UIImageView?
|
||||||
|
private var inputPanelBackgroundBlurView: VariableBlurView?
|
||||||
|
|
||||||
private(set) var inputPanelNode: ChatInputPanelNode?
|
private(set) var inputPanelNode: ChatInputPanelNode?
|
||||||
private(set) var inputPanelOverscrollNode: ChatInputPanelOverscrollNode?
|
private(set) var inputPanelOverscrollNode: ChatInputPanelOverscrollNode?
|
||||||
private weak var currentDismissedInputPanelNode: ChatInputPanelNode?
|
private weak var currentDismissedInputPanelNode: ChatInputPanelNode?
|
||||||
|
|
@ -736,22 +737,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
self.inputPanelClippingNode = SparseNode()
|
self.inputPanelClippingNode = SparseNode()
|
||||||
|
|
||||||
if case let .color(color) = self.chatPresentationInterfaceState.chatWallpaper, UIColor(rgb: color).isEqual(self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) {
|
if case let .color(color) = self.chatPresentationInterfaceState.chatWallpaper, UIColor(rgb: color).isEqual(self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) {
|
||||||
self.inputPanelBackgroundNode = NavigationBackgroundNode(color: self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper)
|
self.inputPanelBackgroundNode = NavigationBackgroundNode(color: .clear)
|
||||||
self.usePlainInputSeparator = true
|
self.usePlainInputSeparator = true
|
||||||
} else {
|
} else {
|
||||||
self.inputPanelBackgroundNode = NavigationBackgroundNode(color: self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColor)
|
self.inputPanelBackgroundNode = NavigationBackgroundNode(color: .clear)
|
||||||
self.usePlainInputSeparator = false
|
self.usePlainInputSeparator = false
|
||||||
self.plainInputSeparatorAlpha = nil
|
self.plainInputSeparatorAlpha = nil
|
||||||
}
|
}
|
||||||
//self.inputPanelBackgroundNode.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.inputPanelBackgroundSeparatorNode = ASDisplayNode()
|
|
||||||
self.inputPanelBackgroundSeparatorNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputPanel.panelSeparatorColor
|
|
||||||
self.inputPanelBackgroundSeparatorNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.inputPanelBottomBackgroundSeparatorNode = ASDisplayNode()
|
|
||||||
self.inputPanelBottomBackgroundSeparatorNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputMediaPanel.panelSeparatorColor
|
|
||||||
self.inputPanelBottomBackgroundSeparatorNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.navigateButtons = ChatHistoryNavigationButtons(theme: self.chatPresentationInterfaceState.theme, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, backgroundNode: self.backgroundNode, isChatRotated: historyNodeRotated)
|
self.navigateButtons = ChatHistoryNavigationButtons(theme: self.chatPresentationInterfaceState.theme, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, backgroundNode: self.backgroundNode, isChatRotated: historyNodeRotated)
|
||||||
self.navigateButtons.accessibilityElementsHidden = true
|
self.navigateButtons.accessibilityElementsHidden = true
|
||||||
|
|
@ -851,8 +843,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode)
|
self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode)
|
||||||
self.inputPanelContainerNode.addSubnode(self.inputPanelOverlayNode)
|
self.inputPanelContainerNode.addSubnode(self.inputPanelOverlayNode)
|
||||||
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode)
|
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode)
|
||||||
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundSeparatorNode)
|
|
||||||
self.inputPanelBackgroundNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode)
|
|
||||||
|
|
||||||
self.wrappingNode.contentNode.addSubnode(self.messageTransitionNode)
|
self.wrappingNode.contentNode.addSubnode(self.messageTransitionNode)
|
||||||
self.contentContainerNode.contentNode.addSubnode(self.navigateButtons)
|
self.contentContainerNode.contentNode.addSubnode(self.navigateButtons)
|
||||||
|
|
@ -1622,7 +1612,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
var dismissedInputContextPanelNode: ChatInputContextPanelNode?
|
var dismissedInputContextPanelNode: ChatInputContextPanelNode?
|
||||||
var dismissedOverlayContextPanelNode: ChatInputContextPanelNode?
|
var dismissedOverlayContextPanelNode: ChatInputContextPanelNode?
|
||||||
|
|
||||||
let inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction)
|
let inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, chatControllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction)
|
||||||
|
|
||||||
let inputPanelBottomInset = max(insets.bottom, inputPanelBottomInsetTerm)
|
let inputPanelBottomInset = max(insets.bottom, inputPanelBottomInsetTerm)
|
||||||
|
|
||||||
|
|
@ -1751,15 +1741,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
var dismissedInputNode: ChatInputNode?
|
var dismissedInputNode: ChatInputNode?
|
||||||
var dismissedInputNodeInputBackgroundExtension: CGFloat = 0.0
|
|
||||||
var dismissedInputNodeExternalTopPanelContainer: UIView?
|
var dismissedInputNodeExternalTopPanelContainer: UIView?
|
||||||
var immediatelyLayoutInputNodeAndAnimateAppearance = false
|
var immediatelyLayoutInputNodeAndAnimateAppearance = false
|
||||||
var inputNodeHeightAndOverflow: (CGFloat, CGFloat)?
|
var inputNodeHeightAndOverflow: (CGFloat, CGFloat)?
|
||||||
if let inputNode = inputNodeForState {
|
if let inputNode = inputNodeForState {
|
||||||
if self.inputNode != inputNode {
|
if self.inputNode != inputNode {
|
||||||
inputNode.topBackgroundExtensionUpdated = { [weak self] transition in
|
|
||||||
self?.updateInputPanelBackgroundExtension(transition: transition)
|
|
||||||
}
|
|
||||||
inputNode.hideInputUpdated = { [weak self] transition in
|
inputNode.hideInputUpdated = { [weak self] transition in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
|
@ -1774,11 +1760,14 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let inputNode = inputNode as? ChatEntityKeyboardInputNode {
|
||||||
|
inputNode.externalTopPanelContainerImpl = nil
|
||||||
|
}
|
||||||
|
inputNode.clipsToBounds = true
|
||||||
|
inputNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
inputNode.layer.cornerRadius = 30.0
|
||||||
|
|
||||||
dismissedInputNode = self.inputNode
|
dismissedInputNode = self.inputNode
|
||||||
if let inputNode = self.inputNode {
|
|
||||||
dismissedInputNodeInputBackgroundExtension = inputNode.topBackgroundExtension
|
|
||||||
}
|
|
||||||
dismissedInputNodeExternalTopPanelContainer = self.inputNode?.externalTopPanelContainer
|
dismissedInputNodeExternalTopPanelContainer = self.inputNode?.externalTopPanelContainer
|
||||||
self.inputNode = inputNode
|
self.inputNode = inputNode
|
||||||
inputNode.alpha = 1.0
|
inputNode.alpha = 1.0
|
||||||
|
|
@ -1812,7 +1801,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
)
|
)
|
||||||
} else if let inputNode = self.inputNode {
|
} else if let inputNode = self.inputNode {
|
||||||
dismissedInputNode = inputNode
|
dismissedInputNode = inputNode
|
||||||
dismissedInputNodeInputBackgroundExtension = inputNode.topBackgroundExtension
|
|
||||||
dismissedInputNodeExternalTopPanelContainer = inputNode.externalTopPanelContainer
|
dismissedInputNodeExternalTopPanelContainer = inputNode.externalTopPanelContainer
|
||||||
self.inputNode = nil
|
self.inputNode = nil
|
||||||
}
|
}
|
||||||
|
|
@ -2142,6 +2130,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
self.overlayContextPanelNode = nil
|
self.overlayContextPanelNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inputPanelsInset: CGFloat = 8.0
|
||||||
|
let accessoryPanelsInset: CGFloat = 8.0
|
||||||
var inputPanelsHeight: CGFloat = 0.0
|
var inputPanelsHeight: CGFloat = 0.0
|
||||||
|
|
||||||
var inputPanelFrame: CGRect?
|
var inputPanelFrame: CGRect?
|
||||||
|
|
@ -2158,7 +2148,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.inputPanelNode != nil {
|
if self.inputPanelNode != nil {
|
||||||
inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - inputPanelSize!.height), size: CGSize(width: layout.size.width, height: inputPanelSize!.height))
|
inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - inputPanelsInset - inputPanelSize!.height), size: CGSize(width: layout.size.width, height: inputPanelSize!.height))
|
||||||
inputPanelFrame = inputPanelFrame!.offsetBy(dx: 0.0, dy: inputPanelHideOffset)
|
inputPanelFrame = inputPanelFrame!.offsetBy(dx: 0.0, dy: inputPanelHideOffset)
|
||||||
if self.dismissedAsOverlay {
|
if self.dismissedAsOverlay {
|
||||||
inputPanelFrame!.origin.y = layout.size.height
|
inputPanelFrame!.origin.y = layout.size.height
|
||||||
|
|
@ -2181,7 +2171,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
var accessoryPanelFrame: CGRect?
|
var accessoryPanelFrame: CGRect?
|
||||||
if self.accessoryPanelNode != nil {
|
if self.accessoryPanelNode != nil {
|
||||||
assert(accessoryPanelSize != nil)
|
assert(accessoryPanelSize != nil)
|
||||||
accessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomOverflowOffset - insets.bottom - inputPanelsHeight - accessoryPanelSize!.height), size: CGSize(width: layout.size.width, height: accessoryPanelSize!.height))
|
accessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomOverflowOffset - insets.bottom - inputPanelsInset - inputPanelsHeight - accessoryPanelsInset - accessoryPanelSize!.height), size: CGSize(width: layout.size.width, height: accessoryPanelSize!.height))
|
||||||
accessoryPanelFrame = accessoryPanelFrame!.offsetBy(dx: 0.0, dy: inputPanelHideOffset)
|
accessoryPanelFrame = accessoryPanelFrame!.offsetBy(dx: 0.0, dy: inputPanelHideOffset)
|
||||||
if self.dismissedAsOverlay {
|
if self.dismissedAsOverlay {
|
||||||
accessoryPanelFrame!.origin.y = layout.size.height
|
accessoryPanelFrame!.origin.y = layout.size.height
|
||||||
|
|
@ -2202,18 +2192,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputBackgroundInset: CGFloat
|
var inputBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - inputPanelsInset), size: CGSize(width: layout.size.width, height: inputPanelsHeight))
|
||||||
if cleanInsets.bottom < insets.bottom {
|
|
||||||
if case .regular = layout.metrics.widthClass, insets.bottom < 88.0 {
|
|
||||||
inputBackgroundInset = insets.bottom
|
|
||||||
} else {
|
|
||||||
inputBackgroundInset = 0.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
inputBackgroundInset = cleanInsets.bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight), size: CGSize(width: layout.size.width, height: inputPanelsHeight + inputBackgroundInset))
|
|
||||||
if self.dismissedAsOverlay {
|
if self.dismissedAsOverlay {
|
||||||
inputBackgroundFrame.origin.y = layout.size.height
|
inputBackgroundFrame.origin.y = layout.size.height
|
||||||
}
|
}
|
||||||
|
|
@ -2223,6 +2202,58 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !"".isEmpty {
|
||||||
|
let blurView: VariableBlurView
|
||||||
|
let blurMask: UIImageView
|
||||||
|
if let current = self.inputPanelBackgroundBlurMask {
|
||||||
|
blurMask = current
|
||||||
|
} else {
|
||||||
|
blurMask = UIImageView()
|
||||||
|
self.inputPanelBackgroundBlurMask = blurMask
|
||||||
|
|
||||||
|
blurMask.image = generateGradientImage(size: CGSize(width: 8.0, height: 16.0), colors: [UIColor(white: 1.0, alpha: 0.0), UIColor(white: 1.0, alpha: 0.0), UIColor(white: 1.0, alpha: 1.0)], locations: [0.0, 0.5, 1.0])?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let current = self.inputPanelBackgroundBlurView {
|
||||||
|
blurView = current
|
||||||
|
} else {
|
||||||
|
let baseGradientAlpha: CGFloat = 0.5
|
||||||
|
let numSteps = 8
|
||||||
|
let firstStep = 1
|
||||||
|
let firstLocation = 0.5
|
||||||
|
let colors = (0 ..< numSteps).map { i -> UIColor in
|
||||||
|
if i < firstStep {
|
||||||
|
return UIColor(white: 1.0, alpha: 1.0)
|
||||||
|
} else {
|
||||||
|
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||||
|
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
||||||
|
return UIColor(white: 1.0, alpha: baseGradientAlpha * value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let locations = (0 ..< numSteps).map { i -> CGFloat in
|
||||||
|
if i < firstStep {
|
||||||
|
return 0.0
|
||||||
|
} else {
|
||||||
|
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||||
|
return (firstLocation + (1.0 - firstLocation) * step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundBlurImage = generateGradientImage(size: CGSize(width: 8.0, height: 100.0), colors: colors.reversed(), locations: locations.reversed().map { 1.0 - $0 })!
|
||||||
|
blurView = VariableBlurView(gradientMask: backgroundBlurImage, maxBlurRadius: 15.0)
|
||||||
|
self.inputPanelBackgroundBlurView = blurView
|
||||||
|
self.historyNodeContainer.view.superview?.insertSubview(blurView, aboveSubview: self.historyNodeContainer.view)
|
||||||
|
|
||||||
|
blurView.mask = blurMask
|
||||||
|
}
|
||||||
|
|
||||||
|
var blurFrame = inputBackgroundFrame
|
||||||
|
blurFrame.origin.y -= 18.0
|
||||||
|
blurFrame.size.height += 100.0
|
||||||
|
transition.updateFrame(view: blurView, frame: blurFrame)
|
||||||
|
transition.updateFrame(view: blurMask, frame: CGRect(origin: CGPoint(), size: blurFrame.size))
|
||||||
|
}
|
||||||
|
|
||||||
let additionalScrollDistance: CGFloat = 0.0
|
let additionalScrollDistance: CGFloat = 0.0
|
||||||
var scrollToTop = false
|
var scrollToTop = false
|
||||||
if dismissedInputByDragging {
|
if dismissedInputByDragging {
|
||||||
|
|
@ -2233,7 +2264,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentBottomInset: CGFloat = inputPanelsHeight + 4.0
|
var contentBottomInset: CGFloat = inputPanelsHeight + 11.0 + inputPanelsInset
|
||||||
|
|
||||||
if let scrollContainerNode = self.scrollContainerNode {
|
if let scrollContainerNode = self.scrollContainerNode {
|
||||||
transition.updateFrame(node: scrollContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: scrollContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
@ -2428,7 +2459,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
self.historyNode.scrollEnabled = !self.isScrollingLockedAtTop
|
self.historyNode.scrollEnabled = !self.isScrollingLockedAtTop
|
||||||
|
|
||||||
let navigateButtonsSize = self.navigateButtons.updateLayout(transition: transition)
|
let navigateButtonsSize = self.navigateButtons.updateLayout(transition: transition)
|
||||||
var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 6.0), size: navigateButtonsSize)
|
var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 8.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 20.0), size: navigateButtonsSize)
|
||||||
if case .overlay = self.chatPresentationInterfaceState.mode {
|
if case .overlay = self.chatPresentationInterfaceState.mode {
|
||||||
navigateButtonsFrame = navigateButtonsFrame.offsetBy(dx: -8.0, dy: -8.0)
|
navigateButtonsFrame = navigateButtonsFrame.offsetBy(dx: -8.0, dy: -8.0)
|
||||||
}
|
}
|
||||||
|
|
@ -2533,45 +2564,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
navigationBarBackgroundContent.update(rect: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), within: layout.size, transition: transition)
|
navigationBarBackgroundContent.update(rect: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), within: layout.size, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let inputPanelBackgroundContent = self.inputPanelBackgroundContent {
|
|
||||||
var extensionValue: CGFloat = 0.0
|
|
||||||
if let inputNode = self.inputNode {
|
|
||||||
extensionValue = inputNode.topBackgroundExtension
|
|
||||||
}
|
|
||||||
let apparentInputBackgroundFrame = CGRect(origin: apparentInputBackgroundFrame.origin, size: CGSize(width: apparentInputBackgroundFrame.width, height: apparentInputBackgroundFrame.height + extensionValue))
|
|
||||||
var transition = transition
|
|
||||||
var delay: Double = 0.0
|
|
||||||
if apparentInputBackgroundFrame.height > inputPanelBackgroundContent.frame.height {
|
|
||||||
transition = .immediate
|
|
||||||
} else if case let .animated(_, curve) = transition, case .spring = curve {
|
|
||||||
delay = 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: inputPanelBackgroundContent, frame: CGRect(origin: .zero, size: apparentInputBackgroundFrame.size), beginWithCurrentState: true, delay: delay)
|
|
||||||
inputPanelBackgroundContent.update(rect: apparentInputBackgroundFrame, within: layout.size, delay: delay, transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentDimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: apparentInputBackgroundFrame.origin.y)))
|
transition.updateFrame(node: self.contentDimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: apparentInputBackgroundFrame.origin.y)))
|
||||||
|
|
||||||
let intrinsicInputPanelBackgroundNodeSize = CGSize(width: apparentInputBackgroundFrame.size.width, height: apparentInputBackgroundFrame.size.height)
|
let intrinsicInputPanelBackgroundNodeSize = CGSize(width: apparentInputBackgroundFrame.size.width, height: apparentInputBackgroundFrame.size.height)
|
||||||
self.intrinsicInputPanelBackgroundNodeSize = intrinsicInputPanelBackgroundNodeSize
|
self.intrinsicInputPanelBackgroundNodeSize = intrinsicInputPanelBackgroundNodeSize
|
||||||
var inputPanelBackgroundExtension: CGFloat = 0.0
|
|
||||||
if let inputNode = self.inputNode {
|
|
||||||
inputPanelBackgroundExtension = inputNode.topBackgroundExtension
|
|
||||||
} else {
|
|
||||||
inputPanelBackgroundExtension = dismissedInputNodeInputBackgroundExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputPanelUpdateTransition = transition
|
|
||||||
if immediatelyLayoutInputNodeAndAnimateAppearance {
|
|
||||||
inputPanelUpdateTransition = .immediate
|
|
||||||
}
|
|
||||||
|
|
||||||
self.inputPanelBackgroundNode.update(size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), transition: inputPanelUpdateTransition, beginWithCurrentState: true)
|
|
||||||
self.inputPanelBottomBackgroundSeparatorBaseOffset = intrinsicInputPanelBackgroundNodeSize.height
|
self.inputPanelBottomBackgroundSeparatorBaseOffset = intrinsicInputPanelBackgroundNodeSize.height
|
||||||
inputPanelUpdateTransition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: UIScreenPixel)), beginWithCurrentState: true)
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel)))
|
|
||||||
transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
|
transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
|
||||||
self.navigateButtons.update(rect: apparentNavigateButtonsFrame, within: layout.size, transition: transition)
|
self.navigateButtons.update(rect: apparentNavigateButtonsFrame, within: layout.size, transition: transition)
|
||||||
|
|
||||||
|
|
@ -2703,10 +2702,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
if immediatelyLayoutInputNodeAndAnimateAppearance {
|
if immediatelyLayoutInputNodeAndAnimateAppearance {
|
||||||
var adjustedForPreviousInputHeightFrame = inputNodeFrame
|
var adjustedForPreviousInputHeightFrame = inputNodeFrame
|
||||||
var heightDifference = inputNodeHeight - previousInputHeight
|
var heightDifference = inputNodeHeight - previousInputHeight
|
||||||
var externalTopPanelContainerOffset: CGFloat = 0.0
|
|
||||||
if previousInputHeight.isLessThanOrEqualTo(cleanInsets.bottom) {
|
if previousInputHeight.isLessThanOrEqualTo(cleanInsets.bottom) {
|
||||||
heightDifference = inputNodeHeight - inputPanelBackgroundExtension
|
heightDifference = inputNodeHeight
|
||||||
externalTopPanelContainerOffset = inputPanelBackgroundExtension
|
|
||||||
}
|
}
|
||||||
adjustedForPreviousInputHeightFrame.origin.y += heightDifference
|
adjustedForPreviousInputHeightFrame.origin.y += heightDifference
|
||||||
inputNode.frame = adjustedForPreviousInputHeightFrame
|
inputNode.frame = adjustedForPreviousInputHeightFrame
|
||||||
|
|
@ -2715,7 +2712,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
inputNode.updateAbsoluteRect(inputNodeFrame, within: layout.size, transition: transition)
|
inputNode.updateAbsoluteRect(inputNodeFrame, within: layout.size, transition: transition)
|
||||||
|
|
||||||
if let externalTopPanelContainer = inputNode.externalTopPanelContainer {
|
if let externalTopPanelContainer = inputNode.externalTopPanelContainer {
|
||||||
externalTopPanelContainer.frame = CGRect(origin: adjustedForPreviousInputHeightFrame.offsetBy(dx: 0.0, dy: externalTopPanelContainerOffset).origin, size: CGSize(width: adjustedForPreviousInputHeightFrame.width, height: 0.0))
|
externalTopPanelContainer.frame = CGRect(origin: adjustedForPreviousInputHeightFrame.offsetBy(dx: 0.0, dy: 0.0).origin, size: CGSize(width: adjustedForPreviousInputHeightFrame.width, height: 0.0))
|
||||||
transition.updateFrame(view: externalTopPanelContainer, frame: CGRect(origin: inputNodeFrame.origin, size: CGSize(width: inputNodeFrame.width, height: 0.0)))
|
transition.updateFrame(view: externalTopPanelContainer, frame: CGRect(origin: inputNodeFrame.origin, size: CGSize(width: inputNodeFrame.width, height: 0.0)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3482,28 +3479,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
//self.notifyTransitionCompletionListeners(transition: transition)
|
//self.notifyTransitionCompletionListeners(transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateInputPanelBackgroundExtension(transition: ContainedViewLayoutTransition) {
|
|
||||||
guard let intrinsicInputPanelBackgroundNodeSize = self.intrinsicInputPanelBackgroundNodeSize else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var extensionValue: CGFloat = 0.0
|
|
||||||
if let inputNode = self.inputNode {
|
|
||||||
extensionValue = inputNode.topBackgroundExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
self.inputPanelBackgroundNode.update(size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: intrinsicInputPanelBackgroundNodeSize.height + extensionValue), transition: transition)
|
|
||||||
transition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.inputPanelBottomBackgroundSeparatorBaseOffset + extensionValue), size: CGSize(width: self.inputPanelBottomBackgroundSeparatorNode.bounds.width, height: UIScreenPixel)), beginWithCurrentState: true)
|
|
||||||
|
|
||||||
if let inputPanelBackgroundContent = self.inputPanelBackgroundContent, let (layout, _) = self.validLayout {
|
|
||||||
var inputPanelBackgroundFrame = self.inputPanelBackgroundNode.frame
|
|
||||||
inputPanelBackgroundFrame.size.height = intrinsicInputPanelBackgroundNodeSize.height + extensionValue
|
|
||||||
|
|
||||||
transition.updateFrame(node: inputPanelBackgroundContent, frame: CGRect(origin: .zero, size: inputPanelBackgroundFrame.size))
|
|
||||||
inputPanelBackgroundContent.update(rect: inputPanelBackgroundFrame, within: layout.size, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var storedHideInputExpanded: Bool?
|
private var storedHideInputExpanded: Bool?
|
||||||
|
|
||||||
private func updateInputPanelBackgroundExpansion(transition: ContainedViewLayoutTransition) {
|
private func updateInputPanelBackgroundExpansion(transition: ContainedViewLayoutTransition) {
|
||||||
|
|
@ -3601,43 +3576,30 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
if case let .color(color) = self.chatPresentationInterfaceState.chatWallpaper, UIColor(rgb: color).isEqual(self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) {
|
if case let .color(color) = self.chatPresentationInterfaceState.chatWallpaper, UIColor(rgb: color).isEqual(self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) {
|
||||||
self.inputPanelBackgroundNode.updateColor(color: self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper, transition: .immediate)
|
|
||||||
self.usePlainInputSeparator = true
|
self.usePlainInputSeparator = true
|
||||||
} else {
|
} else {
|
||||||
self.inputPanelBackgroundNode.updateColor(color: self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColor, transition: .immediate)
|
|
||||||
self.usePlainInputSeparator = false
|
self.usePlainInputSeparator = false
|
||||||
self.plainInputSeparatorAlpha = nil
|
self.plainInputSeparatorAlpha = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updatePlainInputSeparator(transition: .immediate)
|
self.updatePlainInputSeparator(transition: .immediate)
|
||||||
self.inputPanelBackgroundSeparatorNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputPanel.panelSeparatorColor
|
|
||||||
self.inputPanelBottomBackgroundSeparatorNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputMediaPanel.panelSeparatorColor
|
|
||||||
|
|
||||||
self.backgroundNode.updateBubbleTheme(bubbleTheme: chatPresentationInterfaceState.theme, bubbleCorners: chatPresentationInterfaceState.bubbleCorners)
|
self.backgroundNode.updateBubbleTheme(bubbleTheme: chatPresentationInterfaceState.theme, bubbleCorners: chatPresentationInterfaceState.bubbleCorners)
|
||||||
|
|
||||||
if self.backgroundNode.hasExtraBubbleBackground() {
|
if self.backgroundNode.hasExtraBubbleBackground() {
|
||||||
if self.navigationBarBackgroundContent == nil {
|
if self.navigationBarBackgroundContent == nil {
|
||||||
if let navigationBarBackgroundContent = self.backgroundNode.makeBubbleBackground(for: .free),
|
if let navigationBarBackgroundContent = self.backgroundNode.makeBubbleBackground(for: .free) {
|
||||||
let inputPanelBackgroundContent = self.backgroundNode.makeBubbleBackground(for: .free) {
|
|
||||||
self.navigationBarBackgroundContent = navigationBarBackgroundContent
|
self.navigationBarBackgroundContent = navigationBarBackgroundContent
|
||||||
self.inputPanelBackgroundContent = inputPanelBackgroundContent
|
|
||||||
|
|
||||||
navigationBarBackgroundContent.allowsGroupOpacity = true
|
navigationBarBackgroundContent.allowsGroupOpacity = true
|
||||||
navigationBarBackgroundContent.implicitContentUpdate = false
|
navigationBarBackgroundContent.implicitContentUpdate = false
|
||||||
navigationBarBackgroundContent.alpha = 0.3
|
navigationBarBackgroundContent.alpha = 0.3
|
||||||
self.navigationBar?.insertSubnode(navigationBarBackgroundContent, at: 1)
|
self.navigationBar?.insertSubnode(navigationBarBackgroundContent, at: 1)
|
||||||
|
|
||||||
inputPanelBackgroundContent.allowsGroupOpacity = true
|
|
||||||
inputPanelBackgroundContent.implicitContentUpdate = false
|
|
||||||
inputPanelBackgroundContent.alpha = 0.3
|
|
||||||
self.inputPanelBackgroundNode.addSubnode(inputPanelBackgroundContent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.navigationBarBackgroundContent?.removeFromSupernode()
|
self.navigationBarBackgroundContent?.removeFromSupernode()
|
||||||
self.navigationBarBackgroundContent = nil
|
self.navigationBarBackgroundContent = nil
|
||||||
self.inputPanelBackgroundContent?.removeFromSupernode()
|
|
||||||
self.inputPanelBackgroundContent = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4124,7 +4086,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
|
|
||||||
let inputPanelsOffset = self.bounds.size.height - self.inputPanelBackgroundNode.frame.minY
|
let inputPanelsOffset = self.bounds.size.height - self.inputPanelBackgroundNode.frame.minY
|
||||||
transition.animateFrame(node: self.inputPanelBackgroundNode, from: self.inputPanelBackgroundNode.frame.offsetBy(dx: 0.0, dy: inputPanelsOffset))
|
transition.animateFrame(node: self.inputPanelBackgroundNode, from: self.inputPanelBackgroundNode.frame.offsetBy(dx: 0.0, dy: inputPanelsOffset))
|
||||||
transition.animateFrame(node: self.inputPanelBackgroundSeparatorNode, from: self.inputPanelBackgroundSeparatorNode.frame.offsetBy(dx: 0.0, dy: inputPanelsOffset))
|
|
||||||
if let inputPanelNode = self.inputPanelNode {
|
if let inputPanelNode = self.inputPanelNode {
|
||||||
transition.animateFrame(node: inputPanelNode, from: inputPanelNode.frame.offsetBy(dx: 0.0, dy: inputPanelsOffset))
|
transition.animateFrame(node: inputPanelNode, from: inputPanelNode.frame.offsetBy(dx: 0.0, dy: inputPanelsOffset))
|
||||||
}
|
}
|
||||||
|
|
@ -4595,7 +4556,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
return interfaceState.withUpdatedReplyMessageSubject(replyMessageSubject)
|
return interfaceState.withUpdatedReplyMessageSubject(replyMessageSubject)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
presentChatLinkOptions(selfController: controller, sourceNode: controller.displayNode)
|
presentChatLinkOptions(selfController: controller, sourceView: controller.displayNode.view)
|
||||||
}),
|
}),
|
||||||
], parseMarkdown: true), in: .window(.root))
|
], parseMarkdown: true), in: .window(.root))
|
||||||
|
|
||||||
|
|
@ -4865,10 +4826,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedValue = resolvedValue * (1.0 - self.inputPanelContainerNode.expansionFraction)
|
resolvedValue = resolvedValue * (1.0 - self.inputPanelContainerNode.expansionFraction)
|
||||||
|
|
||||||
if resolvedValue != self.inputPanelBackgroundSeparatorNode.alpha {
|
|
||||||
transition.updateAlpha(node: self.inputPanelBackgroundSeparatorNode, alpha: resolvedValue, beginWithCurrentState: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var previousConfettiAnimationTimestamp: Double?
|
private var previousConfettiAnimationTimestamp: Double?
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import Display
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
import AnimatedCountLabelNode
|
import AnimatedCountLabelNode
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
import ComponentFlow
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
|
||||||
private let badgeFont = Font.with(size: 13.0, traits: [.monospacedNumbers])
|
private let badgeFont = Font.with(size: 13.0, traits: [.monospacedNumbers])
|
||||||
|
|
||||||
|
|
@ -18,11 +21,9 @@ enum ChatHistoryNavigationButtonType {
|
||||||
class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
let containerNode: ContextExtractedContentContainingNode
|
let containerNode: ContextExtractedContentContainingNode
|
||||||
let buttonNode: HighlightTrackingButtonNode
|
let buttonNode: HighlightTrackingButtonNode
|
||||||
private let backgroundNode: NavigationBackgroundNode
|
private let backgroundView: GlassBackgroundView
|
||||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
let imageView: GlassBackgroundView.ContentImageView
|
||||||
let backgroundImageNode: ASImageNode
|
private let badgeBackgroundView: GlassBackgroundView
|
||||||
let imageNode: ASImageNode
|
|
||||||
private let badgeBackgroundNode: ASImageNode
|
|
||||||
private let badgeTextNode: ImmediateAnimatedCountLabelNode
|
private let badgeTextNode: ImmediateAnimatedCountLabelNode
|
||||||
|
|
||||||
var tapped: (() -> Void)? {
|
var tapped: (() -> Void)? {
|
||||||
|
|
@ -55,32 +56,22 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
self.containerNode = ContextExtractedContentContainingNode()
|
self.containerNode = ContextExtractedContentContainingNode()
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor)
|
self.backgroundView = GlassBackgroundView()
|
||||||
|
|
||||||
self.backgroundImageNode = ASImageNode()
|
self.imageView = GlassBackgroundView.ContentImageView()
|
||||||
self.backgroundImageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBackground(theme)
|
|
||||||
self.backgroundImageNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.backgroundImageNode.displayWithoutProcessing = true
|
|
||||||
self.imageNode = ASImageNode()
|
|
||||||
self.imageNode.displayWithoutProcessing = true
|
|
||||||
switch type {
|
switch type {
|
||||||
case .down:
|
case .down:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
||||||
case .up:
|
case .up:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
||||||
case .mentions:
|
case .mentions:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
||||||
case .reactions:
|
case .reactions:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme)
|
||||||
}
|
}
|
||||||
self.imageNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.badgeBackgroundNode = ASImageNode()
|
self.badgeBackgroundView = GlassBackgroundView()
|
||||||
self.badgeBackgroundNode.displayWithoutProcessing = true
|
self.badgeBackgroundView.alpha = 0.0
|
||||||
self.badgeBackgroundNode.displaysAsynchronously = false
|
|
||||||
self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme)
|
|
||||||
self.badgeBackgroundNode.alpha = 0.0
|
|
||||||
|
|
||||||
self.badgeTextNode = ImmediateAnimatedCountLabelNode()
|
self.badgeTextNode = ImmediateAnimatedCountLabelNode()
|
||||||
self.badgeTextNode.isUserInteractionEnabled = false
|
self.badgeTextNode.isUserInteractionEnabled = false
|
||||||
|
|
@ -93,7 +84,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
|
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
let size = CGSize(width: 38.0, height: 38.0)
|
let size = CGSize(width: 40.0, height: 40.0)
|
||||||
|
|
||||||
self.containerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.containerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.containerNode.contentRect = CGRect(origin: CGPoint(), size: size)
|
self.containerNode.contentRect = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
@ -101,17 +92,16 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.containerNode.contentNode.addSubnode(self.buttonNode)
|
self.containerNode.contentNode.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
self.buttonNode.addSubnode(self.backgroundNode)
|
self.buttonNode.view.addSubview(self.backgroundView)
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: size.width / 2.0, transition: .immediate)
|
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate)
|
||||||
|
self.imageView.tintColor = theme.chat.inputPanel.inputControlColor
|
||||||
|
|
||||||
self.buttonNode.addSubnode(self.backgroundImageNode)
|
self.backgroundView.contentView.addSubview(self.imageView)
|
||||||
self.buttonNode.addSubnode(self.imageNode)
|
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.backgroundImageNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
|
|
||||||
self.buttonNode.addSubnode(self.badgeBackgroundNode)
|
self.buttonNode.view.addSubview(self.badgeBackgroundView)
|
||||||
self.badgeBackgroundNode.addSubnode(self.badgeTextNode)
|
self.badgeBackgroundView.contentView.addSubview(self.badgeTextNode.view)
|
||||||
|
|
||||||
self.frame = CGRect(origin: CGPoint(), size: size)
|
self.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
}
|
}
|
||||||
|
|
@ -120,19 +110,21 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.backgroundNode.updateColor(color: theme.chat.inputPanel.panelBackgroundColor, transition: .immediate)
|
self.backgroundView.update(size: self.backgroundView.bounds.size, cornerRadius: self.backgroundView.bounds.size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate)
|
||||||
|
self.imageView.tintColor = theme.chat.inputPanel.inputControlColor
|
||||||
|
|
||||||
switch self.type {
|
switch self.type {
|
||||||
case .down:
|
case .down:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
||||||
case .up:
|
case .up:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
||||||
case .mentions:
|
case .mentions:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
||||||
case .reactions:
|
case .reactions:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme)
|
||||||
}
|
}
|
||||||
self.backgroundImageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBackground(theme)
|
|
||||||
self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme)
|
self.badgeBackgroundView.update(size: self.badgeBackgroundView.bounds.size, cornerRadius: self.badgeBackgroundView.bounds.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.actionControlFillColor, transition: .immediate)
|
||||||
|
|
||||||
var segments: [AnimatedCountLabelNode.Segment] = []
|
var segments: [AnimatedCountLabelNode.Segment] = []
|
||||||
if let value = Int(self.badge) {
|
if let value = Int(self.badge) {
|
||||||
|
|
@ -144,34 +136,11 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
}
|
}
|
||||||
self.badgeTextNode.segments = segments
|
self.badgeTextNode.segments = segments
|
||||||
}
|
}
|
||||||
|
|
||||||
if backgroundNode.hasExtraBubbleBackground() {
|
|
||||||
if self.backgroundContent == nil {
|
|
||||||
if let backgroundContent = backgroundNode.makeBubbleBackground(for: .free) {
|
|
||||||
backgroundContent.allowsGroupOpacity = true
|
|
||||||
backgroundContent.clipsToBounds = true
|
|
||||||
backgroundContent.alpha = 0.3
|
|
||||||
backgroundContent.cornerRadius = 19.0
|
|
||||||
backgroundContent.frame = self.backgroundNode.frame
|
|
||||||
self.buttonNode.insertSubnode(backgroundContent, aboveSubnode: self.backgroundNode)
|
|
||||||
self.backgroundContent = backgroundContent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.backgroundContent?.removeFromSupernode()
|
|
||||||
self.backgroundContent = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
|
||||||
self.backgroundContent?.update(rect: rect, within: containerSize, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var absoluteRect: (CGRect, CGSize)?
|
private var absoluteRect: (CGRect, CGSize)?
|
||||||
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
self.absoluteRect = (rect, containerSize)
|
self.absoluteRect = (rect, containerSize)
|
||||||
|
|
||||||
self.backgroundContent?.update(rect: rect, within: containerSize, transition: transition)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func onTap() {
|
@objc func onTap() {
|
||||||
|
|
@ -195,43 +164,47 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
self.badgeTextNode.segments = segments
|
self.badgeTextNode.segments = segments
|
||||||
|
|
||||||
let badgeSize = self.badgeTextNode.updateLayout(size: CGSize(width: 200.0, height: 100.0), animated: true)
|
let badgeSize = self.badgeTextNode.updateLayout(size: CGSize(width: 200.0, height: 100.0), animated: true)
|
||||||
let backgroundSize = CGSize(width: self.badge.count == 1 ? 18.0 : max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0)
|
let backgroundSize = CGSize(width: self.badge.count == 1 ? 20.0 : max(20.0, badgeSize.width + 10.0 + 1.0), height: 20.0)
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(x: floor((38.0 - backgroundSize.width) / 2.0), y: -9.0), size: backgroundSize)
|
let backgroundFrame = CGRect(origin: CGPoint(x: floor((40.0 - backgroundSize.width) / 2.0), y: -7.0), size: backgroundSize)
|
||||||
if backgroundFrame.width < self.badgeBackgroundNode.frame.width {
|
if backgroundFrame.width < self.badgeBackgroundView.frame.width {
|
||||||
self.badgeBackgroundNode.layer.animateFrame(from: self.badgeBackgroundNode.frame, to: backgroundFrame, duration: 0.2)
|
self.badgeBackgroundView.layer.animateFrame(from: self.badgeBackgroundView.frame, to: backgroundFrame, duration: 0.2)
|
||||||
self.badgeBackgroundNode.frame = backgroundFrame
|
self.badgeBackgroundView.frame = backgroundFrame
|
||||||
} else {
|
} else {
|
||||||
self.badgeBackgroundNode.frame = backgroundFrame
|
self.badgeBackgroundView.frame = backgroundFrame
|
||||||
}
|
}
|
||||||
self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.width - badgeSize.width) / 2.0), y: 1.0), size: badgeSize)
|
|
||||||
|
|
||||||
if self.badgeBackgroundNode.alpha < 1.0 {
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||||
self.badgeBackgroundNode.alpha = 1.0
|
self.badgeBackgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: self.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition))
|
||||||
|
|
||||||
|
self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.width - badgeSize.width) / 2.0), y: 2.0), size: badgeSize)
|
||||||
|
|
||||||
|
if self.badgeBackgroundView.alpha < 1.0 {
|
||||||
|
self.badgeBackgroundView.alpha = 1.0
|
||||||
|
|
||||||
self.badgeBackgroundNode.layer.animateScale(from: 0.01, to: 1.2, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
|
self.badgeBackgroundView.layer.animateScale(from: 0.01, to: 1.2, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.badgeBackgroundNode.layer.animateScale(from: 1.15, to: 1.0, duration: 0.12, removeOnCompletion: false, completion: { _ in
|
strongSelf.badgeBackgroundView.layer.animateScale(from: 1.15, to: 1.0, duration: 0.12, removeOnCompletion: false, completion: { _ in
|
||||||
strongSelf.badgeBackgroundNode.layer.removeAllAnimations()
|
strongSelf.badgeBackgroundView.layer.removeAllAnimations()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.badgeBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.badgeBackgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
} else if previousValue < self.currentValue {
|
} else if previousValue < self.currentValue {
|
||||||
self.badgeBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.12, removeOnCompletion: false, completion: { [weak self] finished in
|
self.badgeBackgroundView.layer.animateScale(from: 1.0, to: 1.2, duration: 0.12, removeOnCompletion: false, completion: { [weak self] finished in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.badgeBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.12, removeOnCompletion: false, completion: { _ in
|
strongSelf.badgeBackgroundView.layer.animateScale(from: 1.2, to: 1.0, duration: 0.12, removeOnCompletion: false, completion: { _ in
|
||||||
strongSelf.badgeBackgroundNode.layer.removeAllAnimations()
|
strongSelf.badgeBackgroundView.layer.removeAllAnimations()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.currentValue = 0
|
self.currentValue = 0
|
||||||
if self.badgeBackgroundNode.alpha > 0.0 {
|
if self.badgeBackgroundView.alpha > 0.0 {
|
||||||
self.badgeBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
self.badgeBackgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
self.badgeBackgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
|
self.badgeBackgroundView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
|
||||||
}
|
}
|
||||||
self.badgeBackgroundNode.alpha = 0.0
|
self.badgeBackgroundView.alpha = 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,14 +175,14 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize {
|
func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
let buttonSize = CGSize(width: 38.0, height: 38.0)
|
let buttonSize = CGSize(width: 40.0, height: 40.0)
|
||||||
let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 12.0)
|
let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 8.0)
|
||||||
var upOffset: CGFloat = 0.0
|
var upOffset: CGFloat = 0.0
|
||||||
var mentionsOffset: CGFloat = 0.0
|
var mentionsOffset: CGFloat = 0.0
|
||||||
var reactionsOffset: CGFloat = 0.0
|
var reactionsOffset: CGFloat = 0.0
|
||||||
|
|
||||||
if let down = self.directionButtonState.down {
|
if let down = self.directionButtonState.down {
|
||||||
self.downButton.imageNode.alpha = down.isEnabled ? 1.0 : 0.5
|
self.downButton.imageView.alpha = down.isEnabled ? 1.0 : 0.5
|
||||||
self.downButton.buttonNode.isEnabled = down.isEnabled
|
self.downButton.buttonNode.isEnabled = down.isEnabled
|
||||||
|
|
||||||
mentionsOffset += buttonSize.height + 12.0
|
mentionsOffset += buttonSize.height + 12.0
|
||||||
|
|
@ -202,7 +202,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let up = self.directionButtonState.up {
|
if let up = self.directionButtonState.up {
|
||||||
self.upButton.imageNode.alpha = up.isEnabled ? 1.0 : 0.5
|
self.upButton.imageView.alpha = up.isEnabled ? 1.0 : 0.5
|
||||||
self.upButton.buttonNode.isEnabled = up.isEnabled
|
self.upButton.buttonNode.isEnabled = up.isEnabled
|
||||||
|
|
||||||
mentionsOffset += buttonSize.height + 12.0
|
mentionsOffset += buttonSize.height + 12.0
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,22 @@ import AccessoryPanelNode
|
||||||
import ForwardAccessoryPanelNode
|
import ForwardAccessoryPanelNode
|
||||||
import ReplyAccessoryPanelNode
|
import ReplyAccessoryPanelNode
|
||||||
import SuggestPostAccessoryPanelNode
|
import SuggestPostAccessoryPanelNode
|
||||||
|
import ChatInputAccessoryPanel
|
||||||
|
import ChatInputMessageAccessoryPanel
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramNotices
|
||||||
|
import PresentationDataUtils
|
||||||
|
import Display
|
||||||
|
import Markdown
|
||||||
|
import TextFormat
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AccessoryPanelNode?, chatControllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? {
|
func textInputAccessoryPanel(
|
||||||
|
context: AccountContext,
|
||||||
|
chatPresentationInterfaceState: ChatPresentationInterfaceState,
|
||||||
|
chatControllerInteraction: ChatControllerInteraction?,
|
||||||
|
interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||||
|
) -> AnyComponentWithIdentity<ChatInputAccessoryPanelEnvironment>? {
|
||||||
if case .standard(.previewing) = chatPresentationInterfaceState.mode {
|
if case .standard(.previewing) = chatPresentationInterfaceState.mode {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -30,27 +44,227 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS
|
||||||
|
|
||||||
if let editMessage = chatPresentationInterfaceState.interfaceState.editMessage, chatPresentationInterfaceState.interfaceState.postSuggestionState == nil {
|
if let editMessage = chatPresentationInterfaceState.interfaceState.editMessage, chatPresentationInterfaceState.interfaceState.postSuggestionState == nil {
|
||||||
if let editingUrlPreview = chatPresentationInterfaceState.editingUrlPreview, !editMessage.disableUrlPreviews.contains(editingUrlPreview.url) {
|
if let editingUrlPreview = chatPresentationInterfaceState.editingUrlPreview, !editMessage.disableUrlPreviews.contains(editingUrlPreview.url) {
|
||||||
if let previewPanelNode = currentPanel as? WebpagePreviewAccessoryPanelNode {
|
var previousTapTimestamp: Double?
|
||||||
previewPanelNode.interfaceInteraction = interfaceInteraction
|
return AnyComponentWithIdentity(id: "linkPreview", component: AnyComponent(ChatInputMessageAccessoryPanel(
|
||||||
previewPanelNode.replaceWebpage(url: editingUrlPreview.url, webpage: editingUrlPreview.webPage)
|
context: context,
|
||||||
previewPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
contents: .linkPreview(ChatInputMessageAccessoryPanel.Contents.LinkPreview(
|
||||||
return previewPanelNode
|
url: editingUrlPreview.url,
|
||||||
} else {
|
webpage: editingUrlPreview.webPage
|
||||||
let panelNode = WebpagePreviewAccessoryPanelNode(context: context, url: editingUrlPreview.url, webpage: editingUrlPreview.webPage, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
)),
|
||||||
panelNode.interfaceInteraction = interfaceInteraction
|
chatPeerId: chatPresentationInterfaceState.chatLocation.peerId,
|
||||||
return panelNode
|
action: { sourceView in
|
||||||
}
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
if let previousTapTimestamp, previousTapTimestamp + 1.0 > timestamp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
interfaceInteraction?.presentLinkOptions(sourceView)
|
||||||
|
},
|
||||||
|
dismiss: { _ in
|
||||||
|
interfaceInteraction?.dismissUrlPreview()
|
||||||
|
}
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let editPanelNode = currentPanel as? EditAccessoryPanelNode, editPanelNode.messageId == editMessage.messageId {
|
return AnyComponentWithIdentity(id: "edit", component: AnyComponent(ChatInputMessageAccessoryPanel(
|
||||||
editPanelNode.interfaceInteraction = interfaceInteraction
|
context: context,
|
||||||
editPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
contents: .edit(ChatInputMessageAccessoryPanel.Contents.Edit(
|
||||||
return editPanelNode
|
id: editMessage.messageId,
|
||||||
} else {
|
message: nil
|
||||||
let panelNode = EditAccessoryPanelNode(context: context, messageId: editMessage.messageId, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat, animationCache: chatControllerInteraction?.presentationContext.animationCache, animationRenderer: chatControllerInteraction?.presentationContext.animationRenderer)
|
)),
|
||||||
panelNode.interfaceInteraction = interfaceInteraction
|
chatPeerId: chatPresentationInterfaceState.chatLocation.peerId,
|
||||||
return panelNode
|
action: { _ in
|
||||||
|
},
|
||||||
|
dismiss: { _ in
|
||||||
|
interfaceInteraction?.setupEditMessage(nil, { _ in })
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
} else if let urlPreview = chatPresentationInterfaceState.urlPreview, !chatPresentationInterfaceState.interfaceState.composeDisableUrlPreviews.contains(urlPreview.url) {
|
||||||
|
var previousTapTimestamp: Double?
|
||||||
|
return AnyComponentWithIdentity(id: "linkPreview", component: AnyComponent(ChatInputMessageAccessoryPanel(
|
||||||
|
context: context,
|
||||||
|
contents: .linkPreview(ChatInputMessageAccessoryPanel.Contents.LinkPreview(
|
||||||
|
url: urlPreview.url,
|
||||||
|
webpage: urlPreview.webPage
|
||||||
|
)),
|
||||||
|
chatPeerId: chatPresentationInterfaceState.chatLocation.peerId,
|
||||||
|
action: { sourceView in
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
if let previousTapTimestamp, previousTapTimestamp + 1.0 > timestamp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
interfaceInteraction?.presentLinkOptions(sourceView)
|
||||||
|
},
|
||||||
|
dismiss: { _ in
|
||||||
|
interfaceInteraction?.dismissUrlPreview()
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
} else if let forwardMessageIds = chatPresentationInterfaceState.interfaceState.forwardMessageIds {
|
||||||
|
var chatPeerId: EnginePeer.Id?
|
||||||
|
if let peerId = chatPresentationInterfaceState.chatLocation.peerId {
|
||||||
|
chatPeerId = peerId
|
||||||
|
} else if case .customChatContents = chatPresentationInterfaceState.chatLocation {
|
||||||
|
chatPeerId = context.account.peerId
|
||||||
}
|
}
|
||||||
|
if let chatPeerId {
|
||||||
|
var previousTapTimestamp: Double?
|
||||||
|
let theme = chatPresentationInterfaceState.theme
|
||||||
|
let strings = chatPresentationInterfaceState.strings
|
||||||
|
let nameDisplayOrder = chatPresentationInterfaceState.nameDisplayOrder
|
||||||
|
let fontSize = chatPresentationInterfaceState.fontSize
|
||||||
|
|
||||||
|
return AnyComponentWithIdentity(id: "forward", component: AnyComponent(ChatInputMessageAccessoryPanel(
|
||||||
|
context: context,
|
||||||
|
contents: .forward(ChatInputMessageAccessoryPanel.Contents.Forward(
|
||||||
|
messageIds: forwardMessageIds,
|
||||||
|
forwardOptionsState: chatPresentationInterfaceState.interfaceState.forwardOptionsState
|
||||||
|
)),
|
||||||
|
chatPeerId: chatPeerId,
|
||||||
|
action: { sourceView in
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
if let previousTapTimestamp, previousTapTimestamp + 1.0 > timestamp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
interfaceInteraction?.presentForwardOptions(sourceView)
|
||||||
|
let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: context.sharedContext.accountManager, count: 3).start()
|
||||||
|
},
|
||||||
|
dismiss: { sourceView in
|
||||||
|
Task { @MainActor [weak sourceView] in
|
||||||
|
guard let messageId = forwardMessageIds.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let message = await context.engine.data.get(
|
||||||
|
TelegramEngine.EngineData.Item.Messages.Message(id: messageId)
|
||||||
|
).get() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let peer = message.peers[message.id.peerId] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let peerId = peer.id
|
||||||
|
let peerDisplayTitle = EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||||
|
|
||||||
|
let messageCount = Int32(forwardMessageIds.count)
|
||||||
|
let messages = strings.Conversation_ForwardOptions_Messages(messageCount)
|
||||||
|
let string: PresentationStrings.FormattedString
|
||||||
|
if peerId == context.account.peerId {
|
||||||
|
string = strings.Conversation_ForwardOptions_TextSaved(messages)
|
||||||
|
} else if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||||
|
string = strings.Conversation_ForwardOptions_TextPersonal(messages, peerDisplayTitle)
|
||||||
|
} else {
|
||||||
|
string = strings.Conversation_ForwardOptions_Text(messages, peerDisplayTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
let font = Font.regular(floor(fontSize.baseDisplaySize * 15.0 / 17.0))
|
||||||
|
let boldFont = Font.semibold(floor(fontSize.baseDisplaySize * 15.0 / 17.0))
|
||||||
|
let body = MarkdownAttributeSet(font: font, textColor: theme.actionSheet.secondaryTextColor)
|
||||||
|
let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.actionSheet.secondaryTextColor)
|
||||||
|
|
||||||
|
let title = NSAttributedString(string: strings.Conversation_ForwardOptions_Title(messageCount), font: Font.semibold(floor(fontSize.baseDisplaySize)), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
|
||||||
|
let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .center)
|
||||||
|
|
||||||
|
let alertController = richTextAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: strings.Conversation_ForwardOptions_ShowOptions, action: {
|
||||||
|
guard let sourceView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
interfaceInteraction?.presentForwardOptions(sourceView)
|
||||||
|
let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: context.sharedContext.accountManager, count: 3).start()
|
||||||
|
}), TextAlertAction(type: .destructiveAction, title: strings.Conversation_ForwardOptions_CancelForwarding, action: {
|
||||||
|
interfaceInteraction?.dismissForwardMessages()
|
||||||
|
})], actionLayout: .vertical)
|
||||||
|
interfaceInteraction?.presentController(alertController, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if let replyMessageSubject = chatPresentationInterfaceState.interfaceState.replyMessageSubject {
|
||||||
|
var chatPeerId: EnginePeer.Id?
|
||||||
|
if let peerId = chatPresentationInterfaceState.chatLocation.peerId {
|
||||||
|
chatPeerId = peerId
|
||||||
|
} else if case .customChatContents = chatPresentationInterfaceState.chatLocation {
|
||||||
|
chatPeerId = context.account.peerId
|
||||||
|
}
|
||||||
|
if let chatPeerId {
|
||||||
|
var previousTapTimestamp: Double?
|
||||||
|
return AnyComponentWithIdentity(id: "reply", component: AnyComponent(ChatInputMessageAccessoryPanel(
|
||||||
|
context: context,
|
||||||
|
contents: .reply(ChatInputMessageAccessoryPanel.Contents.Reply(
|
||||||
|
id: replyMessageSubject.messageId,
|
||||||
|
quote: replyMessageSubject.quote,
|
||||||
|
todoItemId: replyMessageSubject.todoItemId,
|
||||||
|
message: nil
|
||||||
|
)),
|
||||||
|
chatPeerId: chatPeerId,
|
||||||
|
action: { sourceView in
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
if let previousTapTimestamp, previousTapTimestamp + 1.0 > timestamp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
interfaceInteraction?.presentReplyOptions(sourceView)
|
||||||
|
},
|
||||||
|
dismiss: { _ in
|
||||||
|
interfaceInteraction?.setupReplyMessage(nil, nil, { _, f in f() })
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if let postSuggestionState = chatPresentationInterfaceState.interfaceState.postSuggestionState {
|
||||||
|
var previousTapTimestamp: Double?
|
||||||
|
return AnyComponentWithIdentity(id: "suggestPost", component: AnyComponent(ChatInputMessageAccessoryPanel(
|
||||||
|
context: context,
|
||||||
|
contents: .suggestPost(ChatInputMessageAccessoryPanel.Contents.SuggestPost(
|
||||||
|
state: postSuggestionState
|
||||||
|
)),
|
||||||
|
chatPeerId: chatPresentationInterfaceState.chatLocation.peerId,
|
||||||
|
action: { sourceView in
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
if let previousTapTimestamp, previousTapTimestamp + 1.0 > timestamp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
interfaceInteraction?.presentSuggestPostOptions()
|
||||||
|
},
|
||||||
|
dismiss: { _ in
|
||||||
|
interfaceInteraction?.dismissSuggestPost()
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AccessoryPanelNode?, chatControllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? {
|
||||||
|
if "".isEmpty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .standard(.previewing) = chatPresentationInterfaceState.mode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if let _ = chatPresentationInterfaceState.interfaceState.selectionState {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if chatPresentationInterfaceState.search != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch chatPresentationInterfaceState.subject {
|
||||||
|
case .pinnedMessages, .messageOptions:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if let editMessage = chatPresentationInterfaceState.interfaceState.editMessage, chatPresentationInterfaceState.interfaceState.postSuggestionState == nil {
|
||||||
|
let _ = editMessage
|
||||||
|
return nil
|
||||||
} else if let urlPreview = chatPresentationInterfaceState.urlPreview, !chatPresentationInterfaceState.interfaceState.composeDisableUrlPreviews.contains(urlPreview.url) {
|
} else if let urlPreview = chatPresentationInterfaceState.urlPreview, !chatPresentationInterfaceState.interfaceState.composeDisableUrlPreviews.contains(urlPreview.url) {
|
||||||
if let previewPanelNode = currentPanel as? WebpagePreviewAccessoryPanelNode {
|
if let previewPanelNode = currentPanel as? WebpagePreviewAccessoryPanelNode {
|
||||||
previewPanelNode.interfaceInteraction = interfaceInteraction
|
previewPanelNode.interfaceInteraction = interfaceInteraction
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ import ChatInputPanelNode
|
||||||
import ChatBotStartInputPanelNode
|
import ChatBotStartInputPanelNode
|
||||||
import ChatChannelSubscriberInputPanelNode
|
import ChatChannelSubscriberInputPanelNode
|
||||||
import ChatMessageSelectionInputPanelNode
|
import ChatMessageSelectionInputPanelNode
|
||||||
|
import ChatControllerInteraction
|
||||||
|
|
||||||
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) {
|
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, chatControllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) {
|
||||||
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
||||||
return (nil, nil)
|
return (nil, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +37,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +49,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState {
|
if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState {
|
||||||
if let currentPanel = (currentPanel as? ChatMessageSelectionInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageSelectionInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatMessageSelectionInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageSelectionInputPanelNode) {
|
||||||
currentPanel.selectedMessages = selectionState.selectedIds
|
currentPanel.selectedMessages = selectionState.selectedIds
|
||||||
|
currentPanel.chatControllerInteraction = chatControllerInteraction
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
currentPanel.updateTheme(theme: chatPresentationInterfaceState.theme)
|
currentPanel.updateTheme(theme: chatPresentationInterfaceState.theme)
|
||||||
selectionPanel = currentPanel
|
selectionPanel = currentPanel
|
||||||
|
|
@ -54,12 +57,14 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
let panel = ChatMessageSelectionInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
let panel = ChatMessageSelectionInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
panel.selectedMessages = selectionState.selectedIds
|
panel.selectedMessages = selectionState.selectedIds
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
selectionPanel = panel
|
selectionPanel = panel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentPanel = (currentPanel as? ChatTagSearchInputPanelNode) ?? (currentSecondaryPanel as? ChatTagSearchInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatTagSearchInputPanelNode) ?? (currentSecondaryPanel as? ChatTagSearchInputPanelNode) {
|
||||||
|
currentPanel.chatControllerInteraction = chatControllerInteraction
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
return (currentPanel, selectionPanel)
|
return (currentPanel, selectionPanel)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -70,6 +75,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
|
|
||||||
let panel = ChatTagSearchInputPanelNode(theme: chatPresentationInterfaceState.theme, alwaysShowTotalMessagesCount: alwaysShowTotalMessagesCount)
|
let panel = ChatTagSearchInputPanelNode(theme: chatPresentationInterfaceState.theme, alwaysShowTotalMessagesCount: alwaysShowTotalMessagesCount)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, selectionPanel)
|
return (panel, selectionPanel)
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +89,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
if let _ = chatPresentationInterfaceState.reportReason {
|
if let _ = chatPresentationInterfaceState.reportReason {
|
||||||
if let currentPanel = (currentPanel as? ChatMessageReportInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageReportInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatMessageReportInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageReportInputPanelNode) {
|
||||||
currentPanel.selectedMessages = selectionState.selectedIds
|
currentPanel.selectedMessages = selectionState.selectedIds
|
||||||
|
currentPanel.chatControllerInteraction = chatControllerInteraction
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
|
|
@ -90,12 +97,14 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
let panel = ChatMessageReportInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
let panel = ChatMessageReportInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
panel.selectedMessages = selectionState.selectedIds
|
panel.selectedMessages = selectionState.selectedIds
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let currentPanel = (currentPanel as? ChatMessageSelectionInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageSelectionInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatMessageSelectionInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageSelectionInputPanelNode) {
|
||||||
currentPanel.selectedMessages = selectionState.selectedIds
|
currentPanel.selectedMessages = selectionState.selectedIds
|
||||||
|
currentPanel.chatControllerInteraction = chatControllerInteraction
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
currentPanel.updateTheme(theme: chatPresentationInterfaceState.theme)
|
currentPanel.updateTheme(theme: chatPresentationInterfaceState.theme)
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
|
|
@ -103,6 +112,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
let panel = ChatMessageSelectionInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
let panel = ChatMessageSelectionInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
panel.selectedMessages = selectionState.selectedIds
|
panel.selectedMessages = selectionState.selectedIds
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +124,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatChannelSubscriberInputPanelNode()
|
let panel = ChatChannelSubscriberInputPanelNode()
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
@ -122,11 +133,13 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
|
|
||||||
if chatPresentationInterfaceState.isPremiumRequiredForMessaging {
|
if chatPresentationInterfaceState.isPremiumRequiredForMessaging {
|
||||||
if let currentPanel = (currentPanel as? ChatPremiumRequiredInputPanelNode) ?? (currentSecondaryPanel as? ChatPremiumRequiredInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatPremiumRequiredInputPanelNode) ?? (currentSecondaryPanel as? ChatPremiumRequiredInputPanelNode) {
|
||||||
|
currentPanel.chatControllerInteraction = chatControllerInteraction
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatPremiumRequiredInputPanelNode(theme: chatPresentationInterfaceState.theme)
|
let panel = ChatPremiumRequiredInputPanelNode(theme: chatPresentationInterfaceState.theme)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -134,12 +147,14 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
|
|
||||||
if chatPresentationInterfaceState.peerIsBlocked, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo == nil {
|
if chatPresentationInterfaceState.peerIsBlocked, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo == nil {
|
||||||
if let currentPanel = (currentPanel as? ChatUnblockInputPanelNode) ?? (currentSecondaryPanel as? ChatUnblockInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatUnblockInputPanelNode) ?? (currentSecondaryPanel as? ChatUnblockInputPanelNode) {
|
||||||
|
currentPanel.chatControllerInteraction = chatControllerInteraction
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatUnblockInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
let panel = ChatUnblockInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -153,6 +168,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatChannelSubscriberInputPanelNode()
|
let panel = ChatChannelSubscriberInputPanelNode()
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
@ -166,6 +182,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +193,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatChannelSubscriberInputPanelNode()
|
let panel = ChatChannelSubscriberInputPanelNode()
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
@ -192,6 +210,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = SecretChatHandshakeStatusInputPanelNode()
|
let panel = SecretChatHandshakeStatusInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +220,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = DeleteChatInputPanelNode()
|
let panel = DeleteChatInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -216,6 +236,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = DeleteChatInputPanelNode()
|
let panel = DeleteChatInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -241,6 +262,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatChannelSubscriberInputPanelNode()
|
let panel = ChatChannelSubscriberInputPanelNode()
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
@ -251,6 +273,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -278,6 +301,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -290,6 +314,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -300,6 +325,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -312,6 +338,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -326,6 +353,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatChannelSubscriberInputPanelNode()
|
let panel = ChatChannelSubscriberInputPanelNode()
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
@ -339,6 +367,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatChannelSubscriberInputPanelNode()
|
let panel = ChatChannelSubscriberInputPanelNode()
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
@ -350,6 +379,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatChannelSubscriberInputPanelNode()
|
let panel = ChatChannelSubscriberInputPanelNode()
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
@ -367,6 +397,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = DeleteChatInputPanelNode()
|
let panel = DeleteChatInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -380,6 +411,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -412,6 +444,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatBotStartInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
let panel = ChatBotStartInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -422,6 +455,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRecordingPreviewInputPanelNode(theme: chatPresentationInterfaceState.theme)
|
let panel = ChatRecordingPreviewInputPanelNode(theme: chatPresentationInterfaceState.theme)
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -445,6 +479,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -457,6 +492,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
|
|
||||||
if displayInputTextPanel {
|
if displayInputTextPanel {
|
||||||
if let currentPanel = (currentPanel as? ChatTextInputPanelNode) ?? (currentSecondaryPanel as? ChatTextInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatTextInputPanelNode) ?? (currentSecondaryPanel as? ChatTextInputPanelNode) {
|
||||||
|
currentPanel.chatControllerInteraction = chatControllerInteraction
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -469,6 +505,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
interfaceInteraction?.presentController(controller, nil)
|
interfaceInteraction?.presentController(controller, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
panel.chatControllerInteraction = chatControllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.context = context
|
panel.context = context
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import ChatSendButtonRadialStatusNode
|
||||||
import ChatSendMessageActionUI
|
import ChatSendMessageActionUI
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import AnimatedCountLabelNode
|
import AnimatedCountLabelNode
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
|
||||||
private final class EffectBadgeView: UIView {
|
private final class EffectBadgeView: UIView {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
@ -131,10 +133,12 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||||
private let presentationContext: ChatPresentationContext?
|
private let presentationContext: ChatPresentationContext?
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
|
let micButtonBackgroundView: GlassBackgroundView
|
||||||
|
let micButtonTintMaskView: UIImageView
|
||||||
let micButton: ChatTextInputMediaRecordingButton
|
let micButton: ChatTextInputMediaRecordingButton
|
||||||
|
|
||||||
let sendContainerNode: ASDisplayNode
|
let sendContainerNode: ASDisplayNode
|
||||||
let backdropNode: ChatMessageBubbleBackdrop
|
let sendButtonBackgroundView: GlassBackgroundView
|
||||||
let backgroundNode: ASDisplayNode
|
|
||||||
let sendButton: HighlightTrackingButtonNode
|
let sendButton: HighlightTrackingButtonNode
|
||||||
var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
|
var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
|
||||||
var sendButtonHasApplyIcon = false
|
var sendButtonHasApplyIcon = false
|
||||||
|
|
@ -142,7 +146,9 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||||
|
|
||||||
let textNode: ImmediateAnimatedCountLabelNode
|
let textNode: ImmediateAnimatedCountLabelNode
|
||||||
|
|
||||||
let expandMediaInputButton: HighlightableButtonNode
|
let expandMediaInputButton: HighlightTrackingButton
|
||||||
|
private let expandMediaInputButtonBackgroundView: GlassBackgroundView
|
||||||
|
private let expandMediaInputButtonIcon: GlassBackgroundView.ContentImageView
|
||||||
private var effectBadgeView: EffectBadgeView?
|
private var effectBadgeView: EffectBadgeView?
|
||||||
|
|
||||||
var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
|
var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
|
||||||
|
|
@ -165,22 +171,31 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||||
let theme = presentationInterfaceState.theme
|
let theme = presentationInterfaceState.theme
|
||||||
let strings = presentationInterfaceState.strings
|
let strings = presentationInterfaceState.strings
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
|
||||||
|
self.micButtonBackgroundView = GlassBackgroundView()
|
||||||
|
self.micButtonTintMaskView = UIImageView()
|
||||||
|
self.micButtonTintMaskView.tintColor = .black
|
||||||
self.micButton = ChatTextInputMediaRecordingButton(context: context, theme: theme, pause: true, strings: strings, presentController: presentController)
|
self.micButton = ChatTextInputMediaRecordingButton(context: context, theme: theme, pause: true, strings: strings, presentController: presentController)
|
||||||
|
self.micButton.animationOutput = self.micButtonTintMaskView
|
||||||
|
self.micButtonBackgroundView.maskContentView.addSubview(self.micButtonTintMaskView)
|
||||||
|
|
||||||
self.sendContainerNode = ASDisplayNode()
|
self.sendContainerNode = ASDisplayNode()
|
||||||
self.sendContainerNode.layer.allowsGroupOpacity = true
|
self.sendContainerNode.layer.allowsGroupOpacity = true
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.sendButtonBackgroundView = GlassBackgroundView()
|
||||||
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
|
|
||||||
self.backgroundNode.clipsToBounds = true
|
|
||||||
self.backdropNode = ChatMessageBubbleBackdrop()
|
|
||||||
self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil)
|
self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil)
|
||||||
|
|
||||||
self.textNode = ImmediateAnimatedCountLabelNode()
|
self.textNode = ImmediateAnimatedCountLabelNode()
|
||||||
self.textNode.isUserInteractionEnabled = false
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.expandMediaInputButton = HighlightableButtonNode(pointerStyle: .circle(36.0))
|
self.expandMediaInputButton = HighlightTrackingButton()
|
||||||
|
self.expandMediaInputButtonBackgroundView = GlassBackgroundView()
|
||||||
|
self.expandMediaInputButtonBackgroundView.isUserInteractionEnabled = false
|
||||||
|
self.expandMediaInputButton.addSubview(self.expandMediaInputButtonBackgroundView)
|
||||||
|
self.expandMediaInputButtonIcon = GlassBackgroundView.ContentImageView()
|
||||||
|
self.expandMediaInputButtonBackgroundView.contentView.addSubview(self.expandMediaInputButtonIcon)
|
||||||
|
self.expandMediaInputButtonIcon.image = PresentationResourcesChat.chatInputPanelExpandButtonImage(presentationInterfaceState.theme)
|
||||||
|
self.expandMediaInputButtonIcon.tintColor = theme.chat.inputPanel.inputControlColor
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|
@ -208,18 +223,25 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||||
}
|
}
|
||||||
|
|
||||||
self.micButton.layer.allowsGroupOpacity = true
|
self.micButton.layer.allowsGroupOpacity = true
|
||||||
|
self.view.addSubview(self.micButtonBackgroundView)
|
||||||
self.view.addSubview(self.micButton)
|
self.view.addSubview(self.micButton)
|
||||||
|
|
||||||
self.addSubnode(self.sendContainerNode)
|
self.addSubnode(self.sendContainerNode)
|
||||||
self.sendContainerNode.addSubnode(self.backgroundNode)
|
self.sendContainerNode.view.addSubview(self.sendButtonBackgroundView)
|
||||||
if let presentationContext = presentationContext {
|
|
||||||
let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: presentationInterfaceState.chatWallpaper, bubbleCorners: presentationInterfaceState.bubbleCorners)
|
|
||||||
self.backdropNode.setType(type: .outgoing(.None), theme: ChatPresentationThemeData(theme: theme, wallpaper: presentationInterfaceState.chatWallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: presentationContext.backgroundNode)
|
|
||||||
self.backgroundNode.addSubnode(self.backdropNode)
|
|
||||||
}
|
|
||||||
self.sendContainerNode.addSubnode(self.sendButton)
|
self.sendContainerNode.addSubnode(self.sendButton)
|
||||||
self.sendContainerNode.addSubnode(self.textNode)
|
self.sendContainerNode.addSubnode(self.textNode)
|
||||||
self.addSubnode(self.expandMediaInputButton)
|
self.view.addSubview(self.expandMediaInputButton)
|
||||||
|
|
||||||
|
self.expandMediaInputButton.highligthedChanged = { [weak self] highlighted in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if highlighted {
|
||||||
|
self.expandMediaInputButton.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
|
||||||
|
} else if let presentationLayer = self.expandMediaInputButton.layer.presentation() {
|
||||||
|
self.expandMediaInputButton.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
|
|
@ -236,30 +258,18 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||||
}
|
}
|
||||||
|
|
||||||
self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle(36.0))
|
self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle(36.0))
|
||||||
self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: self.backgroundNode.view, style: .lift)
|
self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: self.sendButtonBackgroundView, style: .lift)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
|
func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
|
||||||
self.micButton.updateTheme(theme: theme)
|
self.micButton.updateTheme(theme: theme)
|
||||||
self.expandMediaInputButton.setImage(PresentationResourcesChat.chatInputPanelExpandButtonImage(theme), for: [])
|
self.expandMediaInputButtonIcon.tintColor = theme.chat.inputPanel.inputControlColor
|
||||||
|
|
||||||
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
|
|
||||||
|
|
||||||
if [.day, .night].contains(theme.referenceTheme.baseTheme) && !theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor {
|
|
||||||
self.backdropNode.isHidden = false
|
|
||||||
} else {
|
|
||||||
self.backdropNode.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: wallpaper, bubbleCorners: .init(mainRadius: 1, auxiliaryRadius: 1, mergeBubbleCorners: false))
|
|
||||||
self.backdropNode.setType(type: .outgoing(.None), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: false, backgroundNode: self.presentationContext?.backgroundNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var absoluteRect: (CGRect, CGSize)?
|
private var absoluteRect: (CGRect, CGSize)?
|
||||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
let previousContaierSize = self.absoluteRect?.1
|
let previousContaierSize = self.absoluteRect?.1
|
||||||
self.absoluteRect = (rect, containerSize)
|
self.absoluteRect = (rect, containerSize)
|
||||||
self.backdropNode.update(rect: rect, within: containerSize, transition: transition)
|
|
||||||
|
|
||||||
if let previousContaierSize, previousContaierSize != containerSize {
|
if let previousContaierSize, previousContaierSize != containerSize {
|
||||||
Queue.mainQueue().after(0.2) {
|
Queue.mainQueue().after(0.2) {
|
||||||
|
|
@ -322,26 +332,30 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||||
self.textNode.isHidden = true
|
self.textNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(view: self.micButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition))
|
||||||
|
|
||||||
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
self.micButton.layoutItems()
|
self.micButton.layoutItems()
|
||||||
|
|
||||||
|
transition.updateFrame(view: self.sendButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
|
self.sendButtonBackgroundView.update(size: innerSize, cornerRadius: innerSize.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition))
|
||||||
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: innerSize))
|
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: innerSize))
|
transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
|
|
||||||
let backgroundSize = CGSize(width: innerSize.width - 11.0, height: 33.0)
|
let backgroundSize = CGSize(width: innerSize.width, height: 40.0)
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(x: showTitle ? 5.0 + UIScreenPixel : floorToScreenPixels((size.width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)
|
let backgroundFrame = CGRect(origin: CGPoint(x: showTitle ? 5.0 + UIScreenPixel : floorToScreenPixels((size.width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
|
||||||
self.backgroundNode.cornerRadius = backgroundSize.height / 2.0
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.backdropNode, frame: CGRect(origin: CGPoint(x: -2.0, y: -2.0), size: CGSize(width: innerSize.width + 12.0, height: size.height + 2.0)))
|
transition.updateFrame(view: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
transition.updateFrame(view: self.expandMediaInputButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
self.backdropNode.update(rect: rect, within: containerSize)
|
self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition))
|
||||||
|
if let image = self.expandMediaInputButtonIcon.image {
|
||||||
|
let expandIconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)
|
||||||
|
transition.updatePosition(layer: self.expandMediaInputButtonIcon.layer, position: expandIconFrame.center)
|
||||||
|
transition.updateBounds(layer: self.expandMediaInputButtonIcon.layer, bounds: CGRect(origin: CGPoint(), size: expandIconFrame.size))
|
||||||
|
transition.updateTransformScale(layer: self.expandMediaInputButtonIcon.layer, scale: CGPoint(x: 1.0, y: isMediaInputExpanded ? 1.0 : -1.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
|
||||||
let expanded = isMediaInputExpanded
|
|
||||||
transition.updateSublayerTransformScale(node: self.expandMediaInputButton, scale: CGPoint(x: 1.0, y: expanded ? 1.0 : -1.0))
|
|
||||||
|
|
||||||
if let currentMessageEffectId {
|
if let currentMessageEffectId {
|
||||||
let effectBadgeView: EffectBadgeView
|
let effectBadgeView: EffectBadgeView
|
||||||
if let current = self.effectBadgeView {
|
if let current = self.effectBadgeView {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -222,7 +222,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
self.previousTapTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
self.interfaceInteraction?.presentLinkOptions(self)
|
self.interfaceInteraction?.presentLinkOptions(self.view)
|
||||||
Queue.mainQueue().after(1.5) {
|
Queue.mainQueue().after(1.5) {
|
||||||
self.updateThemeAndStrings(theme: self.theme, strings: self.strings, force: true)
|
self.updateThemeAndStrings(theme: self.theme, strings: self.strings, force: true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue