mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 00:01:00 +00:00
Autocomplete wip
This commit is contained in:
parent
2cb8370e68
commit
38ffad5f60
18 changed files with 479 additions and 48 deletions
130
Data Sources/AutocompleteDataSource.swift
Normal file
130
Data Sources/AutocompleteDataSource.swift
Normal file
|
@ -0,0 +1,130 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
enum AutocompleteSection: Int, Hashable {
|
||||
case search
|
||||
case emoji
|
||||
}
|
||||
|
||||
enum AutocompleteItem: Hashable {
|
||||
case account(Account)
|
||||
case tag(Tag)
|
||||
case emoji(PickerEmoji)
|
||||
}
|
||||
|
||||
final class AutocompleteDataSource: UICollectionViewDiffableDataSource<AutocompleteSection, AutocompleteItem> {
|
||||
@Published private var searchViewModel: SearchViewModel
|
||||
@Published private var emojiPickerViewModel: EmojiPickerViewModel
|
||||
|
||||
private let updateQueue =
|
||||
DispatchQueue(label: "com.metabolist.metatext.autocomplete-data-source.update-queue")
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(collectionView: UICollectionView,
|
||||
queryPublisher: AnyPublisher<String?, Never>,
|
||||
parentViewModel: NewStatusViewModel) {
|
||||
searchViewModel = SearchViewModel(identityContext: parentViewModel.identityContext)
|
||||
emojiPickerViewModel = EmojiPickerViewModel(identityContext: parentViewModel.identityContext, queryOnly: true)
|
||||
|
||||
let registration = UICollectionView.CellRegistration<AutocompleteItemCollectionViewCell, AutocompleteItem> {
|
||||
$0.item = $2
|
||||
$0.identityContext = parentViewModel.identityContext
|
||||
}
|
||||
|
||||
let emojiRegistration = UICollectionView.CellRegistration<EmojiCollectionViewCell, PickerEmoji> {
|
||||
$0.emoji = $2.applyingDefaultSkinTone(identityContext: parentViewModel.identityContext)
|
||||
}
|
||||
|
||||
super.init(collectionView: collectionView) {
|
||||
if case let .emoji(emoji) = $2 {
|
||||
return $0.dequeueConfiguredReusableCell(using: emojiRegistration, for: $1, item: emoji)
|
||||
} else {
|
||||
return $0.dequeueConfiguredReusableCell(using: registration, for: $1, item: $2)
|
||||
}
|
||||
}
|
||||
|
||||
queryPublisher
|
||||
.replaceNil(with: "")
|
||||
.removeDuplicates()
|
||||
.combineLatest($searchViewModel, $emojiPickerViewModel)
|
||||
.sink(receiveValue: Self.combine(query:searchViewModel:emojiPickerViewModel:))
|
||||
.store(in: &cancellables)
|
||||
|
||||
$searchViewModel.map(\.updates)
|
||||
.switchToLatest()
|
||||
.combineLatest($emojiPickerViewModel.map(\.$emoji).switchToLatest())
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in self?.apply(searchViewModelUpdate: $0, emojiSections: $1) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
parentViewModel.$identityContext
|
||||
.dropFirst()
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.searchViewModel = SearchViewModel(identityContext: $0)
|
||||
self.emojiPickerViewModel = EmojiPickerViewModel(identityContext: $0, queryOnly: true)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<AutocompleteSection, AutocompleteItem>,
|
||||
animatingDifferences: Bool = true,
|
||||
completion: (() -> Void)? = nil) {
|
||||
updateQueue.async {
|
||||
super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AutocompleteDataSource {
|
||||
static func combine(query: String, searchViewModel: SearchViewModel, emojiPickerViewModel: EmojiPickerViewModel) {
|
||||
if query.starts(with: ":") {
|
||||
searchViewModel.query = ""
|
||||
emojiPickerViewModel.query = String(query.dropFirst())
|
||||
} else {
|
||||
if query.starts(with: "@") {
|
||||
searchViewModel.scope = .accounts
|
||||
} else if query.starts(with: "#") {
|
||||
searchViewModel.scope = .tags
|
||||
}
|
||||
|
||||
searchViewModel.query = String(query.dropFirst())
|
||||
emojiPickerViewModel.query = ""
|
||||
}
|
||||
}
|
||||
|
||||
func apply(searchViewModelUpdate: CollectionUpdate, emojiSections: [PickerEmoji.Category: [PickerEmoji]]) {
|
||||
var newSnapshot = NSDiffableDataSourceSnapshot<AutocompleteSection, AutocompleteItem>()
|
||||
let items: [AutocompleteItem] = searchViewModelUpdate.sections.map(\.items).reduce([], +).compactMap {
|
||||
switch $0 {
|
||||
case let .account(account, _, _):
|
||||
return .account(account)
|
||||
case let .tag(tag):
|
||||
return .tag(tag)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
let emojis = emojiSections.sorted { $0.0 < $1.0 }.map(\.value).reduce([], +).map(AutocompleteItem.emoji)
|
||||
|
||||
newSnapshot.appendSections([.search])
|
||||
|
||||
if !items.isEmpty {
|
||||
newSnapshot.appendItems(items, toSection: .search)
|
||||
} else if !emojis.isEmpty {
|
||||
newSnapshot.appendSections([.emoji])
|
||||
newSnapshot.appendItems(emojis, toSection: .emoji)
|
||||
}
|
||||
|
||||
apply(newSnapshot, animatingDifferences: !UIAccessibility.isReduceMotionEnabled) {
|
||||
// animation causes issue with custom emoji images requiring reload
|
||||
newSnapshot.reloadItems(newSnapshot.itemIdentifiers)
|
||||
self.apply(newSnapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,17 @@
|
|||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
extension PickerEmoji {
|
||||
func applyingDefaultSkinTone(identityContext: IdentityContext) -> PickerEmoji {
|
||||
if case let .system(systemEmoji, inFrequentlyUsed) = self,
|
||||
let defaultEmojiSkinTone = identityContext.appPreferences.defaultEmojiSkinTone {
|
||||
return .system(systemEmoji.applying(skinTone: defaultEmojiSkinTone), inFrequentlyUsed: inFrequentlyUsed)
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary where Key == PickerEmoji.Category, Value == [PickerEmoji] {
|
||||
func snapshot() -> NSDiffableDataSourceSnapshot<PickerEmoji.Category, PickerEmoji> {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<PickerEmoji.Category, PickerEmoji>()
|
||||
|
|
|
@ -306,6 +306,7 @@
|
|||
"tag.accessibility-recent-uses-%ld" = "%ld recent uses";
|
||||
"tag.accessibility-hint.post" = "View posts associated with trend";
|
||||
"tag.accessibility-hint.toot" = "View toots associated with trend";
|
||||
"tag.per-week-%ld" = "%ld per week";
|
||||
"timelines.home" = "Home";
|
||||
"timelines.local" = "Local";
|
||||
"timelines.federated" = "Federated";
|
||||
|
|
|
@ -165,6 +165,13 @@
|
|||
D0D2AC4D25BCD2A9003D5DF2 /* TagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC4C25BCD2A9003D5DF2 /* TagTableViewCell.swift */; };
|
||||
D0D2AC5325BCD2BA003D5DF2 /* TagContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC5225BCD2BA003D5DF2 /* TagContentConfiguration.swift */; };
|
||||
D0D2AC6725BD0484003D5DF2 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */; };
|
||||
D0D93EBA25D9C70400C622ED /* AutocompleteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93EB925D9C70400C622ED /* AutocompleteItemView.swift */; };
|
||||
D0D93EC025D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */; };
|
||||
D0D93EC525D9C75E00C622ED /* AutocompleteItemContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */; };
|
||||
D0D93ECA25D9C76500C622ED /* AutocompleteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93EB925D9C70400C622ED /* AutocompleteItemView.swift */; };
|
||||
D0D93ED025D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */; };
|
||||
D0D93ED925D9CBE200C622ED /* AutocompleteItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */; };
|
||||
D0D93EDE25DA014700C622ED /* SeparatorConfiguredCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */; };
|
||||
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD50CA256B1F24004A04F7 /* ReportView.swift */; };
|
||||
D0DDA76B25C5F20800FA0F91 /* ExploreDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DDA76A25C5F20800FA0F91 /* ExploreDataSource.swift */; };
|
||||
D0DDA77525C5F73F00FA0F91 /* TagCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DDA77425C5F73F00FA0F91 /* TagCollectionViewCell.swift */; };
|
||||
|
@ -173,6 +180,8 @@
|
|||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
||||
D0E39AB425D8BF88009C10F8 /* UITextInput+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E39AB325D8BF88009C10F8 /* UITextInput+Extensions.swift */; };
|
||||
D0E39ABD25D8C046009C10F8 /* UITextInput+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E39AB325D8BF88009C10F8 /* UITextInput+Extensions.swift */; };
|
||||
D0E39B7E25D9AF23009C10F8 /* AutocompleteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E39B7D25D9AF23009C10F8 /* AutocompleteDataSource.swift */; };
|
||||
D0E39B8725D9B7FD009C10F8 /* AutocompleteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E39B7D25D9AF23009C10F8 /* AutocompleteDataSource.swift */; };
|
||||
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
||||
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E569DA2529319100FA1D72 /* LoadMoreView.swift */; };
|
||||
|
@ -373,6 +382,9 @@
|
|||
D0D2AC5225BCD2BA003D5DF2 /* TagContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartView.swift; sourceTree = "<group>"; };
|
||||
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
|
||||
D0D93EB925D9C70400C622ED /* AutocompleteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemView.swift; sourceTree = "<group>"; };
|
||||
D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D0DD50CA256B1F24004A04F7 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
|
||||
D0DDA76A25C5F20800FA0F91 /* ExploreDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreDataSource.swift; sourceTree = "<group>"; };
|
||||
D0DDA77425C5F73F00FA0F91 /* TagCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -381,6 +393,7 @@
|
|||
D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebfingerIndicatorView.swift; sourceTree = "<group>"; };
|
||||
D0E2C1CF24FD8BA400854680 /* ViewModels */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ViewModels; sourceTree = "<group>"; };
|
||||
D0E39AB325D8BF88009C10F8 /* UITextInput+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextInput+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D0E39B7D25D9AF23009C10F8 /* AutocompleteDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteDataSource.swift; sourceTree = "<group>"; };
|
||||
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -537,6 +550,7 @@
|
|||
D021A66F25C3E1F9008A0C0D /* Collection View Cells */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */,
|
||||
D07EC7DB25B13DBB006DF726 /* EmojiCollectionViewCell.swift */,
|
||||
D09D971725C64682007E6394 /* InstanceCollectionViewCell.swift */,
|
||||
D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */,
|
||||
|
@ -549,6 +563,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D0F0B112251A86A000942152 /* AccountContentConfiguration.swift */,
|
||||
D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */,
|
||||
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */,
|
||||
D07EC7E225B13DD3006DF726 /* EmojiContentConfiguration.swift */,
|
||||
D07EC7F125B13E57006DF726 /* EmojiView.swift */,
|
||||
|
@ -566,6 +581,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D0F0B10D251A868200942152 /* AccountView.swift */,
|
||||
D0D93EB925D9C70400C622ED /* AutocompleteItemView.swift */,
|
||||
D00702302555F4AE00F38136 /* ConversationView.swift */,
|
||||
D021A61325C36BFB008A0C0D /* IdentityView.swift */,
|
||||
D09D970725C64522007E6394 /* InstanceView.swift */,
|
||||
|
@ -677,6 +693,7 @@
|
|||
D0A1F4F5252E7D2A004435BF /* Data Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0E39B7D25D9AF23009C10F8 /* AutocompleteDataSource.swift */,
|
||||
D0DDA76A25C5F20800FA0F91 /* ExploreDataSource.swift */,
|
||||
D021A5FF25C3478F008A0C0D /* IdentitiesDataSource.swift */,
|
||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */,
|
||||
|
@ -1031,6 +1048,7 @@
|
|||
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
||||
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
||||
D05E688525B55AE8001FB2C6 /* AVURLAsset+Extensions.swift in Sources */,
|
||||
D0D93EC025D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift in Sources */,
|
||||
D09D970E25C64539007E6394 /* InstanceContentConfiguration.swift in Sources */,
|
||||
D036AA02254B6101009094DF /* NotificationTableViewCell.swift in Sources */,
|
||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
||||
|
@ -1069,6 +1087,7 @@
|
|||
D021A61425C36BFB008A0C0D /* IdentityView.swift in Sources */,
|
||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */,
|
||||
D0D2AC4D25BCD2A9003D5DF2 /* TagTableViewCell.swift in Sources */,
|
||||
D0D93ED025D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift in Sources */,
|
||||
D00CB22A25C92C0F008EF267 /* Attachment+Extensions.swift in Sources */,
|
||||
D0D2AC5325BCD2BA003D5DF2 /* TagContentConfiguration.swift in Sources */,
|
||||
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */,
|
||||
|
@ -1077,6 +1096,7 @@
|
|||
D0F0B10E251A868200942152 /* AccountView.swift in Sources */,
|
||||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
|
||||
D021A60025C3478F008A0C0D /* IdentitiesDataSource.swift in Sources */,
|
||||
D0E39B7E25D9AF23009C10F8 /* AutocompleteDataSource.swift in Sources */,
|
||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
||||
D025B14D25C4E482001C69A8 /* ImageCacheConfiguration.swift in Sources */,
|
||||
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
|
||||
|
@ -1107,6 +1127,7 @@
|
|||
D0BE980425D229D50057E161 /* SeparatorConfiguredTableViewCell.swift in Sources */,
|
||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
||||
D025B17E25C500BC001C69A8 /* CapsuleButton.swift in Sources */,
|
||||
D0D93EBA25D9C70400C622ED /* AutocompleteItemView.swift in Sources */,
|
||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
||||
D0DDA77F25C6058300FA0F91 /* ExploreSectionHeaderView.swift in Sources */,
|
||||
|
@ -1143,8 +1164,10 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0D93EDE25DA014700C622ED /* SeparatorConfiguredCollectionViewListCell.swift in Sources */,
|
||||
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */,
|
||||
D00CB23825C93047008EF267 /* String+Extensions.swift in Sources */,
|
||||
D0D93EC525D9C75E00C622ED /* AutocompleteItemContentConfiguration.swift in Sources */,
|
||||
D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */,
|
||||
D021A67B25C3E32A008A0C0D /* PlayerView.swift in Sources */,
|
||||
D021A69025C3E4B8008A0C0D /* EmojiContentConfiguration.swift in Sources */,
|
||||
|
@ -1173,10 +1196,13 @@
|
|||
D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */,
|
||||
D0E39ABD25D8C046009C10F8 /* UITextInput+Extensions.swift in Sources */,
|
||||
D088406E25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */,
|
||||
D0E39B8725D9B7FD009C10F8 /* AutocompleteDataSource.swift in Sources */,
|
||||
D00CB23325C92F2D008EF267 /* Attachment+Extensions.swift in Sources */,
|
||||
D0D93ECA25D9C76500C622ED /* AutocompleteItemView.swift in Sources */,
|
||||
D025B14725C4D26B001C69A8 /* ImageCacheSerializer.swift in Sources */,
|
||||
D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */,
|
||||
D021A6A625C3E584008A0C0D /* EditAttachmentView.swift in Sources */,
|
||||
D0D93ED925D9CBE200C622ED /* AutocompleteItemCollectionViewCell.swift in Sources */,
|
||||
D0BE97E025D086F80057E161 /* ImagePastableTextView.swift in Sources */,
|
||||
D05936DF25A937EC00754FDF /* EditThumbnailView.swift in Sources */,
|
||||
D021A69525C3E4C1008A0C0D /* EmojiView.swift in Sources */,
|
||||
|
|
|
@ -20,10 +20,6 @@ public struct ExploreService {
|
|||
}
|
||||
|
||||
public extension ExploreService {
|
||||
func searchService() -> SearchService {
|
||||
SearchService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func instanceServicePublisher(uri: String) -> AnyPublisher<InstanceService, Error> {
|
||||
contentDatabase.instancePublisher(uri: uri)
|
||||
.map { InstanceService(instance: $0, mastodonAPIClient: mastodonAPIClient) }
|
||||
|
|
|
@ -270,6 +270,10 @@ public extension IdentityService {
|
|||
ExploreService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func searchService() -> SearchService {
|
||||
SearchService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func notificationsService(excludeTypes: Set<MastodonNotification.NotificationType>) -> NotificationsService {
|
||||
NotificationsService(excludeTypes: excludeTypes,
|
||||
mastodonAPIClient: mastodonAPIClient,
|
||||
|
|
|
@ -20,7 +20,9 @@ final class EmojiPickerViewController: UICollectionViewController {
|
|||
private lazy var dataSource: UICollectionViewDiffableDataSource<PickerEmoji.Category, PickerEmoji> = {
|
||||
let cellRegistration = UICollectionView.CellRegistration
|
||||
<EmojiCollectionViewCell, PickerEmoji> { [weak self] in
|
||||
$0.emoji = self?.applyingDefaultSkinTone(emoji: $2) ?? $2
|
||||
guard let self = self else { return }
|
||||
|
||||
$0.emoji = $2.applyingDefaultSkinTone(identityContext: self.viewModel.identityContext)
|
||||
}
|
||||
|
||||
let headerRegistration = UICollectionView.SupplementaryRegistration
|
||||
|
@ -149,9 +151,11 @@ final class EmojiPickerViewController: UICollectionViewController {
|
|||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
collectionView.deselectItem(at: indexPath, animated: true)
|
||||
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
select(emoji: applyingDefaultSkinTone(emoji: item))
|
||||
select(item.applyingDefaultSkinTone(identityContext: viewModel.identityContext))
|
||||
viewModel.updateUse(emoji: item)
|
||||
}
|
||||
|
||||
|
@ -234,13 +238,4 @@ private extension EmojiPickerViewController {
|
|||
snapshot.reloadItems(visibleItems)
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
func applyingDefaultSkinTone(emoji: PickerEmoji) -> PickerEmoji {
|
||||
if case let .system(systemEmoji, inFrequentlyUsed) = emoji,
|
||||
let defaultEmojiSkinTone = viewModel.identityContext.appPreferences.defaultEmojiSkinTone {
|
||||
return .system(systemEmoji.applying(skinTone: defaultEmojiSkinTone), inFrequentlyUsed: inFrequentlyUsed)
|
||||
} else {
|
||||
return emoji
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ final class ProfileViewController: TableViewController {
|
|||
|
||||
required init(
|
||||
viewModel: ProfileViewModel,
|
||||
rootViewModel: RootViewModel,
|
||||
rootViewModel: RootViewModel?,
|
||||
identityContext: IdentityContext,
|
||||
parentNavigationController: UINavigationController?) {
|
||||
self.viewModel = viewModel
|
||||
|
|
|
@ -13,7 +13,7 @@ class TableViewController: UITableViewController {
|
|||
var transitionViewTag = -1
|
||||
|
||||
private let viewModel: CollectionViewModel
|
||||
private let rootViewModel: RootViewModel
|
||||
private let rootViewModel: RootViewModel?
|
||||
private let loadingTableFooterView = LoadingTableFooterView()
|
||||
private let webfingerIndicatorView = WebfingerIndicatorView()
|
||||
@Published private var loading = false
|
||||
|
@ -29,7 +29,7 @@ class TableViewController: UITableViewController {
|
|||
}()
|
||||
|
||||
init(viewModel: CollectionViewModel,
|
||||
rootViewModel: RootViewModel,
|
||||
rootViewModel: RootViewModel? = nil,
|
||||
insetBottom: Bool = true,
|
||||
parentNavigationController: UINavigationController? = nil) {
|
||||
self.viewModel = viewModel
|
||||
|
@ -540,7 +540,7 @@ private extension TableViewController {
|
|||
}
|
||||
|
||||
func compose(inReplyToViewModel: StatusViewModel?, redraft: Status?) {
|
||||
rootViewModel.navigationViewModel?.presentedNewStatusViewModel = rootViewModel.newStatusViewModel(
|
||||
rootViewModel?.navigationViewModel?.presentedNewStatusViewModel = rootViewModel?.newStatusViewModel(
|
||||
identityContext: viewModel.identityContext,
|
||||
inReplyTo: inReplyToViewModel,
|
||||
redraft: redraft)
|
||||
|
|
|
@ -20,7 +20,7 @@ final public class EmojiPickerViewModel: ObservableObject {
|
|||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
public init(identityContext: IdentityContext) {
|
||||
public init(identityContext: IdentityContext, queryOnly: Bool = false) {
|
||||
self.identityContext = identityContext
|
||||
emojiPickerService = identityContext.service.emojiPickerService()
|
||||
|
||||
|
@ -67,12 +67,16 @@ final public class EmojiPickerViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if queryOnly {
|
||||
return [:]
|
||||
}
|
||||
|
||||
emojis[.frequentlyUsed] = emojiUses.compactMap { use in
|
||||
emojis.values.reduce([], +)
|
||||
.first { use.system == $0.system && use.emoji == $0.name }
|
||||
.map(\.inFrequentlyUsed)
|
||||
if !queryOnly {
|
||||
emojis[.frequentlyUsed] = emojiUses.compactMap { use in
|
||||
emojis.values.reduce([], +)
|
||||
.first { use.system == $0.system && use.emoji == $0.name }
|
||||
.map(\.inFrequentlyUsed)
|
||||
}
|
||||
}
|
||||
|
||||
return emojis.filter { !$0.value.isEmpty }
|
||||
|
|
|
@ -21,9 +21,7 @@ public final class ExploreViewModel: ObservableObject {
|
|||
init(service: ExploreService, identityContext: IdentityContext) {
|
||||
exploreService = service
|
||||
self.identityContext = identityContext
|
||||
searchViewModel = SearchViewModel(
|
||||
searchService: exploreService.searchService(),
|
||||
identityContext: identityContext)
|
||||
searchViewModel = SearchViewModel(identityContext: identityContext)
|
||||
events = eventsSubject.eraseToAnyPublisher()
|
||||
|
||||
identityContext.$identity
|
||||
|
|
|
@ -11,8 +11,8 @@ public final class SearchViewModel: CollectionItemsViewModel {
|
|||
private let searchService: SearchService
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
public init(searchService: SearchService, identityContext: IdentityContext) {
|
||||
self.searchService = searchService
|
||||
public init(identityContext: IdentityContext) {
|
||||
self.searchService = identityContext.service.searchService()
|
||||
|
||||
super.init(collectionService: searchService, identityContext: identityContext)
|
||||
|
||||
|
@ -40,7 +40,7 @@ public final class SearchViewModel: CollectionItemsViewModel {
|
|||
}
|
||||
|
||||
private extension SearchViewModel {
|
||||
static let debounceInterval: TimeInterval = 0.5
|
||||
static let debounceInterval: TimeInterval = 0.2
|
||||
}
|
||||
|
||||
private extension SearchScope {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class AutocompleteItemCollectionViewCell: SeparatorConfiguredCollectionViewListCell {
|
||||
var item: AutocompleteItem?
|
||||
var identityContext: IdentityContext?
|
||||
|
||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||
guard let item = item, let identityContext = identityContext else { return }
|
||||
|
||||
contentConfiguration = AutocompleteItemContentConfiguration(item: item, identityContext: identityContext)
|
||||
|
||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
||||
|
||||
backgroundConfiguration.backgroundColor = state.isHighlighted || state.isSelected ? nil : .clear
|
||||
|
||||
self.backgroundConfiguration = backgroundConfiguration
|
||||
|
||||
accessibilityElements = [contentView]
|
||||
}
|
||||
}
|
|
@ -10,5 +10,12 @@ final class EmojiCollectionViewCell: UICollectionViewCell {
|
|||
guard let emoji = emoji else { return }
|
||||
|
||||
contentConfiguration = EmojiContentConfiguration(emoji: emoji)
|
||||
|
||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
||||
|
||||
backgroundConfiguration.backgroundColor = state.isHighlighted || state.isSelected ? nil : .clear
|
||||
backgroundConfiguration.cornerRadius = .defaultCornerRadius
|
||||
|
||||
self.backgroundConfiguration = backgroundConfiguration
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,17 @@ import Mastodon
|
|||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class CompositionInputAccessoryView: UIToolbar {
|
||||
final class CompositionInputAccessoryView: UIView {
|
||||
let tagForInputView = UUID().hashValue
|
||||
|
||||
private let viewModel: CompositionViewModel
|
||||
private let parentViewModel: NewStatusViewModel
|
||||
private let autocompleteQueryPublisher: AnyPublisher<String?, Never>
|
||||
private let toolbar = UIToolbar()
|
||||
private let autocompleteCollectionView = UICollectionView(
|
||||
frame: .zero,
|
||||
collectionViewLayout: CompositionInputAccessoryView.autocompleteLayout())
|
||||
private let autocompleteDataSource: AutocompleteDataSource
|
||||
private let autocompleteCollectionViewHeightConstraint: NSLayoutConstraint
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: CompositionViewModel,
|
||||
|
@ -19,7 +24,12 @@ final class CompositionInputAccessoryView: UIToolbar {
|
|||
autocompleteQueryPublisher: AnyPublisher<String?, Never>) {
|
||||
self.viewModel = viewModel
|
||||
self.parentViewModel = parentViewModel
|
||||
self.autocompleteQueryPublisher = autocompleteQueryPublisher
|
||||
autocompleteDataSource = AutocompleteDataSource(
|
||||
collectionView: autocompleteCollectionView,
|
||||
queryPublisher: autocompleteQueryPublisher,
|
||||
parentViewModel: parentViewModel)
|
||||
autocompleteCollectionViewHeightConstraint =
|
||||
autocompleteCollectionView.heightAnchor.constraint(equalToConstant: .minimumButtonDimension)
|
||||
|
||||
super.init(
|
||||
frame: .init(
|
||||
|
@ -36,11 +46,42 @@ final class CompositionInputAccessoryView: UIToolbar {
|
|||
}
|
||||
|
||||
private extension CompositionInputAccessoryView {
|
||||
static let autocompleteCollectionViewMaxHeight: CGFloat = 150
|
||||
|
||||
var heightConstraint: NSLayoutConstraint? {
|
||||
superview?.constraints.first(where: { $0.identifier == "accessoryHeight" })
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
func initialSetup() {
|
||||
autoresizingMask = .flexibleHeight
|
||||
|
||||
heightAnchor.constraint(equalToConstant: .minimumButtonDimension).isActive = true
|
||||
addSubview(autocompleteCollectionView)
|
||||
autocompleteCollectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
autocompleteCollectionView.alwaysBounceVertical = false
|
||||
autocompleteCollectionView.backgroundColor = .clear
|
||||
autocompleteCollectionView.layer.cornerRadius = .defaultCornerRadius
|
||||
autocompleteCollectionView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
|
||||
|
||||
let autocompleteBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial))
|
||||
|
||||
autocompleteCollectionView.backgroundView = autocompleteBackgroundView
|
||||
|
||||
addSubview(toolbar)
|
||||
toolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||
toolbar.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
autocompleteCollectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
autocompleteCollectionView.topAnchor.constraint(equalTo: topAnchor),
|
||||
autocompleteCollectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
autocompleteCollectionView.bottomAnchor.constraint(equalTo: toolbar.topAnchor),
|
||||
toolbar.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
toolbar.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
toolbar.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
toolbar.heightAnchor.constraint(equalToConstant: .minimumButtonDimension),
|
||||
autocompleteCollectionViewHeightConstraint
|
||||
])
|
||||
|
||||
var attachmentActions = [
|
||||
UIAction(
|
||||
|
@ -129,15 +170,11 @@ private extension CompositionInputAccessoryView {
|
|||
NSLocalizedString("compose.add-button-accessibility-label.post", comment: "")
|
||||
}
|
||||
|
||||
let charactersLabel = UILabel()
|
||||
let charactersBarItem = UIBarButtonItem()
|
||||
|
||||
charactersLabel.font = .preferredFont(forTextStyle: .callout)
|
||||
charactersLabel.adjustsFontForContentSizeCategory = true
|
||||
charactersLabel.adjustsFontSizeToFitWidth = true
|
||||
charactersBarItem.isEnabled = false
|
||||
|
||||
let charactersBarItem = UIBarButtonItem(customView: charactersLabel)
|
||||
|
||||
items = [
|
||||
toolbar.items = [
|
||||
attachmentButton,
|
||||
UIBarButtonItem.fixedSpace(.defaultSpacing),
|
||||
pollButton,
|
||||
|
@ -162,9 +199,11 @@ private extension CompositionInputAccessoryView {
|
|||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$remainingCharacters.sink {
|
||||
charactersLabel.text = String($0)
|
||||
charactersLabel.textColor = $0 < 0 ? .systemRed : .label
|
||||
charactersLabel.accessibilityLabel = String.localizedStringWithFormat(
|
||||
charactersBarItem.title = String($0)
|
||||
charactersBarItem.setTitleTextAttributes(
|
||||
[.foregroundColor: $0 < 0 ? UIColor.systemRed : UIColor.label],
|
||||
for: .disabled)
|
||||
charactersBarItem.accessibilityHint = String.localizedStringWithFormat(
|
||||
NSLocalizedString("compose.characters-remaining-accessibility-label-%ld", comment: ""),
|
||||
$0)
|
||||
}
|
||||
|
@ -174,9 +213,15 @@ private extension CompositionInputAccessoryView {
|
|||
.sink { addButton.isEnabled = $0 }
|
||||
.store(in: &cancellables)
|
||||
|
||||
autocompleteQueryPublisher
|
||||
.print()
|
||||
.sink { _ in /* TODO */ }
|
||||
self.autocompleteCollectionView.publisher(for: \.contentSize)
|
||||
.map(\.height)
|
||||
.removeDuplicates()
|
||||
.throttle(for: .seconds(TimeInterval.shortAnimationDuration), scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] height in
|
||||
UIView.animate(withDuration: .zeroIfReduceMotion(.shortAnimationDuration)) {
|
||||
self?.setAutocompleteCollectionViewHeight(height)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
parentViewModel.$visibility
|
||||
|
@ -192,6 +237,41 @@ private extension CompositionInputAccessoryView {
|
|||
}
|
||||
|
||||
private extension CompositionInputAccessoryView {
|
||||
static func autocompleteLayout() -> UICollectionViewLayout {
|
||||
var listConfig = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||
|
||||
listConfig.backgroundColor = .clear
|
||||
|
||||
return UICollectionViewCompositionalLayout { index, environment -> NSCollectionLayoutSection? in
|
||||
guard let autocompleteSection = AutocompleteSection(rawValue: index) else { return nil }
|
||||
|
||||
switch autocompleteSection {
|
||||
case .search:
|
||||
return .list(using: listConfig, layoutEnvironment: environment)
|
||||
case .emoji:
|
||||
let itemSize = NSCollectionLayoutSize(
|
||||
widthDimension: .fractionalWidth(1.0),
|
||||
heightDimension: .fractionalHeight(1.0))
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
let groupSize = NSCollectionLayoutSize(
|
||||
widthDimension: .absolute(.minimumButtonDimension),
|
||||
heightDimension: .absolute(.minimumButtonDimension))
|
||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
|
||||
section.interGroupSpacing = .defaultSpacing
|
||||
section.orthogonalScrollingBehavior = .continuous
|
||||
section.contentInsets = NSDirectionalEdgeInsets(
|
||||
top: .compactSpacing,
|
||||
leading: .compactSpacing,
|
||||
bottom: .compactSpacing,
|
||||
trailing: .compactSpacing)
|
||||
|
||||
return section
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func visibilityMenu(selectedVisibility: Status.Visibility) -> UIMenu {
|
||||
UIMenu(children: Status.Visibility.allCasesExceptUnknown.reversed().map { visibility in
|
||||
UIAction(
|
||||
|
@ -203,4 +283,15 @@ private extension CompositionInputAccessoryView {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setAutocompleteCollectionViewHeight(_ height: CGFloat) {
|
||||
let autocompleteCollectionViewHeight = min(max(height, .hairline), Self.autocompleteCollectionViewMaxHeight)
|
||||
|
||||
autocompleteCollectionViewHeightConstraint.constant = autocompleteCollectionViewHeight
|
||||
autocompleteCollectionView.alpha = autocompleteCollectionViewHeightConstraint.constant == .hairline ? 0 : 1
|
||||
|
||||
heightConstraint?.constant = .minimumButtonDimension + autocompleteCollectionViewHeight
|
||||
updateConstraints()
|
||||
superview?.superview?.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
struct AutocompleteItemContentConfiguration {
|
||||
let item: AutocompleteItem
|
||||
let identityContext: IdentityContext
|
||||
}
|
||||
|
||||
extension AutocompleteItemContentConfiguration: UIContentConfiguration {
|
||||
func makeContentView() -> UIView & UIContentView {
|
||||
AutocompleteItemView(configuration: self)
|
||||
}
|
||||
|
||||
func updated(for state: UIConfigurationState) -> AutocompleteItemContentConfiguration {
|
||||
self
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import Kingfisher
|
|||
import UIKit
|
||||
|
||||
final class EmojiView: UIView {
|
||||
private let imageView = UIImageView()
|
||||
private let imageView = AnimatedImageView()
|
||||
private let emojiLabel = UILabel()
|
||||
private var emojiConfiguration: EmojiContentConfiguration
|
||||
|
||||
|
|
126
Views/UIKit/Content Views/AutocompleteItemView.swift
Normal file
126
Views/UIKit/Content Views/AutocompleteItemView.swift
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Kingfisher
|
||||
import UIKit
|
||||
|
||||
final class AutocompleteItemView: UIView {
|
||||
private let imageView = AnimatedImageView()
|
||||
private let primaryLabel = UILabel()
|
||||
private let secondaryLabel = UILabel()
|
||||
private let stackView = UIStackView()
|
||||
private var autocompleteItemConfiguration: AutocompleteItemContentConfiguration
|
||||
|
||||
init(configuration: AutocompleteItemContentConfiguration) {
|
||||
self.autocompleteItemConfiguration = configuration
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
initialSetup()
|
||||
applyAutocompleteItemConfiguration()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension AutocompleteItemView: UIContentView {
|
||||
var configuration: UIContentConfiguration {
|
||||
get { autocompleteItemConfiguration }
|
||||
set {
|
||||
guard let autocompleteItemConfiguration = newValue as? AutocompleteItemContentConfiguration else { return }
|
||||
|
||||
self.autocompleteItemConfiguration = autocompleteItemConfiguration
|
||||
|
||||
applyAutocompleteItemConfiguration()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AutocompleteItemView {
|
||||
func initialSetup() {
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.spacing = .defaultSpacing
|
||||
|
||||
stackView.addArrangedSubview(imageView)
|
||||
imageView.layer.cornerRadius = .barButtonItemDimension / 2
|
||||
imageView.clipsToBounds = true
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
||||
stackView.addArrangedSubview(primaryLabel)
|
||||
primaryLabel.adjustsFontForContentSizeCategory = true
|
||||
primaryLabel.font = .preferredFont(forTextStyle: .headline)
|
||||
primaryLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||
|
||||
stackView.addArrangedSubview(secondaryLabel)
|
||||
secondaryLabel.adjustsFontForContentSizeCategory = true
|
||||
secondaryLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
secondaryLabel.textColor = .secondaryLabel
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.widthAnchor.constraint(equalToConstant: .barButtonItemDimension),
|
||||
imageView.heightAnchor.constraint(equalToConstant: .barButtonItemDimension),
|
||||
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
func applyAutocompleteItemConfiguration() {
|
||||
switch autocompleteItemConfiguration.item {
|
||||
case let .account(account):
|
||||
let appPreferences = autocompleteItemConfiguration.identityContext.appPreferences
|
||||
let avatarURL = appPreferences.animateAvatars == .everywhere
|
||||
&& !appPreferences.shouldReduceMotion
|
||||
? account.avatar
|
||||
: account.avatarStatic
|
||||
|
||||
imageView.kf.setImage(with: avatarURL)
|
||||
imageView.isHidden = false
|
||||
|
||||
let mutableDisplayName = NSMutableAttributedString(string: account.displayName)
|
||||
|
||||
mutableDisplayName.insert(emojis: account.emojis, view: primaryLabel)
|
||||
mutableDisplayName.resizeAttachments(toLineHeight: primaryLabel.font.lineHeight)
|
||||
primaryLabel.attributedText = mutableDisplayName
|
||||
primaryLabel.isHidden = account.displayName.isEmpty
|
||||
secondaryLabel.text = "@".appending(account.acct)
|
||||
primaryLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
secondaryLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
case let .tag(tag):
|
||||
imageView.isHidden = true
|
||||
imageView.image = nil
|
||||
primaryLabel.text = "#".appending(tag.name)
|
||||
primaryLabel.isHidden = false
|
||||
|
||||
if let uses = tag.history?.compactMap({ Int($0.uses) }).reduce(0, +), uses > 0 {
|
||||
secondaryLabel.text =
|
||||
String.localizedStringWithFormat(NSLocalizedString("tag.per-week-%ld", comment: ""), uses)
|
||||
} else {
|
||||
secondaryLabel.text = nil
|
||||
}
|
||||
|
||||
primaryLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
secondaryLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let accessibilityAttributedLabel = NSMutableAttributedString(string: "")
|
||||
|
||||
if !primaryLabel.isHidden, let primaryLabelAttributedText = primaryLabel.attributedText {
|
||||
accessibilityAttributedLabel.append(primaryLabelAttributedText)
|
||||
}
|
||||
|
||||
if let secondaryLabelText = secondaryLabel.text, !secondaryLabelText.isEmpty {
|
||||
accessibilityAttributedLabel.appendWithSeparator(secondaryLabelText)
|
||||
}
|
||||
|
||||
self.accessibilityAttributedLabel = accessibilityAttributedLabel
|
||||
|
||||
isAccessibilityElement = true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue