166 lines
6.4 KiB
Swift
166 lines
6.4 KiB
Swift
//
|
|
// ComposeContentWarningTextField.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 10/12/20.
|
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct ComposeContentWarningTextField: UIViewRepresentable {
|
|
typealias UIViewType = UITextField
|
|
|
|
@Binding var text: String
|
|
|
|
@EnvironmentObject private var uiState: ComposeUIState
|
|
|
|
func makeUIView(context: Context) -> UITextField {
|
|
let view = UITextField()
|
|
|
|
view.placeholder = "Write your warning here"
|
|
view.borderStyle = .roundedRect
|
|
|
|
view.delegate = context.coordinator
|
|
view.addTarget(context.coordinator, action: #selector(Coordinator.didChange(_:)), for: .editingChanged)
|
|
|
|
context.coordinator.textField = view
|
|
context.coordinator.uiState = uiState
|
|
context.coordinator.text = $text
|
|
|
|
return view
|
|
}
|
|
|
|
func updateUIView(_ uiView: UITextField, context: Context) {
|
|
uiView.text = text
|
|
}
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
return Coordinator()
|
|
}
|
|
|
|
class Coordinator: NSObject, UITextFieldDelegate, ComposeAutocompleteHandler {
|
|
weak var textField: UITextField?
|
|
var text: Binding<String>!
|
|
var uiState: ComposeUIState!
|
|
|
|
@objc func didChange(_ textField: UITextField) {
|
|
text.wrappedValue = textField.text ?? ""
|
|
}
|
|
|
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
|
uiState.autocompleteHandler = self
|
|
updateAutocompleteState(textField: textField)
|
|
}
|
|
|
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
|
updateAutocompleteState(textField: textField)
|
|
}
|
|
|
|
func textFieldDidChangeSelection(_ textField: UITextField) {
|
|
updateAutocompleteState(textField: textField)
|
|
}
|
|
|
|
func autocomplete(with string: String) {
|
|
guard let textField = textField,
|
|
let text = textField.text,
|
|
let selectedRange = textField.selectedTextRange,
|
|
let lastWordStartIndex = findAutocompleteLastWord(textField: textField) else {
|
|
return
|
|
}
|
|
|
|
let distanceToEnd = textField.offset(from: selectedRange.start, to: textField.endOfDocument)
|
|
|
|
let selectedRangeStartUTF16 = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
|
|
let characterBeforeCursorIndex = text.utf16.index(text.startIndex, offsetBy: selectedRangeStartUTF16)
|
|
|
|
let insertSpace: Bool
|
|
if distanceToEnd > 0 {
|
|
let charAfterCursor = text[characterBeforeCursorIndex]
|
|
insertSpace = charAfterCursor != " " && charAfterCursor != "\n"
|
|
} else {
|
|
insertSpace = true
|
|
}
|
|
let string = insertSpace ? string + " " : string
|
|
|
|
textField.text!.replaceSubrange(lastWordStartIndex..<characterBeforeCursorIndex, with: string)
|
|
self.didChange(textField)
|
|
self.updateAutocompleteState(textField: textField)
|
|
|
|
// keep the cursor at the same position in the text, immediately after what was inserted
|
|
// if we inserted a space, move the cursor 1 farther so it's immediately after the pre-existing space
|
|
let insertSpaceOffset = insertSpace ? 0 : 1
|
|
let newCursorPosition = textField.position(from: textField.endOfDocument, offset: -distanceToEnd + insertSpaceOffset)!
|
|
textField.selectedTextRange = textField.textRange(from: newCursorPosition, to: newCursorPosition)
|
|
}
|
|
|
|
private func updateAutocompleteState(textField: UITextField) {
|
|
guard let selectedRange = textField.selectedTextRange,
|
|
let text = textField.text,
|
|
let lastWordStartIndex = findAutocompleteLastWord(textField: textField) else {
|
|
uiState.autocompleteState = nil
|
|
return
|
|
}
|
|
|
|
if lastWordStartIndex > text.startIndex {
|
|
let c = text[text.index(before: lastWordStartIndex)]
|
|
if isPermittedForAutocomplete(c) || c == ":" {
|
|
uiState.autocompleteState = nil
|
|
return
|
|
}
|
|
}
|
|
|
|
let selectedRangeStartUTF16 = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
|
|
let cursorIndex = text.utf16.index(text.startIndex, offsetBy: selectedRangeStartUTF16)
|
|
|
|
if lastWordStartIndex >= text.startIndex {
|
|
let lastWord = text[lastWordStartIndex..<cursorIndex]
|
|
let exceptFirst = lastWord[lastWord.index(after: lastWord.startIndex)...]
|
|
|
|
if lastWord.first == ":" {
|
|
uiState.autocompleteState = .emoji(String(exceptFirst))
|
|
} else {
|
|
uiState.autocompleteState = nil
|
|
}
|
|
} else {
|
|
uiState.autocompleteState = nil
|
|
}
|
|
}
|
|
|
|
private func isPermittedForAutocomplete(_ c: Character) -> Bool {
|
|
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || (c >= "0" && c <= "9") || c == "_"
|
|
}
|
|
|
|
private func findAutocompleteLastWord(textField: UITextField) -> String.Index? {
|
|
guard textField.isFirstResponder,
|
|
let selectedRange = textField.selectedTextRange,
|
|
selectedRange.isEmpty,
|
|
let text = textField.text,
|
|
!text.isEmpty else {
|
|
return nil
|
|
}
|
|
|
|
let selectedRangeStartUTF16 = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
|
|
let cursorIndex = text.utf16.index(text.startIndex, offsetBy: selectedRangeStartUTF16)
|
|
|
|
var lastWordStartIndex = text.index(before: cursorIndex)
|
|
while true {
|
|
let c = text[lastWordStartIndex]
|
|
|
|
if !isPermittedForAutocomplete(c) {
|
|
break
|
|
}
|
|
|
|
if lastWordStartIndex > text.startIndex {
|
|
lastWordStartIndex = text.index(before: lastWordStartIndex)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
return lastWordStartIndex
|
|
}
|
|
}
|
|
|
|
}
|