diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj index af531b4..a7f4128 100644 --- a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj +++ b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj @@ -14,7 +14,7 @@ A70171942AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171932AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientTests.swift */; }; A701719E2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701719D2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITests.swift */; }; A70171A02AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701719F2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift */; }; - A70171AD2AA8EF4700064C43 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171AC2AA8EF4700064C43 /* MainViewModel.swift */; }; + A70171AD2AA8EF4700064C43 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171AC2AA8EF4700064C43 /* AppState.swift */; }; A70171AF2AB2116B00064C43 /* NetworkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171AE2AB2116B00064C43 /* NetworkHandler.swift */; }; A70171B12AB211DF00064C43 /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171B02AB211DF00064C43 /* CustomError.swift */; }; A70171B42AB2122900064C43 /* NetworkRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171B32AB2122900064C43 /* NetworkRequests.swift */; }; @@ -85,7 +85,7 @@ A70171992AA8E72000064C43 /* Nextcloud Cookbook iOS ClientUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Nextcloud Cookbook iOS ClientUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; A701719D2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nextcloud_Cookbook_iOS_ClientUITests.swift; sourceTree = ""; }; A701719F2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift; sourceTree = ""; }; - A70171AC2AA8EF4700064C43 /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; + A70171AC2AA8EF4700064C43 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; A70171AE2AB2116B00064C43 /* NetworkHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkHandler.swift; sourceTree = ""; }; A70171B02AB211DF00064C43 /* CustomError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; A70171B32AB2122900064C43 /* NetworkRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRequests.swift; sourceTree = ""; }; @@ -185,6 +185,7 @@ A781E75F2AF8228100452F6F /* RecipeImport */, A9CA6CED2B4C084100F78AB5 /* RecipeExport */, A703226B2ABAF60D00D7C4ED /* Extensions */, + A76B8A702AE002AE00096CEC /* Alerts.swift */, A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */, A7AEAE632AD5521400135378 /* Localizable.xcstrings */, A70171852AA8E71F00064C43 /* Assets.xcassets */, @@ -234,7 +235,7 @@ A70171B72AB2445700064C43 /* ViewModels */ = { isa = PBXGroup; children = ( - A70171AC2AA8EF4700064C43 /* MainViewModel.swift */, + A70171AC2AA8EF4700064C43 /* AppState.swift */, A79AA8E12AFF8C14007D25F2 /* RecipeEditViewModel.swift */, ); path = ViewModels; @@ -244,18 +245,12 @@ isa = PBXGroup; children = ( A70171832AA8E71900064C43 /* MainView.swift */, + A70171CC2AB501B100064C43 /* SettingsView.swift */, A977D0DC2B6002DA009783A9 /* Tabs */, A7FB0D782B25C65200A3469E /* Onboarding */, - A70171BD2AB4987900064C43 /* CategoryDetailView.swift */, - A70171C12AB498C600064C43 /* RecipeCardView.swift */, - A70171BF2AB498A900064C43 /* RecipeDetailView.swift */, - A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */, - A70171CC2AB501B100064C43 /* SettingsView.swift */, - A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */, - A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */, - A76B8A702AE002AE00096CEC /* Alerts.swift */, - A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */, - A9D89AAF2B4FE97800F49D92 /* TimerView.swift */, + A9C3BE502B630E3900562C79 /* Recipes */, + A9C3BE512B630E8300562C79 /* RecipeEditing */, + A9C3BE522B630F1300562C79 /* ReusableViews */, ); path = Views; sourceTree = ""; @@ -335,6 +330,35 @@ path = Tabs; sourceTree = ""; }; + A9C3BE502B630E3900562C79 /* Recipes */ = { + isa = PBXGroup; + children = ( + A70171BD2AB4987900064C43 /* CategoryDetailView.swift */, + A70171C12AB498C600064C43 /* RecipeCardView.swift */, + A70171BF2AB498A900064C43 /* RecipeDetailView.swift */, + A9D89AAF2B4FE97800F49D92 /* TimerView.swift */, + ); + path = Recipes; + sourceTree = ""; + }; + A9C3BE512B630E8300562C79 /* RecipeEditing */ = { + isa = PBXGroup; + children = ( + A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */, + A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */, + A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */, + ); + path = RecipeEditing; + sourceTree = ""; + }; + A9C3BE522B630F1300562C79 /* ReusableViews */ = { + isa = PBXGroup; + children = ( + A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */, + ); + path = ReusableViews; + sourceTree = ""; + }; A9CA6CED2B4C084100F78AB5 /* RecipeExport */ = { isa = PBXGroup; children = ( @@ -528,7 +552,7 @@ A79AA8ED2B063AD5007D25F2 /* NextcloudApi.swift in Sources */, A70171822AA8E71900064C43 /* Nextcloud_Cookbook_iOS_ClientApp.swift in Sources */, A74D33C32AFCD1C300D06555 /* RecipeScraper.swift in Sources */, - A70171AD2AA8EF4700064C43 /* MainViewModel.swift in Sources */, + A70171AD2AA8EF4700064C43 /* AppState.swift in Sources */, A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */, A977D0E02B600318009783A9 /* RecipeTabView.swift in Sources */, A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */, diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate index c2a67e2..b16e423 100644 Binary files a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate and b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Nextcloud Cookbook iOS Client/Views/Alerts.swift b/Nextcloud Cookbook iOS Client/Alerts.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Views/Alerts.swift rename to Nextcloud Cookbook iOS Client/Alerts.swift diff --git a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift index 5498875..f831488 100644 --- a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift +++ b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift @@ -11,7 +11,7 @@ import SwiftUI @main struct Nextcloud_Cookbook_iOS_ClientApp: App { - @StateObject var mainViewModel = MainViewModel() + @StateObject var mainViewModel = AppState() @AppStorage("onboarding") var onboarding = true @AppStorage("language") var language = Locale.current.language.languageCode?.identifier ?? "en" diff --git a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift b/Nextcloud Cookbook iOS Client/ViewModels/AppState.swift similarity index 99% rename from Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift rename to Nextcloud Cookbook iOS Client/ViewModels/AppState.swift index 1c204a7..b1278c9 100644 --- a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift +++ b/Nextcloud Cookbook iOS Client/ViewModels/AppState.swift @@ -10,7 +10,7 @@ import SwiftUI import UIKit -@MainActor class MainViewModel: ObservableObject { +@MainActor class AppState: ObservableObject { @Published var categories: [Category] = [] @Published var recipes: [String: [Recipe]] = [:] @Published var recipeDetails: [Int: RecipeDetail] = [:] @@ -513,7 +513,7 @@ import UIKit -extension MainViewModel { +extension AppState { func loadLocal(path: String) async -> T? { do { return try await dataStore.load(fromPath: path) @@ -612,7 +612,7 @@ extension DateFormatter { // Timer logic -extension MainViewModel { +extension AppState { func createTimer(forRecipe recipeId: String, duration: DurationComponents) -> RecipeTimer { let timer = RecipeTimer(duration: duration) timers[recipeId] = timer diff --git a/Nextcloud Cookbook iOS Client/ViewModels/RecipeEditViewModel.swift b/Nextcloud Cookbook iOS Client/ViewModels/RecipeEditViewModel.swift index b335fcc..a91be4d 100644 --- a/Nextcloud Cookbook iOS Client/ViewModels/RecipeEditViewModel.swift +++ b/Nextcloud Cookbook iOS Client/ViewModels/RecipeEditViewModel.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI @MainActor class RecipeEditViewModel: ObservableObject { - @ObservedObject var mainViewModel: MainViewModel + @ObservedObject var mainViewModel: AppState @Published var recipe: RecipeDetail = RecipeDetail() @Published var prepDuration: DurationComponents = DurationComponents() @@ -29,12 +29,12 @@ import SwiftUI var waitingForUpload: Bool = false - init(mainViewModel: MainViewModel, uploadNew: Bool) { + init(mainViewModel: AppState, uploadNew: Bool) { self.mainViewModel = mainViewModel self.uploadNew = uploadNew } - init(mainViewModel: MainViewModel, recipeDetail: RecipeDetail, uploadNew: Bool) { + init(mainViewModel: AppState, recipeDetail: RecipeDetail, uploadNew: Bool) { self.mainViewModel = mainViewModel self.recipe = recipeDetail self.uploadNew = uploadNew diff --git a/Nextcloud Cookbook iOS Client/Views/MainView.swift b/Nextcloud Cookbook iOS Client/Views/MainView.swift index 5222847..b148126 100644 --- a/Nextcloud Cookbook iOS Client/Views/MainView.swift +++ b/Nextcloud Cookbook iOS Client/Views/MainView.swift @@ -8,19 +8,19 @@ import SwiftUI struct MainView: View { - @StateObject var viewModel = MainViewModel() + @StateObject var viewModel = AppState() @StateObject var groceryList = GroceryList() - - @State var selectedCategory: Category? = nil - @State var showLoadingIndicator: Bool = false + @StateObject var recipeViewModel = RecipeTabView.ViewModel() + @StateObject var searchViewModel = SearchTabView.ViewModel() enum Tab { - case recipes, search, groceryList, settings + case recipes, search, groceryList } var body: some View { TabView { - RecipeTabView(selectedCategory: $selectedCategory, showLoadingIndicator: $showLoadingIndicator) + RecipeTabView() + .environmentObject(recipeViewModel) .environmentObject(viewModel) .environmentObject(groceryList) .tabItem { @@ -29,6 +29,7 @@ struct MainView: View { .tag(Tab.recipes) SearchTabView() + .environmentObject(searchViewModel) .environmentObject(viewModel) .environmentObject(groceryList) .tabItem { @@ -42,16 +43,9 @@ struct MainView: View { Label("Grocery List", systemImage: "storefront") } .tag(Tab.groceryList) - - SettingsView() - .environmentObject(viewModel) - .tabItem { - Label("Settings", systemImage: "gearshape") - } - .tag(Tab.settings) } .task { - showLoadingIndicator = true + recipeViewModel.presentLoadingIndicator = true await viewModel.getCategories() await viewModel.updateAllRecipeDetails() @@ -63,11 +57,11 @@ struct MainView: View { } return false }) { - self.selectedCategory = cat + recipeViewModel.selectedCategory = cat } } - showLoadingIndicator = false await groceryList.load() + recipeViewModel.presentLoadingIndicator = false } } } diff --git a/Nextcloud Cookbook iOS Client/Views/CategoryPickerView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerView.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Views/CategoryPickerView.swift rename to Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerView.swift diff --git a/Nextcloud Cookbook iOS Client/Views/KeywordPickerView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeEditing/KeywordPickerView.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Views/KeywordPickerView.swift rename to Nextcloud Cookbook iOS Client/Views/RecipeEditing/KeywordPickerView.swift diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeEditView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeEditing/RecipeEditView.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Views/RecipeEditView.swift rename to Nextcloud Cookbook iOS Client/Views/RecipeEditing/RecipeEditView.swift diff --git a/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/CategoryDetailView.swift similarity index 97% rename from Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift rename to Nextcloud Cookbook iOS Client/Views/Recipes/CategoryDetailView.swift index 706e762..06607db 100644 --- a/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/CategoryDetailView.swift @@ -13,7 +13,7 @@ import SwiftUI struct CategoryDetailView: View { @State var categoryName: String @State var searchText: String = "" - @ObservedObject var viewModel: MainViewModel + @ObservedObject var viewModel: AppState @Binding var showEditView: Bool var body: some View { diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeCardView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift similarity index 98% rename from Nextcloud Cookbook iOS Client/Views/RecipeCardView.swift rename to Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift index 5f2b44d..abefdd1 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeCardView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI struct RecipeCardView: View { - @State var viewModel: MainViewModel + @State var viewModel: AppState @State var recipe: Recipe @State var recipeThumb: UIImage? @State var isDownloaded: Bool? = nil diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeDetailView.swift similarity index 99% rename from Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift rename to Nextcloud Cookbook iOS Client/Views/Recipes/RecipeDetailView.swift index 361fd0d..8bd3794 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeDetailView.swift @@ -10,7 +10,7 @@ import SwiftUI struct RecipeDetailView: View { - @ObservedObject var viewModel: MainViewModel + @ObservedObject var viewModel: AppState @State var recipe: Recipe @State var recipeDetail: RecipeDetail? @State var recipeImage: UIImage? @@ -214,7 +214,7 @@ fileprivate struct ShareView: View { fileprivate struct RecipeDurationSection: View { - @ObservedObject var viewModel: MainViewModel + @ObservedObject var viewModel: AppState @State var recipeDetail: RecipeDetail var body: some View { diff --git a/Nextcloud Cookbook iOS Client/Views/TimerView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/TimerView.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Views/TimerView.swift rename to Nextcloud Cookbook iOS Client/Views/Recipes/TimerView.swift diff --git a/Nextcloud Cookbook iOS Client/Views/CollapsibleView.swift b/Nextcloud Cookbook iOS Client/Views/ReusableViews/CollapsibleView.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Views/CollapsibleView.swift rename to Nextcloud Cookbook iOS Client/Views/ReusableViews/CollapsibleView.swift diff --git a/Nextcloud Cookbook iOS Client/Views/SettingsView.swift b/Nextcloud Cookbook iOS Client/Views/SettingsView.swift index 902f3d4..5fa365b 100644 --- a/Nextcloud Cookbook iOS Client/Views/SettingsView.swift +++ b/Nextcloud Cookbook iOS Client/Views/SettingsView.swift @@ -11,7 +11,7 @@ import SwiftUI struct SettingsView: View { - @EnvironmentObject var viewModel: MainViewModel + @EnvironmentObject var viewModel: AppState @ObservedObject var userSettings = UserSettings.shared @State fileprivate var alertType: SettingsAlert = .NONE diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift index 3ee7ff7..d9c266f 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift @@ -10,25 +10,18 @@ import SwiftUI struct RecipeTabView: View { - @Binding var selectedCategory: Category? - @Binding var showLoadingIndicator: Bool - - @EnvironmentObject var viewModel: MainViewModel - @StateObject var userSettings: UserSettings = UserSettings.shared - - @State private var showEditView: Bool = false - @State private var serverConnection: Bool = false - + @EnvironmentObject var viewModel: RecipeTabView.ViewModel + @EnvironmentObject var mainViewModel: AppState var body: some View { NavigationSplitView { - List(selection: $selectedCategory) { + List(selection: $viewModel.selectedCategory) { // Categories - ForEach(viewModel.categories) { category in + ForEach(mainViewModel.categories) { category in if category.recipe_count != 0 { NavigationLink(value: category) { HStack(alignment: .center) { - if selectedCategory != nil && category.name == selectedCategory!.name { + if viewModel.selectedCategory != nil && category.name == viewModel.selectedCategory!.name { Image(systemName: "book") } else { Image(systemName: "book.closed.fill") @@ -52,53 +45,59 @@ struct RecipeTabView: View { } .navigationTitle("Cookbooks") .toolbar { - RecipeTabViewToolBar( - viewModel: viewModel, - showEditView: $showEditView, - serverConnection: $serverConnection, - showLoadingIndicator: $showLoadingIndicator - ) + RecipeTabViewToolBar() + } + .navigationDestination(isPresented: $viewModel.presentSettingsView) { + SettingsView() } } detail: { NavigationStack { - if let category = selectedCategory { + if let category = viewModel.selectedCategory { CategoryDetailView( categoryName: category.name, - viewModel: viewModel, - showEditView: $showEditView + viewModel: mainViewModel, + showEditView: $viewModel.presentEditView ) .id(category.id) // Workaround: This is needed to update the detail view when the selection changes } } } .tint(.nextcloudBlue) - .sheet(isPresented: $showEditView) { + .sheet(isPresented: $viewModel.presentEditView) { RecipeEditView( viewModel: RecipeEditViewModel( - mainViewModel: viewModel, + mainViewModel: mainViewModel, uploadNew: true ), - isPresented: $showEditView + isPresented: $viewModel.presentEditView ) } .task { - self.serverConnection = await viewModel.checkServerConnection() + viewModel.serverConnection = await mainViewModel.checkServerConnection() } .refreshable { - self.serverConnection = await viewModel.checkServerConnection() - await viewModel.getCategories() + viewModel.serverConnection = await mainViewModel.checkServerConnection() + await mainViewModel.getCategories() } } + + class ViewModel: ObservableObject { + @Published var presentEditView: Bool = false + @Published var presentSettingsView: Bool = false + + @Published var presentLoadingIndicator: Bool = false + @Published var presentConnectionPopover: Bool = false + @Published var serverConnection: Bool = false + + @Published var selectedCategory: Category? = nil + } } fileprivate struct RecipeTabViewToolBar: ToolbarContent { - @ObservedObject var viewModel: MainViewModel - @Binding var showEditView: Bool - @Binding var serverConnection: Bool - @Binding var showLoadingIndicator: Bool - @State private var presentPopover: Bool = false + @EnvironmentObject var mainViewModel: AppState + @EnvironmentObject var viewModel: RecipeTabView.ViewModel var body: some ToolbarContent { // Top left menu toolbar item @@ -106,20 +105,26 @@ fileprivate struct RecipeTabViewToolBar: ToolbarContent { Menu { Button { Task { - showLoadingIndicator = true + viewModel.presentLoadingIndicator = true UserSettings.shared.lastUpdate = Date.distantPast - await viewModel.getCategories() - for category in viewModel.categories { - await viewModel.getCategory(named: category.name, fetchMode: .preferServer) + await mainViewModel.getCategories() + for category in mainViewModel.categories { + await mainViewModel.getCategory(named: category.name, fetchMode: .preferServer) } - await viewModel.updateAllRecipeDetails() - showLoadingIndicator = false + await mainViewModel.updateAllRecipeDetails() + viewModel.presentLoadingIndicator = false } } label: { Text("Refresh all") Image(systemName: "icloud.and.arrow.down") } + Button { + viewModel.presentSettingsView = true + } label: { + Text("Settings") + Image(systemName: "gearshape") + } } label: { Image(systemName: "ellipsis.circle") } @@ -129,18 +134,18 @@ fileprivate struct RecipeTabViewToolBar: ToolbarContent { ToolbarItem(placement: .topBarTrailing) { Button { print("Check server connection") - presentPopover = true + viewModel.presentConnectionPopover = true } label: { - if showLoadingIndicator { + if viewModel.presentLoadingIndicator { ProgressView() - } else if serverConnection { + } else if viewModel.serverConnection { Image(systemName: "checkmark.icloud") } else { Image(systemName: "xmark.icloud") } - }.popover(isPresented: $presentPopover) { + }.popover(isPresented: $viewModel.presentConnectionPopover) { VStack(alignment: .leading) { - Text(serverConnection ? LocalizedStringKey("Connected to server.") : LocalizedStringKey("Unable to connect to server.")) + Text(viewModel.serverConnection ? LocalizedStringKey("Connected to server.") : LocalizedStringKey("Unable to connect to server.")) .bold() Text("Last updated: \(DateFormatter.utcToString(date: UserSettings.shared.lastUpdate))") @@ -156,7 +161,7 @@ fileprivate struct RecipeTabViewToolBar: ToolbarContent { ToolbarItem(placement: .topBarTrailing) { Button { print("Add new recipe") - showEditView = true + viewModel.presentEditView = true } label: { Image(systemName: "plus.circle.fill") } diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift index 96add22..e5fea0e 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift @@ -10,26 +10,17 @@ import SwiftUI struct SearchTabView: View { - @EnvironmentObject var viewModel: MainViewModel - - var body: some View { - RecipeSearchView(viewModel: viewModel) - } -} - -struct RecipeSearchView: View { - @ObservedObject var viewModel: MainViewModel - @State var searchText: String = "" - @State var allRecipes: [Recipe] = [] + @EnvironmentObject var viewModel: SearchTabView.ViewModel + @EnvironmentObject var mainViewModel: AppState var body: some View { NavigationStack { VStack { ScrollView(showsIndicators: false) { LazyVStack { - ForEach(recipesFiltered(), id: \.recipe_id) { recipe in + ForEach(viewModel.recipesFiltered(), id: \.recipe_id) { recipe in NavigationLink(value: recipe) { - RecipeCardView(viewModel: viewModel, recipe: recipe) + RecipeCardView(viewModel: mainViewModel, recipe: recipe) .shadow(radius: 2) } .buttonStyle(.plain) @@ -37,22 +28,32 @@ struct RecipeSearchView: View { } } .navigationDestination(for: Recipe.self) { recipe in - RecipeDetailView(viewModel: viewModel, recipe: recipe) + RecipeDetailView(viewModel: mainViewModel, recipe: recipe) } - .searchable(text: $searchText, prompt: "Search recipes/keywords") + .searchable(text: $viewModel.searchText, prompt: "Search recipes/keywords") } .navigationTitle("Search recipe") } .task { - allRecipes = await viewModel.getRecipes() + if viewModel.allRecipes.isEmpty { + viewModel.allRecipes = await mainViewModel.getRecipes() + } + } + .refreshable { + viewModel.allRecipes = await mainViewModel.getRecipes() } } - func recipesFiltered() -> [Recipe] { - guard searchText != "" else { return allRecipes } - return allRecipes.filter { recipe in - recipe.name.lowercased().contains(searchText.lowercased()) || // check name for occurence of search term - (recipe.keywords != nil && recipe.keywords!.lowercased().contains(searchText.lowercased())) // check keywords for search term + class ViewModel: ObservableObject { + @Published var allRecipes: [Recipe] = [] + @Published var searchText: String = "" + + func recipesFiltered() -> [Recipe] { + guard searchText != "" else { return allRecipes } + return allRecipes.filter { recipe in + recipe.name.lowercased().contains(searchText.lowercased()) || // check name for occurence of search term + (recipe.keywords != nil && recipe.keywords!.lowercased().contains(searchText.lowercased())) // check keywords for search term + } } } }