Tusker/Tusker/Screens/Customize Timelines/AddHashtagPinnedTimelineVie...

163 lines
5.7 KiB
Swift

//
// AddHashtagPinnedTimelineView.swift
// Tusker
//
// Created by Shadowfacts on 12/20/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import SwiftUI
import Pachyderm
@available(iOS, obsoleted: 16.0)
struct AddHashtagPinnedTimelineRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = UIHostingController<AddHashtagPinnedTimelineView>
@Binding var pinnedTimelines: [PinnedTimeline]
func makeUIViewController(context: Context) -> UIHostingController<AddHashtagPinnedTimelineView> {
return UIHostingController(rootView: AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines))
}
func updateUIViewController(_ uiViewController: UIHostingController<AddHashtagPinnedTimelineView>, context: Context) {
}
}
struct AddHashtagPinnedTimelineView: View {
@EnvironmentObject private var mastodonController: MastodonController
@Environment(\.dismiss) private var dismiss
@Binding var pinnedTimelines: [PinnedTimeline]
@StateObject private var viewModel = SearchViewModel()
@State private var searchTask: Task<Void, Never>?
@State private var isSearching = false
@State private var searchResults: [String] = []
@State private var trendingTags: [String] = []
private var savedAndFollowedHashtags: [String] {
var tags = Set<String>()
let req = SavedHashtag.fetchRequest(account: mastodonController.accountInfo!)
for saved in (try? mastodonController.persistentContainer.viewContext.fetch(req)) ?? [] {
tags.insert(saved.name)
}
for followed in mastodonController.followedHashtags {
tags.insert(followed.name)
}
return tags.sorted(using: SemiCaseSensitiveComparator())
}
var body: some View {
NavigationView {
list
#if !os(visionOS)
.appGroupedListBackground(container: AddHashtagPinnedTimelineRepresentable.UIViewControllerType.self)
#endif
.listStyle(.grouped)
.navigationTitle("Add Hashtag")
.navigationBarTitleDisplayMode(.inline)
.searchable(text: $viewModel.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Search for hashtags"))
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
}
}
.navigationViewStyle(.stack)
.onReceive(viewModel.$searchQuery, perform: { newValue in
isSearching = !newValue.isEmpty
})
.onReceive(viewModel.$searchQuery.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main), perform: { _ in
searchTask?.cancel()
searchTask = Task {
try? await updateSearchResults()
}
})
.task {
await fetchTrendingTags()
}
}
private var list: some View {
List {
if !viewModel.searchQuery.isEmpty {
Section {
ProgressView()
.progressViewStyle(.circular)
.opacity(isSearching ? 1 : 0)
.animation(.linear(duration: 0.1), value: isSearching)
.frame(maxWidth: .infinity, alignment: .center)
.listRowBackground(EmptyView())
.listRowSeparator(.hidden)
forEachTag(searchResults)
}
.appGroupedListRowBackground()
}
if !savedAndFollowedHashtags.isEmpty {
Section {
forEachTag(savedAndFollowedHashtags)
} header: {
Text("Saved and Followed Hashtags")
}
.appGroupedListRowBackground()
}
if !trendingTags.isEmpty {
Section {
forEachTag(trendingTags)
} header: {
Text("Trending Hashtags")
}
.appGroupedListRowBackground()
}
}
}
private func forEachTag(_ tags: [String]) -> some View {
ForEach(tags, id: \.self) { tag in
Button {
pinnedTimelines.append(.tag(hashtag: tag))
dismiss()
} label: {
Text("#\(tag)")
}
.tint(.primary)
.disabled(pinnedTimelines.contains(.tag(hashtag: tag)))
}
}
private func updateSearchResults() async throws {
guard !viewModel.searchQuery.isEmpty else {
return
}
isSearching = true
let req = Client.search(query: viewModel.searchQuery, types: [.hashtags])
let (results, _) = try await mastodonController.run(req)
searchResults = results.hashtags.map(\.name)
isSearching = false
}
private func fetchTrendingTags() async {
guard mastodonController.instanceFeatures.trends else { return }
let req = Client.getTrendingHashtags()
if let (results, _) = try? await mastodonController.run(req) {
trendingTags = results.map(\.name)
}
}
}
private class SearchViewModel: ObservableObject {
@Published var searchQuery = ""
}
//struct AddHashtagPinnedTimelineView_Previews: PreviewProvider {
// static var previews: some View {
// AddHashtagPinnedTimelineView()
// }
//}