forked from shadowfacts/Tusker
Autocomplete custom emojis in CW field
This commit is contained in:
parent
1a4517c43a
commit
ae272582ac
|
@ -238,6 +238,7 @@
|
|||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; };
|
||||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */; };
|
||||
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */; };
|
||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
||||
|
@ -567,6 +568,7 @@
|
|||
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = "<group>"; };
|
||||
D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = "<group>"; };
|
||||
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeContentWarningTextField.swift; sourceTree = "<group>"; };
|
||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
||||
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1004,6 +1006,7 @@
|
|||
D62275A524F1C81800B82A16 /* ComposeReplyView.swift */,
|
||||
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */,
|
||||
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */,
|
||||
D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */,
|
||||
);
|
||||
path = Compose;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1860,6 +1863,7 @@
|
|||
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */,
|
||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
||||
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */,
|
||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// 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) {
|
||||
uiState.autocompleteHandler = nil
|
||||
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 selectedRangeStartUTF16 = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
|
||||
let characterBeforeCursorIndex = text.utf16.index(text.startIndex, offsetBy: selectedRangeStartUTF16)
|
||||
|
||||
textField.text!.replaceSubrange(lastWordStartIndex..<characterBeforeCursorIndex, with: string)
|
||||
didChange(textField)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -114,8 +114,7 @@ struct ComposeView: View {
|
|||
header
|
||||
|
||||
if draft.contentWarningEnabled {
|
||||
TextField("Write your warning here", text: $draft.contentWarning)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
ComposeContentWarningTextField(text: $draft.contentWarning)
|
||||
}
|
||||
|
||||
MainComposeTextView(
|
||||
|
|
|
@ -218,11 +218,13 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
|||
uiState.delegate?.keyboardDidHide(accessoryView: textView!.inputAccessoryView!, notification: notification)
|
||||
}
|
||||
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
uiState.autocompleteHandler = self
|
||||
updateAutocompleteState()
|
||||
}
|
||||
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
uiState.autocompleteHandler = nil
|
||||
updateAutocompleteState()
|
||||
}
|
||||
|
||||
|
@ -312,7 +314,7 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
|||
var lastWordStartIndex = text.index(before: characterBeforeCursorIndex)
|
||||
var foundFirstAtSign = false
|
||||
while true {
|
||||
let c = textView.text[lastWordStartIndex]
|
||||
let c = text[lastWordStartIndex]
|
||||
|
||||
if !isPermittedForAutocomplete(c) {
|
||||
if foundFirstAtSign {
|
||||
|
|
Loading…
Reference in New Issue