// // 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 @Binding var pinnedTimelines: [PinnedTimeline] func makeUIViewController(context: Context) -> UIHostingController { return UIHostingController(rootView: AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)) } func updateUIViewController(_ uiViewController: UIHostingController, 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? @State private var isSearching = false @State private var searchResults: [String] = [] @State private var trendingTags: [String] = [] private var savedAndFollowedHashtags: [String] { var tags = Set() 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() // } //}