From 9e1acf72dd52d14891e9030eb2eafb097c4be551 Mon Sep 17 00:00:00 2001 From: Kylmakalle Date: Sun, 26 Jan 2025 14:41:10 +0200 Subject: [PATCH] Refactor toolbar to be a part of MessageInputPanel --- .../Sources/SGInputToolbar.swift | 43 ++++-- .../Components/LegacyMessageInputPanel/BUILD | 6 +- .../Sources/LegacyMessageInputPanel.swift | 109 +-------------- .../MessageInputPanelComponent/BUILD | 8 +- .../Sources/MessageInputPanelComponent.swift | 126 ++++++++++++++++++ 5 files changed, 166 insertions(+), 126 deletions(-) diff --git a/Swiftgram/SGInputToolbar/Sources/SGInputToolbar.swift b/Swiftgram/SGInputToolbar/Sources/SGInputToolbar.swift index 5604dd4d84..61cc122e4c 100644 --- a/Swiftgram/SGInputToolbar/Sources/SGInputToolbar.swift +++ b/Swiftgram/SGInputToolbar/Sources/SGInputToolbar.swift @@ -19,6 +19,7 @@ public struct ChatToolbarView: View { @Binding private var showNewLine: Bool var onClearFormatting: () -> Void + var preferredColorScheme: ColorScheme? public init( onQuote: @escaping () -> Void, @@ -32,7 +33,8 @@ public struct ChatToolbarView: View { onCode: @escaping () -> Void, onNewLine: @escaping () -> Void, showNewLine: Binding, - onClearFormatting: @escaping () -> Void + onClearFormatting: @escaping () -> Void, + preferredColorScheme: ColorScheme? = nil ) { self.onQuote = onQuote self.onSpoiler = onSpoiler @@ -46,6 +48,7 @@ public struct ChatToolbarView: View { self.onNewLine = onNewLine self._showNewLine = showNewLine self.onClearFormatting = onClearFormatting + self.preferredColorScheme = preferredColorScheme // TODO(swiftgram): Does not work for buttons :( } public func setShowNewLine(_ value: Bool) { @@ -59,36 +62,42 @@ public struct ChatToolbarView: View { Button(action: onNewLine) { Image(systemName: "return") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) } Button(action: onClearFormatting) { Image(systemName: "pencil.slash") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) Spacer() // Quote Button Button(action: onQuote) { Image(systemName: "text.quote") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Spoiler Button Button(action: onSpoiler) { Image(systemName: "eye.slash") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Bold Button Button(action: onBold) { Image(systemName: "bold") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Italic Button Button(action: onItalic) { Image(systemName: "italic") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Monospace Button Button(action: onMonospace) { @@ -98,43 +107,52 @@ public struct ChatToolbarView: View { Text("M") } } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Link Button Button(action: onLink) { Image(systemName: "link") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Underline Button Button(action: onUnderline) { Image(systemName: "underline") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Strikethrough Button Button(action: onStrikethrough) { Image(systemName: "strikethrough") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) // Code Button Button(action: onCode) { Image(systemName: "chevron.left.forwardslash.chevron.right") } - .buttonStyle(ToolbarButtonStyle()) + .buttonStyle(ToolbarButtonStyle(preferredColorScheme: preferredColorScheme)) + .preferredColorScheme(preferredColorScheme) } .padding(.horizontal, 8) .padding(.vertical, 8) } .background(Color(UIColor.clear)) + .preferredColorScheme(preferredColorScheme) } } @available(iOS 13.0, *) struct ToolbarButtonStyle: ButtonStyle { + + var preferredColorScheme: ColorScheme? = nil + func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(size: 17)) @@ -143,5 +161,6 @@ struct ToolbarButtonStyle: ButtonStyle { .cornerRadius(8) // TODO(swiftgram): Does not work for fast taps (like mine) .opacity(configuration.isPressed ? 0.4 : 1.0) + .preferredColorScheme(preferredColorScheme) } } diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD index 5dc02e95d5..b69622b093 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD @@ -1,9 +1,5 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -sgDeps = [ - "//Swiftgram/SGSimpleSettings:SGSimpleSettings", - "//Swiftgram/SGInputToolbar:SGInputToolbar" -] swift_library( name = "LegacyMessageInputPanel", @@ -14,7 +10,7 @@ swift_library( copts = [ "-warnings-as-errors", ], - deps = sgDeps + [ + deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display", diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index fb89855d5f..3da6395ee9 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -1,8 +1,3 @@ -// MARK: Swiftgram -import SwiftUI -import SGInputToolbar -import SGSimpleSettings - import Foundation import UIKit import AsyncDisplayKit @@ -43,9 +38,6 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private let hapticFeedback = HapticFeedback() - // MARK: Swiftgram - private var toolbarNode: ASDisplayNode? - private var inputView: LegacyMessageInputPanelInputView? private var isEmojiKeyboardActive = false @@ -84,9 +76,6 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { self.update(transition: transition.containedViewLayoutTransition) } } - - // MARK: Swiftgram - self.initToolbarIfNeeded() } public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat { @@ -215,7 +204,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { } self.inputPanel.parentState = self.state - var inputPanelSize = self.inputPanel.update( + let inputPanelSize = self.inputPanel.update( transition: ComponentTransition(transition), component: AnyComponent( MessageInputPanelComponent( @@ -306,12 +295,6 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { environment: {}, containerSize: CGSize(width: width, height: maxInputPanelHeight) ) - - // MARK: Swiftgram - var toolbarOffset: CGFloat = 0.0 - toolbarOffset = self.layoutToolbar(transition: transition, panelHeight: inputPanelSize.height - 8.0, width: width, leftInset: leftInset, rightInset: rightInset) - inputPanelSize.height += toolbarOffset - if let view = self.inputPanel.view { if view.superview == nil { self.view.addSubview(view) @@ -556,9 +539,6 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let result = super.hitTest(point, with: event) - if let toolbarNode = self.toolbarNode, let toolbarResult = toolbarNode.hitTest(self.view.convert(point, to: toolbarNode.view), with: event) { - return toolbarResult - } if let view = self.inputPanel.view, let panelResult = view.hitTest(self.view.convert(point, to: view), with: event) { return panelResult } @@ -583,90 +563,3 @@ private final class HeaderContextReferenceContentSource: ContextReferenceContent return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: self.position) } } - - -extension LegacyMessageInputPanelNode { - func initToolbarIfNeeded() { - guard #available(iOS 13.0, *) else { return } - guard SGSimpleSettings.shared.inputToolbar else { return } - guard SGSimpleSettings.shared.b else { return } - guard self.toolbarNode == nil else { return } - let notificationName = Notification.Name("sgToolbarAction") - let toolbarView = ChatToolbarView( - onQuote: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "quote"]) - }, - onSpoiler: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "spoiler"]) - }, - onBold: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "bold"]) - }, - onItalic: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "italic"]) - }, - onMonospace: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "monospace"]) - }, - onLink: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "link"]) - }, - onStrikethrough: { [weak self] - in guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "strikethrough"]) - }, - onUnderline: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "underline"]) - }, - onCode: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "code"]) - }, - onNewLine: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "newline"]) - }, - // TODO(swiftgram): Binding - showNewLine: .constant(true), //.constant(self.sendWithReturnKey) - onClearFormatting: { [weak self] in - guard let _ = self else { return } - NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "clearFormatting"]) - } - ) - let toolbarHostingController = UIHostingController(rootView: toolbarView) - toolbarHostingController.view.backgroundColor = .clear - let toolbarNode = ASDisplayNode { toolbarHostingController.view } - self.toolbarNode = toolbarNode - // assigning toolbarHostingController bugs responsivness and overrides layout - // self.toolbarHostingController = toolbarHostingController - - // Disable "Swipe to go back" gesture when touching scrollview - self.view.interactiveTransitionGestureRecognizerTest = { [weak self] point in - if let self, let _ = self.toolbarNode?.view.hitTest(point, with: nil) { - return false - } - return true - } - self.addSubnode(toolbarNode) - } - - func layoutToolbar(transition: ContainedViewLayoutTransition, panelHeight: CGFloat, width: CGFloat, leftInset: CGFloat, rightInset: CGFloat) -> CGFloat { - // TODO(swiftgram): Do not show if locked formatting - var toolbarHeight: CGFloat = 0.0 - var toolbarSpacing: CGFloat = 0.0 - if let toolbarNode = self.toolbarNode { - toolbarHeight = 44.0 - toolbarSpacing = 1.0 - transition.updateFrame(node: toolbarNode, frame: CGRect(origin: CGPoint(x: leftInset, y: panelHeight + toolbarSpacing), size: CGSize(width: width - rightInset - leftInset, height: toolbarHeight))) - } - return toolbarHeight + toolbarSpacing - } -} - diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index 1963f35d45..b27643f862 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -1,5 +1,11 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +sgDeps = [ + "//Swiftgram/SGSimpleSettings:SGSimpleSettings", + "//Swiftgram/SGInputToolbar:SGInputToolbar" +] + + swift_library( name = "MessageInputPanelComponent", module_name = "MessageInputPanelComponent", @@ -9,7 +15,7 @@ swift_library( copts = [ "-warnings-as-errors", ], - deps = [ + deps = sgDeps + [ "//submodules/Display", "//submodules/ComponentFlow", "//submodules/AppBundle", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index d67d227f54..212805a6b4 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -1,3 +1,8 @@ +// MARK: Swiftgram +import class SwiftUI.UIHostingController +import SGSimpleSettings +import SGInputToolbar + import Foundation import UIKit import Display @@ -467,6 +472,9 @@ public final class MessageInputPanelComponent: Component { private let counter = ComponentView() private var header: ComponentView? + // MARK: Swiftgram + private var toolbarView: UIView? + private var disabledPlaceholder: ComponentView? private var textClippingView = UIView() private let textField = ComponentView() @@ -563,6 +571,9 @@ public final class MessageInputPanelComponent: Component { self.state?.updated() } ) + + // MARK: Swiftgram + self.initToolbarIfNeeded() } required init?(coder: NSCoder) { @@ -732,6 +743,11 @@ public final class MessageInputPanelComponent: Component { if result == nil, let contextQueryResultPanel = self.contextQueryResultPanel?.view, let panelResult = contextQueryResultPanel.hitTest(self.convert(point, to: contextQueryResultPanel), with: event), panelResult !== contextQueryResultPanel { return panelResult } + + // MARK: Swiftgram + if result == nil, let toolbarView = self.toolbarView, let toolbarResult = toolbarView.hitTest(self.convert(point, to: toolbarView), with: event) { + return toolbarResult + } return result } @@ -2253,6 +2269,9 @@ public final class MessageInputPanelComponent: Component { } } + // MARK: Swiftgram + size = self.layoutToolbar(transition: transition, layoutFromTop: layoutFromTop, size: size, availableSize: availableSize, defaultInsets: defaultInsets, textFieldSize: textFieldSize, previousComponent: previousComponent) + return size } } @@ -2306,3 +2325,110 @@ final class ViewForOverlayContent: UIView { return nil } } + + +extension MessageInputPanelComponent.View { + func initToolbarIfNeeded() { + guard #available(iOS 13.0, *) else { return } + guard SGSimpleSettings.shared.inputToolbar else { return } + guard SGSimpleSettings.shared.b else { return } + guard self.toolbarView == nil else { return } + let notificationName = Notification.Name("sgToolbarAction") + let toolbar = ChatToolbarView( + onQuote: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "quote"]) + }, + onSpoiler: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "spoiler"]) + }, + onBold: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "bold"]) + }, + onItalic: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "italic"]) + }, + onMonospace: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "monospace"]) + }, + onLink: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "link"]) + }, + onStrikethrough: { [weak self] + in guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "strikethrough"]) + }, + onUnderline: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "underline"]) + }, + onCode: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "code"]) + }, + onNewLine: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "newline"]) + }, + // TODO(swiftgram): Binding + showNewLine: .constant(true), //.constant(self.sendWithReturnKey) + onClearFormatting: { [weak self] in + guard let _ = self else { return } + NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["action": "clearFormatting"]) + }, + preferredColorScheme: .dark + ) + + let toolbarHostingController = UIHostingController(rootView: toolbar) + toolbarHostingController.view.backgroundColor = .clear + let toolbarView = toolbarHostingController.view + self.toolbarView = toolbarView + // assigning toolbarHostingController bugs responsivness and overrides layout + // self.toolbarHostingController = toolbarHostingController + + // Disable "Swipe to go back" gesture when touching scrollview + self.interactiveTransitionGestureRecognizerTest = { [weak self] point in + if let self, let _ = self.toolbarView?.hitTest(point, with: nil) { + return false + } + return true + } + if let toolbarView = self.toolbarView { + self.addSubview(toolbarView) + } + } + + func layoutToolbar(transition: ComponentTransition, layoutFromTop: Bool, size: CGSize, availableSize: CGSize, defaultInsets: UIEdgeInsets, textFieldSize: CGSize, previousComponent: MessageInputPanelComponent?) -> CGSize { + // TODO(swiftgram): Do not show if locked formatting + var transition = transition + if let previousComponent = previousComponent { + let previousLayoutFromTop = previousComponent.attachmentButtonMode == .captionDown + if previousLayoutFromTop != layoutFromTop { + // attachmentButtonMode changed + transition = .immediate + } + } + var size = size + if let toolbarView = self.toolbarView { + let toolbarHeight: CGFloat = 44.0 + let toolbarSpacing: CGFloat = 1.0 + let toolbarSize = CGSize(width: availableSize.width, height: toolbarHeight) + let hasFirstResponder = self.hasFirstResponder() + transition.setAlpha(view: toolbarView, alpha: hasFirstResponder ? 1.0 : 0.0) + if layoutFromTop { + transition.setFrame(view: toolbarView, frame: CGRect(origin: CGPoint(x: .zero, y: availableSize.height + toolbarSpacing), size: toolbarSize)) + } else { + transition.setFrame(view: toolbarView, frame: CGRect(origin: CGPoint(x: .zero, y: textFieldSize.height + defaultInsets.top + toolbarSpacing), size: toolbarSize)) + if hasFirstResponder { + size.height += toolbarHeight + toolbarSpacing + } + } + } + return size + } +}