mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
implement format menu
This commit is contained in:
@@ -11,6 +11,11 @@ import SwiftyGif
|
||||
import SimpleXChat
|
||||
import PhotosUI
|
||||
|
||||
private let defaultAttributes: [NSAttributedString.Key : Any] = [
|
||||
.font: UIFont.preferredFont(forTextStyle: .body),
|
||||
.foregroundColor: UIColor.label
|
||||
]
|
||||
|
||||
struct NativeTextEditor: UIViewRepresentable {
|
||||
@Binding var text: String
|
||||
@Binding var disableEditing: Bool
|
||||
@@ -29,24 +34,31 @@ struct NativeTextEditor: UIViewRepresentable {
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let field = CustomUITextField(height: _height)
|
||||
field.backgroundColor = .clear
|
||||
field.text = text
|
||||
field.attributedText = NSAttributedString(
|
||||
string: "",
|
||||
attributes: [
|
||||
.font: UIFont.preferredFont(forTextStyle: .body),
|
||||
.foregroundColor: UIColor.label
|
||||
]
|
||||
)
|
||||
field.textAlignment = alignment(text)
|
||||
field.autocapitalizationType = .sentences
|
||||
field.setOnTextChangedListener { newText, images in
|
||||
if !disableEditing {
|
||||
text = newText
|
||||
field.textAlignment = alignment(text)
|
||||
updateFont(field)
|
||||
// Speed up the process of updating layout, reduce jumping content on screen
|
||||
updateHeight(field)
|
||||
self.height = field.frame.size.height
|
||||
} else {
|
||||
field.text = text
|
||||
}
|
||||
if !images.isEmpty {
|
||||
onImagesAdded(images)
|
||||
}
|
||||
}
|
||||
// TODO: Reintegrate with attributed string
|
||||
// field.setOnTextChangedListener { newText, images in
|
||||
// if !disableEditing {
|
||||
// text = newText
|
||||
// field.textAlignment = alignment(text)
|
||||
// updateFont(field)
|
||||
// // Speed up the process of updating layout, reduce jumping content on screen
|
||||
// updateHeight(field)
|
||||
// self.height = field.frame.size.height
|
||||
// } else {
|
||||
// field.text = text
|
||||
// }
|
||||
// if !images.isEmpty {
|
||||
// onImagesAdded(images)
|
||||
// }
|
||||
// }
|
||||
field.setOnFocusChangedListener { focused = $0 }
|
||||
field.delegate = field
|
||||
field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4)
|
||||
@@ -57,7 +69,8 @@ struct NativeTextEditor: UIViewRepresentable {
|
||||
|
||||
func updateUIView(_ field: UITextView, context: Context) {
|
||||
if field.markedTextRange == nil && field.text != text {
|
||||
field.text = text
|
||||
// TODO: Reintegrate with attributed string
|
||||
// field.text = text
|
||||
field.textAlignment = alignment(text)
|
||||
updateFont(field)
|
||||
updateHeight(field)
|
||||
@@ -173,33 +186,34 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
|
||||
}
|
||||
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
if textView.markedTextRange == nil {
|
||||
var images: [UploadContent] = []
|
||||
var rangeDiff = 0
|
||||
let newAttributedText = NSMutableAttributedString(attributedString: textView.attributedText)
|
||||
textView.attributedText.enumerateAttribute(
|
||||
NSAttributedString.Key.attachment,
|
||||
in: NSRange(location: 0, length: textView.attributedText.length),
|
||||
options: [],
|
||||
using: { value, range, _ in
|
||||
if let attachment = (value as? NSTextAttachment)?.fileWrapper?.regularFileContents {
|
||||
do {
|
||||
images.append(.animatedImage(image: try UIImage(gifData: attachment)))
|
||||
} catch {
|
||||
if let img = (value as? NSTextAttachment)?.image {
|
||||
images.append(.simpleImage(image: img))
|
||||
}
|
||||
}
|
||||
newAttributedText.replaceCharacters(in: NSMakeRange(range.location - rangeDiff, range.length), with: "")
|
||||
rangeDiff += range.length
|
||||
}
|
||||
}
|
||||
)
|
||||
if textView.attributedText != newAttributedText {
|
||||
textView.attributedText = newAttributedText
|
||||
}
|
||||
onTextChanged(textView.text, images)
|
||||
}
|
||||
// TODO: Reintegrate with attributed string
|
||||
// if textView.markedTextRange == nil {
|
||||
// var images: [UploadContent] = []
|
||||
// var rangeDiff = 0
|
||||
// let newAttributedText = NSMutableAttributedString(attributedString: textView.attributedText)
|
||||
// textView.attributedText.enumerateAttribute(
|
||||
// NSAttributedString.Key.attachment,
|
||||
// in: NSRange(location: 0, length: textView.attributedText.length),
|
||||
// options: [],
|
||||
// using: { value, range, _ in
|
||||
// if let attachment = (value as? NSTextAttachment)?.fileWrapper?.regularFileContents {
|
||||
// do {
|
||||
// images.append(.animatedImage(image: try UIImage(gifData: attachment)))
|
||||
// } catch {
|
||||
// if let img = (value as? NSTextAttachment)?.image {
|
||||
// images.append(.simpleImage(image: img))
|
||||
// }
|
||||
// }
|
||||
// newAttributedText.replaceCharacters(in: NSMakeRange(range.location - rangeDiff, range.length), with: "")
|
||||
// rangeDiff += range.length
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// if textView.attributedText != newAttributedText {
|
||||
// textView.attributedText = newAttributedText
|
||||
// }
|
||||
// onTextChanged(textView.text, images)
|
||||
// }
|
||||
}
|
||||
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
@@ -211,12 +225,37 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
|
||||
}
|
||||
|
||||
// MARK: Formatting
|
||||
|
||||
/// Toggles an attribute in the currently selected text range
|
||||
/// - Parameters:
|
||||
/// - attribute: Value of the attribute to be enabled or disabled
|
||||
/// - key: Key for which to apply the attribute
|
||||
/// - detect: Block which detects, if the attribute is already present within the selection
|
||||
func toggle(
|
||||
_ attribute: Any,
|
||||
for key: NSAttributedString.Key,
|
||||
detect: (Any) -> Bool?
|
||||
) {
|
||||
var detected = false
|
||||
textStorage.enumerateAttribute(key, in: selectedRange) { value, _, stop in
|
||||
if let value, detect(value) == true {
|
||||
detected = true
|
||||
stop.pointee = true
|
||||
}
|
||||
}
|
||||
|
||||
if key == .backgroundColor {
|
||||
textStorage.removeAttribute(.backgroundColor, range: selectedRange)
|
||||
} else {
|
||||
textStorage.setAttributes(defaultAttributes, range: selectedRange)
|
||||
}
|
||||
|
||||
if !detected {
|
||||
textStorage.addAttribute(key, value: attribute, range: selectedRange)
|
||||
}
|
||||
}
|
||||
|
||||
private var formatMenu: UIMenu {
|
||||
func set(_ attribute: Any, for key: NSAttributedString.Key) {
|
||||
textStorage.setAttributes([key: attribute], range: selectedRange)
|
||||
delegate?.textViewDidChange?(self)
|
||||
}
|
||||
|
||||
func systemImage(_ systemName: String, color: UIColor? = nil) -> UIImage? {
|
||||
if let uiImage = UIImage(systemName: systemName) {
|
||||
@@ -228,23 +267,53 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
|
||||
} else { nil }
|
||||
}
|
||||
|
||||
var bodySize: CGFloat { UIFont.preferredFont(forTextStyle: .body).pointSize }
|
||||
|
||||
|
||||
let colors: [UIColor] = [.red, .green, .blue, .yellow, .cyan, .magenta]
|
||||
|
||||
return UIMenu(
|
||||
title: "Format",
|
||||
children: [
|
||||
UIAction(image: systemImage("bold")) { _ in /*TODO*/ },
|
||||
UIAction(image: systemImage("italic")) { _ in /*TODO*/ },
|
||||
UIAction(image: systemImage("strikethrough")) { _ in /*TODO*/ },
|
||||
UIAction(title: "Mono") { _ in /*TODO*/ },
|
||||
UIAction(image: systemImage("bold")) { _ in
|
||||
self.toggle(UIFont.boldSystemFont(ofSize: bodySize), for: .font) { value in
|
||||
(value as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitBold)
|
||||
}
|
||||
},
|
||||
UIAction(image: systemImage("italic")) { _ in
|
||||
self.toggle(UIFont.italicSystemFont(ofSize: bodySize), for: .font) { value in
|
||||
(value as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitItalic)
|
||||
}
|
||||
},
|
||||
UIAction(image: systemImage("strikethrough")) { _ in
|
||||
self.toggle(NSUnderlineStyle.single.rawValue, for: .strikethroughStyle) { value in
|
||||
(value as? Int).map { $0 == NSUnderlineStyle.single.rawValue }
|
||||
}
|
||||
},
|
||||
UIAction(title: "Mono") { _ in
|
||||
self.toggle(UIFont.monospacedSystemFont(ofSize: bodySize, weight: .regular), for: .font) { value in
|
||||
(value as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitMonoSpace)
|
||||
}
|
||||
},
|
||||
UIMenu(
|
||||
title: "Color",
|
||||
children: Array<UIColor>([.red, .green, .blue, .yellow, .cyan, .magenta])
|
||||
children: colors
|
||||
.map { color in
|
||||
UIAction(image: systemImage("circle.fill", color: color)) { _ in
|
||||
set(color, for: .foregroundColor)
|
||||
self.toggle(color, for: .foregroundColor) { value in
|
||||
(value as? UIColor).map { $0 == color }
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
UIAction(title: "Secret") { test in /*TODO*/ }
|
||||
UIAction(title: "Secret") { _ in
|
||||
self.toggle(UIColor.clear, for: .foregroundColor) { value in
|
||||
(value as? UIColor).map { $0 == .clear }
|
||||
}
|
||||
self.toggle(UIColor.secondarySystemFill, for: .backgroundColor) { value in
|
||||
(value as? UIColor).map { $0 == .secondarySystemFill }
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user