Better file caching and update management
This commit is contained in:
@@ -25,7 +25,7 @@ enum AlertButton: LocalizedStringKey, Identifiable {
|
||||
|
||||
|
||||
|
||||
enum RecipeCreationError: UserAlert {
|
||||
enum RecipeAlert: UserAlert {
|
||||
|
||||
case NO_TITLE,
|
||||
DUPLICATE,
|
||||
@@ -84,7 +84,7 @@ enum RecipeCreationError: UserAlert {
|
||||
}
|
||||
|
||||
|
||||
enum RecipeImportError: UserAlert {
|
||||
enum RecipeImportAlert: UserAlert {
|
||||
case BAD_URL,
|
||||
CHECK_CONNECTION,
|
||||
WEBSITE_NOT_SUPPORTED
|
||||
@@ -113,14 +113,12 @@ enum RecipeImportError: UserAlert {
|
||||
|
||||
enum RequestAlert: UserAlert {
|
||||
case REQUEST_DELAYED,
|
||||
REQUEST_DROPPED,
|
||||
REQUEST_SUCCESS
|
||||
REQUEST_DROPPED
|
||||
|
||||
var localizedDescription: LocalizedStringKey {
|
||||
switch self {
|
||||
case .REQUEST_DELAYED: return "Could not establish a connection to the server. The action will be retried upon reconnection."
|
||||
case .REQUEST_DROPPED: return "Unable to complete action."
|
||||
case .REQUEST_SUCCESS: return "Action completed."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +126,6 @@ enum RequestAlert: UserAlert {
|
||||
switch self {
|
||||
case .REQUEST_DELAYED: return "Action delayed"
|
||||
case .REQUEST_DROPPED: return "Error"
|
||||
case .REQUEST_SUCCESS: return "Success"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ struct CategoryDetailView: View {
|
||||
ForEach(recipesFiltered(), id: \.recipe_id) { recipe in
|
||||
NavigationLink(value: recipe) {
|
||||
RecipeCardView(viewModel: viewModel, recipe: recipe)
|
||||
.shadow(radius: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
@@ -30,41 +31,29 @@ struct CategoryDetailView: View {
|
||||
.navigationDestination(for: Recipe.self) { recipe in
|
||||
RecipeDetailView(viewModel: viewModel, recipe: recipe)
|
||||
}
|
||||
.navigationTitle(categoryName == "*" ? "Other" : categoryName)
|
||||
.navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
|
||||
Menu {
|
||||
Button {
|
||||
print("Add new recipe")
|
||||
showEditView = true
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Add new recipe")
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
print("Downloading all recipes in category \(categoryName) ...")
|
||||
downloadRecipes()
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Download recipes")
|
||||
Image(systemName: "icloud.and.arrow.down")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
print("Add new recipe")
|
||||
showEditView = true
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchText, prompt: "Search recipes")
|
||||
.task {
|
||||
await viewModel.getCategory(named: categoryName, fetchMode: .preferLocal)
|
||||
await viewModel.getCategory(
|
||||
named: categoryName,
|
||||
fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer
|
||||
)
|
||||
}
|
||||
.refreshable {
|
||||
await viewModel.getCategory(named: categoryName, fetchMode: .preferServer)
|
||||
await viewModel.getCategory(
|
||||
named: categoryName,
|
||||
fetchMode: UserSettings.shared.storeRecipes ? .preferServer : .onlyServer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,26 +64,4 @@ struct CategoryDetailView: View {
|
||||
recipe.name.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func downloadRecipes() {
|
||||
if let recipes = viewModel.recipes[categoryName] {
|
||||
Task {
|
||||
for recipe in recipes {
|
||||
let recipeDetail = await viewModel.getRecipe(id: recipe.recipe_id, fetchMode: .onlyServer)
|
||||
await viewModel.saveLocal(recipeDetail, path: "recipe\(recipe.recipe_id).data")
|
||||
|
||||
let thumbnail = await viewModel.getImage(id: recipe.recipe_id, size: .THUMB, fetchMode: .onlyServer)
|
||||
guard let thumbnail = thumbnail else { continue }
|
||||
guard let thumbnailData = thumbnail.pngData() else { continue }
|
||||
await viewModel.saveLocal(thumbnailData.base64EncodedString(), path: "image\(recipe.recipe_id)_thumb")
|
||||
|
||||
let image = await viewModel.getImage(id: recipe.recipe_id, size: .FULL, fetchMode: .onlyServer)
|
||||
guard let image = image else { continue }
|
||||
guard let imageData = image.pngData() else { continue }
|
||||
await viewModel.saveLocal(imageData.base64EncodedString(), path: "image\(recipe.recipe_id)_full")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import SwiftUI
|
||||
|
||||
struct KeywordPickerView: View {
|
||||
@State var title: String
|
||||
@State var searchSuggestions: [String]
|
||||
@State var searchSuggestions: [RecipeKeyword]
|
||||
@Binding var selection: [String]
|
||||
@State var searchText: String = ""
|
||||
|
||||
@@ -35,17 +35,18 @@ struct KeywordPickerView: View {
|
||||
s == keyword ? true : false
|
||||
})
|
||||
searchSuggestions.removeAll(where: { s in
|
||||
s == keyword ? true : false
|
||||
s.name == keyword ? true : false
|
||||
})
|
||||
} else {
|
||||
selection.append(keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
ForEach(suggestionsFiltered(), id: \.self) { suggestion in
|
||||
ForEach(suggestionsFiltered(), id: \.name) { suggestion in
|
||||
KeywordItemView(
|
||||
keyword: suggestion,
|
||||
isSelected: selection.contains(suggestion)
|
||||
keyword: suggestion.name,
|
||||
count: suggestion.recipe_count,
|
||||
isSelected: selection.contains(suggestion.name)
|
||||
) { keyword in
|
||||
if selection.contains(keyword) {
|
||||
selection.removeAll(where: { s in
|
||||
@@ -84,14 +85,17 @@ struct KeywordPickerView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle(title)
|
||||
.padding(5)
|
||||
|
||||
}
|
||||
|
||||
func suggestionsFiltered() -> [String] {
|
||||
func suggestionsFiltered() -> [RecipeKeyword] {
|
||||
guard searchText != "" else { return searchSuggestions }
|
||||
return searchSuggestions.filter { suggestion in
|
||||
suggestion.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
suggestion.name.lowercased().contains(searchText.lowercased())
|
||||
}.sorted(by: { a, b in
|
||||
a.recipe_count > b.recipe_count
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +103,7 @@ struct KeywordPickerView: View {
|
||||
|
||||
struct KeywordItemView: View {
|
||||
var keyword: String
|
||||
var count: Int? = nil
|
||||
var isSelected: Bool
|
||||
var tapped: (String) -> ()
|
||||
|
||||
@@ -110,6 +115,9 @@ struct KeywordItemView: View {
|
||||
Text(keyword)
|
||||
.lineLimit(2)
|
||||
Spacer()
|
||||
if let count = count {
|
||||
Text("(\(count))")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
|
||||
@@ -10,13 +10,14 @@ import SwiftUI
|
||||
|
||||
struct MainView: View {
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@StateObject var userSettings: UserSettings = UserSettings()
|
||||
@StateObject var userSettings: UserSettings = UserSettings.shared
|
||||
|
||||
@State private var selectedCategory: Category? = nil
|
||||
@State private var showEditView: Bool = false
|
||||
@State private var showSearchView: Bool = false
|
||||
@State private var showSettingsView: Bool = false
|
||||
@State private var serverConnection: Bool = false
|
||||
@State private var showLoadingIndicator: Bool = false
|
||||
|
||||
|
||||
var columns: [GridItem] = [GridItem(.adaptive(minimum: 150), spacing: 0)]
|
||||
@@ -43,7 +44,7 @@ struct MainView: View {
|
||||
NavigationLink(value: category) {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: "book.closed.fill")
|
||||
Text(category.name == "*" ? "Other" : category.name)
|
||||
Text(category.name == "*" ? String(localized: "Other") : category.name)
|
||||
.font(.system(size: 20, weight: .light, design: .serif))
|
||||
.italic()
|
||||
}.padding(7)
|
||||
@@ -53,7 +54,7 @@ struct MainView: View {
|
||||
}
|
||||
.navigationTitle("Cookbooks")
|
||||
.navigationDestination(isPresented: $showSettingsView) {
|
||||
SettingsView(userSettings: userSettings, viewModel: viewModel)
|
||||
SettingsView(viewModel: viewModel)
|
||||
}
|
||||
.navigationDestination(isPresented: $showSearchView) {
|
||||
RecipeSearchView(viewModel: viewModel)
|
||||
@@ -63,7 +64,8 @@ struct MainView: View {
|
||||
viewModel: viewModel,
|
||||
showEditView: $showEditView,
|
||||
showSettingsView: $showSettingsView,
|
||||
serverConnection: $serverConnection
|
||||
serverConnection: $serverConnection,
|
||||
showLoadingIndicator: $showLoadingIndicator
|
||||
)
|
||||
}
|
||||
} detail: {
|
||||
@@ -80,17 +82,24 @@ struct MainView: View {
|
||||
}
|
||||
.tint(.nextcloudBlue)
|
||||
.sheet(isPresented: $showEditView) {
|
||||
RecipeEditView(viewModel:
|
||||
RecipeEditViewModel(
|
||||
mainViewModel: viewModel,
|
||||
isPresented: $showEditView,
|
||||
uploadNew: true
|
||||
)
|
||||
RecipeEditView(
|
||||
viewModel:
|
||||
RecipeEditViewModel(
|
||||
mainViewModel: viewModel,
|
||||
uploadNew: true
|
||||
),
|
||||
isPresented: $showEditView
|
||||
)
|
||||
}
|
||||
.task {
|
||||
showLoadingIndicator = true
|
||||
self.serverConnection = await viewModel.checkServerConnection()
|
||||
await viewModel.getCategories()//viewModel.loadCategoryList()
|
||||
await viewModel.getCategories()
|
||||
for category in viewModel.categories {
|
||||
await viewModel.getCategory(named: category.name, fetchMode: .preferLocal)
|
||||
}
|
||||
await viewModel.updateAllRecipeDetails()
|
||||
|
||||
// Open detail view for default category
|
||||
if userSettings.defaultCategory != "" {
|
||||
if let cat = viewModel.categories.first(where: { c in
|
||||
@@ -102,10 +111,11 @@ struct MainView: View {
|
||||
self.selectedCategory = cat
|
||||
}
|
||||
}
|
||||
showLoadingIndicator = false
|
||||
}
|
||||
.refreshable {
|
||||
self.serverConnection = await viewModel.checkServerConnection()
|
||||
await viewModel.getCategories()//loadCategoryList(needsUpdate: true)
|
||||
await viewModel.getCategories()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,30 +129,33 @@ struct MainView: View {
|
||||
@Binding var showEditView: Bool
|
||||
@Binding var showSettingsView: Bool
|
||||
@Binding var serverConnection: Bool
|
||||
@Binding var showLoadingIndicator: Bool
|
||||
@State private var presentPopover: Bool = false
|
||||
|
||||
var body: some ToolbarContent {
|
||||
// Top left menu toolbar item
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Menu {
|
||||
Button {
|
||||
print("Downloading all recipes ...")
|
||||
Task {
|
||||
await viewModel.downloadAllRecipes()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Download all recipes")
|
||||
Image(systemName: "icloud.and.arrow.down")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
self.showSettingsView = true
|
||||
} label: {
|
||||
Text("Settings")
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
Button {
|
||||
Task {
|
||||
showLoadingIndicator = true
|
||||
await viewModel.getCategories()
|
||||
for category in viewModel.categories {
|
||||
await viewModel.getCategory(named: category.name, fetchMode: .preferServer)
|
||||
}
|
||||
await viewModel.updateAllRecipeDetails()
|
||||
showLoadingIndicator = false
|
||||
}
|
||||
} label: {
|
||||
Text("Refresh all")
|
||||
Image(systemName: "icloud.and.arrow.down")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
@@ -154,16 +167,24 @@ struct MainView: View {
|
||||
print("Check server connection")
|
||||
presentPopover = true
|
||||
} label: {
|
||||
if serverConnection {
|
||||
if showLoadingIndicator {
|
||||
ProgressView()
|
||||
} else if serverConnection {
|
||||
Image(systemName: "checkmark.icloud")
|
||||
} else {
|
||||
Image(systemName: "xmark.icloud")
|
||||
}
|
||||
}.popover(isPresented: $presentPopover) {
|
||||
Text(serverConnection ? LocalizedStringKey("Connected to server.") : LocalizedStringKey("Unable to connect to server."))
|
||||
.bold()
|
||||
.padding()
|
||||
.presentationCompactAdaptation(.popover)
|
||||
VStack(alignment: .leading) {
|
||||
Text(serverConnection ? LocalizedStringKey("Connected to server.") : LocalizedStringKey("Unable to connect to server."))
|
||||
.bold()
|
||||
|
||||
Text("Last updated: \(DateFormatter.utcToString(date: UserSettings.shared.lastUpdate))")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.secondary)
|
||||
}
|
||||
.padding()
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +216,7 @@ struct RecipeSearchView: View {
|
||||
ForEach(recipesFiltered(), id: \.recipe_id) { recipe in
|
||||
NavigationLink(value: recipe) {
|
||||
RecipeCardView(viewModel: viewModel, recipe: recipe)
|
||||
.shadow(radius: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
@@ -208,7 +230,7 @@ struct RecipeSearchView: View {
|
||||
.navigationTitle("Search recipe")
|
||||
}
|
||||
.task {
|
||||
allRecipes = await viewModel.getRecipes()//.getAllRecipes()
|
||||
allRecipes = await viewModel.getRecipes()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,11 +51,22 @@ struct RecipeCardView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 17))
|
||||
.padding(.horizontal)
|
||||
.task {
|
||||
recipeThumb = await viewModel.getImage(id: recipe.recipe_id, size: .THUMB, fetchMode: .preferLocal)
|
||||
self.isDownloaded = viewModel.recipeDetailExists(recipeId: recipe.recipe_id)
|
||||
recipeThumb = await viewModel.getImage(
|
||||
id: recipe.recipe_id,
|
||||
size: .THUMB,
|
||||
fetchMode: UserSettings.shared.storeThumb ? .preferLocal : .onlyServer
|
||||
)
|
||||
if recipe.storedLocally == nil {
|
||||
recipe.storedLocally = viewModel.recipeDetailExists(recipeId: recipe.recipe_id)
|
||||
}
|
||||
isDownloaded = recipe.storedLocally
|
||||
}
|
||||
.refreshable {
|
||||
recipeThumb = await viewModel.getImage(id: recipe.recipe_id, size: .THUMB, fetchMode: .preferServer)
|
||||
recipeThumb = await viewModel.getImage(
|
||||
id: recipe.recipe_id,
|
||||
size: .THUMB,
|
||||
fetchMode: UserSettings.shared.storeThumb ? .preferServer : .onlyServer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,14 @@ struct RecipeDetailView: View {
|
||||
RecipeNutritionSection(recipeDetail: recipeDetail, presentNutritionPopover: $presentNutritionPopover)
|
||||
RecipeKeywordSection(recipeDetail: recipeDetail, presentKeywordPopover: $presentKeywordPopover)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
Text("Created: \(Date.convertISOStringToLocalString(isoDateString: recipeDetail.dateCreated) ?? "")")
|
||||
Text("Last modified: \(Date.convertISOStringToLocalString(isoDateString: recipeDetail.dateModified) ?? "")")
|
||||
}
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.secondary)
|
||||
.padding()
|
||||
}.padding(.horizontal, 5)
|
||||
|
||||
}
|
||||
@@ -95,24 +103,42 @@ struct RecipeDetailView: View {
|
||||
}
|
||||
.sheet(isPresented: $presentEditView) {
|
||||
if let recipeDetail = recipeDetail {
|
||||
RecipeEditView(viewModel:
|
||||
RecipeEditViewModel(
|
||||
mainViewModel: viewModel,
|
||||
recipeDetail: recipeDetail,
|
||||
isPresented: $presentEditView,
|
||||
uploadNew: false
|
||||
)
|
||||
RecipeEditView(
|
||||
viewModel:
|
||||
RecipeEditViewModel(
|
||||
mainViewModel: viewModel,
|
||||
recipeDetail: recipeDetail,
|
||||
uploadNew: false
|
||||
),
|
||||
isPresented: $presentEditView
|
||||
)
|
||||
}
|
||||
}
|
||||
.task {
|
||||
recipeDetail = await viewModel.getRecipe(id: recipe.recipe_id, fetchMode: .preferLocal)//loadRecipeDetail(recipeId: recipe.recipe_id)
|
||||
recipeImage = await viewModel.getImage(id: recipe.recipe_id, size: .FULL, fetchMode: .preferLocal)//.loadImage(recipeId: recipe.recipe_id, thumb: false)
|
||||
self.isDownloaded = viewModel.recipeDetailExists(recipeId: recipe.recipe_id)
|
||||
recipeDetail = await viewModel.getRecipe(
|
||||
id: recipe.recipe_id,
|
||||
fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer
|
||||
)
|
||||
recipeImage = await viewModel.getImage(
|
||||
id: recipe.recipe_id,
|
||||
size: .FULL,
|
||||
fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer
|
||||
)
|
||||
if recipe.storedLocally == nil {
|
||||
recipe.storedLocally = viewModel.recipeDetailExists(recipeId: recipe.recipe_id)
|
||||
}
|
||||
self.isDownloaded = recipe.storedLocally
|
||||
}
|
||||
.refreshable {
|
||||
recipeDetail = await viewModel.getRecipe(id: recipe.recipe_id, fetchMode: .preferServer)
|
||||
recipeImage = await viewModel.getImage(id: recipe.recipe_id, size: .FULL, fetchMode: .preferServer)
|
||||
recipeDetail = await viewModel.getRecipe(
|
||||
id: recipe.recipe_id,
|
||||
fetchMode: UserSettings.shared.storeRecipes ? .preferServer : .onlyServer
|
||||
)
|
||||
recipeImage = await viewModel.getImage(
|
||||
id: recipe.recipe_id,
|
||||
size: .FULL,
|
||||
fetchMode: UserSettings.shared.storeImages ? .preferServer : .onlyServer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,19 @@ import PhotosUI
|
||||
|
||||
|
||||
struct RecipeEditView: View {
|
||||
@ObservedObject var viewModel: RecipeEditViewModel
|
||||
@ObservedObject var viewModel: RecipeEditViewModel
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
@State var presentAlert = false
|
||||
@State var alertType: UserAlert = RecipeAlert.GENERIC
|
||||
@State var alertAction: @MainActor () async -> () = { }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
HStack {
|
||||
Button() {
|
||||
viewModel.isPresented.wrappedValue = false
|
||||
isPresented = false
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
.bold()
|
||||
@@ -28,9 +33,17 @@ struct RecipeEditView: View {
|
||||
Menu {
|
||||
Button {
|
||||
print("Delete recipe.")
|
||||
viewModel.alertType = RecipeCreationError.CONFIRM_DELETE
|
||||
viewModel.alertAction = viewModel.deleteRecipe
|
||||
viewModel.presentAlert = true
|
||||
alertType = RecipeAlert.CONFIRM_DELETE
|
||||
alertAction = {
|
||||
if let res = await viewModel.deleteRecipe() {
|
||||
alertType = res
|
||||
alertAction = { }
|
||||
presentAlert = true
|
||||
} else {
|
||||
self.dismissEditView()
|
||||
}
|
||||
}
|
||||
presentAlert = true
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
.foregroundStyle(.red)
|
||||
@@ -47,9 +60,19 @@ struct RecipeEditView: View {
|
||||
Button() {
|
||||
Task {
|
||||
if viewModel.uploadNew {
|
||||
await viewModel.uploadNewRecipe()
|
||||
if let res = await viewModel.uploadNewRecipe() {
|
||||
alertType = res
|
||||
presentAlert = true
|
||||
} else {
|
||||
dismissEditView()
|
||||
}
|
||||
} else {
|
||||
await viewModel.uploadEditedRecipe()
|
||||
if let res = await viewModel.uploadEditedRecipe() {
|
||||
alertType = res
|
||||
presentAlert = true
|
||||
} else {
|
||||
dismissEditView()
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
@@ -58,7 +81,7 @@ struct RecipeEditView: View {
|
||||
}
|
||||
}.padding()
|
||||
HStack {
|
||||
Text(viewModel.recipe.name == "" ? LocalizedStringKey("New recipe") : LocalizedStringKey(viewModel.recipe.name))
|
||||
Text(viewModel.recipe.name == "" ? String(localized: "New recipe") : viewModel.recipe.name)
|
||||
.font(.title)
|
||||
.bold()
|
||||
.padding()
|
||||
@@ -69,7 +92,16 @@ struct RecipeEditView: View {
|
||||
Section {
|
||||
TextField(LocalizedStringKey("URL (e.g. example.com/recipe)"), text: $viewModel.importURL)
|
||||
Button {
|
||||
viewModel.importRecipe()
|
||||
Task {
|
||||
if let res = await viewModel.importRecipe() {
|
||||
alertType = RecipeAlert.CUSTOM(
|
||||
title: res.localizedTitle,
|
||||
description: res.localizedDescription
|
||||
)
|
||||
alertAction = { }
|
||||
presentAlert = true
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text(LocalizedStringKey("Import"))
|
||||
}
|
||||
@@ -148,12 +180,12 @@ struct RecipeEditView: View {
|
||||
.onAppear {
|
||||
viewModel.prepareView()
|
||||
}
|
||||
.alert(viewModel.alertType.localizedTitle, isPresented: $viewModel.presentAlert) {
|
||||
ForEach(viewModel.alertType.alertButtons) { buttonType in
|
||||
.alert(alertType.localizedTitle, isPresented: $presentAlert) {
|
||||
ForEach(alertType.alertButtons) { buttonType in
|
||||
if buttonType == .OK {
|
||||
Button(AlertButton.OK.rawValue, role: .cancel) {
|
||||
Task {
|
||||
await viewModel.alertAction()
|
||||
await alertAction()
|
||||
}
|
||||
}
|
||||
} else if buttonType == .CANCEL {
|
||||
@@ -161,15 +193,24 @@ struct RecipeEditView: View {
|
||||
} else if buttonType == .DELETE {
|
||||
Button(AlertButton.DELETE.rawValue, role: .destructive) {
|
||||
Task {
|
||||
await viewModel.alertAction()
|
||||
await alertAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text(viewModel.alertType.localizedDescription)
|
||||
Text(alertType.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func dismissEditView() {
|
||||
Task {
|
||||
await viewModel.mainViewModel.getCategories()
|
||||
await viewModel.mainViewModel.getCategory(named: viewModel.recipe.recipeCategory, fetchMode: .preferServer)
|
||||
await viewModel.mainViewModel.updateRecipeDetails(in: viewModel.recipe.recipeCategory)
|
||||
}
|
||||
self.isPresented = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import SwiftUI
|
||||
|
||||
|
||||
struct SettingsView: View {
|
||||
@ObservedObject var userSettings: UserSettings
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@ObservedObject var userSettings = UserSettings.shared
|
||||
|
||||
@State fileprivate var alertType: SettingsAlert = .NONE
|
||||
@State var showAlert: Bool = false
|
||||
@@ -20,9 +20,6 @@ struct SettingsView: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
/*Toggle(isOn: $userSettings.downloadRecipes) {
|
||||
Text("Always download new recipes")
|
||||
}*/
|
||||
Picker("Select a default cookbook", selection: $userSettings.defaultCategory) {
|
||||
Text("None").tag("None")
|
||||
ForEach(viewModel.categories, id: \.name) { category in
|
||||
@@ -35,6 +32,22 @@ struct SettingsView: View {
|
||||
Text("The selected cookbook will open on app launch by default.")
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle(isOn: $userSettings.storeRecipes) {
|
||||
Text("Offline recipes")
|
||||
}
|
||||
Toggle(isOn: $userSettings.storeImages) {
|
||||
Text("Store recipe images locally")
|
||||
}
|
||||
Toggle(isOn: $userSettings.storeThumb) {
|
||||
Text("Store recipe thumbnails locally")
|
||||
}
|
||||
} header: {
|
||||
Text("Downloads")
|
||||
} footer: {
|
||||
Text("Configure what is stored on your device.")
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("Language", selection: $userSettings.language) {
|
||||
ForEach(SupportedLanguage.allValues, id: \.self) { lang in
|
||||
@@ -102,6 +115,7 @@ struct SettingsView: View {
|
||||
userSettings.serverAddress = ""
|
||||
userSettings.username = ""
|
||||
userSettings.token = ""
|
||||
userSettings.authString = ""
|
||||
viewModel.deleteAllData()
|
||||
userSettings.onboarding = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user