First Commit
This commit is contained in:
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user