mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Refactor toolbar
This commit is contained in:
parent
3a6690f0ef
commit
a7816937b0
17
Swiftgram/SGInputToolbar/BUILD
Normal file
17
Swiftgram/SGInputToolbar/BUILD
Normal file
@ -0,0 +1,17 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "SGInputToolbar",
|
||||
module_name = "SGInputToolbar",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
147
Swiftgram/SGInputToolbar/Sources/SGInputToolbar.swift
Normal file
147
Swiftgram/SGInputToolbar/Sources/SGInputToolbar.swift
Normal file
@ -0,0 +1,147 @@
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: Swiftgram
|
||||
@available(iOS 13.0, *)
|
||||
public struct ChatToolbarView: View {
|
||||
var onQuote: () -> Void
|
||||
var onSpoiler: () -> Void
|
||||
var onBold: () -> Void
|
||||
var onItalic: () -> Void
|
||||
var onMonospace: () -> Void
|
||||
var onLink: () -> Void
|
||||
var onStrikethrough: () -> Void
|
||||
var onUnderline: () -> Void
|
||||
var onCode: () -> Void
|
||||
|
||||
var onNewLine: () -> Void
|
||||
@Binding private var showNewLine: Bool
|
||||
|
||||
var onClearFormatting: () -> Void
|
||||
|
||||
public init(
|
||||
onQuote: @escaping () -> Void,
|
||||
onSpoiler: @escaping () -> Void,
|
||||
onBold: @escaping () -> Void,
|
||||
onItalic: @escaping () -> Void,
|
||||
onMonospace: @escaping () -> Void,
|
||||
onLink: @escaping () -> Void,
|
||||
onStrikethrough: @escaping () -> Void,
|
||||
onUnderline: @escaping () -> Void,
|
||||
onCode: @escaping () -> Void,
|
||||
onNewLine: @escaping () -> Void,
|
||||
showNewLine: Binding<Bool>,
|
||||
onClearFormatting: @escaping () -> Void
|
||||
) {
|
||||
self.onQuote = onQuote
|
||||
self.onSpoiler = onSpoiler
|
||||
self.onBold = onBold
|
||||
self.onItalic = onItalic
|
||||
self.onMonospace = onMonospace
|
||||
self.onLink = onLink
|
||||
self.onStrikethrough = onStrikethrough
|
||||
self.onUnderline = onUnderline
|
||||
self.onCode = onCode
|
||||
self.onNewLine = onNewLine
|
||||
self._showNewLine = showNewLine
|
||||
self.onClearFormatting = onClearFormatting
|
||||
}
|
||||
|
||||
public func setShowNewLine(_ value: Bool) {
|
||||
self.showNewLine = value
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 12) {
|
||||
if showNewLine {
|
||||
Button(action: onNewLine) {
|
||||
Image(systemName: "return")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
}
|
||||
Button(action: onClearFormatting) {
|
||||
Image(systemName: "pencil.slash")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
Spacer()
|
||||
// Quote Button
|
||||
Button(action: onQuote) {
|
||||
Image(systemName: "text.quote")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Spoiler Button
|
||||
Button(action: onSpoiler) {
|
||||
Image(systemName: "eye.slash")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Bold Button
|
||||
Button(action: onBold) {
|
||||
Image(systemName: "bold")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Italic Button
|
||||
Button(action: onItalic) {
|
||||
Image(systemName: "italic")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Monospace Button
|
||||
Button(action: onMonospace) {
|
||||
if #available(iOS 16.4, *) {
|
||||
Text("M").monospaced()
|
||||
} else {
|
||||
Text("M")
|
||||
}
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Link Button
|
||||
Button(action: onLink) {
|
||||
Image(systemName: "link")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Underline Button
|
||||
Button(action: onUnderline) {
|
||||
Image(systemName: "underline")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
|
||||
// Strikethrough Button
|
||||
Button(action: onStrikethrough) {
|
||||
Image(systemName: "strikethrough")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
|
||||
// Code Button
|
||||
Button(action: onCode) {
|
||||
Image(systemName: "chevron.left.forwardslash.chevron.right")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.background(Color(UIColor.clear))
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct ToolbarButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.font(.system(size: 17))
|
||||
.frame(width: 36, height: 36, alignment: .center)
|
||||
.background(Color(UIColor.tertiarySystemBackground))
|
||||
.cornerRadius(8)
|
||||
// TODO(swiftgram): Does not work for fast taps (like mine)
|
||||
.opacity(configuration.isPressed ? 0.4 : 1.0)
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgDeps = [
|
||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings",
|
||||
"//Swiftgram/SGInputToolbar:SGInputToolbar"
|
||||
]
|
||||
|
||||
swift_library(
|
||||
name = "AttachmentTextInputPanelNode",
|
||||
module_name = "AttachmentTextInputPanelNode",
|
||||
@ -9,7 +14,7 @@ swift_library(
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
deps = sgDeps + [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
|
@ -63,6 +63,8 @@ public enum ChatTranslationDisplayType {
|
||||
|
||||
public final class ChatPanelInterfaceInteraction {
|
||||
public let setupReplyMessage: (MessageId?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void
|
||||
public let sgSelectLastWordIfIdle: () -> Void
|
||||
public let sgSetNewLine: () -> Void
|
||||
public let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void
|
||||
public let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void
|
||||
public let cancelMessageSelection: (ContainedViewLayoutTransition) -> Void
|
||||
@ -405,6 +407,93 @@ public final class ChatPanelInterfaceInteraction {
|
||||
|
||||
self.chatController = chatController
|
||||
self.statuses = statuses
|
||||
|
||||
// MARK: Swiftgram
|
||||
self.sgSelectLastWordIfIdle = {
|
||||
updateTextInputStateAndMode { current, inputMode in
|
||||
// No changes to current selection
|
||||
if !current.selectionRange.isEmpty {
|
||||
return (current, inputMode)
|
||||
}
|
||||
|
||||
let inputText = (current.inputText.mutableCopy() as? NSMutableAttributedString) ?? NSMutableAttributedString()
|
||||
|
||||
// If text is empty or cursor is at the start, return current state
|
||||
guard inputText.length > 0, current.selectionRange.lowerBound > 0 else {
|
||||
return (current, inputMode)
|
||||
}
|
||||
|
||||
let plainText = inputText.string
|
||||
let nsString = plainText as NSString
|
||||
|
||||
// Create character set for word boundaries
|
||||
let wordBoundaries = CharacterSet.whitespacesAndNewlines
|
||||
|
||||
// Start from cursor position instead of end of text
|
||||
var endIndex = current.selectionRange.lowerBound - 1
|
||||
|
||||
// Find last non-whitespace character before cursor
|
||||
while endIndex >= 0 &&
|
||||
(nsString.substring(with: NSRange(location: endIndex, length: 1)) as NSString)
|
||||
.rangeOfCharacter(from: wordBoundaries).location != NSNotFound {
|
||||
endIndex -= 1
|
||||
}
|
||||
|
||||
// If we only had whitespace before cursor, return current state
|
||||
guard endIndex >= 0 else {
|
||||
return (current, inputMode)
|
||||
}
|
||||
|
||||
// Find start of the current word by looking backwards for whitespace
|
||||
var startIndex = endIndex
|
||||
while startIndex > 0 {
|
||||
let char = nsString.substring(with: NSRange(location: startIndex - 1, length: 1))
|
||||
if (char as NSString).rangeOfCharacter(from: wordBoundaries).location != NSNotFound {
|
||||
break
|
||||
}
|
||||
startIndex -= 1
|
||||
}
|
||||
|
||||
// Create range for the word at cursor
|
||||
let wordLength = endIndex - startIndex + 1
|
||||
let wordRange = NSRange(location: startIndex, length: wordLength)
|
||||
|
||||
// Create new selection range
|
||||
let newSelectionRange = wordRange.location ..< (wordRange.location + wordLength)
|
||||
|
||||
return (ChatTextInputState(inputText: inputText, selectionRange: newSelectionRange), inputMode)
|
||||
}
|
||||
}
|
||||
self.sgSetNewLine = {
|
||||
updateTextInputStateAndMode { current, inputMode in
|
||||
let inputText = (current.inputText.mutableCopy() as? NSMutableAttributedString) ?? NSMutableAttributedString()
|
||||
|
||||
// Check if there's selected text
|
||||
let hasSelection = current.selectionRange.count > 0
|
||||
|
||||
if hasSelection {
|
||||
// Move selected text to new line
|
||||
let selectedText = inputText.attributedSubstring(from: NSRange(current.selectionRange))
|
||||
let newLineAttr = NSAttributedString(string: "\n")
|
||||
|
||||
// Insert newline and selected text
|
||||
inputText.replaceCharacters(in: NSRange(current.selectionRange), with: newLineAttr)
|
||||
inputText.insert(selectedText, at: current.selectionRange.lowerBound + 1)
|
||||
|
||||
// Update selection range to end of moved text
|
||||
let newPosition = current.selectionRange.lowerBound + 1 + selectedText.length
|
||||
return (ChatTextInputState(inputText: inputText, selectionRange: newPosition ..< newPosition), inputMode)
|
||||
} else {
|
||||
// Simple newline insertion at current position
|
||||
let attributedString = NSAttributedString(string: "\n")
|
||||
inputText.replaceCharacters(in: NSRange(current.selectionRange), with: attributedString)
|
||||
|
||||
// Update cursor position
|
||||
let newPosition = current.selectionRange.lowerBound + attributedString.length
|
||||
return (ChatTextInputState(inputText: inputText, selectionRange: newPosition ..< newPosition), inputMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init(
|
||||
|
@ -16,7 +16,8 @@ sgdeps = [
|
||||
"//Swiftgram/SGActionRequestHandlerSanitizer:SGActionRequestHandlerSanitizer",
|
||||
"//Swiftgram/Wrap:Wrap",
|
||||
"//Swiftgram/SGDeviceToken:SGDeviceToken",
|
||||
"//Swiftgram/SGDebugUI:SGDebugUI"
|
||||
"//Swiftgram/SGDebugUI:SGDebugUI",
|
||||
"//Swiftgram/SGInputToolbar:SGInputToolbar",
|
||||
# "//Swiftgram/SGContentAnalysis:SGContentAnalysis"
|
||||
]
|
||||
|
||||
|
@ -1,5 +1,10 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgDeps = [
|
||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings",
|
||||
"//Swiftgram/SGInputToolbar:SGInputToolbar"
|
||||
]
|
||||
|
||||
swift_library(
|
||||
name = "LegacyMessageInputPanel",
|
||||
module_name = "LegacyMessageInputPanel",
|
||||
@ -9,7 +14,7 @@ swift_library(
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
deps = sgDeps + [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
|
@ -2,6 +2,7 @@
|
||||
import TelegramUIPreferences
|
||||
import SGSimpleSettings
|
||||
import SwiftUI
|
||||
import SGInputToolbar
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
@ -5063,62 +5064,6 @@ private final class BoostSlowModeButton: HighlightTrackingButtonNode {
|
||||
// MARK: Swiftgram
|
||||
extension ChatTextInputPanelNode {
|
||||
|
||||
func selectLastWordIfIdle() {
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||
// No changes to current selection
|
||||
if !current.selectionRange.isEmpty {
|
||||
return (current, inputMode)
|
||||
}
|
||||
|
||||
let inputText = (current.inputText.mutableCopy() as? NSMutableAttributedString) ?? NSMutableAttributedString()
|
||||
|
||||
// If text is empty or cursor is at the start, return current state
|
||||
guard inputText.length > 0, current.selectionRange.lowerBound > 0 else {
|
||||
return (current, inputMode)
|
||||
}
|
||||
|
||||
let plainText = inputText.string
|
||||
let nsString = plainText as NSString
|
||||
|
||||
// Create character set for word boundaries
|
||||
let wordBoundaries = CharacterSet.whitespacesAndNewlines
|
||||
|
||||
// Start from cursor position instead of end of text
|
||||
var endIndex = current.selectionRange.lowerBound - 1
|
||||
|
||||
// Find last non-whitespace character before cursor
|
||||
while endIndex >= 0 &&
|
||||
(nsString.substring(with: NSRange(location: endIndex, length: 1)) as NSString)
|
||||
.rangeOfCharacter(from: wordBoundaries).location != NSNotFound {
|
||||
endIndex -= 1
|
||||
}
|
||||
|
||||
// If we only had whitespace before cursor, return current state
|
||||
guard endIndex >= 0 else {
|
||||
return (current, inputMode)
|
||||
}
|
||||
|
||||
// Find start of the current word by looking backwards for whitespace
|
||||
var startIndex = endIndex
|
||||
while startIndex > 0 {
|
||||
let char = nsString.substring(with: NSRange(location: startIndex - 1, length: 1))
|
||||
if (char as NSString).rangeOfCharacter(from: wordBoundaries).location != NSNotFound {
|
||||
break
|
||||
}
|
||||
startIndex -= 1
|
||||
}
|
||||
|
||||
// Create range for the word at cursor
|
||||
let wordLength = endIndex - startIndex + 1
|
||||
let wordRange = NSRange(location: startIndex, length: wordLength)
|
||||
|
||||
// Create new selection range
|
||||
let newSelectionRange = wordRange.location ..< (wordRange.location + wordLength)
|
||||
|
||||
return (ChatTextInputState(inputText: inputText, selectionRange: newSelectionRange), inputMode)
|
||||
}
|
||||
}
|
||||
|
||||
func initToolbarIfNeeded() {
|
||||
guard #available(iOS 13.0, *) else { return }
|
||||
guard SGSimpleSettings.shared.inputToolbar else { return }
|
||||
@ -5127,87 +5072,59 @@ extension ChatTextInputPanelNode {
|
||||
let toolbarView = ChatToolbarView(
|
||||
onQuote: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesQuote(strongSelf)
|
||||
},
|
||||
onSpoiler: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesSpoiler(strongSelf)
|
||||
},
|
||||
onBold: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesBold(strongSelf)
|
||||
},
|
||||
onItalic: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesItalic(strongSelf)
|
||||
},
|
||||
onMonospace: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesMonospace(strongSelf)
|
||||
},
|
||||
onLink: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesLink(self!)
|
||||
},
|
||||
onStrikethrough: { [weak self]
|
||||
in guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesStrikethrough(strongSelf)
|
||||
},
|
||||
onUnderline: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesUnderline(strongSelf)
|
||||
},
|
||||
onCode: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selectLastWordIfIdle()
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesCodeBlock(strongSelf)
|
||||
},
|
||||
onNewLine: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
|
||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||
let inputText = (current.inputText.mutableCopy() as? NSMutableAttributedString) ?? NSMutableAttributedString()
|
||||
|
||||
// Check if there's selected text
|
||||
let hasSelection = current.selectionRange.count > 0
|
||||
|
||||
if hasSelection {
|
||||
// Move selected text to new line
|
||||
let selectedText = inputText.attributedSubstring(from: NSRange(current.selectionRange))
|
||||
let newLineAttr = NSAttributedString(string: "\n")
|
||||
|
||||
// Insert newline and selected text
|
||||
inputText.replaceCharacters(in: NSRange(current.selectionRange), with: newLineAttr)
|
||||
inputText.insert(selectedText, at: current.selectionRange.lowerBound + 1)
|
||||
|
||||
// Update selection range to end of moved text
|
||||
let newPosition = current.selectionRange.lowerBound + 1 + selectedText.length
|
||||
return (ChatTextInputState(inputText: inputText, selectionRange: newPosition ..< newPosition), inputMode)
|
||||
} else {
|
||||
// Simple newline insertion at current position
|
||||
let attributedString = NSAttributedString(string: "\n")
|
||||
inputText.replaceCharacters(in: NSRange(current.selectionRange), with: attributedString)
|
||||
|
||||
// Update cursor position
|
||||
let newPosition = current.selectionRange.lowerBound + attributedString.length
|
||||
return (ChatTextInputState(inputText: inputText, selectionRange: newPosition ..< newPosition), inputMode)
|
||||
}
|
||||
}
|
||||
strongSelf.interfaceInteraction?.sgSetNewLine()
|
||||
},
|
||||
// TODO(swiftgram): Binding
|
||||
showNewLine: .constant(true), //.constant(self.sendWithReturnKey)
|
||||
onClearFormatting: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||
return (chatTextInputAddFormattingAttribute(forceRemoveAll: true,current, attribute: ChatTextInputAttributes.allAttributes[0], value: nil), inputMode)
|
||||
return (chatTextInputAddFormattingAttribute(forceRemoveAll: true, current, attribute: ChatTextInputAttributes.allAttributes[0], value: nil), inputMode)
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -5216,7 +5133,7 @@ extension ChatTextInputPanelNode {
|
||||
let toolbarNode = ASDisplayNode { toolbarHostingController.view }
|
||||
self.toolbarNode = toolbarNode
|
||||
// assigning toolbarHostingController bugs responsivness and overrides layout
|
||||
// self.toolbarHostingController = toolbarHostingController
|
||||
// self.toolbarHostingController = toolbarHostingController
|
||||
|
||||
// Disable "Swipe to go back" gesture when touching scrollview
|
||||
self.view.interactiveTransitionGestureRecognizerTest = { [weak self] point in
|
||||
@ -5248,150 +5165,4 @@ extension ChatTextInputPanelNode {
|
||||
}
|
||||
return toolbarHeight + toolbarSpacing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: Swiftgram
|
||||
@available(iOS 13.0, *)
|
||||
struct ChatToolbarView: View {
|
||||
var onQuote: () -> Void
|
||||
var onSpoiler: () -> Void
|
||||
var onBold: () -> Void
|
||||
var onItalic: () -> Void
|
||||
var onMonospace: () -> Void
|
||||
var onLink: () -> Void
|
||||
var onStrikethrough: () -> Void
|
||||
var onUnderline: () -> Void
|
||||
var onCode: () -> Void
|
||||
|
||||
var onNewLine: () -> Void
|
||||
@Binding private var showNewLine: Bool
|
||||
|
||||
var onClearFormatting: () -> Void
|
||||
|
||||
public init(
|
||||
onQuote: @escaping () -> Void,
|
||||
onSpoiler: @escaping () -> Void,
|
||||
onBold: @escaping () -> Void,
|
||||
onItalic: @escaping () -> Void,
|
||||
onMonospace: @escaping () -> Void,
|
||||
onLink: @escaping () -> Void,
|
||||
onStrikethrough: @escaping () -> Void,
|
||||
onUnderline: @escaping () -> Void,
|
||||
onCode: @escaping () -> Void,
|
||||
onNewLine: @escaping () -> Void,
|
||||
showNewLine: Binding<Bool>,
|
||||
onClearFormatting: @escaping () -> Void
|
||||
) {
|
||||
self.onQuote = onQuote
|
||||
self.onSpoiler = onSpoiler
|
||||
self.onBold = onBold
|
||||
self.onItalic = onItalic
|
||||
self.onMonospace = onMonospace
|
||||
self.onLink = onLink
|
||||
self.onStrikethrough = onStrikethrough
|
||||
self.onUnderline = onUnderline
|
||||
self.onCode = onCode
|
||||
self.onNewLine = onNewLine
|
||||
self._showNewLine = showNewLine
|
||||
self.onClearFormatting = onClearFormatting
|
||||
}
|
||||
|
||||
public func setShowNewLine(_ value: Bool) {
|
||||
self.showNewLine = value
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 12) {
|
||||
if showNewLine {
|
||||
Button(action: onNewLine) {
|
||||
Image(systemName: "return")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
}
|
||||
Button(action: onClearFormatting) {
|
||||
Image(systemName: "pencil.slash")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
Spacer()
|
||||
// Quote Button
|
||||
Button(action: onQuote) {
|
||||
Image(systemName: "text.quote")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Spoiler Button
|
||||
Button(action: onSpoiler) {
|
||||
Image(systemName: "eye.slash")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Bold Button
|
||||
Button(action: onBold) {
|
||||
Image(systemName: "bold")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Italic Button
|
||||
Button(action: onItalic) {
|
||||
Image(systemName: "italic")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Monospace Button
|
||||
Button(action: onMonospace) {
|
||||
if #available(iOS 16.4, *) {
|
||||
Text("M").monospaced()
|
||||
} else {
|
||||
Text("M")
|
||||
}
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Link Button
|
||||
Button(action: onLink) {
|
||||
Image(systemName: "link")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
// Underline Button
|
||||
Button(action: onUnderline) {
|
||||
Image(systemName: "underline")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
|
||||
// Strikethrough Button
|
||||
Button(action: onStrikethrough) {
|
||||
Image(systemName: "strikethrough")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
|
||||
|
||||
// Code Button
|
||||
Button(action: onCode) {
|
||||
Image(systemName: "chevron.left.forwardslash.chevron.right")
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.background(Color(UIColor.clear))
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct ToolbarButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.font(.system(size: 17))
|
||||
.frame(width: 36, height: 36, alignment: .center)
|
||||
.background(Color(UIColor.tertiarySystemBackground))
|
||||
.cornerRadius(8)
|
||||
// TODO(swiftgram): Does not work for fast taps (like mine)
|
||||
.opacity(configuration.isPressed ? 0.4 : 1.0)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user