feat: add auto complete replace input action

This commit is contained in:
CMK 2021-05-18 15:06:00 +08:00
parent 3927f1630a
commit 672bfab200
5 changed files with 51 additions and 2 deletions

View File

@ -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))
} }

View File

@ -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
} }
} }

View File

@ -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) {

View File

@ -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)
} }

View File

@ -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)
}
}
}