PopupView and Transimission can interfere with each other and cause an issue with the root screen becoming black and the app getting stuck.
221 lines
6.9 KiB
Swift
221 lines
6.9 KiB
Swift
#if os(iOS)
|
|
import CoreData
|
|
import Models
|
|
import Services
|
|
import SwiftUI
|
|
import Views
|
|
|
|
@MainActor final class RecommendToViewModel: ObservableObject {
|
|
@Published var isLoading = false
|
|
@Published var networkError = false
|
|
@Published var recommendationGroups = [InternalRecommendationGroup]()
|
|
@Published var selectedGroups = [InternalRecommendationGroup]()
|
|
@Published var isRunning = false
|
|
@Published var showError = false
|
|
@Published var showNoteView = false
|
|
@Published var note: String = ""
|
|
@Published var withHighlights: Bool = true
|
|
|
|
let pageID: String
|
|
let highlightCount: Int
|
|
|
|
init(pageID: String, highlightCount: Int) {
|
|
self.pageID = pageID
|
|
self.highlightCount = highlightCount
|
|
}
|
|
|
|
func loadGroups(dataService: DataService) async {
|
|
isLoading = true
|
|
|
|
do {
|
|
dataService.viewContext.performAndWait {
|
|
let fetchRequest: NSFetchRequest<Models.RecommendationGroup> = RecommendationGroup.fetchRequest()
|
|
let sort = NSSortDescriptor(key: #keyPath(RecommendationGroup.name), ascending: true)
|
|
fetchRequest.predicate = NSPredicate(format: "canPost == %@", NSNumber(value: true))
|
|
fetchRequest.sortDescriptors = [sort]
|
|
|
|
// If this fails we will fallback to making the API call
|
|
let groups = try? dataService.viewContext.fetch(fetchRequest).compactMap { object in
|
|
InternalRecommendationGroup.make(from: object)
|
|
}
|
|
if let groups = groups {
|
|
self.recommendationGroups = groups
|
|
}
|
|
}
|
|
recommendationGroups = try await dataService.recommendationGroups()
|
|
.filter(\.canPost)
|
|
.sorted(by: { $0.name < $1.name })
|
|
|
|
} catch {
|
|
print("ERROR fetching recommendationGroups: ", error)
|
|
networkError = true
|
|
}
|
|
|
|
isLoading = false
|
|
}
|
|
|
|
func recommend(dataService: DataService) async -> Bool {
|
|
isRunning = true
|
|
defer { isRunning = false }
|
|
|
|
do {
|
|
try await dataService.recommendPage(pageID: pageID,
|
|
groupIDs: selectedGroups.map(\.id),
|
|
note: note.isEmpty ? nil : note,
|
|
withHighlights: withHighlights)
|
|
} catch {
|
|
showError = true
|
|
return false
|
|
}
|
|
|
|
isRunning = false
|
|
return true
|
|
}
|
|
}
|
|
|
|
struct RecommendToView: View {
|
|
var dataService: DataService
|
|
@StateObject var viewModel: RecommendToViewModel
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var nextButton: some View {
|
|
Button(action: {
|
|
self.viewModel.showNoteView = true
|
|
}, label: {
|
|
Text(LocalText.genericNext)
|
|
.bold()
|
|
})
|
|
.disabled(viewModel.selectedGroups.isEmpty)
|
|
}
|
|
|
|
var sendButton: some View {
|
|
if viewModel.isRunning {
|
|
return AnyView(ProgressView())
|
|
} else {
|
|
return AnyView(Button(action: {
|
|
Task {
|
|
if await viewModel.recommend(dataService: dataService) {
|
|
Snackbar.show(message: "Recommendation sent", dismissAfter: 2000)
|
|
dismiss()
|
|
}
|
|
}
|
|
}, label: {
|
|
Text(LocalText.genericSend)
|
|
.bold()
|
|
})
|
|
.disabled(viewModel.selectedGroups.isEmpty)
|
|
)
|
|
}
|
|
}
|
|
|
|
var noteView: some View {
|
|
VStack {
|
|
HStack {
|
|
Text(LocalText.recommendationToPrefix)
|
|
.font(.appCaption)
|
|
.foregroundColor(.appGrayText)
|
|
Text(InternalRecommendationGroup.readable(list: viewModel.selectedGroups))
|
|
.font(.appCaption)
|
|
.foregroundColor(.appGrayTextContrast)
|
|
Spacer()
|
|
}
|
|
TextEditor(text: $viewModel.note)
|
|
.lineSpacing(6)
|
|
.accentColor(.appGraySolid)
|
|
.foregroundColor(.appGrayTextContrast)
|
|
.font(.appBody)
|
|
.padding(12)
|
|
.frame(height: 200)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.strokeBorder(Color.appGrayBorder, lineWidth: 1)
|
|
.background(RoundedRectangle(cornerRadius: 8).fill(Color.systemBackground))
|
|
)
|
|
.overlay(
|
|
Text(LocalText.recommendationAddNote)
|
|
.allowsHitTesting(false)
|
|
.opacity(viewModel.note.isEmpty ? 0.4 : 0.0)
|
|
.font(.appBody)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
.padding(.top, 24)
|
|
.padding(.leading, 16)
|
|
)
|
|
if viewModel.highlightCount > 0 {
|
|
Toggle(isOn: $viewModel.withHighlights, label: {
|
|
HStack(alignment: .firstTextBaseline) {
|
|
Text("Include your \(viewModel.highlightCount) highlight\(viewModel.highlightCount > 1 ? "s" : "")")
|
|
}
|
|
})
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding(16)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.navigationViewStyle(.stack)
|
|
.navigationBarItems(trailing: sendButton)
|
|
}
|
|
|
|
var body: some View {
|
|
VStack {
|
|
NavigationLink(destination: noteView,
|
|
isActive: $viewModel.showNoteView) {
|
|
EmptyView()
|
|
}
|
|
List {
|
|
if !viewModel.isLoading, viewModel.recommendationGroups.count < 1 {
|
|
Text("""
|
|
\(LocalText.clubsNoneJoined)
|
|
|
|
[Learn more about clubs](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc)
|
|
""")
|
|
.accentColor(.blue)
|
|
} else {
|
|
Section("Select clubs to recommend to") {
|
|
ForEach(viewModel.recommendationGroups) { group in
|
|
HStack {
|
|
Text(group.name)
|
|
|
|
Spacer()
|
|
|
|
if viewModel.selectedGroups.contains(where: { $0.id == group.id }) {
|
|
Image(systemName: "checkmark")
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
.onTapGesture {
|
|
let idx = viewModel.selectedGroups.firstIndex(where: { $0.id == group.id })
|
|
if let idx = idx {
|
|
viewModel.selectedGroups.remove(at: idx)
|
|
} else {
|
|
viewModel.selectedGroups.append(group)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.grouped)
|
|
|
|
Spacer()
|
|
}
|
|
.alert(isPresented: $viewModel.showError) {
|
|
Alert(
|
|
title: Text(LocalText.recommendationError),
|
|
dismissButton: .cancel(Text(LocalText.genericOk)) {
|
|
viewModel.showError = false
|
|
}
|
|
)
|
|
}
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.navigationViewStyle(.stack)
|
|
.navigationBarItems(leading: Button(action: {
|
|
dismiss()
|
|
}, label: { Text(LocalText.cancelGeneric) }),
|
|
trailing: nextButton)
|
|
.task {
|
|
await viewModel.loadGroups(dataService: dataService)
|
|
}
|
|
}
|
|
}
|
|
#endif
|