This commit is contained in:
Isaac 2025-09-01 18:44:03 +02:00
parent 8e7e5fefb1
commit 4337026fba
37 changed files with 2883 additions and 786 deletions

View file

@ -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 {

View file

@ -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: {

View file

@ -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: {

View file

@ -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)
})
}
}
} }

View file

@ -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 {

View file

@ -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
} }
} }

View file

@ -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! {

View file

@ -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),

View file

@ -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)
}) })
} }

View file

@ -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": [],

View file

@ -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",
],
)

View file

@ -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 }
}

View file

@ -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",
],
)

View file

@ -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)
}
}

View file

@ -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",

View file

@ -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?

View file

@ -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: {

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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? {

View file

@ -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
}
} }
} }
} }

View file

@ -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,

View file

@ -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

View file

@ -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",
],
)

View file

@ -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]
}
}

View file

@ -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: {

View file

@ -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
} }

View file

@ -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

View file

@ -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 {

View file

@ -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?

View file

@ -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
} }
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)
} }