Tusker/Tusker/Screens/Compose/EmojiPickerCollectionViewCo...

129 lines
4.7 KiB
Swift

//
// EmojiPickerCollectionViewController.swift
// Tusker
//
// Created by Shadowfacts on 10/12/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
private let reuseIdentifier = "EmojiCell"
protocol EmojiPickerCollectionViewControllerDelegate: AnyObject {
func selectedEmoji(_ emoji: Emoji)
}
// It would be nice to replace this with a LazyVGrid when the deployment target is bumped to 14.0
class EmojiPickerCollectionViewController: UICollectionViewController {
weak var delegate: EmojiPickerCollectionViewControllerDelegate?
private weak var mastodonController: MastodonController!
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
var searchQuery: String = "" {
didSet {
guard let emojis = mastodonController.customEmojis else { return }
let snapshot = createFilteredSnapshot(emojis: emojis)
DispatchQueue.main.async {
self.dataSource.apply(snapshot)
}
}
}
init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
let itemWidth = NSCollectionLayoutDimension.fractionalWidth(1.0 / 10)
let itemSize = NSCollectionLayoutSize(widthDimension: itemWidth, heightDimension: itemWidth)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemWidth)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
group.interItemSpacing = .fixed(4)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 4
let layout = UICollectionViewCompositionalLayout(section: section)
super.init(collectionViewLayout: layout)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
// use negative indicator insets to bring the indicators back to the edge of the containing view
// using collectionView.contentInset doesn't work the compositional layout ignores the inset when calculating fractional widths
collectionView.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: -8, bottom: 0, right: -8)
collectionView.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
collectionView.backgroundColor = .clear
collectionView.register(EmojiCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! EmojiCollectionViewCell
cell.updateUI(emoji: item.emoji)
return cell
}
mastodonController.getCustomEmojis { (emojis) in
DispatchQueue.main.async {
self.dataSource.apply(self.createFilteredSnapshot(emojis: emojis))
}
}
}
private func createFilteredSnapshot(emojis: [Emoji]) -> NSDiffableDataSourceSnapshot<Section, Item> {
let items: [Item]
if searchQuery.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
items = emojis.map { Item(emoji: $0) }
} else {
items = emojis
.map { ($0, FuzzyMatcher.match(pattern: searchQuery, str: $0.shortcode)) }
.filter(\.1.matched)
.sorted { $0.1.score > $1.1.score }
.map { Item(emoji: $0.0) }
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.emojis])
snapshot.appendItems(items)
return snapshot
}
// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
delegate?.selectedEmoji(item.emoji)
}
}
extension EmojiPickerCollectionViewController {
enum Section {
case emojis
}
struct Item: Hashable, Equatable {
let emoji: Emoji
func hash(into hasher: inout Hasher) {
hasher.combine(emoji.shortcode)
}
static func ==(lhs: Item, rhs: Item) -> Bool {
lhs.emoji.shortcode == rhs.emoji.shortcode
}
}
}