diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd6e1ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/hendrik.hogertz.xcuserdatad/UserInterfaceState.xcuserstate +Nextcloud Cookbook iOS Client.xcodeproj/xcuserdata/hendrik.hogertz.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj index f6ccd62..e993896 100644 --- a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj +++ b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ C1F0AB022D0B000100000001 /* ImportURLSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F0AB012D0B000100000001 /* ImportURLSheet.swift */; }; A9E78A2B2BE7799F00206866 /* JsonAny.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E78A2A2BE7799F00206866 /* JsonAny.swift */; }; A9FA2AB62B5079B200A43702 /* alarm_sound_0.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A9FA2AB52B5079B200A43702 /* alarm_sound_0.mp3 */; }; + A1B2C3D52F0A000100000001 /* AppearanceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42F0A000100000001 /* AppearanceMode.swift */; }; D1A0CE012D0A000100000001 /* GroceryListMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0CE002D0A000100000001 /* GroceryListMode.swift */; }; D1A0CE032D0A000200000002 /* RemindersGroceryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0CE022D0A000200000002 /* RemindersGroceryStore.swift */; }; D1A0CE052D0A000300000003 /* GroceryListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0CE042D0A000300000003 /* GroceryListManager.swift */; }; @@ -165,6 +166,7 @@ A9DA25D42B82096B0061FC2B /* Nextcloud-Cookbook-iOS-Client-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Nextcloud-Cookbook-iOS-Client-Info.plist"; sourceTree = SOURCE_ROOT; }; A9E78A2A2BE7799F00206866 /* JsonAny.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonAny.swift; sourceTree = ""; }; A9FA2AB52B5079B200A43702 /* alarm_sound_0.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = alarm_sound_0.mp3; sourceTree = ""; }; + A1B2C3D42F0A000100000001 /* AppearanceMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceMode.swift; sourceTree = ""; }; D1A0CE002D0A000100000001 /* GroceryListMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroceryListMode.swift; sourceTree = ""; }; D1A0CE022D0A000200000002 /* RemindersGroceryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindersGroceryStore.swift; sourceTree = ""; }; D1A0CE042D0A000300000003 /* GroceryListManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroceryListManager.swift; sourceTree = ""; }; @@ -312,6 +314,7 @@ A70171C52AB4C43A00064C43 /* DataModels.swift */, A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */, A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */, + A1B2C3D42F0A000100000001 /* AppearanceMode.swift */, D1A0CE002D0A000100000001 /* GroceryListMode.swift */, D1A0CE022D0A000200000002 /* RemindersGroceryStore.swift */, D1A0CE042D0A000300000003 /* GroceryListManager.swift */, @@ -656,6 +659,7 @@ A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */, A977D0E02B600318009783A9 /* RecipeTabView.swift in Sources */, A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */, + A1B2C3D52F0A000100000001 /* AppearanceMode.swift in Sources */, D1A0CE012D0A000100000001 /* GroceryListMode.swift in Sources */, D1A0CE032D0A000200000002 /* RemindersGroceryStore.swift in Sources */, D1A0CE052D0A000300000003 /* GroceryListManager.swift in Sources */, diff --git a/Nextcloud Cookbook iOS Client/Data/AppearanceMode.swift b/Nextcloud Cookbook iOS Client/Data/AppearanceMode.swift new file mode 100644 index 0000000..fceb0aa --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Data/AppearanceMode.swift @@ -0,0 +1,22 @@ +// +// AppearanceMode.swift +// Nextcloud Cookbook iOS Client +// + +import Foundation + +enum AppearanceMode: String, CaseIterable { + case system = "system" + case light = "light" + case dark = "dark" + + func descriptor() -> String { + switch self { + case .system: return String(localized: "System") + case .light: return String(localized: "Light") + case .dark: return String(localized: "Dark") + } + } + + static let allValues: [AppearanceMode] = AppearanceMode.allCases +} diff --git a/Nextcloud Cookbook iOS Client/Data/UserSettings.swift b/Nextcloud Cookbook iOS Client/Data/UserSettings.swift index b978aa0..657d2a2 100644 --- a/Nextcloud Cookbook iOS Client/Data/UserSettings.swift +++ b/Nextcloud Cookbook iOS Client/Data/UserSettings.swift @@ -144,7 +144,13 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(mealPlanSyncEnabled, forKey: "mealPlanSyncEnabled") } } - + + @Published var appearanceMode: String { + didSet { + UserDefaults.standard.set(appearanceMode, forKey: "appearanceMode") + } + } + init() { self.username = UserDefaults.standard.object(forKey: "username") as? String ?? "" self.token = UserDefaults.standard.object(forKey: "token") as? String ?? "" @@ -168,7 +174,8 @@ class UserSettings: ObservableObject { self.remindersListIdentifier = UserDefaults.standard.object(forKey: "remindersListIdentifier") as? String ?? "" self.grocerySyncEnabled = UserDefaults.standard.object(forKey: "grocerySyncEnabled") as? Bool ?? true self.mealPlanSyncEnabled = UserDefaults.standard.object(forKey: "mealPlanSyncEnabled") as? Bool ?? true - + self.appearanceMode = UserDefaults.standard.object(forKey: "appearanceMode") as? String ?? AppearanceMode.system.rawValue + if authString == "" { if token != "" && username != "" { let loginString = "\(self.username):\(self.token)" diff --git a/Nextcloud Cookbook iOS Client/Localizable.xcstrings b/Nextcloud Cookbook iOS Client/Localizable.xcstrings index 73ccf28..0966c6a 100644 --- a/Nextcloud Cookbook iOS Client/Localizable.xcstrings +++ b/Nextcloud Cookbook iOS Client/Localizable.xcstrings @@ -694,6 +694,7 @@ } }, "An HTML parsing and web scraping library for Swift. Used for importing schema.org recipes from websites." : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -782,6 +783,28 @@ } } }, + "Appearance" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erscheinungsbild" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apariencia" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apparence" + } + } + } + }, "Apple Reminders" : { "localizations" : { "de" : { @@ -995,6 +1018,28 @@ } } }, + "Choose whether the app follows the system appearance or always uses light or dark mode." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle, ob die App dem Erscheinungsbild des Systems folgt oder immer den hellen oder dunklen Modus verwendet." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige si la app sigue la apariencia del sistema o siempre usa el modo claro u oscuro." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez si l'app suit l'apparence du système ou utilise toujours le mode clair ou sombre." + } + } + } + }, "Clear" : { "localizations" : { "de" : { @@ -1293,6 +1338,28 @@ } } }, + "Dark" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dunkel" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oscuro" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sombre" + } + } + } + }, "Decimal number format" : { "localizations" : { "de" : { @@ -2536,6 +2603,28 @@ } } }, + "Light" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hell" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Claro" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clair" + } + } + } + }, "List your tools here. 🍴" : { "extractionState" : "stale", "localizations" : { @@ -4460,6 +4549,7 @@ } }, "SwiftSoup" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4483,6 +4573,28 @@ }, "Sync grocery list across devices" : { + }, + "System" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "System" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sistema" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Système" + } + } + } }, "Thank you for downloading" : { "localizations" : { @@ -5170,6 +5282,7 @@ } }, "Username: %@" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { diff --git a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift index 430b325..dcf6168 100644 --- a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift +++ b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift @@ -13,7 +13,16 @@ import SwiftUI struct Nextcloud_Cookbook_iOS_ClientApp: App { @AppStorage("onboarding") var onboarding = true @AppStorage("language") var language = Locale.current.language.languageCode?.identifier ?? "en" - + @AppStorage("appearanceMode") var appearanceMode = AppearanceMode.system.rawValue + + var colorScheme: ColorScheme? { + switch appearanceMode { + case AppearanceMode.light.rawValue: return .light + case AppearanceMode.dark.rawValue: return .dark + default: return nil + } + } + var body: some Scene { WindowGroup { ZStack { @@ -23,6 +32,7 @@ struct Nextcloud_Cookbook_iOS_ClientApp: App { MainView() } } + .preferredColorScheme(colorScheme) .transition(.slide) .environment( \.locale, diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/AllRecipesListView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/AllRecipesListView.swift index e426680..af24122 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/AllRecipesListView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/AllRecipesListView.swift @@ -56,7 +56,7 @@ struct AllRecipesListView: View { .bold() } .buttonStyle(.bordered) - .tint(.nextcloudBlue) + .tint(.primary) }.padding() } } diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift index 0cf1f59..9e8cec0 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift @@ -68,7 +68,7 @@ struct RecipeListView: View { .bold() } .buttonStyle(.bordered) - .tint(.nextcloudBlue) + .tint(.primary) }.padding() } } diff --git a/Nextcloud Cookbook iOS Client/Views/SettingsView.swift b/Nextcloud Cookbook iOS Client/Views/SettingsView.swift index 6067d5d..0fc54d4 100644 --- a/Nextcloud Cookbook iOS Client/Views/SettingsView.swift +++ b/Nextcloud Cookbook iOS Client/Views/SettingsView.swift @@ -22,30 +22,6 @@ struct SettingsView: View { var body: some View { Form { - HStack(alignment: .center) { - if let avatarImage = viewModel.avatarImage { - Image(uiImage: avatarImage) - .resizable() - .clipShape(Circle()) - .frame(width: 100, height: 100) - - } - if let userData = viewModel.userData { - VStack(alignment: .leading) { - Text(userData.userDisplayName) - .font(.title) - .padding(.leading) - Text("Username: \(userData.userId)") - .font(.subheadline) - .padding(.leading) - - - // TODO: Add actions - } - } - Spacer() - } - Section { Picker("Select a default cookbook", selection: $userSettings.defaultCategory) { Text("None").tag("None") @@ -59,6 +35,16 @@ struct SettingsView: View { Text("The selected cookbook will open on app launch by default.") } + Section { + Picker("Appearance", selection: $userSettings.appearanceMode) { + ForEach(AppearanceMode.allValues, id: \.self) { mode in + Text(mode.descriptor()).tag(mode.rawValue) + } + } + } footer: { + Text("Choose whether the app follows the system appearance or always uses light or dark mode.") + } + Section { Picker("Grocery list storage", selection: $userSettings.groceryListMode) { ForEach(GroceryListMode.allValues, id: \.self) { mode in @@ -211,13 +197,6 @@ struct SettingsView: View { } Section(header: Text("Acknowledgements")) { - VStack(alignment: .leading) { - if let url = URL(string: "https://github.com/scinfu/SwiftSoup") { - Link("SwiftSoup", destination: url) - .font(.headline) - Text("An HTML parsing and web scraping library for Swift. Used for importing schema.org recipes from websites.") - } - } VStack(alignment: .leading) { if let url = URL(string: "https://github.com/techprimate/TPPDF") { Link("TPPDF", destination: url) @@ -240,7 +219,6 @@ struct SettingsView: View { Text(viewModel.alertType.getMessage()) } .task { - await viewModel.getUserData() remindersPermission = groceryListManager.remindersPermissionStatus if remindersPermission == .fullAccess { reminderLists = groceryListManager.availableReminderLists() @@ -270,9 +248,6 @@ struct SettingsView: View { extension SettingsView { class ViewModel: ObservableObject { - @Published var avatarImage: UIImage? = nil - @Published var userData: UserData? = nil - @Published var showAlert: Bool = false fileprivate var alertType: SettingsAlert = .NONE @@ -297,16 +272,6 @@ extension SettingsView { } } } - - func getUserData() async { - let (data, _) = await NextcloudApi.getAvatar() - let (userData, _) = await NextcloudApi.getHoverCard() - - DispatchQueue.main.async { - self.avatarImage = data - self.userData = userData - } - } } } diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift index 4936bf7..12288c2 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift @@ -55,7 +55,7 @@ struct GroceryListTabView: View { groceryList.deleteAll() } label: { Text("Delete") - .foregroundStyle(Color.nextcloudBlue) + .foregroundStyle(.primary) } } } diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/MealPlanTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/MealPlanTabView.swift index 31d64b6..2a39b0d 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/MealPlanTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/MealPlanTabView.swift @@ -191,7 +191,7 @@ fileprivate struct MealPlanDayRow: View { .frame(maxWidth: .infinity, minHeight: 44) .background( RoundedRectangle(cornerRadius: 8) - .fill(Color.nextcloudBlue.opacity(0.1)) + .fill(Color.nextcloudBlue.opacity(0.15)) ) } } diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift index b729f6c..5b829d1 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift @@ -163,7 +163,7 @@ struct RecipeTabView: View { } } } - .tint(.nextcloudBlue) + .tint(.primary) .sheet(isPresented: $viewModel.showImportURLSheet) { ImportURLSheet { recipeDetail in viewModel.navigateToImportedRecipe(recipeDetail: recipeDetail)