@@ -6,56 +6,92 @@ struct SettingsView: View {
66 @Environment ( \. dismiss) private var dismiss
77
88 var body : some View {
9- VStack ( alignment: . leading, spacing: 16 ) {
10- Text ( " Settings " )
11- . font ( . title2. bold ( ) )
9+ VStack ( spacing: 0 ) {
10+ header
1211
1312 ScrollView {
1413 VStack ( alignment: . leading, spacing: 16 ) {
15- AccountSettingsSection ( model: model)
14+ if !model. config. accounts. isEmpty {
15+ AccountSettingsSection ( model: model)
16+ }
1617 GeneralSettingsSection ( model: model)
1718 AlertSettingsSection ( model: model)
1819 InsightSettingsSection ( model: model)
1920 UpdateSettingsSection ( model: model)
2021 LoginSettingsSection ( model: model, dismiss: dismiss)
2122 DiagnosticsSettingsSection ( model: model)
2223 }
24+ . padding ( 20 )
25+ . frame ( maxWidth: . infinity, alignment: . leading)
2326 }
27+ . frame ( maxWidth: . infinity, maxHeight: . infinity)
2428
2529 if let error = model. settingsError {
2630 Text ( error)
2731 . font ( . caption)
2832 . foregroundStyle ( . red)
2933 . lineLimit ( 3 )
34+ . frame ( maxWidth: . infinity, alignment: . leading)
35+ . padding ( . horizontal, 20 )
36+ . padding ( . vertical, 10 )
37+ . background ( Color . red. opacity ( 0.08 ) )
3038 }
3139
40+ Divider ( )
41+ footer
42+ }
43+ . frame ( width: 520 , height: 680 )
44+ . background ( PanelBackground ( ) )
45+ }
46+
47+ private var header : some View {
48+ HStack ( spacing: 12 ) {
49+ Image ( systemName: " gearshape.fill " )
50+ . font ( . system( size: 18 , weight: . semibold) )
51+ . foregroundStyle ( . blue)
52+ . frame ( width: 34 , height: 34 )
53+ . background ( Color . blue. opacity ( 0.13 ) , in: RoundedRectangle ( cornerRadius: 8 ) )
54+
55+ Text ( " Settings " )
56+ . font ( . title2. bold ( ) )
57+
3258 Spacer ( )
59+ }
60+ . padding ( . horizontal, 20 )
61+ . padding ( . vertical, 16 )
62+ . background ( PanelColors . elevatedSurface. opacity ( 0.88 ) )
63+ . overlay ( alignment: . bottom) {
64+ Rectangle ( )
65+ . fill ( PanelColors . border)
66+ . frame ( height: 1 )
67+ }
68+ }
3369
34- HStack {
35- Spacer ( )
36- Button ( " Cancel " ) {
37- dismiss ( )
38- }
39- Button ( " Save " ) {
40- model. saveSettings ( )
41- dismiss ( )
42- }
43- . buttonStyle ( . borderedProminent)
70+ private var footer : some View {
71+ HStack {
72+ Spacer ( )
73+ Button ( " Cancel " ) {
74+ dismiss ( )
75+ }
76+ Button ( " Save " ) {
77+ model. saveSettings ( )
78+ dismiss ( )
4479 }
80+ . buttonStyle ( . borderedProminent)
4581 }
46- . padding ( 20 )
82+ . padding ( . horizontal, 20 )
83+ . padding ( . vertical, 14 )
84+ . background ( PanelColors . elevatedSurface. opacity ( 0.92 ) )
4785 }
4886}
4987
5088struct AccountSettingsSection : View {
5189 @ObservedObject var model : MonitorViewModel
5290
5391 var body : some View {
54- if !model. config. accounts. isEmpty {
55- VStack ( alignment: . leading, spacing: 8 ) {
56- Text ( " Accounts " )
57- . font ( . headline)
58- Picker ( " Active Account " , selection: Binding (
92+ SettingsSection ( title: " Accounts " ) {
93+ SettingsControlRow ( title: " Active Account " ) {
94+ Picker ( " " , selection: Binding (
5995 get: { model. config. selectedAccountID ?? " " } ,
6096 set: { id in
6197 if let account = model. config. accounts. first ( where: { $0. id == id } ) {
@@ -68,6 +104,8 @@ struct AccountSettingsSection: View {
68104 Text ( account. displayName) . tag ( account. id)
69105 }
70106 }
107+ . labelsHidden ( )
108+ . frame ( maxWidth: . infinity, alignment: . leading)
71109 }
72110 }
73111 }
@@ -77,14 +115,12 @@ struct GeneralSettingsSection: View {
77115 @ObservedObject var model : MonitorViewModel
78116
79117 var body : some View {
80- VStack ( alignment: . leading, spacing: 8 ) {
81- Text ( " General " )
82- . font ( . headline)
83-
118+ SettingsSection ( title: " General " ) {
84119 VStack ( spacing: 8 ) {
85120 SettingsControlRow ( title: " Base URL " ) {
86121 TextField ( " https://sub2api.example.com " , text: $model. settingsDraft. baseURL)
87122 . textFieldStyle ( . roundedBorder)
123+ . frame ( maxWidth: . infinity)
88124 }
89125
90126 SettingsControlRow ( title: " Menu Bar " ) {
@@ -98,7 +134,9 @@ struct GeneralSettingsSection: View {
98134 }
99135 }
100136 . pickerStyle ( . segmented)
137+ . controlSize ( . small)
101138 . disabled ( !model. settingsDraft. showsMenuBarText)
139+ . frame ( maxWidth: . infinity)
102140 }
103141
104142 SettingsControlRow ( title: " Startup " ) {
@@ -110,6 +148,7 @@ struct GeneralSettingsSection: View {
110148
111149 SettingsControlRow ( title: " Refresh " ) {
112150 Slider ( value: $model. settingsDraft. refreshIntervalSeconds, in: 5 ... 300 , step: 5 )
151+ . frame ( maxWidth: . infinity)
113152 Text ( " \( Int ( model. settingsDraft. refreshIntervalSeconds) ) s " )
114153 . font ( . callout. monospacedDigit ( ) )
115154 . frame ( width: 42 , alignment: . trailing)
@@ -118,6 +157,7 @@ struct GeneralSettingsSection: View {
118157 SettingsControlRow ( title: " Token " ) {
119158 SecureField ( " Bearer Token " , text: $model. settingsDraft. authToken)
120159 . textFieldStyle ( . roundedBorder)
160+ . frame ( maxWidth: . infinity)
121161 }
122162 }
123163
@@ -132,10 +172,7 @@ struct AlertSettingsSection: View {
132172 @ObservedObject var model : MonitorViewModel
133173
134174 var body : some View {
135- VStack ( alignment: . leading, spacing: 8 ) {
136- Text ( " Alerts " )
137- . font ( . headline)
138-
175+ SettingsSection ( title: " Alerts " ) {
139176 VStack ( spacing: 8 ) {
140177 SettingsControlRow ( title: " Notify " ) {
141178 Toggle ( " Usage insights " , isOn: $model. settingsDraft. insightAlertSettings. isEnabled)
@@ -147,10 +184,13 @@ struct AlertSettingsSection: View {
147184 Text ( " Error only " ) . tag ( MonitorSeverity . error)
148185 }
149186 . pickerStyle ( . segmented)
187+ . controlSize ( . small)
188+ . frame ( maxWidth: 320 , alignment: . leading)
150189 }
151190
152191 SettingsControlRow ( title: " Quiet " ) {
153192 Slider ( value: $model. settingsDraft. insightAlertSettings. cooldownMinutes, in: 5 ... 360 , step: 5 )
193+ . frame ( maxWidth: . infinity)
154194 Text ( " \( Int ( model. settingsDraft. insightAlertSettings. cooldownMinutes) ) m " )
155195 . font ( . callout. monospacedDigit ( ) )
156196 . frame ( width: 48 , alignment: . trailing)
@@ -166,10 +206,7 @@ struct InsightSettingsSection: View {
166206 @ObservedObject var model : MonitorViewModel
167207
168208 var body : some View {
169- VStack ( alignment: . leading, spacing: 8 ) {
170- Text ( " Insights " )
171- . font ( . headline)
172-
209+ SettingsSection ( title: " Insights " ) {
173210 VStack ( spacing: 8 ) {
174211 ThresholdSliderRow (
175212 title: " Quota warn " ,
@@ -202,6 +239,7 @@ struct InsightSettingsSection: View {
202239 format: . number. precision ( . fractionLength( 0 ... 2 ) )
203240 )
204241 . textFieldStyle ( . roundedBorder)
242+ . frame ( maxWidth: . infinity)
205243 Text ( " USD " )
206244 . font ( . callout)
207245 . foregroundStyle ( . secondary)
@@ -341,6 +379,7 @@ struct ThresholdSliderRow: View {
341379 var body : some View {
342380 SettingsControlRow ( title: title) {
343381 Slider ( value: $value, in: range, step: step)
382+ . frame ( maxWidth: . infinity)
344383 Text ( valueText)
345384 . font ( . callout. monospacedDigit ( ) )
346385 . frame ( width: 48 , alignment: . trailing)
@@ -353,15 +392,36 @@ struct SettingsControlRow<Content: View>: View {
353392 @ViewBuilder let content : Content
354393
355394 var body : some View {
356- HStack ( alignment: . center , spacing: 10 ) {
395+ VStack ( alignment: . leading , spacing: 6 ) {
357396 Text ( title)
358- . font ( . callout )
397+ . font ( . caption . weight ( . medium ) )
359398 . foregroundStyle ( . secondary)
360- . frame ( width: 76 , alignment: . leading)
361399 HStack ( spacing: 8 ) {
362400 content
363401 }
402+ . frame ( maxWidth: . infinity, alignment: . leading)
364403 }
404+ . frame ( maxWidth: . infinity, alignment: . leading)
405+ }
406+ }
407+
408+ struct SettingsSection < Content: View > : View {
409+ let title : String
410+ @ViewBuilder let content : Content
411+
412+ var body : some View {
413+ VStack ( alignment: . leading, spacing: 10 ) {
414+ Text ( title)
415+ . font ( . headline)
416+ content
417+ }
418+ . padding ( 14 )
419+ . frame ( maxWidth: . infinity, alignment: . leading)
420+ . background ( PanelColors . elevatedSurface. opacity ( 0.88 ) , in: RoundedRectangle ( cornerRadius: 8 ) )
421+ . overlay (
422+ RoundedRectangle ( cornerRadius: 8 )
423+ . stroke ( PanelColors . border, lineWidth: 1 )
424+ )
365425 }
366426}
367427
@@ -370,11 +430,11 @@ struct LoginSettingsSection: View {
370430 let dismiss : DismissAction
371431
372432 var body : some View {
373- VStack ( alignment: . leading, spacing: 8 ) {
374- Text ( " Login " )
375- . font ( . headline)
433+ SettingsSection ( title: " Login " ) {
376434 TextField ( " Email " , text: $model. loginEmail)
435+ . textFieldStyle ( . roundedBorder)
377436 SecureField ( " Password " , text: $model. loginPassword)
437+ . textFieldStyle ( . roundedBorder)
378438 Button {
379439 model. loginAndSave ( )
380440 } label: {
@@ -397,9 +457,7 @@ struct DiagnosticsSettingsSection: View {
397457 @ObservedObject var model : MonitorViewModel
398458
399459 var body : some View {
400- VStack ( alignment: . leading, spacing: 8 ) {
401- Text ( " Diagnostics " )
402- . font ( . headline)
460+ SettingsSection ( title: " Diagnostics " ) {
403461 VStack ( alignment: . leading, spacing: 8 ) {
404462 HStack {
405463 Button {
@@ -440,10 +498,8 @@ struct UpdateSettingsSection: View {
440498 @ObservedObject var model : MonitorViewModel
441499
442500 var body : some View {
443- VStack ( alignment : . leading , spacing : 8 ) {
501+ SettingsSection ( title : " Updates " ) {
444502 HStack {
445- Text ( " Updates " )
446- . font ( . headline)
447503 Spacer ( )
448504 if model. isCheckingForUpdates {
449505 ProgressView ( )
0 commit comments