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
This commit is contained in:
parent
f3719fa9d4
commit
4b788650b8
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
.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)
|
||||
// Font Settings als NavigationLink
|
||||
NavigationLink {
|
||||
FontSelectionView(viewModel: fontViewModel)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Font")
|
||||
Spacer()
|
||||
Text("\(fontViewModel.selectedFontFamily.displayName) · \(fontViewModel.selectedFontSize.displayName)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
.listRowBackground(Color(.systemGray6))
|
||||
}
|
||||
|
||||
// Theme Picker (Menu statt Segmented)
|
||||
Picker("Theme", selection: $selectedTheme) {
|
||||
@ -97,6 +66,7 @@ struct AppearanceSettingsView: View {
|
||||
}
|
||||
|
||||
// Open external links in
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Picker("Open links in", selection: $generalViewModel.urlOpener) {
|
||||
ForEach(UrlOpener.allCases, id: \.self) { urlOpener in
|
||||
Text(urlOpener.displayName).tag(urlOpener)
|
||||
@ -108,7 +78,14 @@ struct AppearanceSettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Picker("Tag sort order", selection: $selectedTagSortOrder) {
|
||||
ForEach(TagSortOrder.allCases, id: \.self) { sortOrder in
|
||||
Text(sortOrder.displayName).tag(sortOrder)
|
||||
@ -117,10 +94,14 @@ struct AppearanceSettingsView: View {
|
||||
.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 {
|
||||
|
||||
105
readeck/UI/Settings/FontSelectionView.swift
Normal file
105
readeck/UI/Settings/FontSelectionView.swift
Normal file
@ -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()
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
readeck/UI/Settings/ReadingSettingsView.swift
Normal file
55
readeck/UI/Settings/ReadingSettingsView.swift
Normal file
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
64
readeck/UI/Settings/SyncSettingsView.swift
Normal file
64
readeck/UI/Settings/SyncSettingsView.swift
Normal file
@ -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)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user