@@ -12,17 +12,23 @@ import DiscordKitCore
1212struct SettingsView : View {
1313 @EnvironmentObject var gateway : DiscordGateway
1414
15+ @AppStorage ( " local.newSettingsUI " ) private var newUI = true
16+
1517 var body : some View {
1618 if let user = gateway. cache. user {
17- SettingsWithUserView ( user: user)
19+ if #available( macOS 13 . 0 , * ) , newUI {
20+ ModernSettings ( user: user)
21+ } else {
22+ LegacySettings ( user: user)
23+ }
1824 } else {
1925 NoGatewayView ( )
2026 }
2127 }
2228}
2329
2430private extension SettingsView {
25- struct SettingsWithUserView : View {
31+ struct LegacySettings : View {
2632 let user : CurrentUser
2733
2834 var body : some View {
@@ -50,6 +56,150 @@ private extension SettingsView {
5056 . frame ( width: 900 , height: 600 )
5157 }
5258 }
59+ @available ( macOS 13 , * )
60+ struct ModernSettings : View {
61+ let user : CurrentUser
62+
63+ private struct Page : Hashable , Identifiable {
64+ internal init ( _ name: Name , icon: Icon ? = nil , children: [ SettingsView . ModernSettings . Page ] = [ ] ) {
65+ self . children = children
66+ self . name = name
67+ self . icon = icon
68+ }
69+
70+ var id : String { name. rawValue }
71+
72+ let children : [ Page ]
73+ let name : Name
74+ var nameString : LocalizedStringKey { LocalizedStringKey ( name. rawValue) }
75+ let icon : Icon ?
76+
77+ struct Icon : Hashable {
78+ let baseColor : Color
79+ let icon : String
80+ }
81+
82+ enum Name : String {
83+ // MARK: User Settings
84+ case userSection = " User Settings "
85+ case account = " My Account "
86+ case profile = " User Profile "
87+ case privacy = " Privacy & Safety "
88+ case apps = " Authorized Apps "
89+ case connections = " Connections "
90+ case logOut = " Log Out "
91+ // MARK: Payment Settings
92+ case paymentSection = " Payment Settings "
93+ case nitro = " Nitro "
94+ case boost = " Server Boost "
95+ case subscriptions = " Subscriptions "
96+ case gift = " Gift Inventory "
97+ case billing = " Billing "
98+ // MARK: App Settings
99+ case appSection = " App Settings "
100+ case appearance = " settings.app.appearance "
101+ case accessibility = " settings.app.accessibility "
102+ case voiceVideo = " settings.app.voiceVideo "
103+ case textImages = " settings.app.textImages "
104+ case notifs = " settings.app.notifs "
105+ case keybinds = " settings.app.keybinds "
106+ case lang = " settings.app.lang "
107+ case streamer = " settings.app.streamer "
108+ case advanced = " settings.app.advanced "
109+ }
110+ }
111+ private static let pages : [ Page ] = [
112+ . init( . userSection, children: [
113+ . init( . account, icon: . init( baseColor: . blue, icon: " person.fill " ) ) ,
114+ . init( . profile, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
115+ . init( . privacy, icon: . init( baseColor: . red, icon: " shield.lefthalf.filled " ) )
116+ ] ) ,
117+ . init( . paymentSection, children: [
118+ . init( . nitro, icon: . init( baseColor: . accentColor, icon: " person.crop.circle " ) ) ,
119+ . init( . boost, icon: . init( baseColor: . green, icon: " person.crop.circle " ) ) ,
120+ . init( . subscriptions, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
121+ . init( . gift, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
122+ . init( . billing, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) )
123+ ] ) ,
124+ . init( . appSection, children: [
125+ . init( . appearance, icon: . init( baseColor: . black, icon: " person.crop.circle " ) ) ,
126+ . init( . accessibility, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
127+ . init( . voiceVideo, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
128+ . init( . textImages, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
129+ . init( . notifs, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
130+ . init( . keybinds, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
131+ . init( . lang, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
132+ . init( . streamer, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) ) ,
133+ . init( . advanced, icon: . init( baseColor: . blue, icon: " person.crop.circle " ) )
134+ ] )
135+ ]
136+
137+ @State private var selectedPage = pages. first!. children. first!
138+ @State private var filter = " "
139+
140+ @ViewBuilder
141+ private func navigationItem( item: Page ) -> some View {
142+ if filter. isEmpty || item. name. rawValue. lowercased ( ) . contains ( filter. lowercased ( ) ) {
143+ NavigationLink ( value: item) {
144+ if item. name == . account {
145+ HStack {
146+ BetterImageView ( url: user. avatarURL ( size: 160 ) )
147+ . frame ( width: 40 , height: 40 )
148+ . clipShape ( Circle ( ) )
149+ VStack ( alignment: . leading) {
150+ Text ( user. username) . font ( . headline)
151+ Text ( " Discord Account " ) . font ( . caption)
152+ }
153+ }
154+ } else {
155+ Label {
156+ Text ( item. nameString)
157+ } icon: {
158+ if let icon = item. icon {
159+ Image ( systemName: icon. icon)
160+ . foregroundColor ( . primary)
161+ . frame ( width: 20 , height: 20 )
162+ . background ( RoundedRectangle ( cornerRadius: 5 , style: . continuous) . fill ( icon. baseColor. gradient) )
163+ } else {
164+ EmptyView ( )
165+ }
166+ }
167+ }
168+ }
169+ }
170+ }
171+
172+ var body : some View {
173+ NavigationSplitView {
174+ List ( Self . pages, selection: $selectedPage) { category in
175+ Section ( category. nameString) {
176+ ForEach ( category. children) { child in
177+ navigationItem ( item: child)
178+ }
179+ }
180+ } . frame ( idealWidth: 215 )
181+ } detail: {
182+ ScrollView {
183+ Group {
184+ switch selectedPage. name {
185+ case . account:
186+ UserSettingsAccountView ( user: user)
187+ case . profile:
188+ UserSettingsProfileView ( user: user)
189+ case . privacy:
190+ UserSettingsPrivacySafetyView ( )
191+ case . advanced:
192+ AppSettingsAdvancedView ( )
193+ default :
194+ Text ( " Unimplemented view: \( selectedPage. name. rawValue) " )
195+ }
196+ } . padding ( 20 )
197+ }
198+ }
199+ . searchable ( text: $filter, placement: . sidebar)
200+ . navigationTitle ( selectedPage. nameString)
201+ }
202+ }
53203
54204 struct NoGatewayView : View {
55205 var body : some View {
0 commit comments