Various improvements

This commit is contained in:
Isaac 2025-09-19 15:36:36 +04:00
parent 1d5de1be39
commit d4a4305f5b
21 changed files with 417 additions and 238 deletions

57
MODULE.bazel.lock generated
View file

@ -10,8 +10,6 @@
"https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
"https://bcr.bazel.build/modules/aexml/4.7.0/MODULE.bazel": "4030ff1555ade0956c08c74722851fcca0dc02ec7b8e7c61d0bbc4806ec4b2de",
"https://bcr.bazel.build/modules/aexml/4.7.0/source.json": "641c9de95dc10b8bf3685b5de9f9a84e7470ec3e40a09d7ddc9e3c9f2289a931",
"https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
@ -20,6 +18,7 @@
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
"https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
"https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
"https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
@ -50,8 +49,6 @@
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9",
"https://bcr.bazel.build/modules/pathkit/1.0.1/MODULE.bazel": "fae93989a10f8d90d5ac02453e6632ae7f71111687862c01f468858cef40bb5e",
"https://bcr.bazel.build/modules/pathkit/1.0.1/source.json": "3215e6b4b08f96f34024eaf186d247744ca255925d7ee3f50cf94f7cf885696b",
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
"https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
@ -86,7 +83,8 @@
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
"https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0",
"https://bcr.bazel.build/modules/rules_cc/0.1.2/source.json": "53fcb09b5816c83ca60d9d7493faf3bfaf410dfc2f15deb52d6ddd146b8d43f0",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
@ -137,7 +135,8 @@
"https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
"https://bcr.bazel.build/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0",
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
"https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95",
"https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
"https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3",
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
"https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef",
@ -148,8 +147,6 @@
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/MODULE.bazel": "49276599207dae3df1e4336c2067505323dfb0606b53ef63e144087d1226e0eb",
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/source.json": "bbbb718187dcbdfbb3a9a0ec7d49446cdf48c67657cafd79b5cf33aa8918f608",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
@ -157,37 +154,6 @@
},
"selectedYankedVersions": {},
"moduleExtensions": {
"@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
"general": {
"bzlTransitiveDigest": "gv4nokEMGNye4Jvoh7Tw0Lzs63zfklj+n4t0UegI7Ms=",
"usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"local_config_apple_cc_toolchains": {
"repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains",
"attributes": {}
},
"local_config_apple_cc": {
"repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf",
"attributes": {}
}
},
"recordedRepoMappingEntries": [
[
"apple_support+",
"bazel_tools",
"bazel_tools"
],
[
"bazel_tools",
"rules_cc",
"rules_cc+"
]
]
}
},
"@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
"general": {
"bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
@ -290,7 +256,7 @@
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%internal": {
"general": {
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
"bzlTransitiveDigest": "/r7IWMTdpceHqqiSh7G9bQLHvIkfqE/cesswbkTSJZw=",
"usagesDigest": "fvsnMonVwKDYnBfww4bXuYie3WU0d9VSqT2gePSdQco=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -312,7 +278,7 @@
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%non_module_deps": {
"general": {
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
"bzlTransitiveDigest": "/r7IWMTdpceHqqiSh7G9bQLHvIkfqE/cesswbkTSJZw=",
"usagesDigest": "jzxYhnOC9BE0dJ0biFLfxWXi/+R19uAAZkJ0p9CY0JI=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -345,15 +311,6 @@
"url": "https://github.com/apple/swift-argument-parser/archive/refs/tags/1.2.3.tar.gz"
}
},
"com_github_tadija_aexml": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "load(\"@build_bazel_rules_swift//swift:swift.bzl\", \"swift_library\")\n\nswift_library(\n name = \"AEXML\",\n srcs = glob([\"Sources/AEXML/**/*.swift\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"sha256": "5a76c28e4fa9dcc1cbfb87a8518652628e990e522ecfbc98bdad17eabf4631d5",
"strip_prefix": "AEXML-4.6.1",
"url": "https://github.com/tadija/AEXML/archive/refs/tags/4.6.1.tar.gz"
}
},
"com_github_michaeleisel_jjliso8601dateformatter": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {

View file

@ -847,7 +847,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return
}
strongSelf.forwardMessages(messageIds: nil)
}, displayCopyProtectionTip: { [weak self] node, save in
}, displayCopyProtectionTip: { [weak self] view, save in
guard let strongSelf = self, let messageIds = strongSelf.stateValue.selectedMessageIds, !messageIds.isEmpty else {
return
}
@ -910,7 +910,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
strongSelf.present?(tooltipController, TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
let rect = node.view.convert(node.view.bounds, to: strongSelf.view).offsetBy(dx: 0.0, dy: 3.0)
let rect = view.convert(view.bounds, to: strongSelf.view).offsetBy(dx: 0.0, dy: 3.0)
return (strongSelf, rect)
}
return nil

View file

@ -17,7 +17,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
private let deleteMessages: () -> Void
private let shareMessages: () -> Void
private let forwardMessages: () -> Void
private let displayCopyProtectionTip: (ASDisplayNode, Bool) -> Void
private let displayCopyProtectionTip: (UIView, Bool) -> Void
private let separatorNode: ASDisplayNode
private let backgroundNode: NavigationBackgroundNode
@ -61,7 +61,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
}
}
init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, displayCopyProtectionTip: @escaping (ASDisplayNode, Bool) -> Void) {
init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, displayCopyProtectionTip: @escaping (UIView, Bool) -> Void) {
self.context = context
self.deleteMessages = deleteMessages
self.shareMessages = shareMessages
@ -176,7 +176,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
@objc func forwardButtonPressed() {
if let actions = self.actions, actions.isCopyProtected {
self.displayCopyProtectionTip(self.forwardButton, false)
self.displayCopyProtectionTip(self.forwardButton.view, false)
} else {
self.forwardMessages()
}
@ -184,7 +184,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
@objc func shareButtonPressed() {
if let actions = self.actions, actions.isCopyProtected {
self.displayCopyProtectionTip(self.shareButton, true)
self.displayCopyProtectionTip(self.shareButton.view, true)
} else {
self.shareMessages()
}

View file

@ -166,7 +166,7 @@ public final class ChatPanelInterfaceInteraction {
public let openInviteRequests: () -> Void
public let openSendAsPeer: (ASDisplayNode, ContextGesture?) -> Void
public let presentChatRequestAdminInfo: () -> Void
public let displayCopyProtectionTip: (ASDisplayNode, Bool) -> Void
public let displayCopyProtectionTip: (UIView, Bool) -> Void
public let openWebView: (String, String, Bool, ChatOpenWebViewSource) -> Void
public let updateShowWebView: ((Bool) -> Bool) -> Void
public let insertText: (NSAttributedString) -> Void
@ -294,7 +294,7 @@ public final class ChatPanelInterfaceInteraction {
openInviteRequests: @escaping () -> Void,
openSendAsPeer: @escaping (ASDisplayNode, ContextGesture?) -> Void,
presentChatRequestAdminInfo: @escaping () -> Void,
displayCopyProtectionTip: @escaping (ASDisplayNode, Bool) -> Void,
displayCopyProtectionTip: @escaping (UIView, Bool) -> Void,
openWebView: @escaping (String, String, Bool, ChatOpenWebViewSource) -> Void,
updateShowWebView: @escaping ((Bool) -> Bool) -> Void,
insertText: @escaping (NSAttributedString) -> Void,

View file

@ -38,6 +38,7 @@ swift_library(
"//submodules/ActivityIndicator",
"//submodules/UndoUI",
"//submodules/RadialStatusNode",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",

View file

@ -1116,15 +1116,15 @@ final class ChatSendMessageContextScreenComponent: Component {
}
}
let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 44.0), height: sourceSendButtonFrame.height)
let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 40.0), height: sourceSendButtonFrame.height)
var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize)
var sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
var sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - 8.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
if !isMessageVisible {
sourceActionsStackFrame.origin.y = sourceSendButtonFrame.maxY - sourceActionsStackFrame.height - 5.0
}
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize)
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 0.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize)
if let mediaPreview {
switch mediaPreview.layoutType {
case .message, .media:

View file

@ -17,6 +17,7 @@ import WallpaperBackgroundNode
import AppBundle
import ActivityIndicator
import RadialStatusNode
import GlassBackgroundComponent
final class SendButton: HighlightTrackingButton {
enum Kind {
@ -27,8 +28,7 @@ final class SendButton: HighlightTrackingButton {
private let kind: Kind
private let containerView: UIView
private var backgroundContent: WallpaperBubbleBackgroundNode?
private let backgroundLayer: SimpleLayer
private let backgroundView: GlassBackgroundView
private let iconView: UIImageView
private var activityIndicator: RadialStatusNode?
@ -41,7 +41,7 @@ final class SendButton: HighlightTrackingButton {
self.containerView = UIView()
self.containerView.isUserInteractionEnabled = false
self.backgroundLayer = SimpleLayer()
self.backgroundView = GlassBackgroundView()
self.iconView = UIImageView()
self.iconView.isUserInteractionEnabled = false
@ -51,7 +51,7 @@ final class SendButton: HighlightTrackingButton {
self.containerView.clipsToBounds = true
self.addSubview(self.containerView)
self.containerView.layer.addSublayer(self.backgroundLayer)
self.containerView.addSubview(self.backgroundView)
self.containerView.addSubview(self.iconView)
}
@ -69,34 +69,13 @@ final class SendButton: HighlightTrackingButton {
size: CGSize,
transition: ComponentTransition
) {
let innerSize = CGSize(width: size.width - 5.5 * 2.0, height: 33.0)
let innerSize = CGSize(width: size.width, height: size.height)
let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - innerSize.width) * 0.5), y: floorToScreenPixels((size.height - innerSize.height) * 0.5)), size: innerSize)
transition.setFrame(view: self.containerView, frame: containerFrame)
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: innerSize.height * 0.5)
if self.window != nil {
if self.backgroundContent == nil, let backgroundNode = backgroundNode as? WallpaperBackgroundNodeImpl {
if let backgroundContent = backgroundNode.makeLegacyBubbleBackground(for: .outgoing) {
self.backgroundContent = backgroundContent
self.containerView.insertSubview(backgroundContent.view, at: 0)
}
}
}
if let backgroundContent = self.backgroundContent {
transition.setFrame(view: backgroundContent.view, frame: CGRect(origin: CGPoint(), size: innerSize))
}
if backgroundNode != nil && [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor {
self.backgroundContent?.isHidden = false
self.backgroundLayer.isHidden = true
} else {
self.backgroundContent?.isHidden = true
self.backgroundLayer.isHidden = false
}
self.backgroundLayer.backgroundColor = presentationData.theme.chat.inputPanel.actionControlFillColor.cgColor
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: innerSize))
self.backgroundView.update(size: innerSize, cornerRadius: innerSize.height * 0.5, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: presentationData.theme.chat.inputPanel.actionControlFillColor), transition: .immediate)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: innerSize))
if self.previousIsAnimatedIn != isAnimatedIn {
self.previousIsAnimatedIn = isAnimatedIn
@ -140,7 +119,7 @@ final class SendButton: HighlightTrackingButton {
}
if let icon = self.iconView.image {
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - icon.size.width) * 0.5) - UIScreenPixel, y: floorToScreenPixels((innerSize.height - icon.size.height) * 0.5)), size: icon.size)
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - icon.size.width) * 0.5), y: floorToScreenPixels((innerSize.height - icon.size.height) * 0.5)), size: icon.size)
transition.setPosition(view: self.iconView, position: iconFrame.center)
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
@ -198,8 +177,5 @@ final class SendButton: HighlightTrackingButton {
}
func updateGlobalRect(rect: CGRect, within containerSize: CGSize, transition: ComponentTransition) {
if let backgroundContent = self.backgroundContent {
backgroundContent.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.containerView.frame.minX, y: rect.minY + self.containerView.frame.minY), size: backgroundContent.bounds.size), within: containerSize, transition: transition.containedViewLayoutTransition)
}
}
}

View file

@ -151,7 +151,13 @@ final class TabBarControllerNode: ASDisplayNode {
transition.updateAlpha(node: self.disabledOverlayNode, alpha: value ? 0.0 : 1.0)
}
var tabBarHidden = false
var tabBarHidden = false {
didSet {
if self.tabBarHidden != oldValue {
self.requestUpdate()
}
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) -> CGFloat {
let params = Params(layout: layout, toolbar: toolbar)

View file

@ -331,7 +331,7 @@ public final class ChatInputMessageAccessoryPanel: Component {
let environment = environment[EnvironmentType.self].value
if self.component == nil {
let messageIdsFromComponent: (ChatInputMessageAccessoryPanel) -> [EngineMessage.Id] = { component in
let messageIds: [EngineMessage.Id]
switch component.contents {
case let .edit(edit):
@ -343,7 +343,11 @@ public final class ChatInputMessageAccessoryPanel: Component {
case .linkPreview, .suggestPost:
messageIds = []
}
return messageIds
}
let messageIds = messageIdsFromComponent(component)
if self.component == nil || self.component.flatMap(messageIdsFromComponent) != messageIds {
self.contentDisposable?.dispose()
if !messageIds.isEmpty {
self.contentDisposable = (component.context.engine.data.subscribe(

View file

@ -22,6 +22,9 @@ swift_library(
"//submodules/TelegramUI/Components/EntityKeyboard",
"//submodules/TelegramUI/Components/Chat/TopMessageReactions",
"//submodules/ReactionSelectionNode",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",

View file

@ -13,6 +13,9 @@ import ChatInputPanelNode
import ReactionSelectionNode
import EntityKeyboard
import TopMessageReactions
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
private final class ChatMessageSelectionInputPanelNodeViewForOverlayContent: UIView, ChatInputPanelViewForOverlayContent {
var reactionContextNode: ReactionContextNode?
@ -61,14 +64,125 @@ private final class ChatMessageSelectionInputPanelNodeViewForOverlayContent: UIV
}
}
private final class GlassButtonView: HighlightTrackingButton {
private struct Params: Equatable {
let theme: PresentationTheme
let size: CGSize
init(theme: PresentationTheme, size: CGSize) {
self.theme = theme
self.size = size
}
static func ==(lhs: Params, rhs: Params) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.size != rhs.size {
return false
}
return true
}
}
private let backgroundView: GlassBackgroundView
private let iconView: GlassBackgroundView.ContentImageView
private var params: Params?
var isImplicitlyDisabled: Bool = false {
didSet {
self.updateIsEnabled()
}
}
override var isEnabled: Bool {
didSet {
self.updateIsEnabled()
}
}
var icon: String? {
didSet {
if self.icon == oldValue {
return
}
if let icon = self.icon {
self.iconView.image = UIImage(bundleImageName: icon)?.withRenderingMode(.alwaysTemplate)
} else {
self.iconView.image = nil
}
if let params = self.params {
self.updateImpl(params: params, transition: .immediate)
}
}
}
override init(frame: CGRect) {
self.backgroundView = GlassBackgroundView()
self.backgroundView.isUserInteractionEnabled = false
self.iconView = GlassBackgroundView.ContentImageView()
self.backgroundView.contentView.addSubview(self.iconView)
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted && self.isEnabled && !self.isImplicitlyDisabled {
self.backgroundView.contentView.alpha = 0.6
} else {
self.backgroundView.contentView.alpha = 1.0
self.backgroundView.contentView.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(theme: PresentationTheme, size: CGSize, transition: ComponentTransition) {
let params = Params(theme: theme, size: size)
if self.params != params {
self.iconView.tintColor = params.theme.chat.inputPanel.inputControlColor
self.params = params
self.updateImpl(params: params, transition: transition)
}
}
private func updateImpl(params: Params, transition: ComponentTransition) {
if let image = self.iconView.image {
let iconFrame = image.size.centered(in: CGRect(origin: CGPoint(), size: params.size))
transition.setFrame(view: self.iconView, frame: iconFrame)
}
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
self.backgroundView.update(size: params.size, cornerRadius: min(params.size.width, params.size.height) * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition)
let isEnabled = self.isEnabled && !self.isImplicitlyDisabled
self.iconView.alpha = isEnabled ? 1.0 : 0.5
self.iconView.tintMask.alpha = self.iconView.alpha
}
private func updateIsEnabled() {
if let params = self.params {
self.updateImpl(params: params, transition: .immediate)
}
}
}
public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
private let deleteButton: HighlightableButtonNode
private let reportButton: HighlightableButtonNode
private let forwardButton: HighlightableButtonNode
private let shareButton: HighlightableButtonNode
private let tagButton: HighlightableButtonNode
private let tagEditButton: HighlightableButtonNode
private let separatorNode: ASDisplayNode
private let deleteButton: GlassButtonView
private let reportButton: GlassButtonView
private let forwardButton: GlassButtonView
private let shareButton: GlassButtonView
private let tagButton: GlassButtonView
private let tagEditButton: GlassButtonView
private let reactionOverlayContainer: ChatMessageSelectionInputPanelNodeViewForOverlayContent
@ -93,69 +207,60 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
self.theme = theme
self.peerMedia = peerMedia
self.deleteButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0)))
self.deleteButton = GlassButtonView()
self.deleteButton.icon = "Chat/Input/Accessory Panels/MessageSelectionTrash"
self.deleteButton.isEnabled = false
self.deleteButton.isAccessibilityElement = true
self.deleteButton.accessibilityLabel = strings.VoiceOver_MessageContextDelete
self.reportButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0)))
self.reportButton = GlassButtonView()
self.reportButton.icon = "Chat/Input/Accessory Panels/MessageSelectionReport"
self.reportButton.isEnabled = false
self.reportButton.isAccessibilityElement = true
self.reportButton.accessibilityLabel = strings.VoiceOver_MessageContextReport
self.forwardButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0)))
self.forwardButton = GlassButtonView()
self.forwardButton.icon = "Chat/Input/Accessory Panels/MessageSelectionForward"
self.forwardButton.isAccessibilityElement = true
self.forwardButton.accessibilityLabel = strings.VoiceOver_MessageContextForward
self.shareButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0)))
self.shareButton = GlassButtonView()
self.shareButton.icon = "Chat/Input/Accessory Panels/MessageSelectionAction"
self.shareButton.isAccessibilityElement = true
self.shareButton.accessibilityLabel = strings.VoiceOver_MessageContextShare
self.tagButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0)))
self.tagButton = GlassButtonView()
self.tagButton.icon = "Chat/Input/Accessory Panels/TagIcon"
self.tagButton.isAccessibilityElement = true
self.tagButton.accessibilityLabel = strings.VoiceOver_MessageSelectionButtonTag
self.tagEditButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0)))
self.tagEditButton = GlassButtonView()
self.tagEditButton.icon = "Chat/Input/Accessory Panels/TagEditIcon"
self.tagEditButton.isAccessibilityElement = true
self.tagEditButton.accessibilityLabel = strings.VoiceOver_MessageSelectionButtonTag
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.reportButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionReport"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.reportButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionReport"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.tagButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TagIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.tagEditButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TagEditIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor
self.reactionOverlayContainer = ChatMessageSelectionInputPanelNodeViewForOverlayContent()
super.init()
self.addSubnode(self.deleteButton)
self.addSubnode(self.reportButton)
self.addSubnode(self.forwardButton)
self.addSubnode(self.shareButton)
self.addSubnode(self.tagButton)
self.addSubnode(self.tagEditButton)
self.addSubnode(self.separatorNode)
self.view.addSubview(self.deleteButton)
self.view.addSubview(self.reportButton)
self.view.addSubview(self.forwardButton)
self.view.addSubview(self.shareButton)
self.view.addSubview(self.tagButton)
self.view.addSubview(self.tagEditButton)
self.viewForOverlayContent = self.reactionOverlayContainer
self.forwardButton.isImplicitlyDisabled = true
self.shareButton.isImplicitlyDisabled = true
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), forControlEvents: .touchUpInside)
self.reportButton.addTarget(self, action: #selector(self.reportButtonPressed), forControlEvents: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), forControlEvents: .touchUpInside)
self.tagButton.addTarget(self, action: #selector(self.tagButtonPressed), forControlEvents: .touchUpInside)
self.tagEditButton.addTarget(self, action: #selector(self.tagButtonPressed), forControlEvents: .touchUpInside)
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: .touchUpInside)
self.reportButton.addTarget(self, action: #selector(self.reportButtonPressed), for: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), for: .touchUpInside)
self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), for: .touchUpInside)
self.tagButton.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside)
self.tagEditButton.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside)
}
deinit {
@ -187,19 +292,6 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
public func updateTheme(theme: PresentationTheme) {
if self.theme !== theme {
self.theme = theme
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.reportButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionReport"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.reportButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionReport"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.tagButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/WebpageIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.tagEditButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/LinkSettingsIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.separatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor
}
}
@ -356,6 +448,12 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded)
var leftInset = leftInset
leftInset += 8.0
var rightInset = rightInset
rightInset += 8.0
let panelHeight = defaultHeight(metrics: metrics)
if self.presentationInterfaceState != interfaceState {
@ -419,14 +517,14 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
width -= additionalSideInsets.right
}
var tagButton: HighlightableButtonNode?
var tagButton: GlassButtonView?
if !self.tagButton.isHidden {
tagButton = self.tagButton
} else if !self.tagEditButton.isHidden {
tagButton = self.tagEditButton
}
let buttons: [HighlightableButtonNode]
let buttons: [GlassButtonView]
if self.reportButton.isHidden {
if let tagButton {
buttons = [
@ -478,24 +576,25 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
}
}
let buttonSize = CGSize(width: 57.0, height: panelHeight)
let buttonSize = CGSize(width: 40.0, height: 40.0)
let availableWidth = width - leftInset - rightInset
let spacing: CGFloat = floor((availableWidth - buttonSize.width * CGFloat(buttons.count)) / CGFloat(buttons.count - 1))
var offset: CGFloat = leftInset
for i in 0 ..< buttons.count {
let button = buttons[i]
let buttonFrame: CGRect
if i == buttons.count - 1 {
button.frame = CGRect(origin: CGPoint(x: width - rightInset - buttonSize.width, y: 0.0), size: buttonSize)
buttonFrame = CGRect(origin: CGPoint(x: width - rightInset - buttonSize.width, y: 0.0), size: buttonSize)
} else {
button.frame = CGRect(origin: CGPoint(x: offset, y: 0.0), size: buttonSize)
buttonFrame = CGRect(origin: CGPoint(x: offset, y: 0.0), size: buttonSize)
}
transition.updateFrame(view: button, frame: buttonFrame)
button.update(theme: interfaceState.theme, size: buttonFrame.size, transition: ComponentTransition(transition))
offset += buttonSize.width + spacing
}
transition.updateAlpha(node: self.separatorNode, alpha: isSecondary ? 1.0 : 0.0)
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: panelHeight), size: CGSize(width: width, height: UIScreenPixel))
if let reactionContextNode = self.reactionOverlayContainer.reactionContextNode, let tagButton {
let isFirstTime = reactionContextNode.bounds.isEmpty

View file

@ -1434,7 +1434,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
} else {
self.sendAsAvatarNode.isHidden = true
}
if mediaRecordingState != nil {
if mediaRecordingState != nil || interfaceState.interfaceState.mediaDraftState != nil {
hasMenuButton = false
}
@ -1449,7 +1449,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if hasMenuButton {
hideOffset = CGPoint(x: width, y: 0.0)
} else {
hideOffset = CGPoint(x: 0.0, y: 80.0)
hideOffset = CGPoint(x: 0.0, y: 80.0 + 60.0)
}
if self.startButton.isHidden {
self.startButton.isHidden = false
@ -1885,11 +1885,14 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
panelHeight += 27.0
}
let menuButtonOriginY: CGFloat
var menuButtonOriginY: CGFloat
if displayBotStartButton {
menuButtonOriginY = floorToScreenPixels((minimalHeight - menuButtonHeight) / 2.0)
} else {
menuButtonOriginY = panelHeight - minimalHeight + floorToScreenPixels((minimalHeight - menuButtonHeight) / 2.0)
if accessoryPanel != nil {
menuButtonOriginY += 52.0
}
}
let menuButtonFrame = CGRect(x: leftInset + 8.0, y: menuButtonOriginY, width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight)
@ -1937,6 +1940,28 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.actionButtons.micButton.updateMode(mode: interfaceState.interfaceState.mediaRecordingMode, animated: transition.isAnimated)
var hideMicButton = false
if inputHasText || self.extendedSearchLayout {
hideMicButton = true
}
if let mediaRecordingState {
switch mediaRecordingState {
case .audio:
break
case let .video(status, _):
switch status {
case .recording:
break
case .editing:
hideMicButton = true
}
case .waitingForPreview:
break
}
}
self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: transition.isAnimated)
var actionButtonsSize = CGSize(width: 40.0, height: 40.0)
if let presentationInterfaceState = self.presentationInterfaceState {
var showTitle = false
@ -1955,7 +1980,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
var textFieldInsets = self.textFieldInsets(metrics: metrics)
if actionButtonsSize.width > 40.0 {
textFieldInsets.right = actionButtonsSize.width - 2.0
textFieldInsets.right = actionButtonsSize.width + 14.0
}
if additionalSideInsets.right > 0.0 {
textFieldInsets.right += additionalSideInsets.right / 3.0
@ -1967,12 +1992,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
textFieldInsets.left = 8.0
}
var hideMicButton = false
var audioRecordingItemsAlpha: CGFloat = 1.0
if interfaceState.interfaceState.mediaDraftState != nil {
audioRecordingItemsAlpha = 0.0
}
if mediaRecordingState != nil {
if let mediaRecordingState {
audioRecordingItemsAlpha = 0.0
let audioRecordingInfoContainerNode: ASDisplayNode
@ -1993,7 +2017,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.audioRecordingTimeNode = audioRecordingTimeNode
audioRecordingInfoContainerNode.addSubnode(audioRecordingTimeNode)
if transition.isAnimated && mediaRecordingState != nil {
if transition.isAnimated {
animateTimeSlideIn = true
}
}
@ -2003,7 +2027,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if let currentAudioRecordingCancelIndicator = self.audioRecordingCancelIndicator {
audioRecordingCancelIndicator = currentAudioRecordingCancelIndicator
} else {
animateCancelSlideIn = transition.isAnimated && mediaRecordingState != nil
animateCancelSlideIn = transition.isAnimated
audioRecordingCancelIndicator = ChatTextInputAudioRecordingCancelIndicator(theme: interfaceState.theme, strings: interfaceState.strings, cancel: { [weak self] in
self?.viewOnce = false
@ -2014,38 +2038,35 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.textInputContainerBackgroundView.contentView.addSubview(audioRecordingCancelIndicator)
}
let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.interfaceState.mediaDraftState != nil)
let isLocked = mediaRecordingState.isLocked
var hideInfo = false
if let mediaRecordingState = mediaRecordingState {
switch mediaRecordingState {
case let .audio(recorder, isLocked):
let hadAudioRecorder = self.actionButtons.micButton.audioRecorder != nil
if !hadAudioRecorder, isLocked {
self.actionButtons.micButton.lock()
}
self.actionButtons.micButton.audioRecorder = recorder
audioRecordingTimeNode.audioRecorder = recorder
case let .video(status, _):
let hadVideoRecorder = self.actionButtons.micButton.videoRecordingStatus != nil
if !hadVideoRecorder, isLocked {
self.actionButtons.micButton.lock()
}
switch status {
case let .recording(recordingStatus):
audioRecordingTimeNode.videoRecordingStatus = recordingStatus
self.actionButtons.micButton.videoRecordingStatus = recordingStatus
case .editing:
audioRecordingTimeNode.videoRecordingStatus = nil
self.actionButtons.micButton.videoRecordingStatus = nil
hideMicButton = true
hideInfo = true
}
case .waitingForPreview:
Queue.mainQueue().after(0.5, {
self.actionButtons.micButton.audioRecorder = nil
})
switch mediaRecordingState {
case let .audio(recorder, isLocked):
let hadAudioRecorder = self.actionButtons.micButton.audioRecorder != nil
if !hadAudioRecorder, isLocked {
self.actionButtons.micButton.lock()
}
self.actionButtons.micButton.audioRecorder = recorder
audioRecordingTimeNode.audioRecorder = recorder
case let .video(status, _):
let hadVideoRecorder = self.actionButtons.micButton.videoRecordingStatus != nil
if !hadVideoRecorder, isLocked {
self.actionButtons.micButton.lock()
}
switch status {
case let .recording(recordingStatus):
audioRecordingTimeNode.videoRecordingStatus = recordingStatus
self.actionButtons.micButton.videoRecordingStatus = recordingStatus
case .editing:
audioRecordingTimeNode.videoRecordingStatus = nil
self.actionButtons.micButton.videoRecordingStatus = nil
hideInfo = true
}
case .waitingForPreview:
Queue.mainQueue().after(0.5, {
self.actionButtons.micButton.audioRecorder = nil
})
}
transition.updateAlpha(layer: self.textInputBackgroundNode.layer, alpha: 0.0)
@ -2075,7 +2096,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
audioRecordingCancelIndicator.layer.animatePosition(from: CGPoint(x: width + audioRecordingCancelIndicator.bounds.size.width, y: position.y), to: position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
}
audioRecordingCancelIndicator.updateIsDisplayingCancel(isLocked, animated: !animateCancelSlideIn && mediaRecordingState != nil)
audioRecordingCancelIndicator.updateIsDisplayingCancel(isLocked, animated: !animateCancelSlideIn)
if isLocked || self.actionButtons.micButton.cancelTranslation > cancelTransformThreshold {
var deltaOffset: CGFloat = 0.0
@ -2229,12 +2250,6 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
self.updateCounterTextNode(transition: transition)
if inputHasText || self.extendedSearchLayout {
hideMicButton = true
}
self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: transition.isAnimated)
var textInputViewRealInsets = UIEdgeInsets()
if let presentationInterfaceState = self.presentationInterfaceState {

View file

@ -286,6 +286,7 @@ public class GlassBackgroundView: UIView {
let glassEffect = UIGlassEffect(style: .clear)
glassEffect.isInteractive = false
let nativeView = UIVisualEffectView(effect: glassEffect)
nativeView.layer.cornerCurve = .circular
self.nativeView = nativeView
nativeView.overrideUserInterfaceStyle = .light
nativeView.traitOverrides.userInterfaceStyle = .light
@ -382,7 +383,7 @@ public class GlassBackgroundView: UIView {
let glassEffect = UIGlassEffect(style: .clear)
switch tintColor.kind {
case .panel:
glassEffect.tintColor = tintColor.color
glassEffect.tintColor = tintColor.color.withMultipliedAlpha(1.2)
case .custom:
glassEffect.tintColor = tintColor.color
}

View file

@ -290,7 +290,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
private let shareMessages: () -> Void
private let forwardMessages: () -> Void
private let reportMessages: () -> Void
private let displayCopyProtectionTip: (ASDisplayNode, Bool) -> Void
private let displayCopyProtectionTip: (UIView, Bool) -> Void
let selectionPanel: ChatMessageSelectionInputPanelNode
let separatorNode: ASDisplayNode
@ -300,7 +300,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
return self.selectionPanel.viewForOverlayContent
}
init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, reportMessages: @escaping () -> Void, displayCopyProtectionTip: @escaping (ASDisplayNode, Bool) -> Void) {
init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, reportMessages: @escaping () -> Void, displayCopyProtectionTip: @escaping (UIView, Bool) -> Void) {
self.context = context
self.peerId = peerId
self.deleteMessages = deleteMessages
@ -420,8 +420,8 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, openInviteRequests: {
}, openSendAsPeer: { _, _ in
}, presentChatRequestAdminInfo: {
}, displayCopyProtectionTip: { node, save in
displayCopyProtectionTip(node, save)
}, displayCopyProtectionTip: { view, save in
displayCopyProtectionTip(view, save)
}, openWebView: { _, _, _, _ in
}, updateShowWebView: { _ in
}, insertText: { _ in
@ -12645,7 +12645,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
strongSelf.context.sharedContext.makeContentReportScreen(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), forceDark: false, present: { [weak self] controller in
self?.controller?.push(controller)
}, completion: {}, requestSelectMessages: nil)
}, displayCopyProtectionTip: { [weak self] node, save in
}, displayCopyProtectionTip: { [weak self] sourceView, save in
if let strongSelf = self, let peer = strongSelf.data?.peer, let messageIds = strongSelf.state.selectedMessageIds, !messageIds.isEmpty {
let _ = (strongSelf.context.engine.data.get(EngineDataMap(
messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init)
@ -12707,7 +12707,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
strongSelf.controller?.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
let rect = node.view.convert(node.view.bounds, to: strongSelf.view).offsetBy(dx: 0.0, dy: 3.0)
let rect = sourceView.convert(sourceView.bounds, to: strongSelf.view).offsetBy(dx: 0.0, dy: 3.0)
return (strongSelf, rect)
}
return nil

View file

@ -928,7 +928,6 @@ public class VideoMessageCameraScreen: ViewController {
self.backgroundColor = .clear
self.view.addSubview(self.backgroundView)
self.view.addSubview(self.containerView)
self.containerView.addSubview(self.previewContainerView)
@ -963,6 +962,7 @@ public class VideoMessageCameraScreen: ViewController {
deinit {
self.cameraStateDisposable?.dispose()
self.idleTimerExtensionDisposable.dispose()
self.backgroundView.removeFromSuperview()
}
func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) {
@ -1068,10 +1068,6 @@ public class VideoMessageCameraScreen: ViewController {
func animateIn() {
self.animatingIn = true
// if let chatNode = self.controller?.chatNode {
// chatNode.supernode?.view.insertSubview(self.backgroundView, aboveSubview: chatNode.view)
// }
self.backgroundView.alpha = 0.0
UIView.animate(withDuration: 0.4, animations: {
self.backgroundView.alpha = 1.0
@ -1430,12 +1426,13 @@ public class VideoMessageCameraScreen: ViewController {
}
var backgroundFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: controller.inputPanelFrame.0.minY))
let actualBackgroundFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: layout.size.height))
if backgroundFrame.maxY < layout.size.height - 100.0 && (layout.inputHeight ?? 0.0).isZero && !controller.inputPanelFrame.1 && layout.additionalInsets.bottom.isZero {
backgroundFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: layout.size.height - layout.intrinsicInsets.bottom - controller.inputPanelFrame.0.height))
}
transition.setPosition(view: self.backgroundView, position: backgroundFrame.center)
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: .zero, size: backgroundFrame.size))
transition.setPosition(view: self.backgroundView, position: actualBackgroundFrame.center)
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: .zero, size: actualBackgroundFrame.size))
transition.setPosition(view: self.containerView, position: backgroundFrame.center)
transition.setBounds(view: self.containerView, bounds: CGRect(origin: .zero, size: backgroundFrame.size))
@ -1637,6 +1634,10 @@ public class VideoMessageCameraScreen: ViewController {
public var onResume: () -> Void = {
}
public var backgroundView: UIVisualEffectView {
return self.node.backgroundView
}
public struct RecordedVideoData {
public let duration: Double
public let frames: [UIImage]

View file

@ -3881,7 +3881,7 @@ extension ChatControllerImpl {
let cleanInsets = layout.intrinsicInsets
let insets = layout.insets(options: .input)
let bottomInset = max(insets.bottom, cleanInsets.bottom) + 43.0
let bottomInset = max(insets.bottom, cleanInsets.bottom) + 54.0
let defaultMyPeerId: PeerId
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) {
@ -3932,7 +3932,7 @@ extension ChatControllerImpl {
})
}, presentChatRequestAdminInfo: { [weak self] in
self?.presentChatRequestAdminInfo()
}, displayCopyProtectionTip: { [weak self] node, save in
}, displayCopyProtectionTip: { [weak self] sourceView, save in
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds {
let _ = (strongSelf.context.engine.data.get(EngineDataMap(
messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init)
@ -3994,7 +3994,7 @@ extension ChatControllerImpl {
}
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
let rect = node.view.convert(node.view.bounds, to: strongSelf.chatDisplayNode.view).offsetBy(dx: 0.0, dy: 3.0)
let rect = sourceView.convert(sourceView.bounds, to: strongSelf.chatDisplayNode.view).offsetBy(dx: 0.0, dy: 3.0)
return (strongSelf.chatDisplayNode, rect)
}
return nil

View file

@ -5676,7 +5676,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
if let videoRecorder = videoRecorder {
if let videoRecorder {
strongSelf.recorderFeedback?.impact(.light)
videoRecorder.onStop = {
@ -5684,6 +5684,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.dismissMediaRecorder(.pause)
}
}
strongSelf.chatDisplayNode.setVideoRecorder(backgroundView: videoRecorder.backgroundView)
strongSelf.present(videoRecorder, in: .window(.root))
if strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId {

View file

@ -5095,10 +5095,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
inputPanelNodeSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak inputPanelNodeSnapshot] _ in
inputPanelNodeSnapshot?.removeFromSuperview()
})
inputPanelNodeSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -5.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
inputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
inputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 5.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
@ -5262,4 +5259,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
)
}
}
func setVideoRecorder(backgroundView: UIView?) {
if let backgroundView {
self.wrappingNode.contentNode.view.insertSubview(backgroundView, belowSubview: self.inputPanelContainerNode.view)
}
}
}

View file

@ -16,6 +16,7 @@ import PlainButtonComponent
import ComponentDisplayAdapters
import BundleIconComponent
import AnimatedTextComponent
import GlassBackgroundComponent
private let labelFont = Font.regular(15.0)
@ -58,6 +59,8 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
}
private let leftControlsBackgroundView: GlassBackgroundView
private let rightControlsBackgroundView: GlassBackgroundView
private let calendarButton = ComponentView<Empty>()
private var membersButton: ComponentView<Empty>?
private var resultsText: ComponentView<Empty>?
@ -90,7 +93,13 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
init(theme: PresentationTheme, alwaysShowTotalMessagesCount: Bool) {
self.alwaysShowTotalMessagesCount = alwaysShowTotalMessagesCount
self.leftControlsBackgroundView = GlassBackgroundView()
self.rightControlsBackgroundView = GlassBackgroundView()
super.init()
self.view.addSubview(self.leftControlsBackgroundView)
self.view.addSubview(self.rightControlsBackgroundView)
}
deinit {
@ -282,6 +291,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
let size = CGSize(width: params.width - params.additionalSideInsets.left * 2.0 - params.leftInset * 2.0, height: height)
var listModeButtonFrameValue: CGRect?
if canChangeListMode {
var listModeButtonTransition = transition
let listModeButton: ComponentView<Empty>
@ -298,11 +308,11 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(AnimatedTextComponent(
font: Font.regular(15.0),
color: params.interfaceState.theme.rootController.navigationBar.accentTextColor,
color: params.interfaceState.theme.chat.inputPanel.inputControlColor,
items: modeButtonTitle
)),
effectAlignment: .right,
minSize: CGSize(width: 1.0, height: size.height),
minSize: CGSize(width: 1.0, height: 40.0),
contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
action: { [weak self] in
guard let self, let params = self.currentLayout?.params else {
@ -322,7 +332,8 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
buttonView.alpha = 0.0
self.view.addSubview(buttonView)
}
let listModeFrame = CGRect(origin: CGPoint(x: size.width - params.rightInset - 11.0 - buttonSize.width, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize)
let listModeFrame = CGRect(origin: CGPoint(x: size.width - params.rightInset - 20.0 - 8.0 - buttonSize.width, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize)
listModeButtonFrameValue = listModeFrame
listModeButtonTransition.setPosition(view: buttonView, position: CGPoint(x: listModeFrame.minX + listModeFrame.width * buttonView.layer.anchorPoint.x, y: listModeFrame.minY + listModeFrame.height * buttonView.layer.anchorPoint.y))
listModeButtonTransition.setBounds(view: buttonView, bounds: CGRect(origin: CGPoint(), size: listModeFrame.size))
transition.setAlpha(view: buttonView, alpha: 1.0)
@ -338,20 +349,23 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
}
var nextLeftX: CGFloat = 16.0
var nextLeftX: CGFloat = 16.0 + 8.0
var calendarButtonFrameValue: CGRect?
var membersButtonFrameValue: CGRect?
var resultsTextFrameValue: CGRect?
if !self.alwaysShowTotalMessagesCount && self.externalSearchResultsCount == nil {
nextLeftX = 12.0
nextLeftX -= 4.0
let calendarButtonSize = self.calendarButton.update(
transition: .immediate,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(BundleIconComponent(
name: "Chat/Input/Search/Calendar",
tintColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor
tintColor: params.interfaceState.theme.chat.inputPanel.inputControlColor
)),
effectAlignment: .center,
minSize: CGSize(width: 1.0, height: size.height),
contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
minSize: CGSize(width: 40.0, height: 40.0),
action: { [weak self] in
guard let self else {
return
@ -363,6 +377,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
containerSize: size
)
let calendarButtonFrame = CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - calendarButtonSize.height) * 0.5)), size: calendarButtonSize)
calendarButtonFrameValue = calendarButtonFrame
if let calendarButtonView = self.calendarButton.view {
if calendarButtonView.superview == nil {
self.view.addSubview(calendarButtonView)
@ -375,7 +390,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
transition.setFrame(view: calendarButtonView, frame: calendarButtonFrame)
}
nextLeftX += calendarButtonSize.width + 8.0
nextLeftX += calendarButtonSize.width + 0.0
} else if let calendarButtonView = self.calendarButton.view {
if transition.animation.isImmediate {
calendarButtonView.removeFromSuperview()
@ -404,11 +419,10 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(BundleIconComponent(
name: "Chat/Input/Search/Members",
tintColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor
tintColor: params.interfaceState.theme.chat.inputPanel.inputControlColor
)),
effectAlignment: .center,
minSize: CGSize(width: 1.0, height: size.height),
contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
minSize: CGSize(width: 40.0, height: 40.0),
action: { [weak self] in
guard let self else {
return
@ -428,14 +442,16 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
animateIn = true
self.view.addSubview(buttonView)
}
membersButtonTransition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize))
let membersButtonFrame = CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize)
membersButtonFrameValue = membersButtonFrame
membersButtonTransition.setFrame(view: buttonView, frame: membersButtonFrame)
transition.setAlpha(view: buttonView, alpha: 1.0)
if animateIn {
transition.animateScale(view: buttonView, from: 0.001, to: 1.0)
}
}
nextLeftX += buttonSize.width + 8.0
nextLeftX += buttonSize.width + 0.0
} else {
if let membersButton = self.membersButton {
self.membersButton = nil
@ -468,7 +484,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(AnimatedTextComponent(
font: Font.regular(15.0),
color: (params.interfaceState.displayHistoryFilterAsList && canChangeListMode) ? params.interfaceState.theme.rootController.navigationBar.accentTextColor : params.interfaceState.theme.rootController.navigationBar.secondaryTextColor,
color: params.interfaceState.theme.rootController.navigationBar.secondaryTextColor,
items: resultsTextString
)),
effectAlignment: .center,
@ -484,6 +500,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
containerSize: CGSize(width: 200.0, height: 100.0)
)
let resultsTextFrame = CGRect(origin: CGPoint(x: nextLeftX - 3.0, y: floor((size.height - resultsTextSize.height) * 0.5)), size: resultsTextSize)
resultsTextFrameValue = resultsTextFrame
if let resultsTextView = resultsText.view {
if resultsTextView.superview == nil {
resultsTextView.alpha = 0.0
@ -503,6 +520,57 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
}
}
let adjustedResultsTextFrameValue = resultsTextFrameValue.flatMap { rect in
var rect = rect
rect.size.width += 8.0
return rect
}
let leftControlsFrames: [CGRect?] = [
calendarButtonFrameValue,
membersButtonFrameValue,
adjustedResultsTextFrameValue
]
var leftControlsRect = CGRect()
for rect in leftControlsFrames {
guard let rect else {
continue
}
if leftControlsRect.isEmpty {
leftControlsRect = rect
} else {
leftControlsRect = leftControlsRect.union(rect)
}
}
var leftControlsBackgroundFrame = CGRect(origin: CGPoint(x: 20.0, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0))
leftControlsBackgroundFrame.size.width = max(40.0, leftControlsRect.maxX - leftControlsBackgroundFrame.minX)
transition.setFrame(view: self.leftControlsBackgroundView, frame: leftControlsBackgroundFrame)
self.leftControlsBackgroundView.update(size: leftControlsBackgroundFrame.size, cornerRadius: leftControlsBackgroundFrame.height * 0.5, isDark: params.interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition)
transition.setAlpha(view: self.leftControlsBackgroundView, alpha: leftControlsRect.isEmpty ? 0.0 : 1.0)
let rightControlsFrames: [CGRect?] = [
listModeButtonFrameValue
]
var rightControlsRect = CGRect()
for rect in rightControlsFrames {
guard let rect else {
continue
}
if rightControlsRect.isEmpty {
rightControlsRect = rect
} else {
rightControlsRect = rightControlsRect.union(rect)
}
}
var rightControlsBackgroundFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 20.0, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0))
rightControlsBackgroundFrame.size.width = max(40.0, rightControlsRect.maxX - rightControlsRect.minX + 8.0 * 2.0)
rightControlsBackgroundFrame.origin.x -= rightControlsBackgroundFrame.width
transition.setFrame(view: self.rightControlsBackgroundView, frame: rightControlsBackgroundFrame)
self.rightControlsBackgroundView.update(size: rightControlsBackgroundFrame.size, cornerRadius: rightControlsBackgroundFrame.height * 0.5, isDark: params.interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition)
transition.setAlpha(view: self.rightControlsBackgroundView, alpha: rightControlsRect.isEmpty ? 0.0 : 1.0)
return height
}

View file

@ -16,6 +16,7 @@ import ChatInputContextPanelNode
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
import EdgeEffect
private struct CommandMenuChatInputContextPanelEntryStableId: Hashable {
let command: PeerCommand
@ -66,6 +67,7 @@ private func preparedTransition(from fromEntries: [CommandMenuChatInputContextPa
final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
private let backgroundView: GlassBackgroundView
private let listView: ListView
private let listMaskView: UIImageView
private var currentEntries: [CommandMenuChatInputContextPanelEntry]?
private var enqueuedTransitions: [(CommandMenuChatInputContextPanelTransition, Bool)] = []
@ -87,6 +89,8 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
return strings.VoiceOver_ScrollStatus(row, count).string
}
self.listMaskView = UIImageView()
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
@ -94,6 +98,7 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
self.listView.view.mask = self.listMaskView
self.backgroundView.isHidden = true
self.listView.visibleContentOffsetChanged = { [weak self] offset in
@ -240,6 +245,15 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
let hadValidLayout = self.validLayout != nil
self.validLayout = (size, leftInset, rightInset, bottomInset)
self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0))
self.backgroundView.update(
size: self.backgroundView.bounds.size,
cornerRadius: 20.0,
isDark: interfaceState.theme.overallDarkAppearance,
tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)),
transition: ComponentTransition(transition)
)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset)
insets.left = leftInset
@ -247,6 +261,37 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
insets.bottom = bottomInset
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
let listMaskHeight: CGFloat = bottomInset + 1.0
if self.listMaskView.image?.size.height != listMaskHeight {
let baseGradientAlpha: CGFloat = 0.65
let numSteps = 8
let firstStep = 1
let firstLocation = 0.0
let colors: [UIColor] = (0 ..< numSteps).map { i in
if i < firstStep {
return UIColor(white: 0.0, alpha: 0.0)
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 0.0, alpha: 1.0 - baseGradientAlpha * value)
}
}
let locations: [CGFloat] = (0 ..< numSteps).map { i in
if i < firstStep {
return 0.0
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step)
}
}
self.listMaskView.image = generateGradientImage(
size: CGSize(width: 8.0, height: listMaskHeight),
colors: colors,
locations: locations
)?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)
}
transition.updateFrame(view: self.listMaskView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve)

View file

@ -163,7 +163,6 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
//self.view.addSubview(self.listMaskView)
self.listView.view.mask = self.listMaskView
self.listView.visibleBottomContentOffsetChanged = { [weak self] offset in