From 4b788650b89f73f243e4bacb17b753cc3fcaecc7 Mon Sep 17 00:00:00 2001 From: Ilyas Hallak Date: Sat, 8 Nov 2025 19:12:08 +0100 Subject: [PATCH] Redesign settings screen with native iOS style - Move font settings to dedicated detail screen with larger preview - Add inline explanations directly under each setting - Reorganize sections: split General into Reading Settings and Sync Settings - Combine Legal, Privacy and Support into single section - Move "What's New" to combined Legal/Privacy/Support section - Redesign app info footer with muted styling and center alignment - Remove white backgrounds from font preview for better light/dark mode support --- readeck.xcodeproj/project.pbxproj | 8 +- readeck/UI/Resources/RELEASE_NOTES.md | 9 ++ .../UI/Settings/AppearanceSettingsView.swift | 91 ++++++--------- readeck/UI/Settings/FontSelectionView.swift | 105 ++++++++++++++++++ .../Settings/LegalPrivacySettingsView.swift | 25 ++++- readeck/UI/Settings/ReadingSettingsView.swift | 55 +++++++++ .../UI/Settings/SettingsContainerView.swift | 25 +++-- readeck/UI/Settings/SyncSettingsView.swift | 64 +++++++++++ 8 files changed, 308 insertions(+), 74 deletions(-) create mode 100644 readeck/UI/Settings/FontSelectionView.swift create mode 100644 readeck/UI/Settings/ReadingSettingsView.swift create mode 100644 readeck/UI/Settings/SyncSettingsView.swift diff --git a/readeck.xcodeproj/project.pbxproj b/readeck.xcodeproj/project.pbxproj index 7e456a5..8ec5892 100644 --- a/readeck.xcodeproj/project.pbxproj +++ b/readeck.xcodeproj/project.pbxproj @@ -452,7 +452,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = URLShare/URLShare.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = 34; DEVELOPMENT_TEAM = 8J69P655GN; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = URLShare/Info.plist; @@ -485,7 +485,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = URLShare/URLShare.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = 34; DEVELOPMENT_TEAM = 8J69P655GN; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = URLShare/Info.plist; @@ -640,7 +640,7 @@ CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = 34; DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\""; DEVELOPMENT_TEAM = 8J69P655GN; ENABLE_HARDENED_RUNTIME = YES; @@ -684,7 +684,7 @@ CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = 34; DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\""; DEVELOPMENT_TEAM = 8J69P655GN; ENABLE_HARDENED_RUNTIME = YES; diff --git a/readeck/UI/Resources/RELEASE_NOTES.md b/readeck/UI/Resources/RELEASE_NOTES.md index 559c38f..738cfeb 100644 --- a/readeck/UI/Resources/RELEASE_NOTES.md +++ b/readeck/UI/Resources/RELEASE_NOTES.md @@ -23,6 +23,15 @@ Thanks for using the Readeck iOS app! Below are the release notes for each versi - Better performance when working with many labels - Improved overall app stability +### Settings Redesign + +- **Completely redesigned settings screen** with native iOS style +- Font settings moved to dedicated screen with larger preview +- Reorganized sections for better overview +- Inline explanations directly under settings +- Cleaner app info footer with muted styling +- Combined legal, privacy and support into one section + ### Fixes & Improvements - Better color consistency throughout the app diff --git a/readeck/UI/Settings/AppearanceSettingsView.swift b/readeck/UI/Settings/AppearanceSettingsView.swift index a423b69..ce09698 100644 --- a/readeck/UI/Settings/AppearanceSettingsView.swift +++ b/readeck/UI/Settings/AppearanceSettingsView.swift @@ -28,48 +28,17 @@ struct AppearanceSettingsView: View { var body: some View { Group { Section { - // Font Family - Picker("Font family", selection: $fontViewModel.selectedFontFamily) { - ForEach(FontFamily.allCases, id: \.self) { family in - Text(family.displayName).tag(family) + // Font Settings als NavigationLink + NavigationLink { + FontSelectionView(viewModel: fontViewModel) + } label: { + HStack { + Text("Font") + Spacer() + Text("\(fontViewModel.selectedFontFamily.displayName) · \(fontViewModel.selectedFontSize.displayName)") + .foregroundColor(.secondary) } } - .onChange(of: fontViewModel.selectedFontFamily) { - Task { - await fontViewModel.saveFontSettings() - } - } - - // Font Size - Picker("Font size", selection: $fontViewModel.selectedFontSize) { - ForEach(FontSize.allCases, id: \.self) { size in - Text(size.displayName).tag(size) - } - } - .pickerStyle(.segmented) - .onChange(of: fontViewModel.selectedFontSize) { - Task { - await fontViewModel.saveFontSettings() - } - } - - // Font Preview - direkt in der gleichen Section - VStack(alignment: .leading, spacing: 6) { - Text("readeck Bookmark Title") - .font(fontViewModel.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(fontViewModel.previewBodyFont) - .lineLimit(3) - - Text("12 min • Today • example.com") - .font(fontViewModel.previewCaptionFont) - .foregroundColor(.secondary) - } - .padding(.vertical, 4) - .listRowBackground(Color(.systemGray6)) // Theme Picker (Menu statt Segmented) Picker("Theme", selection: $selectedTheme) { @@ -97,30 +66,42 @@ struct AppearanceSettingsView: View { } // Open external links in - Picker("Open links in", selection: $generalViewModel.urlOpener) { - ForEach(UrlOpener.allCases, id: \.self) { urlOpener in - Text(urlOpener.displayName).tag(urlOpener) + VStack(alignment: .leading, spacing: 4) { + Picker("Open links in", selection: $generalViewModel.urlOpener) { + ForEach(UrlOpener.allCases, id: \.self) { urlOpener in + Text(urlOpener.displayName).tag(urlOpener) + } } - } - .onChange(of: generalViewModel.urlOpener) { - Task { - await generalViewModel.saveGeneralSettings() + .onChange(of: generalViewModel.urlOpener) { + Task { + await generalViewModel.saveGeneralSettings() + } } + + Text("Choose where external links should open: In-App Browser keeps you in readeck, Default Browser opens in Safari or your default browser.") + .font(.caption) + .foregroundColor(.secondary) + .padding(.top, 2) } // Tag Sort Order - Picker("Tag sort order", selection: $selectedTagSortOrder) { - ForEach(TagSortOrder.allCases, id: \.self) { sortOrder in - Text(sortOrder.displayName).tag(sortOrder) + VStack(alignment: .leading, spacing: 4) { + Picker("Tag sort order", selection: $selectedTagSortOrder) { + ForEach(TagSortOrder.allCases, id: \.self) { sortOrder in + Text(sortOrder.displayName).tag(sortOrder) + } } - } - .onChange(of: selectedTagSortOrder) { - saveTagSortOrderSettings() + .onChange(of: selectedTagSortOrder) { + saveTagSortOrderSettings() + } + + Text("Determines how tags are displayed when adding or editing bookmarks.") + .font(.caption) + .foregroundColor(.secondary) + .padding(.top, 2) } } header: { Text("Appearance") - } footer: { - Text("Choose where external links should open: In-App Browser keeps you in readeck, Default Browser opens in Safari or your default browser.\n\nTag sort order determines how tags are displayed when adding or editing bookmarks.") } } .task { diff --git a/readeck/UI/Settings/FontSelectionView.swift b/readeck/UI/Settings/FontSelectionView.swift new file mode 100644 index 0000000..b6df041 --- /dev/null +++ b/readeck/UI/Settings/FontSelectionView.swift @@ -0,0 +1,105 @@ +// +// FontSelectionView.swift +// readeck +// +// Created by Ilyas Hallak on 08.11.25. +// + +import SwiftUI + +struct FontSelectionView: View { + @State private var viewModel: FontSettingsViewModel + @Environment(\.dismiss) private var dismiss + + init(viewModel: FontSettingsViewModel = FontSettingsViewModel()) { + self.viewModel = viewModel + } + + var body: some View { + List { + // Preview Section + Section { + VStack(alignment: .leading, spacing: 12) { + Text("readeck Bookmark Title") + .font(viewModel.previewTitleFont) + .fontWeight(.semibold) + .lineLimit(2) + + Text("This is how your bookmark descriptions and article text will appear in the app. The quick brown fox jumps over the lazy dog. Lorem ipsum dolor sit amet, consectetur adipiscing elit.") + .font(viewModel.previewBodyFont) + .lineLimit(4) + + Text("12 min • Today • example.com") + .font(viewModel.previewCaptionFont) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) + .background(Color(.systemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(color: Color.black.opacity(0.08), radius: 8, x: 0, y: 2) + .listRowInsets(EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)) + .listRowBackground(Color.clear) + } header: { + Text("Preview") + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.primary) + .textCase(nil) + } + + // Font Settings Section + Section { + Picker("Font family", selection: $viewModel.selectedFontFamily) { + ForEach(FontFamily.allCases, id: \.self) { family in + Text(family.displayName).tag(family) + } + } + .onChange(of: viewModel.selectedFontFamily) { + Task { + await viewModel.saveFontSettings() + } + } + + VStack(alignment: .leading, spacing: 8) { + Text("Font size") + .font(.subheadline) + .foregroundColor(.primary) + + Picker("Font size", selection: $viewModel.selectedFontSize) { + ForEach(FontSize.allCases, id: \.self) { size in + Text(size.displayName).tag(size) + } + } + .pickerStyle(.segmented) + .onChange(of: viewModel.selectedFontSize) { + Task { + await viewModel.saveFontSettings() + } + } + } + .padding(.vertical, 4) + } header: { + Text("Font Settings") + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.primary) + .textCase(nil) + } + } + .listStyle(.insetGrouped) + .navigationTitle("Font") + .navigationBarTitleDisplayMode(.inline) + .task { + await viewModel.loadFontSettings() + } + } +} + +#Preview { + NavigationStack { + FontSelectionView(viewModel: .init( + factory: MockUseCaseFactory() + )) + } +} diff --git a/readeck/UI/Settings/LegalPrivacySettingsView.swift b/readeck/UI/Settings/LegalPrivacySettingsView.swift index aae51df..7c0b35e 100644 --- a/readeck/UI/Settings/LegalPrivacySettingsView.swift +++ b/readeck/UI/Settings/LegalPrivacySettingsView.swift @@ -3,10 +3,26 @@ import SwiftUI struct LegalPrivacySettingsView: View { @State private var showingPrivacyPolicy = false @State private var showingLegalNotice = false + @State private var showReleaseNotes = false var body: some View { Group { Section { + Button(action: { + showReleaseNotes = true + }) { + HStack { + Text("What's New") + Spacer() + Text("Version \(VersionManager.shared.currentVersion)") + .font(.caption) + .foregroundColor(.secondary) + Image(systemName: "chevron.right") + .font(.caption) + .foregroundColor(.secondary) + } + } + Button(action: { showingPrivacyPolicy = true }) { @@ -30,11 +46,7 @@ struct LegalPrivacySettingsView: View { .foregroundColor(.secondary) } } - } header: { - Text("Legal & Privacy") - } - Section { Button(action: { if let url = URL(string: "https://github.com/ilyas-hallak/readeck-ios/issues") { UIApplication.shared.open(url) @@ -63,7 +75,7 @@ struct LegalPrivacySettingsView: View { } } } header: { - Text("Support") + Text("Legal, Privacy & Support") } } .sheet(isPresented: $showingPrivacyPolicy) { @@ -72,6 +84,9 @@ struct LegalPrivacySettingsView: View { .sheet(isPresented: $showingLegalNotice) { LegalNoticeView() } + .sheet(isPresented: $showReleaseNotes) { + ReleaseNotesView() + } } } diff --git a/readeck/UI/Settings/ReadingSettingsView.swift b/readeck/UI/Settings/ReadingSettingsView.swift new file mode 100644 index 0000000..1cb03c2 --- /dev/null +++ b/readeck/UI/Settings/ReadingSettingsView.swift @@ -0,0 +1,55 @@ +// +// ReadingSettingsView.swift +// readeck +// +// Created by Ilyas Hallak on 08.11.25. +// + +import SwiftUI + +struct ReadingSettingsView: View { + @State private var viewModel: SettingsGeneralViewModel + + init(viewModel: SettingsGeneralViewModel = SettingsGeneralViewModel()) { + self.viewModel = viewModel + } + + var body: some View { + Group { + Section { + VStack(alignment: .leading, spacing: 4) { + Toggle("Read Aloud Feature", isOn: $viewModel.enableTTS) + .onChange(of: viewModel.enableTTS) { + Task { + await viewModel.saveGeneralSettings() + } + } + + Text("Activate the Read Aloud Feature to read aloud your articles. This is a really early preview and might not work perfectly.") + .font(.caption) + .foregroundColor(.secondary) + .padding(.top, 2) + } + + #if DEBUG + Toggle("Safari Reader Mode", isOn: $viewModel.enableReaderMode) + Toggle("Automatically mark articles as read", isOn: $viewModel.autoMarkAsRead) + #endif + } header: { + Text("Reading Settings") + } + } + .task { + await viewModel.loadGeneralSettings() + } + } +} + +#Preview { + List { + ReadingSettingsView(viewModel: .init( + MockUseCaseFactory() + )) + } + .listStyle(.insetGrouped) +} diff --git a/readeck/UI/Settings/SettingsContainerView.swift b/readeck/UI/Settings/SettingsContainerView.swift index 01425ea..f466ee8 100644 --- a/readeck/UI/Settings/SettingsContainerView.swift +++ b/readeck/UI/Settings/SettingsContainerView.swift @@ -19,9 +19,11 @@ struct SettingsContainerView: View { List { AppearanceSettingsView() + ReadingSettingsView() + CacheSettingsView() - SettingsGeneralView() + SyncSettingsView() SettingsServerView() @@ -80,39 +82,42 @@ struct SettingsContainerView: View { @ViewBuilder private var appInfoSection: some View { Section { - VStack(spacing: 8) { - HStack(spacing: 8) { + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 6) { Image(systemName: "info.circle") + .font(.caption) .foregroundColor(.secondary) Text("Version \(appVersion)") - .font(.footnote) + .font(.caption) .foregroundColor(.secondary) } HStack(spacing: 4) { Image(systemName: "person.crop.circle") + .font(.caption) .foregroundColor(.secondary) Text("Developer:") - .font(.footnote) + .font(.caption) .foregroundColor(.secondary) Button("Ilyas Hallak") { if let url = URL(string: "https://ilyashallak.de") { UIApplication.shared.open(url) } } - .font(.footnote) - .foregroundColor(.blue) + .font(.caption) } - HStack(spacing: 8) { + HStack(spacing: 6) { Image(systemName: "globe") + .font(.caption) .foregroundColor(.secondary) Text("From Bremen with 💚") - .font(.footnote) + .font(.caption) .foregroundColor(.secondary) } } - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity, alignment: .center) + .listRowBackground(Color.clear) .padding(.vertical, 8) } } diff --git a/readeck/UI/Settings/SyncSettingsView.swift b/readeck/UI/Settings/SyncSettingsView.swift new file mode 100644 index 0000000..f8a49df --- /dev/null +++ b/readeck/UI/Settings/SyncSettingsView.swift @@ -0,0 +1,64 @@ +// +// SyncSettingsView.swift +// readeck +// +// Created by Ilyas Hallak on 08.11.25. +// + +import SwiftUI + +struct SyncSettingsView: View { + @State private var viewModel: SettingsGeneralViewModel + + init(viewModel: SettingsGeneralViewModel = SettingsGeneralViewModel()) { + self.viewModel = viewModel + } + + var body: some View { + Group { + #if DEBUG + Section { + Toggle("Automatic sync", isOn: $viewModel.autoSyncEnabled) + if viewModel.autoSyncEnabled { + Stepper("Sync interval: \(viewModel.syncInterval) minutes", value: $viewModel.syncInterval, in: 1...60) + } + } header: { + Text("Sync Settings") + } + + if let successMessage = viewModel.successMessage { + Section { + HStack { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + Text(successMessage) + .foregroundColor(.green) + } + } + } + if let errorMessage = viewModel.errorMessage { + Section { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) + Text(errorMessage) + .foregroundColor(.red) + } + } + } + #endif + } + .task { + await viewModel.loadGeneralSettings() + } + } +} + +#Preview { + List { + SyncSettingsView(viewModel: .init( + MockUseCaseFactory() + )) + } + .listStyle(.insetGrouped) +}