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:
Ilyas Hallak 2025-06-14 23:34:44 +02:00
parent 42f6c0dc92
commit 2bc93abe24
2 changed files with 278 additions and 23 deletions

View File

@ -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 {

View File

@ -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)
}
}