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
This commit is contained in:
parent
2bc93abe24
commit
789d581705
@ -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 {
|
||||
|
||||
@ -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> = 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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -10,4 +10,4 @@ class GetBookmarkArticleUseCase {
|
||||
func execute(id: String) async throws -> String {
|
||||
return try await repository.fetchBookmarkArticle(id: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = """
|
||||
<html>
|
||||
<head>
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="24B83" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23788.4" systemVersion="24F74" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
</entity>
|
||||
<entity name="SettingEntity" representedClassName="SettingEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="endpoint" optional="YES" attributeType="String"/>
|
||||
<attribute name="fontFamily" optional="YES" attributeType="String"/>
|
||||
<attribute name="fontSize" optional="YES" attributeType="String"/>
|
||||
<attribute name="password" optional="YES" attributeType="String"/>
|
||||
<attribute name="token" optional="YES" attributeType="String"/>
|
||||
<attribute name="username" optional="YES" attributeType="String"/>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user