feat: add auto complete replace input action
This commit is contained in:
parent
3927f1630a
commit
672bfab200
|
@ -47,6 +47,7 @@ extension AutoCompleteItem: Hashable {
|
||||||
hasher.combine(account.id)
|
hasher.combine(account.id)
|
||||||
case .emoji(let emoji):
|
case .emoji(let emoji):
|
||||||
hasher.combine(emoji.shortcode)
|
hasher.combine(emoji.shortcode)
|
||||||
|
hasher.combine(emoji.url)
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
hasher.combine(String(describing: AutoCompleteItem.bottomLoader.self))
|
hasher.combine(String(describing: AutoCompleteItem.bottomLoader.self))
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ extension AutoCompleteSection {
|
||||||
return cell
|
return cell
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||||
|
cell.startAnimating()
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,18 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
protocol AutoCompleteViewControllerDelegate: AnyObject {
|
||||||
|
func autoCompleteViewController(_ viewController: AutoCompleteViewController, didSelectItem item: AutoCompleteItem)
|
||||||
|
}
|
||||||
|
|
||||||
final class AutoCompleteViewController: UIViewController {
|
final class AutoCompleteViewController: UIViewController {
|
||||||
|
|
||||||
static let chevronViewHeight: CGFloat = 24
|
static let chevronViewHeight: CGFloat = 24
|
||||||
|
|
||||||
var viewModel: AutoCompleteViewModel!
|
var viewModel: AutoCompleteViewModel!
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
weak var delegate: AutoCompleteViewControllerDelegate?
|
||||||
|
|
||||||
let chevronView = AutoCompleteTopChevronView()
|
let chevronView = AutoCompleteTopChevronView()
|
||||||
let containerBackgroundView: UIView = {
|
let containerBackgroundView: UIView = {
|
||||||
|
@ -26,6 +32,7 @@ final class AutoCompleteViewController: UIViewController {
|
||||||
let tableView: UITableView = {
|
let tableView: UITableView = {
|
||||||
let tableView = ControlContainableTableView()
|
let tableView = ControlContainableTableView()
|
||||||
tableView.register(AutoCompleteTableViewCell.self, forCellReuseIdentifier: String(describing: AutoCompleteTableViewCell.self))
|
tableView.register(AutoCompleteTableViewCell.self, forCellReuseIdentifier: String(describing: AutoCompleteTableViewCell.self))
|
||||||
|
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
|
@ -96,6 +103,10 @@ extension AutoCompleteViewController: UITableViewDelegate {
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
delegate?.autoCompleteViewController(self, didSelectItem: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
|
||||||
|
|
|
@ -99,9 +99,9 @@ extension AutoCompleteViewModel.State {
|
||||||
let searchPattern = ArraySlice(String(searchText.dropFirst()))
|
let searchPattern = ArraySlice(String(searchText.dropFirst()))
|
||||||
let passthroughs = emojiTrie.passthrough(searchPattern)
|
let passthroughs = emojiTrie.passthrough(searchPattern)
|
||||||
let matchingEmojis = passthroughs
|
let matchingEmojis = passthroughs
|
||||||
.map { $0.values } // [Set<Emoji>]
|
.map { $0.values } // [Set<Emoji>]
|
||||||
.map { set in set.compactMap { $0 as? Mastodon.Entity.Emoji } } // [[Emoji]]
|
.map { set in set.compactMap { $0 as? Mastodon.Entity.Emoji } } // [[Emoji]]
|
||||||
.flatMap { $0 } // [Emoji]
|
.flatMap { $0 } // [Emoji]
|
||||||
let items: [AutoCompleteItem] = matchingEmojis.map { emoji in
|
let items: [AutoCompleteItem] = matchingEmojis.map { emoji in
|
||||||
AutoCompleteItem.emoji(emoji: emoji)
|
AutoCompleteItem.emoji(emoji: emoji)
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
||||||
private(set) lazy var autoCompleteViewController: AutoCompleteViewController = {
|
private(set) lazy var autoCompleteViewController: AutoCompleteViewController = {
|
||||||
let viewController = AutoCompleteViewController()
|
let viewController = AutoCompleteViewController()
|
||||||
viewController.viewModel = AutoCompleteViewModel(context: context)
|
viewController.viewModel = AutoCompleteViewModel(context: context)
|
||||||
|
viewController.delegate = self
|
||||||
viewModel.customEmojiViewModel
|
viewModel.customEmojiViewModel
|
||||||
.assign(to: \.value, on: viewController.viewModel.customEmojiViewModel)
|
.assign(to: \.value, on: viewController.viewModel.customEmojiViewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
@ -1221,3 +1222,38 @@ extension ComposeViewController: ComposeStatusPollExpiresOptionCollectionViewCel
|
||||||
viewModel.pollExpiresOptionAttribute.expiresOption.value = expiresOption
|
viewModel.pollExpiresOptionAttribute.expiresOption.value = expiresOption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AutoCompleteViewControllerDelegate
|
||||||
|
extension ComposeViewController: AutoCompleteViewControllerDelegate {
|
||||||
|
func autoCompleteViewController(_ viewController: AutoCompleteViewController, didSelectItem item: AutoCompleteItem) {
|
||||||
|
guard let info = viewModel.autoCompleteInfo.value else { return }
|
||||||
|
let _replacedText: String? = {
|
||||||
|
var text: String
|
||||||
|
switch item {
|
||||||
|
case .hashtag(let hashtag):
|
||||||
|
text = "#" + hashtag.name
|
||||||
|
case .hashtagV1(let hashtagName):
|
||||||
|
text = "#" + hashtagName
|
||||||
|
case .account(let account):
|
||||||
|
text = "@" + account.acct
|
||||||
|
case .emoji(let emoji):
|
||||||
|
text = ":" + emoji.shortcode + ":"
|
||||||
|
case .bottomLoader:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
text.append(" ")
|
||||||
|
return text
|
||||||
|
}()
|
||||||
|
guard let replacedText = _replacedText else { return }
|
||||||
|
|
||||||
|
guard let textEditorView = textEditorView() else { return }
|
||||||
|
let text = textEditorView.text
|
||||||
|
|
||||||
|
do {
|
||||||
|
try textEditorView.updateByReplacing(range: NSRange(info.toHighlightEndRange, in: text), with: replacedText)
|
||||||
|
viewModel.autoCompleteInfo.value = nil
|
||||||
|
} catch {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: auto complete fail %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue