Marks .notifications implemented, adds the shared setNotifications save path (per-event subscribe/unsubscribe, sequential calls for toggles that group more than one wire event type) and the master "All notifications" row. Individual event toggles land in follow-up commits.
145 lines
5.5 KiB
Swift
145 lines
5.5 KiB
Swift
import Observation
|
|
|
|
/// Top-level groupings shown as section headers in `SettingsSidebarList`.
|
|
/// `general` holds everything that's ours, not Outline's own settings
|
|
/// categories — labeled "Outpost" so it reads as clearly distinct from the
|
|
/// Outline-sourced groups below it.
|
|
enum SettingsCategory: String, CaseIterable, Identifiable {
|
|
case general
|
|
case account
|
|
case workspace
|
|
case integrationsInstallation
|
|
|
|
var id: String { rawValue }
|
|
|
|
var title: String? {
|
|
switch self {
|
|
case .general: return "Outpost"
|
|
case .account: return "Account"
|
|
case .workspace: return "Workspace"
|
|
case .integrationsInstallation: return "Integrations & Installation"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One entry in the Settings sidebar. Mirrors Outline's own settings
|
|
/// categories (Account/Workspace/Integrations & Installation) so this app's
|
|
/// settings read as a native counterpart to the web app's, plus a `general`
|
|
/// group for things that are ours and don't map onto Outline's structure
|
|
/// (offline/sync, advanced, about, appearance).
|
|
///
|
|
/// Most of the Account/Workspace/Integrations cases are navigation-only for
|
|
/// now — `SettingsView` renders a "Coming Soon" placeholder for anything not
|
|
/// explicitly built yet. Content lands section by section.
|
|
enum SettingsSection: String, CaseIterable, Identifiable, Hashable {
|
|
// General (ours)
|
|
case appearance, offlineSync, advanced, about
|
|
|
|
// Account
|
|
case profile, preferences, notifications, passkeys, apiAccess
|
|
|
|
// Workspace
|
|
case details, authentication, security, ai, members, groups, templates, emojis, applications, shared, links, webhooks, importData, exportData
|
|
|
|
// Integrations & Installation
|
|
case installation
|
|
|
|
var id: String { rawValue }
|
|
|
|
var category: SettingsCategory {
|
|
switch self {
|
|
case .appearance, .offlineSync, .advanced, .about:
|
|
return .general
|
|
case .profile, .preferences, .notifications, .passkeys, .apiAccess:
|
|
return .account
|
|
case .details, .authentication, .security, .ai, .members, .groups, .templates, .emojis, .applications, .shared, .links, .webhooks, .importData, .exportData:
|
|
return .workspace
|
|
case .installation:
|
|
return .integrationsInstallation
|
|
}
|
|
}
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .appearance: return "Appearance"
|
|
case .offlineSync: return "Offline & Sync"
|
|
case .advanced: return "Advanced"
|
|
case .about: return "About"
|
|
case .profile: return "Profile"
|
|
case .preferences: return "Preferences"
|
|
case .notifications: return "Notifications"
|
|
case .passkeys: return "Passkeys"
|
|
case .apiAccess: return "API & Access"
|
|
case .details: return "Details"
|
|
case .authentication: return "Authentication"
|
|
case .security: return "Security"
|
|
case .ai: return "AI"
|
|
case .members: return "Members"
|
|
case .groups: return "Groups"
|
|
case .templates: return "Templates"
|
|
case .emojis: return "Emojis"
|
|
case .applications: return "Applications"
|
|
case .shared: return "Shared"
|
|
case .links: return "Links"
|
|
case .webhooks: return "Webhooks"
|
|
case .importData: return "Import"
|
|
case .exportData: return "Export"
|
|
case .installation: return "Installation"
|
|
}
|
|
}
|
|
|
|
var icon: String {
|
|
switch self {
|
|
case .appearance: return "paintbrush"
|
|
case .offlineSync: return "arrow.triangle.2.circlepath"
|
|
case .advanced: return "wrench.and.screwdriver"
|
|
case .about: return "info.circle"
|
|
case .profile: return "person.crop.circle"
|
|
case .preferences: return "gearshape"
|
|
case .notifications: return "bell"
|
|
case .passkeys: return "key"
|
|
case .apiAccess: return "chevron.left.forwardslash.chevron.right"
|
|
case .details: return "building.2"
|
|
case .authentication: return "lock"
|
|
case .security: return "shield"
|
|
case .ai: return "sparkles"
|
|
case .members: return "person.2"
|
|
case .groups: return "person.3"
|
|
case .templates: return "doc.on.doc"
|
|
case .emojis: return "face.smiling"
|
|
case .applications: return "app.badge"
|
|
case .shared: return "square.and.arrow.up.on.square"
|
|
case .links: return "link"
|
|
case .webhooks: return "bolt.horizontal"
|
|
case .importData: return "square.and.arrow.down"
|
|
case .exportData: return "square.and.arrow.up"
|
|
case .installation: return "shippingbox"
|
|
}
|
|
}
|
|
|
|
/// Everything actually built so far — everything else in Account/
|
|
/// Workspace/Integrations & Installation renders a "Coming Soon"
|
|
/// placeholder until its content is specified and built.
|
|
var isImplemented: Bool {
|
|
switch self {
|
|
case .appearance, .offlineSync, .advanced, .about, .profile, .preferences, .notifications:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Cross-cutting UI state that doesn't belong to any one screen. Lives at
|
|
/// `OutpostApp` (so both `RootView`'s content and its `⌘,` command can reach
|
|
/// it) and is read wherever something needs to open Settings (the profile
|
|
/// menu) or render it — as a swap of the *existing* sidebar/detail panes in
|
|
/// `ContentView_macOS`, not a separate popup window or an overlay that hides
|
|
/// the sidebar.
|
|
@Observable
|
|
@MainActor
|
|
final class AppNavigation {
|
|
var isShowingSettings = false
|
|
var selectedSettingsSection: SettingsSection? = .appearance
|
|
}
|