First Commit

This commit is contained in:
2026-03-20 19:34:06 +01:00
parent 4c5415b4b8
commit 677b927edf
35 changed files with 5064 additions and 10 deletions
+191
View File
@@ -0,0 +1,191 @@
import SwiftUI
import SafariServices
struct SettingsView: View {
@AppStorage("onboardingComplete") private var onboardingComplete = false
@AppStorage("syncWiFiOnly") private var syncWiFiOnly = true
@AppStorage("showComments") private var showComments = true
@AppStorage("appTheme") private var appTheme = "system"
@State private var serverURL = UserDefaults.standard.string(forKey: "serverURL") ?? ""
@State private var showSignOutAlert = false
@State private var isSyncing = false
@State private var lastSynced = UserDefaults.standard.object(forKey: "lastSynced") as? Date
@State private var showSafari: URL? = nil
@State private var selectedLanguage: LanguageManager.Language = LanguageManager.shared.current
private let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0"
private let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
var body: some View {
NavigationStack {
Form {
// Language section
Section {
ForEach(LanguageManager.Language.allCases) { lang in
Button {
selectedLanguage = lang
LanguageManager.shared.set(lang)
} label: {
HStack {
Text(lang.flag)
Text(lang.displayName)
.foregroundStyle(.primary)
Spacer()
if selectedLanguage == lang {
Image(systemName: "checkmark")
.foregroundStyle(.blue)
}
}
}
}
} header: {
Text(L("settings.language.header"))
}
// Appearance section
Section(L("settings.appearance")) {
Picker(L("settings.appearance.theme"), selection: $appTheme) {
Text(L("settings.appearance.theme.system")).tag("system")
Text(L("settings.appearance.theme.light")).tag("light")
Text(L("settings.appearance.theme.dark")).tag("dark")
}
.pickerStyle(.segmented)
}
// Account section
Section(L("settings.account")) {
HStack {
Image(systemName: "person.circle.fill")
.font(.title)
.foregroundStyle(.blue)
VStack(alignment: .leading) {
Text(L("settings.account.connected"))
.font(.headline)
Text(serverURL)
.font(.footnote)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
.padding(.vertical, 4)
Button {
UIPasteboard.general.string = serverURL
} label: {
Label(L("settings.account.copyurl"), systemImage: "doc.on.doc")
}
Button(role: .destructive) {
showSignOutAlert = true
} label: {
Label(L("settings.account.signout"), systemImage: "rectangle.portrait.and.arrow.right")
}
}
// Reader section
Section(L("settings.reader")) {
Toggle(L("settings.reader.showcomments"), isOn: $showComments)
}
// Sync section
Section(L("settings.sync")) {
Toggle(L("settings.sync.wifionly"), isOn: $syncWiFiOnly)
Button {
Task { await syncNow() }
} label: {
HStack {
Label(L("settings.sync.now"), systemImage: "arrow.clockwise")
if isSyncing {
Spacer()
ProgressView()
}
}
}
.disabled(isSyncing)
if let lastSynced {
LabeledContent(L("settings.sync.lastsynced")) {
Text(lastSynced.bookStackFormattedWithTime)
.foregroundStyle(.secondary)
}
}
}
// About section
Section(L("settings.about")) {
LabeledContent(L("settings.about.version"), value: "\(appVersion) (\(buildNumber))")
Button {
showSafari = URL(string: "https://www.bookstackapp.com/docs")
} label: {
Label(L("settings.about.docs"), systemImage: "book.pages")
}
Button {
showSafari = URL(string: "https://github.com/BookStackApp/BookStack/issues")
} label: {
Label(L("settings.about.issue"), systemImage: "exclamationmark.bubble")
}
Text(L("settings.about.credit"))
.font(.footnote)
.foregroundStyle(.secondary)
}
}
.navigationTitle(L("settings.title"))
.alert(L("settings.signout.alert.title"), isPresented: $showSignOutAlert) {
Button(L("settings.signout.alert.confirm"), role: .destructive) { signOut() }
Button(L("settings.signout.alert.cancel"), role: .cancel) {}
} message: {
Text(L("settings.signout.alert.message"))
}
.sheet(item: $showSafari) { url in
SafariView(url: url)
.ignoresSafeArea()
}
}
}
// MARK: - Actions
private func signOut() {
Task {
try? await KeychainService.shared.deleteCredentials()
UserDefaults.standard.removeObject(forKey: "serverURL")
UserDefaults.standard.removeObject(forKey: "lastSynced")
onboardingComplete = false
}
}
private func syncNow() async {
isSyncing = true
// SyncService.shared.syncAll() requires ModelContext from environment
// For now just update last synced date
try? await Task.sleep(for: .seconds(1))
let now = Date()
UserDefaults.standard.set(now, forKey: "lastSynced")
lastSynced = now
isSyncing = false
}
}
// MARK: - Safari View
struct SafariView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: Context) -> SFSafariViewController {
SFSafariViewController(url: url)
}
func updateUIViewController(_ vc: SFSafariViewController, context: Context) {}
}
extension URL: @retroactive Identifiable {
public var id: String { absoluteString }
}
#Preview {
SettingsView()
}