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