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 */; };
|
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; };
|
||||||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
||||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.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 */; };
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
||||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1004,6 +1006,7 @@
|
||||||
D62275A524F1C81800B82A16 /* ComposeReplyView.swift */,
|
D62275A524F1C81800B82A16 /* ComposeReplyView.swift */,
|
||||||
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */,
|
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */,
|
||||||
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */,
|
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */,
|
||||||
|
D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */,
|
||||||
);
|
);
|
||||||
path = Compose;
|
path = Compose;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1860,6 +1863,7 @@
|
||||||
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */,
|
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */,
|
||||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
||||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
||||||
|
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */,
|
||||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||||
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.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
|
header
|
||||||
|
|
||||||
if draft.contentWarningEnabled {
|
if draft.contentWarningEnabled {
|
||||||
TextField("Write your warning here", text: $draft.contentWarning)
|
ComposeContentWarningTextField(text: $draft.contentWarning)
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MainComposeTextView(
|
MainComposeTextView(
|
||||||
|
|
|
@ -218,11 +218,13 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
||||||
uiState.delegate?.keyboardDidHide(accessoryView: textView!.inputAccessoryView!, notification: notification)
|
uiState.delegate?.keyboardDidHide(accessoryView: textView!.inputAccessoryView!, notification: notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
func textViewDidEndEditing(_ textView: UITextView) {
|
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||||
|
uiState.autocompleteHandler = self
|
||||||
updateAutocompleteState()
|
updateAutocompleteState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
func textViewDidEndEditing(_ textView: UITextView) {
|
||||||
|
uiState.autocompleteHandler = nil
|
||||||
updateAutocompleteState()
|
updateAutocompleteState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,7 +314,7 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
||||||
var lastWordStartIndex = text.index(before: characterBeforeCursorIndex)
|
var lastWordStartIndex = text.index(before: characterBeforeCursorIndex)
|
||||||
var foundFirstAtSign = false
|
var foundFirstAtSign = false
|
||||||
while true {
|
while true {
|
||||||
let c = textView.text[lastWordStartIndex]
|
let c = text[lastWordStartIndex]
|
||||||
|
|
||||||
if !isPermittedForAutocomplete(c) {
|
if !isPermittedForAutocomplete(c) {
|
||||||
if foundFirstAtSign {
|
if foundFirstAtSign {
|
||||||
|
|
Loading…
Reference in New Issue