Add Settings view with server config and font customization
- Add SettingsView with server login, theme selection, and font preview - Implement SettingsViewModel with @Observable for state management - Add font family and size selection with live preview - Include sync settings, reading preferences, and data management options
This commit is contained in:
parent
42f6c0dc92
commit
2bc93abe24
@ -3,6 +3,9 @@ import SwiftUI
|
||||
struct SettingsView: View {
|
||||
@State private var viewModel = SettingsViewModel()
|
||||
|
||||
@State var selectedTheme: Theme = .system
|
||||
@State var selectedFontSize: FontSize = .medium
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
@ -18,27 +21,8 @@ struct SettingsView: View {
|
||||
|
||||
SecureField("Passwort", text: $viewModel.password)
|
||||
.textContentType(.password)
|
||||
}
|
||||
|
||||
Section {
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.saveSettings()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if viewModel.isSaving {
|
||||
ProgressView()
|
||||
.scaleEffect(0.8)
|
||||
}
|
||||
Text("Einstellungen speichern")
|
||||
}
|
||||
}
|
||||
.disabled(!viewModel.canSave || viewModel.isSaving)
|
||||
}
|
||||
|
||||
Section("Anmeldung") {
|
||||
Button {
|
||||
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.login()
|
||||
}
|
||||
@ -62,6 +46,149 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.saveSettings()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if viewModel.isSaving {
|
||||
ProgressView()
|
||||
.scaleEffect(0.8)
|
||||
}
|
||||
Text("Einstellungen speichern")
|
||||
}
|
||||
}
|
||||
.disabled(!viewModel.canSave || viewModel.isSaving)
|
||||
}
|
||||
|
||||
|
||||
Section("Erscheinungsbild") {
|
||||
Picker("Theme", selection: $viewModel.selectedTheme) {
|
||||
ForEach(Theme.allCases, id: \.self) { theme in
|
||||
Text(theme.displayName).tag(theme)
|
||||
}
|
||||
}
|
||||
|
||||
// Font Settings with Preview
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Schrift-Einstellungen")
|
||||
.font(.headline)
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Picker("Schriftart", selection: $viewModel.selectedFontFamily) {
|
||||
ForEach(FontFamily.allCases, id: \.self) { family in
|
||||
Text(family.displayName).tag(family)
|
||||
}
|
||||
}
|
||||
.pickerStyle(MenuPickerStyle())
|
||||
|
||||
Picker("Schriftgröße", selection: $viewModel.selectedFontSize) {
|
||||
ForEach(FontSize.allCases, id: \.self) { size in
|
||||
Text(size.displayName).tag(size)
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
// Font Preview
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Vorschau")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("readeck Bookmark Title")
|
||||
.font(viewModel.previewTitleFont)
|
||||
.fontWeight(.semibold)
|
||||
.lineLimit(1)
|
||||
|
||||
Text("This is how your bookmark descriptions and article text will appear in the app. The quick brown fox jumps over the lazy dog.")
|
||||
.font(viewModel.previewBodyFont)
|
||||
.lineLimit(3)
|
||||
|
||||
Text("12 min • Today • example.com")
|
||||
.font(viewModel.previewCaptionFont)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(12)
|
||||
.background(Color(.systemGray6))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
||||
Section("Sync-Einstellungen") {
|
||||
Toggle("Automatischer Sync", isOn: $viewModel.autoSyncEnabled)
|
||||
.toggleStyle(SwitchToggleStyle())
|
||||
|
||||
if viewModel.autoSyncEnabled {
|
||||
Stepper("Sync-Intervall: \(viewModel.syncInterval) Minuten", value: $viewModel.syncInterval, in: 1...60)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
|
||||
Section("Leseeinstellungen") {
|
||||
Toggle("Safari Reader Modus", isOn: $viewModel.enableReaderMode)
|
||||
.toggleStyle(SwitchToggleStyle())
|
||||
|
||||
Toggle("Externe Links in In-App Safari öffnen", isOn: $viewModel.openExternalLinksInApp)
|
||||
.toggleStyle(SwitchToggleStyle())
|
||||
|
||||
Toggle("Artikel automatisch als gelesen markieren", isOn: $viewModel.autoMarkAsRead)
|
||||
.toggleStyle(SwitchToggleStyle())
|
||||
}
|
||||
|
||||
Section("Datenmanagement") {
|
||||
Button(role: .destructive) {
|
||||
Task {
|
||||
// await viewModel.clearCache()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "trash")
|
||||
.foregroundColor(.red)
|
||||
Text("Cache leeren")
|
||||
}
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
Task {
|
||||
// await viewModel.resetSettings()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
.foregroundColor(.red)
|
||||
Text("Einstellungen zurücksetzen")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Über die App") {
|
||||
HStack {
|
||||
Image(systemName: "info.circle")
|
||||
Text("Version \(viewModel.appVersion)")
|
||||
}
|
||||
|
||||
HStack {
|
||||
Image(systemName: "person.crop.circle")
|
||||
Text("Entwickler: \(viewModel.developerName)")
|
||||
}
|
||||
|
||||
HStack {
|
||||
Image(systemName: "globe")
|
||||
Link("Website", destination: URL(string: "https://example.com")!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Success/Error Messages
|
||||
if let successMessage = viewModel.successMessage {
|
||||
Section {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class SettingsViewModel {
|
||||
@ -6,14 +8,79 @@ class SettingsViewModel {
|
||||
private let saveSettingsUseCase: SaveSettingsUseCase
|
||||
private let loadSettingsUseCase: LoadSettingsUseCase
|
||||
|
||||
// MARK: - Server Settings
|
||||
var endpoint = ""
|
||||
var username = ""
|
||||
var password = ""
|
||||
var isLoading = false
|
||||
var isSaving = false
|
||||
var isLoggedIn = false
|
||||
var errorMessage: String?
|
||||
var successMessage: String?
|
||||
|
||||
// MARK: - UI Settings
|
||||
var selectedTheme: Theme = .system
|
||||
|
||||
// MARK: - Sync Settings
|
||||
var autoSyncEnabled: Bool = true
|
||||
var syncInterval: Int = 15
|
||||
|
||||
// MARK: - Reading Settings
|
||||
var enableReaderMode: Bool = false
|
||||
var openExternalLinksInApp: Bool = true
|
||||
var autoMarkAsRead: Bool = false
|
||||
|
||||
// MARK: - App Info
|
||||
var appVersion: String = "1.0.0"
|
||||
var developerName: String = "Your Name"
|
||||
|
||||
|
||||
// MARK: - Messages
|
||||
var errorMessage: String?
|
||||
var successMessage: String?
|
||||
|
||||
// MARK: - Font Settings
|
||||
var selectedFontFamily: FontFamily = .system
|
||||
var selectedFontSize: FontSize = .medium
|
||||
|
||||
// MARK: - Computed Font Properties for Preview
|
||||
var previewTitleFont: Font {
|
||||
switch selectedFontFamily {
|
||||
case .system:
|
||||
return selectedFontSize.systemFont.weight(.semibold)
|
||||
case .serif:
|
||||
return Font.custom("Times New Roman", size: selectedFontSize.size).weight(.semibold)
|
||||
case .sansSerif:
|
||||
return Font.custom("Helvetica Neue", size: selectedFontSize.size).weight(.semibold)
|
||||
case .monospace:
|
||||
return Font.custom("Menlo", size: selectedFontSize.size).weight(.semibold)
|
||||
}
|
||||
}
|
||||
|
||||
var previewBodyFont: Font {
|
||||
switch selectedFontFamily {
|
||||
case .system:
|
||||
return selectedFontSize.systemFont
|
||||
case .serif:
|
||||
return Font.custom("Times New Roman", size: selectedFontSize.size)
|
||||
case .sansSerif:
|
||||
return Font.custom("Helvetica Neue", size: selectedFontSize.size)
|
||||
case .monospace:
|
||||
return Font.custom("Menlo", size: selectedFontSize.size)
|
||||
}
|
||||
}
|
||||
|
||||
var previewCaptionFont: Font {
|
||||
let captionSize = selectedFontSize.size * 0.85
|
||||
switch selectedFontFamily {
|
||||
case .system:
|
||||
return Font.system(size: captionSize)
|
||||
case .serif:
|
||||
return Font.custom("Times New Roman", size: captionSize)
|
||||
case .sansSerif:
|
||||
return Font.custom("Helvetica Neue", size: captionSize)
|
||||
case .monospace:
|
||||
return Font.custom("Menlo", size: captionSize)
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
let factory = DefaultUseCaseFactory.shared
|
||||
@ -102,3 +169,64 @@ class SettingsViewModel {
|
||||
!username.isEmpty && !password.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum Theme: String, CaseIterable {
|
||||
case system = "system"
|
||||
case light = "light"
|
||||
case dark = "dark"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .system: return "System"
|
||||
case .light: return "Hell"
|
||||
case .dark: return "Dunkel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Font Enums
|
||||
enum FontFamily: String, CaseIterable {
|
||||
case system = "system"
|
||||
case serif = "serif"
|
||||
case sansSerif = "sansSerif"
|
||||
case monospace = "monospace"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .system: return "System"
|
||||
case .serif: return "Serif"
|
||||
case .sansSerif: return "Sans Serif"
|
||||
case .monospace: return "Monospace"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum FontSize: String, CaseIterable {
|
||||
case small = "small"
|
||||
case medium = "medium"
|
||||
case large = "large"
|
||||
case extraLarge = "extraLarge"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .small: return "S"
|
||||
case .medium: return "M"
|
||||
case .large: return "L"
|
||||
case .extraLarge: return "XL"
|
||||
}
|
||||
}
|
||||
|
||||
var size: CGFloat {
|
||||
switch self {
|
||||
case .small: return 14
|
||||
case .medium: return 16
|
||||
case .large: return 18
|
||||
case .extraLarge: return 20
|
||||
}
|
||||
}
|
||||
|
||||
var systemFont: Font {
|
||||
return Font.system(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user