Feature: user-selectable theme (#460)
* Fix #457 - Persist entry sorting preferences across app launches Changed sorting state from @State to @AppStorage in EntriesView to ensure user preferences are saved to UserDefaults. Also made sorting criteria mutually exclusive for a better UX. * Add option to pick theme (light/dark/auto). Moved language files and other resources into a sub-folder to clean up the structure a little. * use #EnvironmentObject
@@ -4,18 +4,28 @@ import SharedLib
|
||||
|
||||
final class AppSetting: ObservableObject {
|
||||
@Published var webFontSizePercent: Double
|
||||
@Published var theme: Theme
|
||||
|
||||
private var cancellable = Set<AnyCancellable>()
|
||||
|
||||
init() {
|
||||
webFontSizePercent = WallabagUserDefaults.webFontSizePercent
|
||||
theme = Theme(rawValue: WallabagUserDefaults.theme) ?? .auto
|
||||
|
||||
$webFontSizePercent
|
||||
.sink(receiveValue: updateWebFontSizePercent)
|
||||
.store(in: &cancellable)
|
||||
|
||||
$theme
|
||||
.sink(receiveValue: updateTheme)
|
||||
.store(in: &cancellable)
|
||||
}
|
||||
|
||||
private func updateWebFontSizePercent(_ value: Double) {
|
||||
WallabagUserDefaults.webFontSizePercent = value
|
||||
}
|
||||
|
||||
private func updateTheme(_ value: Theme) {
|
||||
WallabagUserDefaults.theme = value.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Factory
|
||||
import SharedLib
|
||||
import SwiftUI
|
||||
|
||||
@@ -7,9 +8,17 @@ struct SettingView: View {
|
||||
@AppStorage("badge") var badge: Bool = true
|
||||
@AppStorage("defaultMode") var defaultMode: String = RetrieveMode.allArticles.rawValue
|
||||
@AppStorage("itemPerPageDuringSync") var itemPerPageDuringSync: Int = 50
|
||||
@EnvironmentObject var appSetting: AppSetting
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section("Appearance") {
|
||||
Picker("Theme", selection: $appSetting.theme) {
|
||||
ForEach(Theme.allCases) { theme in
|
||||
Text(theme.name).tag(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("Entries list") {
|
||||
Toggle("Show image in list", isOn: $showImageInList)
|
||||
Picker("Default mode", selection: $defaultMode) {
|
||||
@@ -35,5 +44,6 @@ struct SettingView: View {
|
||||
struct SettingView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingView()
|
||||
.environmentObject(AppSetting())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import SwiftUI
|
||||
|
||||
enum Theme: String, CaseIterable, Identifiable {
|
||||
case auto
|
||||
case light
|
||||
case dark
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var colorScheme: ColorScheme? {
|
||||
switch self {
|
||||
case .auto: return nil
|
||||
case .light: return .light
|
||||
case .dark: return .dark
|
||||
}
|
||||
}
|
||||
|
||||
var name: LocalizedStringKey {
|
||||
switch self {
|
||||
case .auto: return "Auto"
|
||||
case .light: return "Light"
|
||||
case .dark: return "Dark"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "المظهر";
|
||||
"Theme" = "السمة";
|
||||
"Auto" = "تلقائي";
|
||||
"Light" = "فاتح";
|
||||
"Dark" = "داكن";
|
||||
@@ -17,3 +17,10 @@
|
||||
|
||||
// Menu
|
||||
"Menu" = "Nabídka";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Vzhled";
|
||||
"Theme" = "Motiv";
|
||||
"Auto" = "Automaticky";
|
||||
"Light" = "Světlý";
|
||||
"Dark" = "Tmavý";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Udseende";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Lyst";
|
||||
"Dark" = "Mørkt";
|
||||
@@ -48,3 +48,10 @@
|
||||
|
||||
//Player
|
||||
"Select one entry" = "Eintrag auswählen";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Erscheinungsbild";
|
||||
"Theme" = "Design";
|
||||
"Auto" = "Automatisch";
|
||||
"Light" = "Hell";
|
||||
"Dark" = "Dunkel";
|
||||
@@ -50,3 +50,10 @@
|
||||
|
||||
//Player
|
||||
"Select one entry" = "Select one entry";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Appearance";
|
||||
"Theme" = "Theme";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Light";
|
||||
"Dark" = "Dark";
|
||||
@@ -1,10 +1,13 @@
|
||||
|
||||
|
||||
|
||||
|
||||
// Tip View
|
||||
"This application is developed on free time" = "Esta aplicación se desarrolla en tiempo libre.";
|
||||
"Logout" = "Desconectarse";
|
||||
"Select one entry" = "Selecciona una entrada";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Apariencia";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Automático";
|
||||
"Light" = "Claro";
|
||||
"Dark" = "Oscuro";
|
||||
"Setting" = "Configuración";
|
||||
"About" = "Acerca de";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "ظاهر";
|
||||
"Theme" = "پوسته";
|
||||
"Auto" = "خودکار";
|
||||
"Light" = "روشن";
|
||||
"Dark" = "تیره";
|
||||
@@ -54,3 +54,10 @@
|
||||
"Order by id" = "Trier par id";
|
||||
"Order by reading time" = "Trier par temps de lecture";
|
||||
"Sorting" = "Tri";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Apparence";
|
||||
"Theme" = "Thème";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Clair";
|
||||
"Dark" = "Sombre";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Aparencia";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Claro";
|
||||
"Dark" = "Escuro";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "दिखावट";
|
||||
"Theme" = "थीम";
|
||||
"Auto" = "ऑटो";
|
||||
"Light" = "हल्का";
|
||||
"Dark" = "गहरा";
|
||||
@@ -48,3 +48,10 @@
|
||||
|
||||
//Player
|
||||
"Select one entry" = "Odaberi unos";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Izgled";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Automatski";
|
||||
"Light" = "Svijetlo";
|
||||
"Dark" = "Tamno";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Megjelenés";
|
||||
"Theme" = "Téma";
|
||||
"Auto" = "Automatikus";
|
||||
"Light" = "Világos";
|
||||
"Dark" = "Sötét";
|
||||
@@ -33,3 +33,10 @@
|
||||
|
||||
// Menu
|
||||
"Menu" = "Menù";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Aspetto";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Automatico";
|
||||
"Light" = "Chiaro";
|
||||
"Dark" = "Scuro";
|
||||
@@ -42,3 +42,10 @@
|
||||
"Loading..." = "読み込み中...";
|
||||
"Don" = "寄付";
|
||||
"Starred" = "スター";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "外観";
|
||||
"Theme" = "テーマ";
|
||||
"Auto" = "自動";
|
||||
"Light" = "ライト";
|
||||
"Dark" = "ダーク";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "모양";
|
||||
"Theme" = "테마";
|
||||
"Auto" = "자동";
|
||||
"Light" = "라이트";
|
||||
"Dark" = "다크";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Utseende";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Lyst";
|
||||
"Dark" = "Mørkt";
|
||||
@@ -33,3 +33,10 @@
|
||||
"Loading..." = "Laden...";
|
||||
"But you can contribute financially by making a donation whenever you want to support the project." = "Maar u kunt ook financieel bijdragen door een donatie te doen u het project wilt steunen.";
|
||||
"Entries" = "Items";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Weergave";
|
||||
"Theme" = "Thema";
|
||||
"Auto" = "Automatisch";
|
||||
"Light" = "Licht";
|
||||
"Dark" = "Donker";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Aparéncia";
|
||||
"Theme" = "Tèma";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Clar";
|
||||
"Dark" = "Escur";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Wygląd";
|
||||
"Theme" = "Motyw";
|
||||
"Auto" = "Automatyczny";
|
||||
"Light" = "Jasny";
|
||||
"Dark" = "Ciemny";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Aparência";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Automático";
|
||||
"Light" = "Claro";
|
||||
"Dark" = "Escuro";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Aspect";
|
||||
"Theme" = "Temă";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Luminos";
|
||||
"Dark" = "Întunecat";
|
||||
@@ -40,3 +40,10 @@
|
||||
|
||||
// Search
|
||||
"Search" = "Поиск";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Оформление";
|
||||
"Theme" = "Тема";
|
||||
"Auto" = "Автоматически";
|
||||
"Light" = "Светлая";
|
||||
"Dark" = "Темная";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Utseende";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Auto";
|
||||
"Light" = "Ljust";
|
||||
"Dark" = "Mörkt";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "ลักษณะ";
|
||||
"Theme" = "ธีม";
|
||||
"Auto" = "อัตโนมัติ";
|
||||
"Light" = "สว่าง";
|
||||
"Dark" = "มืด";
|
||||
@@ -46,3 +46,10 @@
|
||||
|
||||
//Player
|
||||
"Select one entry" = "Bir makale seçin";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Görünüm";
|
||||
"Theme" = "Tema";
|
||||
"Auto" = "Otomatik";
|
||||
"Light" = "Açık";
|
||||
"Dark" = "Koyu";
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// Theme
|
||||
"Appearance" = "Вигляд";
|
||||
"Theme" = "Тема";
|
||||
"Auto" = "Авто";
|
||||
"Light" = "Світла";
|
||||
"Dark" = "Темна";
|
||||
@@ -48,3 +48,10 @@
|
||||
|
||||
//Player
|
||||
"Select one entry" = "选择一个条目";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "外观";
|
||||
"Theme" = "主题";
|
||||
"Auto" = "自动";
|
||||
"Light" = "浅色";
|
||||
"Dark" = "深色";
|
||||
@@ -42,3 +42,10 @@
|
||||
|
||||
// Menu
|
||||
"Menu" = "菜單";
|
||||
|
||||
// Theme
|
||||
"Appearance" = "外觀";
|
||||
"Theme" = "主題";
|
||||
"Auto" = "自動";
|
||||
"Light" = "淺色";
|
||||
"Dark" = "深色";
|
||||
@@ -39,6 +39,7 @@ struct WallabagApp: App {
|
||||
.environment(errorHandler)
|
||||
.environmentObject(appSetting)
|
||||
.environment(\.managedObjectContext, coreData.viewContext)
|
||||
.preferredColorScheme(appSetting.theme.colorScheme)
|
||||
}
|
||||
.onChange(of: scenePhase) { _, newScenePhase in
|
||||
if newScenePhase == .active {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -48,4 +48,7 @@ public enum WallabagUserDefaults {
|
||||
|
||||
@GeneralSetting("itemPerPageDuringSync", defaultValue: 50)
|
||||
public static var itemPerPageDuringSync: Int
|
||||
|
||||
@GeneralSetting("theme", defaultValue: "auto")
|
||||
public static var theme: String
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
09644B5725C9810A000FFDA1 /* WallabagApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644B5625C9810A000FFDA1 /* WallabagApp.swift */; };
|
||||
09644B5E25C98116000FFDA1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644B5D25C98116000FFDA1 /* AppDelegate.swift */; };
|
||||
09644B7E25C98152000FFDA1 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644B7D25C98152000FFDA1 /* AppState.swift */; };
|
||||
09FA20240000000000000002 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FA20240000000000000001 /* Theme.swift */; };
|
||||
09644B8C25C98176000FFDA1 /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644B8825C98176000FFDA1 /* Route.swift */; };
|
||||
09644B9725C9819F000FFDA1 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644B9425C9819F000FFDA1 /* PlayerView.swift */; };
|
||||
09644B9825C9819F000FFDA1 /* PlayerPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644B9525C9819F000FFDA1 /* PlayerPublisher.swift */; };
|
||||
@@ -171,6 +172,7 @@
|
||||
09644B5625C9810A000FFDA1 /* WallabagApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WallabagApp.swift; sourceTree = "<group>"; };
|
||||
09644B5D25C98116000FFDA1 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
09644B7D25C98152000FFDA1 /* AppState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||
09FA20240000000000000001 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||
09644B8825C98176000FFDA1 /* Route.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = "<group>"; };
|
||||
09644B9425C9819F000FFDA1 /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
|
||||
09644B9525C9819F000FFDA1 /* PlayerPublisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerPublisher.swift; sourceTree = "<group>"; };
|
||||
@@ -399,6 +401,7 @@
|
||||
09644B7D25C98152000FFDA1 /* AppState.swift */,
|
||||
09644BD125C9833D000FFDA1 /* DependencyInjection.swift */,
|
||||
09644BD325C9833D000FFDA1 /* WallabagError.swift */,
|
||||
09FA20240000000000000001 /* Theme.swift */,
|
||||
);
|
||||
path = Lib;
|
||||
sourceTree = "<group>";
|
||||
@@ -744,6 +747,18 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
09FA20300000000000000001 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
09AD759C2624F93D00708A1E /* html-ressources */,
|
||||
09AD75C02624FEEE00708A1E /* Localizable.strings */,
|
||||
09644D1E25C98782000FFDA1 /* wallabagStore.xcdatamodeld */,
|
||||
0951C61729CC2EB000D8E8C6 /* Assets.xcassets */,
|
||||
099CD12E2B5019F40029E94A /* WallabagStoreKit.storekit */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
09BFB28625C8348E00E12B4D /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -753,13 +768,9 @@
|
||||
097F824C25CB1B17006C85F6 /* Entity */,
|
||||
09644D1625C9874D000FFDA1 /* Extension */,
|
||||
09644B8425C98161000FFDA1 /* Features */,
|
||||
09AD759C2624F93D00708A1E /* html-ressources */,
|
||||
09644B7C25C9814C000FFDA1 /* Lib */,
|
||||
09AD75C02624FEEE00708A1E /* Localizable.strings */,
|
||||
09644BE425C98343000FFDA1 /* PropertyWrapper */,
|
||||
09644D1E25C98782000FFDA1 /* wallabagStore.xcdatamodeld */,
|
||||
0951C61729CC2EB000D8E8C6 /* Assets.xcassets */,
|
||||
099CD12E2B5019F40029E94A /* WallabagStoreKit.storekit */,
|
||||
09FA20300000000000000001 /* Resources */,
|
||||
);
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
@@ -1067,6 +1078,7 @@
|
||||
09644BAF25C98213000FFDA1 /* RefreshButton.swift in Sources */,
|
||||
09BE0AF42A9F45E900193FBF /* View+Extension.swift in Sources */,
|
||||
09644C7025C985A9000FFDA1 /* ArchiveEntryButton.swift in Sources */,
|
||||
09FA20240000000000000002 /* Theme.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||