From 4350cad1e055b0b6d963469cb5e284c6c0736589 Mon Sep 17 00:00:00 2001 From: Vicnet <35202538+VincentMeilinger@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:39:08 +0200 Subject: [PATCH] Download function for all recipes or recipes in a certain category --- .../Data/DataStore.swift | 19 ++++++++--- .../ViewModels/MainViewModel.swift | 26 ++++++++++++++- .../Views/CategoryCardView.swift | 2 +- .../Views/CategoryDetailView.swift | 33 ++++++++++++++++++- .../Views/MainView.swift | 20 +++++++++-- .../Views/RecipeCardView.swift | 8 +++++ 6 files changed, 99 insertions(+), 9 deletions(-) diff --git a/Nextcloud Cookbook iOS Client/Data/DataStore.swift b/Nextcloud Cookbook iOS Client/Data/DataStore.swift index 42b9380..39b721b 100644 --- a/Nextcloud Cookbook iOS Client/Data/DataStore.swift +++ b/Nextcloud Cookbook iOS Client/Data/DataStore.swift @@ -9,6 +9,9 @@ import Foundation import SwiftUI class DataStore { + let fileManager = FileManager.default + + private static func fileURL(appending: String) throws -> URL { try FileManager.default.url( for: .documentDirectory, @@ -53,16 +56,24 @@ class DataStore { } } + func recipeDetailExists(recipeId: Int) -> Bool { + let filePath = "recipe\(recipeId).data" + guard let folderPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first?.path() else { return false } + let exists = fileManager.fileExists(atPath: folderPath + filePath) + print("Path: ", folderPath + filePath) + print("Recipe detail with id \(recipeId)", exists ? "exists" : "does not exist") + return exists + } + func clearAll() -> Bool { print("Attempting to delete all data ...") - let fm = FileManager.default - guard let folderPath = fm.urls(for: .documentDirectory, in: .userDomainMask).first?.path() else { return false } + guard let folderPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first?.path() else { return false } print("Folder path: ", folderPath) do { - let filePaths = try fm.contentsOfDirectory(atPath: folderPath) + let filePaths = try fileManager.contentsOfDirectory(atPath: folderPath) for filePath in filePaths { print("File path: ", filePath) - try fm.removeItem(atPath: folderPath + filePath) + try fileManager.removeItem(atPath: folderPath + filePath) } } catch { print("Could not delete documents folder contents: \(error)") diff --git a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift index c57cedc..2f218ee 100644 --- a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift +++ b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift @@ -69,6 +69,30 @@ import UIKit return RecipeDetail.error() } + func downloadAllRecipes() async { + for category in categories { + await loadRecipeList(categoryName: category.name, needsUpdate: true) + guard let recipeList = recipes[category.name] else { continue } + for recipe in recipeList { + let _ = await loadRecipeDetail(recipeId: recipe.recipe_id, needsUpdate: true) + let _ = await loadImage(recipeId: recipe.recipe_id, full: false) + } + } + } + + /// Check if recipeDetail is stored locally, either in cache or on disk + /// - Parameters + /// - recipeId: The id of a recipe. + /// - Returns: True if the recipeDetail is stored, otherwise false + func recipeDetailExists(recipeId: Int) -> Bool { + if recipeDetails[recipeId] != nil { + return true + } else if (dataStore.recipeDetailExists(recipeId: recipeId)) { + return true + } + return false + } + /// Try to load the recipe image from cache. If not found, try to load from store or the server. /// - Parameters @@ -156,7 +180,7 @@ extension MainViewModel { let (data, error): (D?, Error?) = await networkController.sendDataRequest(request) print(error as Any) if let data = data { - try await dataStore.save(data: data, toPath: localPath) + await dataStore.save(data: data, toPath: localPath) } return data } diff --git a/Nextcloud Cookbook iOS Client/Views/CategoryCardView.swift b/Nextcloud Cookbook iOS Client/Views/CategoryCardView.swift index 08e3782..14ba63b 100644 --- a/Nextcloud Cookbook iOS Client/Views/CategoryCardView.swift +++ b/Nextcloud Cookbook iOS Client/Views/CategoryCardView.swift @@ -26,7 +26,7 @@ struct CategoryCardView: View { Text(category.name) .font(.headline) ) - .frame(maxHeight: 30) + .frame(maxHeight: 25) } ) .clipShape(RoundedRectangle(cornerRadius: 10)) diff --git a/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift b/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift index 8ae2be7..bef7e70 100644 --- a/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift +++ b/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift @@ -13,13 +13,14 @@ import SwiftUI struct RecipeBookView: View { @State var categoryName: String @ObservedObject var viewModel: MainViewModel + var body: some View { ScrollView(showsIndicators: false) { LazyVStack { if let recipes = viewModel.recipes[categoryName] { ForEach(recipes, id: \.recipe_id) { recipe in NavigationLink(destination: RecipeDetailView(viewModel: viewModel, recipe: recipe)) { - RecipeCardView(viewModel: viewModel, recipe: recipe) + RecipeCardView(viewModel: viewModel, recipe: recipe, isDownloaded: viewModel.recipeDetailExists(recipeId: recipe.recipe_id)) } .buttonStyle(.plain) } @@ -27,6 +28,22 @@ struct RecipeBookView: View { } } .navigationTitle(categoryName) + .toolbar { + Menu { + Button { + print("Downloading all recipes in category \(categoryName) ...") + downloadRecipes() + } label: { + HStack { + Text("Download recipes") + Image(systemName: "icloud.and.arrow.down") + } + } + + } label: { + Image(systemName: "ellipsis.circle") + } + } .task { await viewModel.loadRecipeList(categoryName: categoryName) } @@ -34,4 +51,18 @@ struct RecipeBookView: View { await viewModel.loadRecipeList(categoryName: categoryName, needsUpdate: true) } } + + func downloadRecipes() { + if let recipes = viewModel.recipes[categoryName] { + let dispatchQueue = DispatchQueue(label: "RecipeDownload", qos: .background) + dispatchQueue.async { + for recipe in recipes { + Task { + let _ = await viewModel.loadRecipeDetail(recipeId: recipe.recipe_id) + let _ = await viewModel.loadImage(recipeId: recipe.recipe_id, full: true) + } + } + } + } + } } diff --git a/Nextcloud Cookbook iOS Client/Views/MainView.swift b/Nextcloud Cookbook iOS Client/Views/MainView.swift index 36592c5..db58207 100644 --- a/Nextcloud Cookbook iOS Client/Views/MainView.swift +++ b/Nextcloud Cookbook iOS Client/Views/MainView.swift @@ -12,7 +12,7 @@ struct MainView: View { @StateObject var userSettings: UserSettings var columns: [GridItem] = [GridItem(.adaptive(minimum: 150), spacing: 0)] var body: some View { - NavigationStack { + NavigationView { ScrollView(.vertical, showsIndicators: false) { LazyVGrid(columns: columns, spacing: 0) { ForEach(viewModel.categories, id: \.name) { category in @@ -30,8 +30,24 @@ struct MainView: View { } .navigationTitle("CookBook") .toolbar { + Menu { + Button { + print("Downloading all recipes ...") + Task { + await viewModel.downloadAllRecipes() + } + } label: { + HStack { + Text("Download all recipes") + Image(systemName: "icloud.and.arrow.down") + } + } + + } label: { + Image(systemName: "ellipsis.circle") + } NavigationLink( destination: SettingsView(userSettings: userSettings, viewModel: viewModel)) { - Image(systemName: "gear") + Image(systemName: "gearshape") } } } diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeCardView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeCardView.swift index 8cd3790..083aba1 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeCardView.swift +++ b/Nextcloud Cookbook iOS Client/Views/RecipeCardView.swift @@ -12,6 +12,8 @@ struct RecipeCardView: View { @State var viewModel: MainViewModel @State var recipe: Recipe @State var recipeThumb: UIImage? + @State var isDownloaded: Bool + var body: some View { HStack { Image(uiImage: recipeThumb ?? UIImage(named: "CookBook")!) @@ -23,6 +25,12 @@ struct RecipeCardView: View { .font(.headline) Spacer() + VStack { + Image(systemName: isDownloaded ? "checkmark.icloud" : "icloud.and.arrow.down") + .foregroundColor(.secondary) + .padding() + Spacer() + } } .background(.ultraThickMaterial) .clipShape(RoundedRectangle(cornerRadius: 10))