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

View file

@ -445,7 +445,7 @@ public class AnimatedCountLabelView: UIView {
effectiveSegmentWidth = max(effectiveSegmentWidth, 4.0)
}
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 {

View file

@ -1273,6 +1273,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, dismissUrlPreview: {
}, dismissForwardMessages: {
}, dismissSuggestPost: {
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatInputAccessoryPanel",
module_name = "ChatInputAccessoryPanel",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/TelegramPresentationData",
"//submodules/TelegramUIPreferences",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,44 @@
import Foundation
import UIKit
import TelegramPresentationData
import TelegramUIPreferences
import GlassBackgroundComponent
public final class ChatInputAccessoryPanelEnvironment: Equatable {
public let theme: PresentationTheme
public let strings: PresentationStrings
public let nameDisplayOrder: PresentationPersonNameOrder
public let dateTimeFormat: PresentationDateTimeFormat
public init(
theme: PresentationTheme,
strings: PresentationStrings,
nameDisplayOrder: PresentationPersonNameOrder,
dateTimeFormat: PresentationDateTimeFormat
) {
self.theme = theme
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
self.dateTimeFormat = dateTimeFormat
}
public static func ==(lhs: ChatInputAccessoryPanelEnvironment, rhs: ChatInputAccessoryPanelEnvironment) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
return false
}
if lhs.dateTimeFormat != rhs.dateTimeFormat {
return false
}
return true
}
}
public protocol ChatInputAccessoryPanelView: UIView {
var contentTintView: UIView { get }
}

View file

@ -0,0 +1,32 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatInputMessageAccessoryPanel",
module_name = "ChatInputMessageAccessoryPanel",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/AccountContext",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramUI/Components/Chat/ChatInputAccessoryPanel",
"//submodules/Display",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/TelegramStringFormatting",
"//submodules/PhotoResources",
"//submodules/TextFormat",
"//submodules/TelegramUI/Components/CompositeTextNode",
"//submodules/ChatInterfaceState",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,911 @@
import Foundation
import UIKit
import TelegramPresentationData
import ChatInputAccessoryPanel
import AccountContext
import TelegramCore
import SwiftSignalKit
import ComponentFlow
import Display
import GlassBackgroundComponent
import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import TelegramStringFormatting
import PhotoResources
import TextFormat
import CompositeTextNode
import ChatInterfaceState
private func generateCloseIcon() -> UIImage {
return generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})!.withRenderingMode(.alwaysTemplate)
}
private func textStringForForwardedMessage(_ message: EngineMessage, strings: PresentationStrings) -> (text: String, entities: [MessageTextEntity], isMedia: Bool) {
for media in message.media {
switch media {
case _ as TelegramMediaImage:
return (strings.Message_Photo, [], true)
case let file as TelegramMediaFile:
if file.isVideoSticker || file.isAnimatedSticker {
return (strings.Message_Sticker, [], true)
}
var fileName: String = strings.Message_File
for attribute in file.attributes {
switch attribute {
case .Sticker:
return (strings.Message_Sticker, [], true)
case let .FileName(name):
fileName = name
case let .Audio(isVoice, _, title, performer, _):
if isVoice {
return (strings.Message_Audio, [], true)
} else {
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
return (title + "" + performer, [], true)
} else if let title = title, !title.isEmpty {
return (title, [], true)
} else if let performer = performer, !performer.isEmpty {
return (performer, [], true)
} else {
return (strings.Message_Audio, [], true)
}
}
case .Video:
if file.isAnimated {
return (strings.Message_Animation, [], true)
} else {
return (strings.Message_Video, [], true)
}
default:
break
}
}
return (fileName, [], true)
case _ as TelegramMediaContact:
return (strings.Message_Contact, [], true)
case let game as TelegramMediaGame:
return (game.title, [], true)
case _ as TelegramMediaMap:
return (strings.Message_Location, [], true)
case _ as TelegramMediaAction:
return ("", [], true)
case _ as TelegramMediaPoll:
return (strings.ForwardedPolls(1), [], true)
case let todo as TelegramMediaTodo:
return (todo.text, [], true)
case let dice as TelegramMediaDice:
return (dice.emoji, [], true)
case let invoice as TelegramMediaInvoice:
return (invoice.title, [], true)
default:
break
}
}
return (message.text, message._asMessage().textEntitiesAttribute?.entities ?? [], false)
}
public final class ChatInputMessageAccessoryPanel: Component {
public typealias EnvironmentType = ChatInputAccessoryPanelEnvironment
public enum Contents: Equatable {
public final class Reply: Equatable {
public let id: EngineMessage.Id
public let quote: EngineMessageReplyQuote?
public let todoItemId: Int32?
public let message: EngineMessage?
public init(id: EngineMessage.Id, quote: EngineMessageReplyQuote?, todoItemId: Int32?, message: EngineMessage?) {
self.id = id
self.quote = quote
self.todoItemId = todoItemId
self.message = message
}
public static func ==(lhs: Reply, rhs: Reply) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.quote != rhs.quote {
return false
}
if lhs.todoItemId != rhs.todoItemId {
return false
}
if lhs.message?.id != rhs.message?.id {
return false
}
if lhs.message?.stableVersion != rhs.message?.stableVersion {
return false
}
return true
}
}
public final class Edit: Equatable {
public let id: EngineMessage.Id
public let message: EngineMessage?
public init(id: EngineMessage.Id, message: EngineMessage?) {
self.id = id
self.message = message
}
public static func ==(lhs: Edit, rhs: Edit) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.message?.id != rhs.message?.id {
return false
}
if lhs.message?.stableVersion != rhs.message?.stableVersion {
return false
}
return true
}
}
public final class Forward: Equatable {
public let messageIds: [EngineMessage.Id]
public let forwardOptionsState: ChatInterfaceForwardOptionsState?
public init(messageIds: [EngineMessage.Id], forwardOptionsState: ChatInterfaceForwardOptionsState?) {
self.messageIds = messageIds
self.forwardOptionsState = forwardOptionsState
}
public static func ==(lhs: Forward, rhs: Forward) -> Bool {
if lhs.messageIds != rhs.messageIds {
return false
}
if lhs.forwardOptionsState != rhs.forwardOptionsState {
return false
}
return true
}
}
public final class LinkPreview: Equatable {
public let url: String
public let webpage: TelegramMediaWebpage
public init(url: String, webpage: TelegramMediaWebpage) {
self.url = url
self.webpage = webpage
}
public static func ==(lhs: LinkPreview, rhs: LinkPreview) -> Bool {
if lhs.url != rhs.url {
return false
}
if lhs.webpage != rhs.webpage {
return false
}
return true
}
}
public final class SuggestPost: Equatable {
public let state: ChatInterfaceState.PostSuggestionState
public init(state: ChatInterfaceState.PostSuggestionState) {
self.state = state
}
public static func ==(lhs: SuggestPost, rhs: SuggestPost) -> Bool {
if lhs.state != rhs.state {
return false
}
return true
}
}
case reply(Reply)
case edit(Edit)
case forward(Forward)
case linkPreview(LinkPreview)
case suggestPost(SuggestPost)
}
let context: AccountContext
let contents: Contents
let chatPeerId: EnginePeer.Id?
let action: ((UIView) -> Void)?
let dismiss: (UIView) -> Void
public init(
context: AccountContext,
contents: Contents,
chatPeerId: EnginePeer.Id?,
action: ((UIView) -> Void)?,
dismiss: @escaping (UIView) -> Void
) {
self.context = context
self.contents = contents
self.chatPeerId = chatPeerId
self.action = action
self.dismiss = dismiss
}
public static func ==(lhs: ChatInputMessageAccessoryPanel, rhs: ChatInputMessageAccessoryPanel) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.contents != rhs.contents {
return false
}
if lhs.chatPeerId != rhs.chatPeerId {
return false
}
if (lhs.action == nil) != (rhs.action == nil) {
return false
}
return true
}
public final class View: UIView, ChatInputAccessoryPanelView {
private let closeButton: HighlightTrackingButton
private let closeButtonIcon: GlassBackgroundView.ContentImageView
private let lineView: UIImageView
private let titleNode: CompositeTextNode
private let text = ComponentView<Empty>()
private let tintText = ComponentView<Empty>()
public let contentTintView: UIView
private var isUpdating: Bool = false
private var component: ChatInputMessageAccessoryPanel?
private weak var state: EmptyComponentState?
private var environment: EnvironmentType?
private var messages: [EngineMessage] = []
private var contentDisposable: Disposable?
private var inlineTextStarImage: UIImage?
private var inlineTextTonImage: (UIImage, UIColor)?
override public init(frame: CGRect) {
self.contentTintView = UIView()
self.closeButton = HighlightTrackingButton()
self.closeButtonIcon = GlassBackgroundView.ContentImageView()
self.lineView = UIImageView()
self.titleNode = CompositeTextNode()
super.init(frame: frame)
self.addSubview(self.lineView)
self.addSubview(self.titleNode.view)
self.addSubview(self.closeButtonIcon)
self.contentTintView.addSubview(self.closeButtonIcon.tintMask)
self.addSubview(self.closeButton)
self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), for: .touchUpInside)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.contentDisposable?.dispose()
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
component.action?(self)
}
}
@objc private func closeButtonPressed() {
guard let component = self.component else {
return
}
component.dismiss(self)
}
public func update(component: ChatInputMessageAccessoryPanel, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let environment = environment[EnvironmentType.self].value
if self.component == nil {
let messageIds: [EngineMessage.Id]
switch component.contents {
case let .edit(edit):
messageIds = [edit.id]
case let .reply(reply):
messageIds = [reply.id]
case let .forward(forward):
messageIds = forward.messageIds
case .linkPreview, .suggestPost:
messageIds = []
}
self.contentDisposable?.dispose()
if !messageIds.isEmpty {
self.contentDisposable = (component.context.engine.data.subscribe(
EngineDataList(messageIds.map { id in
return TelegramEngine.EngineData.Item.Messages.Message(id: id)
})
)
|> deliverOnMainQueue).startStrict(next: { [weak self] messages in
guard let self else {
return
}
self.messages = messages.compactMap { $0 }
if !self.isUpdating {
self.state?.updated(transition: .immediate, isLocal: true)
}
})
}
}
self.component = component
self.state = state
self.environment = environment
if self.closeButtonIcon.image == nil {
self.closeButtonIcon.image = generateCloseIcon()
}
if self.lineView.image == nil {
self.lineView.image = generateImage(CGSize(width: 2.0, height: 3.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 1.0).cgPath)
context.fillPath()
})?.withRenderingMode(.alwaysTemplate).stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)
}
let size = CGSize(width: availableSize.width, height: 52.0)
let containerInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 6.0, right: 0.0)
let lineSize = CGSize(width: 2.0, height: size.height - containerInsets.top - containerInsets.bottom)
let lineFrame = CGRect(origin: CGPoint(x: containerInsets.left, y: containerInsets.top), size: lineSize)
transition.setFrame(view: self.lineView, frame: lineFrame)
self.lineView.tintColor = environment.theme.chat.inputPanel.panelControlAccentColor
let closeButtonSize = CGSize(width: 44.0, height: 44.0)
let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - closeButtonSize.width, y: floor((size.height - closeButtonSize.height) * 0.5)), size: closeButtonSize)
transition.setFrame(view: self.closeButton, frame: closeButtonFrame)
if let image = self.closeButtonIcon.image {
let closeButtonIconFrame = image.size.centered(in: closeButtonFrame)
transition.setFrame(view: self.closeButtonIcon, frame: closeButtonIconFrame)
}
self.closeButtonIcon.tintColor = environment.theme.chat.inputPanel.inputControlColor
let secondaryTextColor = environment.theme.chat.inputPanel.inputControlColor.withMultipliedBrightnessBy(0.5)
var textString: NSAttributedString
var isPhoto = false
if self.messages.count == 1, let message = self.messages.first {
var text = ""
let effectiveMessage = message
//TODO:release media
/*if let currentEditMediaReference = self.currentEditMediaReference {
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
}*/
let (attributedText, _, _) = descriptionStringForMessage(
contentSettings: component.context.currentContentSettings.with { $0 },
message: effectiveMessage,
strings: environment.strings,
nameDisplayOrder: environment.nameDisplayOrder,
dateTimeFormat: environment.dateTimeFormat,
accountPeerId: component.context.account.peerId
)
text = attributedText.string
var updatedMediaReference: AnyMediaReference?
var imageDimensions: CGSize?
if !message._asMessage().containsSecretMedia {
var candidateMediaReference: AnyMediaReference?
for media in message.media {
if media is TelegramMediaImage || media is TelegramMediaFile {
candidateMediaReference = .message(message: MessageReference(message._asMessage()), media: media)
break
}
}
if let imageReference = candidateMediaReference?.concrete(TelegramMediaImage.self) {
updatedMediaReference = imageReference.abstract
if let representation = largestRepresentationForPhoto(imageReference.media) {
imageDimensions = representation.dimensions.cgSize
}
} else if let fileReference = candidateMediaReference?.concrete(TelegramMediaFile.self) {
updatedMediaReference = fileReference.abstract
if !fileReference.media.isInstantVideo, let representation = largestImageRepresentation(fileReference.media.previewRepresentations), !fileReference.media.isSticker {
imageDimensions = representation.dimensions.cgSize
}
}
}
/*let imageNodeLayout = self.imageNode.asyncLayout()
var applyImage: (() -> Void)?
if let imageDimensions = imageDimensions {
let boundingSize = CGSize(width: 35.0, height: 35.0)
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
}
var mediaUpdated = false
if let updatedMediaReference = updatedMediaReference, let previousMediaReference = self.previousMediaReference {
mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media)
} else if (updatedMediaReference != nil) != (self.previousMediaReference != nil) {
mediaUpdated = true
}
self.previousMediaReference = updatedMediaReference*/
let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute })
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
let _ = updateImageSignal
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
updateImageSignal = chatMessagePhotoThumbnail(account: component.context.account, userLocation: MediaResourceUserLocation.peer(message.id.peerId), photoReference: imageReference, blurred: hasSpoiler)
isPhoto = true
} else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
if fileReference.media.isVideo {
updateImageSignal = chatMessageVideoThumbnail(account: component.context.account, userLocation: MediaResourceUserLocation.peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler)
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
updateImageSignal = chatWebpageSnippetFile(account: component.context.account, userLocation: MediaResourceUserLocation.peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation)
}
}
} else {
updateImageSignal = .single({ _ in return nil })
}
let isMedia: Bool
let isText: Bool
/*if let currentEditMediaReference = self.currentEditMediaReference {
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
}*/
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
switch messageContentKind(contentSettings: component.context.currentContentSettings.with { $0 }, message: effectiveMessage, strings: environment.strings, nameDisplayOrder: environment.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: component.context.account.peerId) {
case .text:
isMedia = false
isText = true
default:
isMedia = true
isText = false
}
let textFont = Font.regular(14.0)
let messageText: NSAttributedString
if isText {
let entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
switch entity.type {
case .Spoiler, .CustomEmoji:
return true
default:
return false
}
}
let textColor = environment.theme.chat.inputPanel.primaryTextColor
if entities.count > 0 {
messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
} else {
messageText = NSAttributedString(string: text, font: textFont, textColor: isMedia ? secondaryTextColor : environment.theme.chat.inputPanel.primaryTextColor)
}
} else {
messageText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: isMedia ? secondaryTextColor : environment.theme.chat.inputPanel.primaryTextColor)
}
textString = messageText
} else {
textString = NSAttributedString()
}
var titleText: [CompositeTextNode.Component] = []
switch component.contents {
case .edit:
let canEditMedia: Bool
//TODO:release
/*if let message = self.message, !messageMediaEditingOptions(message: message).isEmpty {
canEditMedia = true
} else {
canEditMedia = false
}*/
canEditMedia = !"".isEmpty
let titleStringValue: String
if let message = self.messages.first, message.id.namespace == Namespaces.Message.QuickReplyCloud {
titleStringValue = environment.strings.Conversation_EditingQuickReplyPanelTitle
} else if canEditMedia {
titleStringValue = isPhoto ? environment.strings.Conversation_EditingPhotoPanelTitle : environment.strings.Conversation_EditingCaptionPanelTitle
} else {
titleStringValue = environment.strings.Conversation_EditingMessagePanelTitle
}
titleText = [.text(NSAttributedString(string: titleStringValue, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
case let .reply(reply):
if let peer = self.messages.first?.peers[reply.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
let icon: UIImage?
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon"), color: environment.theme.chat.inputPanel.panelControlAccentColor)
if let icon {
let rawString: PresentationStrings.FormattedString
if reply.quote != nil {
rawString = environment.strings.Chat_ReplyPanel_ReplyToQuoteBy(peer.debugDisplayTitle)
} else {
rawString = environment.strings.Chat_ReplyPanel_ReplyTo(peer.debugDisplayTitle)
}
if let nameRange = rawString.ranges.first {
titleText = []
let rawNsString = rawString.string as NSString
if nameRange.range.lowerBound != 0 {
titleText.append(.text(NSAttributedString(string: rawNsString.substring(with: NSRange(location: 0, length: nameRange.range.lowerBound)), font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
}
titleText.append(.icon(icon))
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
if nameRange.range.upperBound != rawNsString.length {
titleText.append(.text(NSAttributedString(string: rawNsString.substring(with: NSRange(location: nameRange.range.upperBound, length: rawNsString.length - nameRange.range.upperBound)), font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
}
} else {
titleText.append(.text(NSAttributedString(string: rawString.string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
}
}
} else {
var authorName = ""
if let forwardInfo = self.messages.first?._asMessage().forwardInfo, forwardInfo.flags.contains(.isImported) {
if let author = forwardInfo.author {
authorName = EnginePeer(author).displayTitle(strings: environment.strings, displayOrder: environment.nameDisplayOrder)
} else if let authorSignature = forwardInfo.authorSignature {
authorName = authorSignature
}
} else if let author = self.messages.first?._asMessage().effectiveAuthor {
authorName = EnginePeer(author).displayTitle(strings: environment.strings, displayOrder: environment.nameDisplayOrder)
}
if let _ = reply.todoItemId {
let string = environment.strings.Chat_ReplyPanel_ReplyToTodoItem
titleText = [.text(NSAttributedString(string: string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
} else if let _ = reply.quote {
let string = environment.strings.Chat_ReplyPanel_ReplyToQuoteBy(authorName).string
titleText = [.text(NSAttributedString(string: string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
} else {
let string = environment.strings.Conversation_ReplyMessagePanelTitle(authorName).string
titleText = [.text(NSAttributedString(string: string, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
}
if reply.id.peerId != component.chatPeerId {
if let peer = self.messages.first?.peers[reply.id.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
let icon: UIImage?
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon")
} else {
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextGroupIcon")
}
if let iconImage = generateTintedImage(image: icon, color: environment.theme.chat.inputPanel.panelControlAccentColor) {
titleText.append(.icon(iconImage))
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
}
}
}
if let message = self.messages.first {
let textFont = Font.regular(14.0)
if let quote = reply.quote {
let textColor = environment.theme.chat.inputPanel.primaryTextColor
textString = stringWithAppliedEntities(trimToLineCount(quote.text, lineCount: 1), entities: quote.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
} else if let todoItemId = reply.todoItemId, let todo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo, let todoItem = todo.items.first(where: { $0.id == todoItemId }) {
let textColor = environment.theme.chat.inputPanel.primaryTextColor
textString = stringWithAppliedEntities(trimToLineCount(todoItem.text, lineCount: 1), entities: todoItem.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
}
}
}
case let .forward(forward):
var title = ""
var authors = ""
var uniquePeerIds = Set<EnginePeer.Id>()
var text = NSMutableAttributedString(string: "")
for message in self.messages {
if let author = message.forwardInfo?.author ?? message._asMessage().effectiveAuthor, !uniquePeerIds.contains(author.id) {
uniquePeerIds.insert(author.id)
if !authors.isEmpty {
authors.append(", ")
}
if author.id == component.context.account.peerId {
authors.append(environment.strings.DialogList_You)
} else {
authors.append(EnginePeer(author).compactDisplayTitle)
}
}
}
if self.messages.count == 1 {
title = environment.strings.Conversation_ForwardOptions_ForwardTitleSingle
let (string, entities, _) = textStringForForwardedMessage(messages[0], strings: environment.strings)
text = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(authors): ", font: Font.regular(14.0), textColor: secondaryTextColor))
let additionalText = NSMutableAttributedString(attributedString: NSAttributedString(string: string, font: Font.regular(14.0), textColor: secondaryTextColor))
for entity in entities {
switch entity.type {
case let .CustomEmoji(_, fileId):
let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
if range.lowerBound >= 0 && range.upperBound <= additionalText.length {
additionalText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: messages[0].associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: range)
}
default:
break
}
}
text.append(additionalText)
} else {
title = environment.strings.Conversation_ForwardOptions_ForwardTitle(Int32(messages.count))
text = NSMutableAttributedString(attributedString: NSAttributedString(string: environment.strings.Conversation_ForwardFrom(authors).string, font: Font.regular(14.0), textColor: secondaryTextColor))
}
if forward.forwardOptionsState?.hideNames == true {
text = NSMutableAttributedString(attributedString: NSAttributedString(string: environment.strings.Conversation_ForwardOptions_SenderNamesRemoved, font: Font.regular(14.0), textColor: secondaryTextColor))
}
titleText = [.text(NSAttributedString(string: title, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
textString = text
case let .linkPreview(linkPreview):
var authorName = ""
var text = ""
switch linkPreview.webpage.content {
case .Pending:
authorName = environment.strings.Channel_NotificationLoading
text = linkPreview.url
case let .Loaded(content):
if let contentText = content.text {
text = contentText
} else {
if let file = content.file, let mediaKind = mediaContentKind(EngineMedia(file)) {
if content.type == "telegram_background" {
text = environment.strings.Message_Wallpaper
} else if content.type == "telegram_theme" {
text = environment.strings.Message_Theme
} else {
text = stringForMediaKind(mediaKind, strings: environment.strings).0.string
}
} else if content.type == "telegram_theme" {
text = environment.strings.Message_Theme
} else if content.type == "video" {
text = stringForMediaKind(.video, strings: environment.strings).0.string
} else if content.type == "telegram_story" {
text = stringForMediaKind(.story, strings: environment.strings).0.string
} else if let _ = content.image {
text = stringForMediaKind(.image, strings: environment.strings).0.string
}
}
if let title = content.title {
authorName = title
} else if let websiteName = content.websiteName {
authorName = websiteName
} else {
authorName = content.displayUrl
}
}
titleText = [.text(NSAttributedString(string: authorName, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor))]
textString = NSAttributedString(string: text, font: Font.regular(14.0), textColor: environment.theme.chat.inputPanel.primaryTextColor)
case let .suggestPost(suggestPost):
if suggestPost.state.editingOriginalMessageId != nil {
titleText.append(.text(NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputEditTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
} else {
titleText.append(.text(NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputTitle, font: Font.medium(14.0), textColor: environment.theme.chat.inputPanel.panelControlAccentColor)))
}
let textFont = Font.regular(14.0)
if let price = suggestPost.state.price, price.amount != .zero {
let currencySymbol: String
let amountString: String
switch price.currency {
case .stars:
currencySymbol = "#"
amountString = "\(price.amount)"
case .ton:
currencySymbol = "$"
amountString = formatTonAmountText(price.amount.value, dateTimeFormat: environment.dateTimeFormat)
}
if let timestamp = suggestPost.state.timestamp {
let timeString = humanReadableStringForTimestamp(strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, timestamp: timestamp, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat(
dateFormatString: { value in
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_Date(value).string, ranges: [])
},
tomorrowFormatString: { value in
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: [])
},
todayFormatString: { value in
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: [])
},
yesterdayFormatString: { value in
return PresentationStrings.FormattedString(string: environment.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: [])
}
)).string
textString = NSAttributedString(string: "\(currencySymbol)\(amountString) 📅 \(timeString)", font: textFont, textColor: environment.theme.chat.inputPanel.primaryTextColor)
} else {
textString = NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputSubtitleAnytime("\(currencySymbol)\(amountString)").string, font: textFont, textColor: environment.theme.chat.inputPanel.primaryTextColor)
}
} else {
textString = NSAttributedString(string: environment.strings.Chat_PostSuggestion_Suggest_InputSubtitleEmpty, font: textFont, textColor: environment.theme.chat.inputPanel.primaryTextColor)
}
let mutableTextString = NSMutableAttributedString(attributedString: textString)
for currency in [.stars, .ton] as [CurrencyAmount.Currency] {
var inlineTextStarImage: UIImage?
if let current = self.inlineTextStarImage {
inlineTextStarImage = current
} else {
if let image = UIImage(bundleImageName: "Premium/Stars/StarSmall") {
let starInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
inlineTextStarImage = generateImage(CGSize(width: starInsets.left + image.size.width + starInsets.right, height: image.size.height), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
image.draw(at: CGPoint(x: starInsets.left, y: starInsets.top))
})?.withRenderingMode(.alwaysOriginal)
self.inlineTextStarImage = inlineTextStarImage
}
}
var inlineTextTonImage: UIImage?
if let current = self.inlineTextTonImage, current.1 == environment.theme.list.itemAccentColor {
inlineTextTonImage = current.0
} else {
if let image = UIImage(bundleImageName: "Ads/TonMedium") {
let tonInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
let inlineTextTonImageValue = generateTintedImage(image: generateImage(CGSize(width: tonInsets.left + image.size.width + tonInsets.right, height: image.size.height), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
image.draw(at: CGPoint(x: tonInsets.left, y: tonInsets.top))
}), color: environment.theme.list.itemAccentColor)!.withRenderingMode(.alwaysOriginal)
inlineTextTonImage = inlineTextTonImageValue
self.inlineTextTonImage = (inlineTextTonImageValue, environment.theme.list.itemAccentColor)
}
}
let currencySymbol: String
let currencyImage: UIImage?
switch currency {
case .stars:
currencySymbol = "#"
currencyImage = inlineTextStarImage
case .ton:
currencySymbol = "$"
currencyImage = inlineTextTonImage
}
if let range = mutableTextString.string.range(of: currencySymbol), let currencyImage {
final class RunDelegateData {
let ascent: CGFloat
let descent: CGFloat
let width: CGFloat
init(ascent: CGFloat, descent: CGFloat, width: CGFloat) {
self.ascent = ascent
self.descent = descent
self.width = width
}
}
let runDelegateData = RunDelegateData(
ascent: Font.regular(14.0).ascender,
descent: Font.regular(14.0).descender,
width: currencyImage.size.width + 2.0
)
var callbacks = CTRunDelegateCallbacks(
version: kCTRunDelegateCurrentVersion,
dealloc: { dataRef in
Unmanaged<RunDelegateData>.fromOpaque(dataRef).release()
},
getAscent: { dataRef in
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
return data.takeUnretainedValue().ascent
},
getDescent: { dataRef in
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
return data.takeUnretainedValue().descent
},
getWidth: { dataRef in
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
return data.takeUnretainedValue().width
}
)
if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) {
mutableTextString.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: NSRange(range, in: mutableTextString.string))
}
mutableTextString.addAttribute(.attachment, value: currencyImage, range: NSRange(range, in: mutableTextString.string))
mutableTextString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: mutableTextString.string))
mutableTextString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: mutableTextString.string))
}
textString = mutableTextString
}
}
let textInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 0.0, right: 44.0)
self.titleNode.components = titleText
let titleSize = self.titleNode.update(constrainedSize: CGSize(width: availableSize.width - lineFrame.maxX - textInsets.left - textInsets.right, height: 100.0))
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(textString),
)),
environment: {},
containerSize: CGSize(width: availableSize.width - lineFrame.maxX - textInsets.left - textInsets.right, height: 100.0)
)
let tintTextString = NSMutableAttributedString(attributedString: textString)
tintTextString.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(location: 0, length: tintTextString.length))
let _ = self.tintText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(tintTextString),
)),
environment: {},
containerSize: CGSize(width: availableSize.width - lineFrame.maxX - textInsets.left - textInsets.right, height: 100.0)
)
let titleTextSpacing: CGFloat = 1.0
let titleFrame = CGRect(origin: CGPoint(x: lineFrame.maxX + textInsets.left, y: textInsets.top), size: titleSize)
let textFrame = CGRect(origin: CGPoint(x: lineFrame.maxX + textInsets.left, y: titleFrame.maxY + titleTextSpacing), size: textSize)
transition.setFrame(view: self.titleNode.view, frame: titleFrame)
if let textView = self.text.view, let tintTextView = self.tintText.view {
if textView.superview == nil {
textView.layer.anchorPoint = CGPoint()
self.addSubview(textView)
tintTextView.layer.anchorPoint = CGPoint()
self.contentTintView.addSubview(tintTextView)
}
transition.setPosition(view: textView, position: textFrame.origin)
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
transition.setPosition(view: tintTextView, position: textFrame.origin)
tintTextView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
}
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -16,6 +16,7 @@ swift_library(
"//submodules/TelegramCore",
"//submodules/AccountContext",
"//submodules/ChatPresentationInterfaceState",
"//submodules/TelegramUI/Components/ChatControllerInteraction",
],
visibility = [
"//visibility:public",

View file

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

View file

@ -173,6 +173,9 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, dismissUrlPreview: {
}, dismissForwardMessages: {
}, dismissSuggestPost: {
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

@ -388,7 +388,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
let alertController = richTextAlertController(context: self.context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: self.strings.Conversation_ForwardOptions_ShowOptions, action: { [weak self] in
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)
}

View file

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

View file

@ -407,7 +407,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
}
fileprivate var clipContentToTopPanel: Bool = false
public var clipContentToTopPanel: Bool = false
public var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
public override var externalTopPanelContainer: UIView? {

View file

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

View file

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

View file

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

View file

@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "GlassBackgroundComponent",
module_name = "GlassBackgroundComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,517 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import ComponentDisplayAdapters
private func generateForegroundImage(size: CGSize, isDark: Bool, fillColor: UIColor) -> UIImage {
var size = size
if size == .zero {
size = CGSize(width: 1.0, height: 1.0)
}
return generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let maxColor = UIColor(white: 1.0, alpha: isDark ? 0.67 : 0.9)
let minColor = UIColor(white: 1.0, alpha: 0.0)
context.setFillColor(fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
let lineWidth: CGFloat = isDark ? 0.66 : 0.66
context.saveGState()
let darkShadeColor = UIColor(white: isDark ? 1.0 : 0.0, alpha: 0.035)
let lightShadeColor = UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.035)
let innerShadowBlur: CGFloat = 24.0
context.resetClip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.clip()
context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0))
context.addEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.black.cgColor)
context.setShadow(offset: CGSize(width: 10.0, height: -10.0), blur: innerShadowBlur, color: darkShadeColor.cgColor)
context.fillPath(using: .evenOdd)
context.resetClip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.clip()
context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0))
context.addEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.black.cgColor)
context.setShadow(offset: CGSize(width: -10.0, height: 10.0), blur: innerShadowBlur, color: lightShadeColor.cgColor)
context.fillPath(using: .evenOdd)
context.restoreGState()
context.setLineWidth(lineWidth)
context.addRect(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)))
context.clip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.replacePathWithStrokedPath()
context.clip()
do {
var locations: [CGFloat] = [0.0, 0.5, 0.5 + 0.2, 1.0 - 0.1, 1.0]
let colors: [CGColor] = [maxColor.cgColor, maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
}
context.resetClip()
context.addRect(CGRect(origin: CGPoint(x: size.width - size.width * 0.5, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)))
context.clip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.replacePathWithStrokedPath()
context.clip()
do {
var locations: [CGFloat] = [0.0, 0.1, 0.5 - 0.2, 0.5, 1.0]
let colors: [CGColor] = [maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor, maxColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
}
})!.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5))
}
private final class ContentContainer: UIView {
private let maskContentView: UIView
init(maskContentView: UIView) {
self.maskContentView = maskContentView
super.init(frame: CGRect())
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let result = super.hitTest(point, with: event) else {
return nil
}
if result === self {
return nil
}
return result
}
override func didAddSubview(_ subview: UIView) {
super.didAddSubview(subview)
if let subview = subview as? GlassBackgroundView.ContentView {
self.maskContentView.addSubview(subview.tintMask)
}
}
override func willRemoveSubview(_ subview: UIView) {
super.willRemoveSubview(subview)
if let subview = subview as? GlassBackgroundView.ContentView {
subview.tintMask.removeFromSuperview()
}
}
}
public final class GlassBackgroundView: UIView {
public protocol ContentView: UIView {
var tintMask: UIView { get }
}
open class ContentLayer: SimpleLayer {
public var targetLayer: CALayer?
override init() {
super.init()
}
override init(layer: Any) {
super.init(layer: layer)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public var position: CGPoint {
get {
return super.position
} set(value) {
if let targetLayer = self.targetLayer {
targetLayer.position = value
}
super.position = value
}
}
override public var bounds: CGRect {
get {
return super.bounds
} set(value) {
if let targetLayer = self.targetLayer {
targetLayer.bounds = value
}
super.bounds = value
}
}
override public var anchorPoint: CGPoint {
get {
return super.anchorPoint
} set(value) {
if let targetLayer = self.targetLayer {
targetLayer.anchorPoint = value
}
super.anchorPoint = value
}
}
override public var anchorPointZ: CGFloat {
get {
return super.anchorPointZ
} set(value) {
if let targetLayer = self.targetLayer {
targetLayer.anchorPointZ = value
}
super.anchorPointZ = value
}
}
override public var opacity: Float {
get {
return super.opacity
} set(value) {
if let targetLayer = self.targetLayer {
targetLayer.opacity = value
}
super.opacity = value
}
}
override public var sublayerTransform: CATransform3D {
get {
return super.sublayerTransform
} set(value) {
if let targetLayer = self.targetLayer {
targetLayer.sublayerTransform = value
}
super.sublayerTransform = value
}
}
override public var transform: CATransform3D {
get {
return super.transform
} set(value) {
if let targetLayer = self.targetLayer {
targetLayer.transform = value
}
super.transform = value
}
}
override public func add(_ animation: CAAnimation, forKey key: String?) {
if let targetLayer = self.targetLayer {
targetLayer.add(animation, forKey: key)
}
super.add(animation, forKey: key)
}
override public func removeAllAnimations() {
if let targetLayer = self.targetLayer {
targetLayer.removeAllAnimations()
}
super.removeAllAnimations()
}
override public func removeAnimation(forKey: String) {
if let targetLayer = self.targetLayer {
targetLayer.removeAnimation(forKey: forKey)
}
super.removeAnimation(forKey: forKey)
}
}
public final class ContentColorView: UIView, ContentView {
override public static var layerClass: AnyClass {
return ContentLayer.self
}
public let tintMask: UIView
override public init(frame: CGRect) {
self.tintMask = UIView()
super.init(frame: CGRect())
self.tintMask.tintColor = .black
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
public final class ContentImageView: UIImageView, ContentView {
override public static var layerClass: AnyClass {
return ContentLayer.self
}
private let tintImageView: UIImageView
public var tintMask: UIView {
return self.tintImageView
}
override public var image: UIImage? {
didSet {
self.tintImageView.image = self.image
}
}
override public init(frame: CGRect) {
self.tintImageView = UIImageView()
super.init(frame: CGRect())
self.tintImageView.tintColor = .black
}
override public init(image: UIImage?) {
self.tintImageView = UIImageView()
super.init(image: image)
self.tintImageView.image = image
self.tintImageView.tintColor = .black
}
override public init(image: UIImage?, highlightedImage: UIImage?) {
self.tintImageView = UIImageView()
super.init(image: image, highlightedImage: highlightedImage)
self.tintImageView.image = image
self.tintImageView.tintColor = .black
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private struct Params: Equatable {
let cornerRadius: CGFloat
let isDark: Bool
let tintColor: UIColor
init(cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor) {
self.cornerRadius = cornerRadius
self.isDark = isDark
self.tintColor = tintColor
}
}
private let backgroundNode: NavigationBackgroundNode?
private let nativeView: UIVisualEffectView?
private let foregroundView: UIImageView?
public let maskContentView: UIView
private let contentContainer: ContentContainer
public var contentView: UIView {
return self.contentContainer
}
private var params: Params?
public override init(frame: CGRect) {
if #available(iOS 26.0, *) {
self.backgroundNode = nil
let glassEffect = UIGlassEffect(style: .clear)
glassEffect.isInteractive = false
let nativeView = UIVisualEffectView(effect: glassEffect)
self.nativeView = nativeView
//nativeView.overrideUserInterfaceStyle = .light
//nativeView.traitOverrides.userInterfaceStyle = .light
self.foregroundView = UIImageView()
//self.foregroundView = nil
} else {
self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0)
self.nativeView = nil
self.foregroundView = UIImageView()
}
self.maskContentView = UIView()
self.maskContentView.backgroundColor = .white
if let filter = CALayer.luminanceToAlpha() {
self.maskContentView.layer.filters = [filter]
}
self.contentContainer = ContentContainer(maskContentView: self.maskContentView)
super.init(frame: frame)
if let nativeView = self.nativeView {
self.addSubview(nativeView)
}
if let backgroundNode = self.backgroundNode {
self.addSubview(backgroundNode.view)
}
if let foregroundView = self.foregroundView {
self.addSubview(foregroundView)
foregroundView.mask = self.maskContentView
}
self.addSubview(self.contentContainer)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor, transition: ComponentTransition) {
if let nativeView = self.nativeView {
let previousFrame = nativeView.frame
transition.containedViewLayoutTransition.animateView {
nativeView.layer.cornerRadius = cornerRadius
nativeView.frame = CGRect(origin: CGPoint(), size: size)
}
nativeView.layer.animateFrame(from: previousFrame, to: CGRect(origin: CGPoint(), size: size), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
}
if let backgroundNode = self.backgroundNode {
backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.alpha != 1.0, transition: transition.containedViewLayoutTransition)
backgroundNode.update(size: size, cornerRadius: cornerRadius, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
}
let params = Params(cornerRadius: cornerRadius, isDark: isDark, tintColor: tintColor)
if self.params != params {
self.params = params
/*if let nativeView {
if #available(iOS 26.0, *) {
let glassEffect = UIGlassEffect(style: .regular)
glassEffect.tintColor = tintColor
glassEffect.isInteractive = false
nativeView.effect = glassEffect
}
}*/
if let foregroundView = self.foregroundView {
foregroundView.image = generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor)
}
}
transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size))
if let foregroundView {
transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size))
}
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size))
}
}
public final class VariableBlurView: UIVisualEffectView {
public let maxBlurRadius: CGFloat
public var gradientMask: UIImage {
didSet {
if self.gradientMask !== oldValue {
self.resetEffect()
}
}
}
public init(gradientMask: UIImage, maxBlurRadius: CGFloat = 20.0) {
self.gradientMask = gradientMask
self.maxBlurRadius = maxBlurRadius
super.init(effect: UIBlurEffect(style: .regular))
self.resetEffect()
if self.subviews.indices.contains(1) {
let tintOverlayView = subviews[1]
tintOverlayView.alpha = 0
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
self.resetEffect()
}
}
private func resetEffect() {
let filterClassStringEncoded = "Q0FGaWx0ZXI="
let filterClassString: String = {
if
let data = Data(base64Encoded: filterClassStringEncoded),
let string = String(data: data, encoding: .utf8)
{
return string
}
return ""
}()
let filterWithTypeStringEncoded = "ZmlsdGVyV2l0aFR5cGU6"
let filterWithTypeString: String = {
if
let data = Data(base64Encoded: filterWithTypeStringEncoded),
let string = String(data: data, encoding: .utf8)
{
return string
}
return ""
}()
let filterWithTypeSelector = Selector(filterWithTypeString)
guard let filterClass = NSClassFromString(filterClassString) as AnyObject as? NSObjectProtocol else {
return
}
guard filterClass.responds(to: filterWithTypeSelector) else {
return
}
let variableBlur = filterClass.perform(filterWithTypeSelector, with: "variableBlur").takeUnretainedValue()
guard let variableBlur = variableBlur as? NSObject else {
return
}
guard let gradientImageRef = self.gradientMask.cgImage else {
return
}
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage")
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
variableBlur.setValue(UIScreenScale, forKey: "scale")
let backdropLayer = self.subviews.first?.layer
backdropLayer?.filters = [variableBlur]
}
}

View file

@ -439,6 +439,9 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, dismissUrlPreview: {
}, dismissForwardMessages: {
}, dismissSuggestPost: {
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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