Ferrite/Ferrite/Views/MainView.swift
kingbri 7202a95bb2 Ferrite: Parallel tasks and logging
Make all tasks run in parallel to increase responsiveness and efficiency
when fetching new data.

However, parallel tasks means that toast errors are no longer feasible.
Instead, add a logging system which has a more detailed view of
app messages and direct the user there if there is an error.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-03-09 18:48:28 -05:00

242 lines
9.1 KiB
Swift

//
// MainView.swift
// Ferrite
//
// Created by Brian Dashore on 7/11/22.
//
import SwiftUI
import SwiftUIX
struct MainView: View {
@EnvironmentObject var navModel: NavigationViewModel
@EnvironmentObject var logManager: LoggingManager
@EnvironmentObject var debridManager: DebridManager
@EnvironmentObject var scrapingModel: ScrapingViewModel
@EnvironmentObject var backupManager: BackupManager
@EnvironmentObject var pluginManager: PluginManager
@AppStorage("Updates.AutomaticNotifs") var autoUpdateNotifs = true
@State private var showUpdateAlert = false
@State private var releaseVersionString: String = ""
@State private var releaseUrlString: String = ""
@State private var viewTask: Task<Void, Never>?
var body: some View {
TabView(selection: $navModel.selectedTab) {
ContentView()
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
.tag(NavigationViewModel.ViewTab.search)
LibraryView()
.tabItem {
Label("Library", systemImage: "book.closed")
}
.tag(NavigationViewModel.ViewTab.library)
PluginsView()
.tabItem {
Label("Plugins", systemImage: "doc.text")
}
.tag(NavigationViewModel.ViewTab.plugins)
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
.tag(NavigationViewModel.ViewTab.settings)
}
.sheet(item: $navModel.currentChoiceSheet) { item in
switch item {
case .action:
ActionChoiceView()
.environmentObject(debridManager)
.environmentObject(scrapingModel)
.environmentObject(navModel)
.environmentObject(pluginManager)
.environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
case .batch:
BatchChoiceView()
.environmentObject(debridManager)
.environmentObject(scrapingModel)
.environmentObject(navModel)
case .activity:
if #available(iOS 16, *) {
AppActivityView(activityItems: navModel.activityItems)
.presentationDetents([.medium, .large])
} else {
AppActivityView(activityItems: navModel.activityItems)
}
}
}
.backport.onAppear {
if
autoUpdateNotifs,
Application.shared.osVersion.majorVersion >= Application.shared.minVersion.majorVersion
{
// MARK: If scope bar duplication happens, this may be the problem
logManager.info("Ferrite started")
viewTask = Task {
// Sleep for 2 seconds to allow for view layout and app init
try? await Task.sleep(seconds: 2)
do {
guard let latestRelease = try await Github().fetchLatestRelease() else {
logManager.error(
"Github: No releases found",
description: "Github error: No releases found"
)
return
}
let releaseVersion = String(latestRelease.tagName.dropFirst())
if releaseVersion > Application.shared.appVersion {
releaseVersionString = latestRelease.tagName
releaseUrlString = latestRelease.htmlUrl
showUpdateAlert.toggle()
}
} catch {
let error = error as NSError
if error.code == -1009 {
logManager.info(
"Github: The connection is offline",
description: "The connection is offline"
)
} else {
logManager.error(
"Github: \(error)",
description: "A Github error was logged"
)
}
}
logManager.info("Github release updates checked")
}
}
}
.onDisappear {
viewTask?.cancel()
}
.onOpenURL { url in
if url.scheme == "file" {
// Attempt to copy to backups directory if backup doesn't exist
backupManager.copyBackup(backupUrl: url)
backupManager.showRestoreAlert.toggle()
}
}
// Global alerts and dialogs for backups
.backport.confirmationDialog(
isPresented: $backupManager.showRestoreAlert,
title: "Restore backup?",
message:
"Merge (preferred): Will merge your current data with the backup \n\n" +
"Overwrite: Will delete and replace all your data \n\n" +
"If Merge causes app instability, uninstall Ferrite and use the Overwrite option.",
buttons: [
.init("Merge", role: .destructive) {
Task {
await backupManager.restoreBackup(pluginManager: pluginManager, doOverwrite: false)
}
},
.init("Overwrite", role: .destructive) {
Task {
await backupManager.restoreBackup(pluginManager: pluginManager, doOverwrite: true)
}
}
]
)
.backport.alert(
isPresented: $backupManager.showRestoreCompletedAlert,
title: "Backup restored",
message: backupManager.restoreCompletedMessage.joined(separator: " \n\n"),
buttons: [
.init("OK") {
backupManager.restoreCompletedMessage = []
}
]
)
// Updater alert
.backport.alert(
isPresented: $showUpdateAlert,
title: "Update available",
message:
"Ferrite \(releaseVersionString) can be downloaded. \n\n" +
"This alert can be disabled in Settings.",
buttons: [
.init("Download") {
guard let releaseUrl = URL(string: releaseUrlString) else {
return
}
UIApplication.shared.open(releaseUrl)
},
.init(role: .cancel)
]
)
.overlay {
VStack {
Spacer()
if logManager.showToast {
Group {
switch logManager.toastType {
case .info:
Text(logManager.toastDescription ?? "This shouldn't be showing up... Contact the dev!")
case .warn:
Text("Warn: \(logManager.toastDescription ?? "This shouldn't be showing up... Contact the dev!")")
case .error:
Text("Error: \(logManager.toastDescription ?? "This shouldn't be showing up... Contact the dev!")")
}
}
.padding(12)
.font(.caption)
.background {
VisualEffectBlurView(blurStyle: .systemThinMaterial)
}
.cornerRadius(10)
}
if logManager.showIndeterminateToast {
VStack {
Text(logManager.indeterminateToastDescription ?? "Loading...")
.lineLimit(1)
HStack {
IndeterminateProgressView()
if let cancelAction = logManager.indeterminateCancelAction {
Button("Cancel") {
cancelAction()
logManager.hideIndeterminateToast()
}
}
}
}
.padding(12)
.font(.caption)
.background {
VisualEffectBlurView(blurStyle: .systemThinMaterial)
}
.cornerRadius(10)
.frame(width: 200)
}
Rectangle()
.foregroundColor(.clear)
.frame(height: 60)
}
.animation(.easeInOut(duration: 0.3), value: logManager.showToast || logManager.showIndeterminateToast)
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}