From 789d581705df5f98d173e8c5a25f29cf32a9b3c6 Mon Sep 17 00:00:00 2001 From: Ilyas Hallak Date: Sun, 15 Jun 2025 00:56:19 +0200 Subject: [PATCH] Integrate Settings with WebView font customization - Connect SettingsView font selection to WebView CSS rendering - Add dynamic font size and family mapping in WebView - Implement CSS custom properties for responsive font scaling - Add font family fallback stacks for cross-platform compatibility - Update WebView to use Settings object for typography configuration --- readeck/Data/Repository/AuthRepository.swift | 4 -- .../Data/Repository/SettingsRepository.swift | 53 ++++++++++----- .../Domain/Protocols/PAuthRepository.swift | 1 - .../UseCase/GetBookmarkArticleUseCase.swift | 2 +- .../Domain/UseCase/SaveSettingsUseCase.swift | 37 ++++++++--- .../BookmarkDetail/BookmarkDetailView.swift | 4 +- .../BookmarkDetailViewModel.swift | 4 ++ readeck/UI/Components/WebView.swift | 65 ++++++++++++++++--- readeck/UI/DefaultUseCaseFactory.swift | 5 +- readeck/UI/Settings/SettingsView.swift | 19 +++++- readeck/UI/Settings/SettingsViewModel.swift | 20 ++++-- .../readeck.xcdatamodel/contents | 4 +- 12 files changed, 165 insertions(+), 53 deletions(-) diff --git a/readeck/Data/Repository/AuthRepository.swift b/readeck/Data/Repository/AuthRepository.swift index 484073a..09a2097 100644 --- a/readeck/Data/Repository/AuthRepository.swift +++ b/readeck/Data/Repository/AuthRepository.swift @@ -22,10 +22,6 @@ class AuthRepository: PAuthRepository { func getCurrentSettings() async throws -> Settings? { return try await settingsRepository.loadSettings() } - - func saveSettings(_ settings: Settings) async throws { - try await settingsRepository.saveSettings(settings) - } } struct User { diff --git a/readeck/Data/Repository/SettingsRepository.swift b/readeck/Data/Repository/SettingsRepository.swift index 973e9d6..8876a13 100644 --- a/readeck/Data/Repository/SettingsRepository.swift +++ b/readeck/Data/Repository/SettingsRepository.swift @@ -2,10 +2,13 @@ import Foundation import CoreData struct Settings { - let endpoint: String - let username: String - let password: String - var token: String? + var endpoint: String? = nil + var username: String? = nil + var password: String? = nil + var token: String? = nil + + var fontFamily: FontFamily? = nil + var fontSize: FontSize? = nil var isLoggedIn: Bool { token != nil && !token!.isEmpty @@ -34,19 +37,35 @@ class SettingsRepository: PSettingsRepository { do { // Vorhandene Einstellungen löschen let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() - let existingSettings = try context.fetch(fetchRequest) - for setting in existingSettings { - context.delete(setting) + if let existingSettings = try context.fetch(fetchRequest).first { + + if let endpoint = settings.endpoint, !endpoint.isEmpty { + existingSettings.endpoint = endpoint + } + + if let username = settings.username, !username.isEmpty { + existingSettings.username = username + } + + if let password = settings.password, !password.isEmpty { + existingSettings.password = password + } + + if let token = settings.token, !token.isEmpty { + existingSettings.token = token + } + + if let fontFamily = settings.fontFamily { + existingSettings.fontFamily = fontFamily.rawValue + } + + if let fontSize = settings.fontSize { + existingSettings.fontSize = fontSize.rawValue + } + + try context.save() } - // Neue Einstellungen erstellen - let settingEntity = SettingEntity(context: context) - settingEntity.endpoint = settings.endpoint - settingEntity.username = settings.username - settingEntity.password = settings.password - settingEntity.token = settings.token - - try context.save() continuation.resume() } catch { continuation.resume(throwing: error) @@ -71,7 +90,9 @@ class SettingsRepository: PSettingsRepository { endpoint: settingEntity.endpoint ?? "", username: settingEntity.username ?? "", password: settingEntity.password ?? "", - token: settingEntity.token + token: settingEntity.token, + fontFamily: FontFamily(rawValue: settingEntity.fontFamily ?? FontFamily.system.rawValue), + fontSize: FontSize(rawValue: settingEntity.fontSize ?? FontSize.medium.rawValue) ) continuation.resume(returning: settings) } else { diff --git a/readeck/Domain/Protocols/PAuthRepository.swift b/readeck/Domain/Protocols/PAuthRepository.swift index 3ad50f2..feff181 100644 --- a/readeck/Domain/Protocols/PAuthRepository.swift +++ b/readeck/Domain/Protocols/PAuthRepository.swift @@ -10,5 +10,4 @@ protocol PAuthRepository { func login(username: String, password: String) async throws -> User func logout() async throws func getCurrentSettings() async throws -> Settings? - func saveSettings(_ settings: Settings) async throws } diff --git a/readeck/Domain/UseCase/GetBookmarkArticleUseCase.swift b/readeck/Domain/UseCase/GetBookmarkArticleUseCase.swift index 3f10957..54e9d5a 100644 --- a/readeck/Domain/UseCase/GetBookmarkArticleUseCase.swift +++ b/readeck/Domain/UseCase/GetBookmarkArticleUseCase.swift @@ -10,4 +10,4 @@ class GetBookmarkArticleUseCase { func execute(id: String) async throws -> String { return try await repository.fetchBookmarkArticle(id: id) } -} \ No newline at end of file +} diff --git a/readeck/Domain/UseCase/SaveSettingsUseCase.swift b/readeck/Domain/UseCase/SaveSettingsUseCase.swift index f9db546..e987b7c 100644 --- a/readeck/Domain/UseCase/SaveSettingsUseCase.swift +++ b/readeck/Domain/UseCase/SaveSettingsUseCase.swift @@ -1,19 +1,36 @@ import Foundation class SaveSettingsUseCase { - private let authRepository: PAuthRepository + private let settingsRepository: PSettingsRepository - init(authRepository: PAuthRepository) { - self.authRepository = authRepository + init(settingsRepository: PSettingsRepository) { + self.settingsRepository = settingsRepository } func execute(endpoint: String, username: String, password: String) async throws { - let settings = Settings( - endpoint: endpoint, - username: username, - password: password, - token: nil + try await settingsRepository.saveSettings( + .init( + endpoint: endpoint, + username: username, + password: password + ) ) - try await authRepository.saveSettings(settings) } -} \ No newline at end of file + + func execute(token: String) async throws { + try await settingsRepository.saveSettings( + .init( + token: token + ) + ) + } + + func execute(selectedFontFamily: FontFamily, selectedFontSize: FontSize) async throws { + try await settingsRepository.saveSettings( + .init( + fontFamily: selectedFontFamily, + fontSize: selectedFontSize + ) + ) + } +} diff --git a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift index d314a3d..56fb904 100644 --- a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift +++ b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift @@ -36,8 +36,8 @@ struct BookmarkDetailView: View { Divider() // Artikel-Inhalt mit WebView - if !viewModel.articleContent.isEmpty { - WebView(htmlContent: viewModel.articleContent) { height in + if let settings = viewModel.settings, !viewModel.articleContent.isEmpty { + WebView(htmlContent: viewModel.articleContent, settings: settings) { height in webViewHeight = height } .frame(height: webViewHeight) diff --git a/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift b/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift index 1ef3999..0a72498 100644 --- a/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift +++ b/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift @@ -4,6 +4,7 @@ import Foundation class BookmarkDetailViewModel { private let getBookmarkUseCase: GetBookmarkUseCase private let getBookmarkArticleUseCase: GetBookmarkArticleUseCase + private let loadSettingsUseCase: LoadSettingsUseCase var bookmarkDetail: BookmarkDetail = BookmarkDetail.empty var articleContent: String = "" @@ -12,11 +13,13 @@ class BookmarkDetailViewModel { var isLoading = false var isLoadingArticle = false var errorMessage: String? + var settings: Settings? init() { let factory = DefaultUseCaseFactory.shared self.getBookmarkUseCase = factory.makeGetBookmarkUseCase() self.getBookmarkArticleUseCase = factory.makeGetBookmarkArticleUseCase() + self.loadSettingsUseCase = factory.makeLoadSettingsUseCase() } @MainActor @@ -25,6 +28,7 @@ class BookmarkDetailViewModel { errorMessage = nil do { + settings = try await loadSettingsUseCase.execute() bookmarkDetail = try await getBookmarkUseCase.execute(id: id) // Auch das vollständige Bookmark für readProgress laden diff --git a/readeck/UI/Components/WebView.swift b/readeck/UI/Components/WebView.swift index b5e490a..78726cd 100644 --- a/readeck/UI/Components/WebView.swift +++ b/readeck/UI/Components/WebView.swift @@ -3,6 +3,7 @@ import WebKit struct WebView: UIViewRepresentable { let htmlContent: String + let settings: Settings let onHeightChange: (CGFloat) -> Void @Environment(\.colorScheme) private var colorScheme @@ -26,6 +27,10 @@ struct WebView: UIViewRepresentable { let isDarkMode = colorScheme == .dark + // Font Settings aus Settings-Objekt + let fontSize = getFontSize(from: settings.fontSize ?? .extraLarge) + let fontFamily = getFontFamily(from: settings.fontFamily ?? .serif) + let styledHTML = """ @@ -42,16 +47,20 @@ struct WebView: UIViewRepresentable { --code-background: \(isDarkMode ? "#1C1C1E" : "#f5f5f5"); --code-text: \(isDarkMode ? "#ffffff" : "#000000"); --separator-color: \(isDarkMode ? "#38383A" : "#e0e0e0"); + + /* Font Settings from Settings */ + --base-font-size: \(fontSize)px; + --font-family: \(fontFamily); } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-family: var(--font-family); line-height: 1.8; margin: 0; padding: 16px; background-color: var(--background-color); color: var(--text-color); - font-size: 16px; + font-size: var(--base-font-size); -webkit-text-size-adjust: 100%; } @@ -60,13 +69,19 @@ struct WebView: UIViewRepresentable { margin-top: 24px; margin-bottom: 12px; font-weight: 600; + font-family: var(--font-family); } - h1 { font-size: 24px; } - h2 { font-size: 20px; } - h3 { font-size: 18px; } + h1 { font-size: calc(var(--base-font-size) * 1.5); } + h2 { font-size: calc(var(--base-font-size) * 1.25); } + h3 { font-size: calc(var(--base-font-size) * 1.125); } + h4 { font-size: var(--base-font-size); } + h5 { font-size: calc(var(--base-font-size) * 0.875); } + h6 { font-size: calc(var(--base-font-size) * 0.75); } p { margin-bottom: 16px; + font-family: var(--font-family); + font-size: var(--base-font-size); } img { @@ -79,6 +94,7 @@ struct WebView: UIViewRepresentable { a { color: var(--link-color); text-decoration: none; + font-family: var(--font-family); } a:hover { text-decoration: underline; @@ -93,6 +109,8 @@ struct WebView: UIViewRepresentable { background-color: \(isDarkMode ? "rgba(58, 58, 60, 0.3)" : "rgba(0, 122, 255, 0.05)"); border-radius: 4px; padding: 12px 16px; + font-family: var(--font-family); + font-size: var(--base-font-size); } code { @@ -100,8 +118,8 @@ struct WebView: UIViewRepresentable { color: var(--code-text); padding: 2px 6px; border-radius: 4px; - font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; - font-size: 14px; + font-family: \(settings.fontFamily == .monospace ? "var(--font-family)" : "'SF Mono', Menlo, Monaco, Consolas, monospace"); + font-size: calc(var(--base-font-size) * 0.875); } pre { @@ -110,14 +128,15 @@ struct WebView: UIViewRepresentable { padding: 16px; border-radius: 8px; overflow-x: auto; - font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; - font-size: 14px; + font-family: \(settings.fontFamily == .monospace ? "var(--font-family)" : "'SF Mono', Menlo, Monaco, Consolas, monospace"); + font-size: calc(var(--base-font-size) * 0.875); border: 1px solid var(--separator-color); } pre code { background-color: transparent; padding: 0; + font-family: inherit; } hr { @@ -131,6 +150,8 @@ struct WebView: UIViewRepresentable { width: 100%; border-collapse: collapse; margin: 16px 0; + font-family: var(--font-family); + font-size: var(--base-font-size); } th, td { @@ -147,6 +168,8 @@ struct WebView: UIViewRepresentable { ul, ol { padding-left: 20px; margin-bottom: 16px; + font-family: var(--font-family); + font-size: var(--base-font-size); } li { @@ -211,6 +234,28 @@ struct WebView: UIViewRepresentable { func makeCoordinator() -> WebViewCoordinator { WebViewCoordinator() } + + private func getFontSize(from fontSize: FontSize) -> Int { + switch fontSize { + case .small: return 14 + case .medium: return 16 + case .large: return 18 + case .extraLarge: return 20 + } + } + + private func getFontFamily(from fontFamily: FontFamily) -> String { + switch fontFamily { + case .system: + return "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" + case .serif: + return "'Times New Roman', Times, 'Liberation Serif', serif" + case .sansSerif: + return "'Helvetica Neue', Helvetica, Arial, sans-serif" + case .monospace: + return "'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', monospace" + } + } } class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { @@ -238,4 +283,4 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler deinit { // Der Message Handler wird automatisch mit der WebView entfernt } -} \ No newline at end of file +} diff --git a/readeck/UI/DefaultUseCaseFactory.swift b/readeck/UI/DefaultUseCaseFactory.swift index 07ec72c..64f1044 100644 --- a/readeck/UI/DefaultUseCaseFactory.swift +++ b/readeck/UI/DefaultUseCaseFactory.swift @@ -15,8 +15,9 @@ protocol UseCaseFactory { class DefaultUseCaseFactory: UseCaseFactory { private let tokenProvider = CoreDataTokenProvider() private lazy var api: PAPI = API(tokenProvider: tokenProvider) - private lazy var authRepository: PAuthRepository = AuthRepository(api: api, settingsRepository: SettingsRepository()) + private lazy var authRepository: PAuthRepository = AuthRepository(api: api, settingsRepository: settingsRepository) private lazy var bookmarksRepository: PBookmarksRepository = BookmarksRepository(api: api) + private let settingsRepository: PSettingsRepository = SettingsRepository() static let shared = DefaultUseCaseFactory() @@ -39,7 +40,7 @@ class DefaultUseCaseFactory: UseCaseFactory { } func makeSaveSettingsUseCase() -> SaveSettingsUseCase { - SaveSettingsUseCase(authRepository: authRepository) + SaveSettingsUseCase(settingsRepository: settingsRepository) } func makeLoadSettingsUseCase() -> LoadSettingsUseCase { diff --git a/readeck/UI/Settings/SettingsView.swift b/readeck/UI/Settings/SettingsView.swift index 726d135..47e71cf 100644 --- a/readeck/UI/Settings/SettingsView.swift +++ b/readeck/UI/Settings/SettingsView.swift @@ -37,6 +37,11 @@ struct SettingsView: View { } .disabled(!viewModel.canLogin || viewModel.isLoading) + Button("Debug-Anmeldung") { + viewModel.username = "admin" + viewModel.password = "Diggah123" + } + if viewModel.isLoggedIn { HStack { Image(systemName: "checkmark.circle.fill") @@ -69,7 +74,7 @@ struct SettingsView: View { ForEach(Theme.allCases, id: \.self) { theme in Text(theme.displayName).tag(theme) } - } + } // Font Settings with Preview VStack(alignment: .leading, spacing: 12) { @@ -84,13 +89,23 @@ struct SettingsView: View { } } .pickerStyle(MenuPickerStyle()) + .onChange(of: viewModel.selectedFontFamily) { + Task { + await viewModel.saveFontSettings() + } + } Picker("Schriftgröße", selection: $viewModel.selectedFontSize) { ForEach(FontSize.allCases, id: \.self) { size in Text(size.displayName).tag(size) } - } + } .pickerStyle(SegmentedPickerStyle()) + .onChange(of: viewModel.selectedFontSize) { + Task { + await viewModel.saveFontSettings() + } + } } Spacer() diff --git a/readeck/UI/Settings/SettingsViewModel.swift b/readeck/UI/Settings/SettingsViewModel.swift index 698c4d0..53c54fb 100644 --- a/readeck/UI/Settings/SettingsViewModel.swift +++ b/readeck/UI/Settings/SettingsViewModel.swift @@ -93,9 +93,9 @@ class SettingsViewModel { func loadSettings() async { do { if let settings = try await loadSettingsUseCase.execute() { - endpoint = settings.endpoint - username = settings.username - password = settings.password + endpoint = settings.endpoint ?? "" + username = settings.username ?? "" + password = settings.password ?? "" isLoggedIn = settings.isLoggedIn // Verwendet die neue Hilfsmethode } } catch { @@ -103,6 +103,17 @@ class SettingsViewModel { } } + @MainActor + func saveFontSettings() async { + do { + try await saveSettingsUseCase.execute( + selectedFontFamily: selectedFontFamily, selectedFontSize: selectedFontSize + ) + } catch { + errorMessage = "Fehler beim Speichern der Font-Einstellungen" + } + } + @MainActor func saveSettings() async { isSaving = true @@ -134,7 +145,8 @@ class SettingsViewModel { successMessage = nil do { - _ = try await loginUseCase.execute(username: username, password: password) + let user = try await loginUseCase.execute(username: username, password: password) + isLoggedIn = true successMessage = "Erfolgreich angemeldet" diff --git a/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents b/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents index 4fa8738..22b2a9e 100644 --- a/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents +++ b/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents @@ -1,10 +1,12 @@ - + + +