@@ -3,31 +3,30 @@ import SwiftUI
struct SettingsView : View {
let api : CalendarrAPI
@ AppStorage ( " liquidGlass " ) private var liquidGlass = false
@ AppStorage ( " settingsSync " ) private var settingsSync = false
@ AppStorage ( " cacheMonths " ) private var cacheMonths = 3
@ AppStorage ( " appLanguage " ) private var appLang = " system "
@ AppStorage ( " monthDividerColor " ) private var dividerHex = " #7090c 0 "
@ AppStorage ( " monthLabelColor " ) private var labelHex = " #7090c 0 "
@ AppStorage ( " todayColor " ) private var todayHex = " #4285f 4 "
@ AppStorage ( " monthDividerColor " ) private var dividerHex = " #7090C 0 "
@ AppStorage ( " monthLabelColor " ) private var labelHex = " #7090C 0 "
@ AppStorage ( " todayColor " ) private var todayHex = " #4285F 4 "
@ AppStorage ( " textColor " ) private var textHex = " #FFFFFF "
@ AppStorage ( " backgroundColor " ) private var bgHex = " #000000 "
@ AppStorage ( " lineColor " ) private var lineHex = " #3A3A3C "
@ AppStorage ( " primaryColor " ) private var primaryHex = " #4285f 4 "
@ AppStorage ( " accentColor " ) private var accentHex = " #ea 4335 "
// P r e v i o u s l y s e r v e r - o n l y ; n o w A p p S t o r a g e - b a c k e d s o t h e y p e r s i s t a n d t h e
// c a l e n d a r v i e w s a c t u a l l y a p p l y t h e m .
@ AppStorage ( " lineColor " ) private var lineHex = " #3A3A52 "
@ AppStorage ( " primaryColor " ) private var primaryHex = " #4285F 4 "
@ AppStorage ( " accentColor " ) private var accentHex = " #EA 4335 "
// i O S - o n l y o p a c i t y c o n t r o l s ( d r i v e s e c o n d a r y t e x t / g r i d - l i n e o p a c i t y i n t h e
// c a l e n d a r v i e w s ) ; k e p t d e v i c e - l o c a l , n o t p a r t o f t h e c r o s s - d e v i c e s y n c .
@ AppStorage ( " textContrast " ) private var textContrast = 3
@ AppStorage ( " lineContrast " ) private var lineContrast = 3
@ AppStorage ( " hourHeight " ) private var hourHeight = 60
@ AppStorage ( " defaultView " ) private var defaultView = " month "
@ AppStorage ( " weekStartDay " ) private var weekStartDay = " monday "
@ AppStorage ( " dimPastEvents " ) private var dimPastEvents = false
// D e v i c e - l o c a l : m o n t h v i e w a s h o r i z o n t a l p a g e d ( s w i p e ) v s . s c r o l l f e e d .
@ AppStorage ( " monthViewPaged " ) private var monthViewPaged = false
@ AppStorage ( " defaultReminderMinutes " ) private var defaultReminderMinutes = - 1
@ AppStorage ( " defaultEventDurationMinutes " ) private var defaultEventDurationMinutes = 60
// P r o f i l e c h a p t e r ( s e r v e r - b a c k e d ; l o a d e d o n a p p e a r ) .
// P r o f i l e c h a p t e r ( s e r v e r - b a c k e d ; l o a d e d o n a p p e a r ) . A c c o u n t s e t t i n g s a r e n o t
// p a r t o f t h e p e r - d e v i c e s y n c — t h e y a r e a l w a y s a c c o u n t - w i d e .
@ State private var displayName = " "
@ State private var loginName = " "
@ State private var email = " "
@@ -37,52 +36,127 @@ struct SettingsView: View {
@ State private var directoryHidden = false
@ State private var profileMsg = " "
// P e r - s e t t i n g s y n c f l a g s ( a c c o u n t - w i d e ; r e f r e s h e d f r o m t h e s e r v e r o n p u l l ) .
@ State private var syncFlags : [ String : Bool ] = SettingsSync . flags ( )
// C a n o n i c a l d e f a u l t c o l o u r s ( s i n g l e s o u r c e f o r t h e r e s e t b u t t o n s ) .
private static let defaultHex : [ String : String ] = [
" primaryColor " : " #4285F4 " , " accentColor " : " #EA4335 " , " todayColor " : " #4285F4 " ,
" textColor " : " #FFFFFF " , " backgroundColor " : " #000000 " , " lineColor " : " #3A3A52 " ,
" monthDividerColor " : " #7090C0 " , " monthLabelColor " : " #7090C0 " ,
]
var body : some View {
NavigationStack {
Form {
globalSyncSection
profilSection
privatsphaereSection
termindauerSection
benachrichtigungenSection
geteilterKalenderSection
liquidGlass Section
cacheSection
spracheSection
farbenSection
schriftSection
linienSection
termine Section
ansichtSection
stund enSection
farb enSection
cacheSection
geraetSection
}
. navigationTitle ( L10n . t ( " settings.title " , appLang ) )
. navigationBarTitleDisplayMode ( . large )
}
// R e f l e c t t h e l a t e s t s e r v e r v a l u e s w h e n o p e n i n g t h e s c r e e n .
. task { await SettingsSync . pull ( api : api ) }
// R e f l e c t t h e l a t e s t s e r v e r v a l u e s ( a n d f l a g m a p ) wh e n o p e n i n g t h e s c r e e n .
. task { await SettingsSync . pull ( api : api ) ; syncFlags = SettingsSync . flags ( ) }
. task { await loadProfile ( ) }
// A p p e a r a n c e c h a n g e s u p d a t e w i d g e t s l i v e ; s y n c e d v a l u e s a r e a l s o p u s h e d
// t o t h e s e r v e r ( d e b o u n c e d ) . ` p u s h ` i t se l f d e c i d e s w h a t a c t u a l l y g e t s
// s e n t b a s e d o n t h e s y n c t o g g l e , s o e v e r y c h a n g e c a n s i m p l y c a l l i t .
// V a l u e e d i t s u p d a t e w i d g e t s l i v e ; a sy n c e d v a l u e i s a l s o p u s h e d ( d e b o u n c e d ) .
// ` p u s h ` d e c i d e s w h a t a c t u a l l y g e t s s e n t b a s e d o n e a c h k e y ' s f l a g .
. onChange ( of : primaryHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync . push ( api : api ) }
. onChange ( of : accentHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync . push ( api : api ) }
. onChange ( of : todayHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync . push ( api : api ) }
. onChange ( of : textHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync . push ( api : api ) }
. onChange ( of : bgHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync . push ( api : api ) }
. onChange ( of : lineHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync . push ( api : api ) }
. onChange ( of : dividerHex ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : labelHex ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : textContrast ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : lineContrast ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : dividerHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync. push ( api : api ) }
. onChange ( of : labelHex ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) ; SettingsSync. push ( api : api ) }
. onChange ( of : hourHeight ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : defaultView ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : weekStartDay ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : dimPastEvents ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : monthViewPaged ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : cacheMonths ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : defaultEventDurationMinutes ) { _ , _ in SettingsSync . push ( api : api ) }
. onChange ( of : appLang ) { _ , _ in WidgetStore . republishAppearanceOnly ( ) }
// E n a b l i n g s y n c a d o p t s t h e s e r v e r ' s a p p e a r a n c e ( s e r v e r w i n s ) .
. onChange ( of : settingsSync ) { _ , on in if on { Task { await SettingsSync . pull ( api : api ) } } }
}
// MARK: – P r o f i l
// MARK: – R e u s a b l e r o w b u i l d e r s
// / L e a d i n g p e r - r o w s y n c t o g g l e : l i n k e d ( a c c e n t ) = s y n c e d a c r o s s d e v i c e s .
@ ViewBuilder
private func syncIcon ( _ key : String ) -> some View {
let on = syncFlags [ key ] = = true
Button {
SettingsSync . setSynced ( key , ! on , api : api )
syncFlags = SettingsSync . flags ( )
} label : {
Image ( systemName : on ? " link.circle.fill " : " link.circle " )
. imageScale ( . large )
. foregroundStyle ( on ? Color . accentColor : Color . secondary )
}
. buttonStyle ( . plain )
. accessibilityLabel ( L10n . t ( " settings.sync_this " , appLang ) )
}
private func colorBinding ( _ hex : Binding < String > ) -> Binding < Color > {
Binding ( get : { Color ( hex : hex . wrappedValue ) } , set : { hex . wrappedValue = $0 . toHex ( ) } )
}
@ ViewBuilder
private func colorRow ( _ syncKey : String , _ defaultsKey : String , _ label : String , _ hex : Binding < String > ) -> some View {
HStack ( spacing : 12 ) {
syncIcon ( syncKey )
Text ( label )
Spacer ( )
ColorPicker ( " " , selection : colorBinding ( hex ) , supportsOpacity : false )
. labelsHidden ( )
Text ( hex . wrappedValue . uppercased ( ) )
. font ( . system ( . caption , design : . monospaced ) )
. foregroundStyle ( . secondary )
. frame ( width : 64 , alignment : . trailing )
Button {
hex . wrappedValue = Self . defaultHex [ defaultsKey ] ? ? " #000000 "
} label : {
Image ( systemName : " arrow.uturn.backward " )
}
. buttonStyle ( . plain )
. foregroundStyle ( . secondary )
. accessibilityLabel ( L10n . t ( " settings.reset " , appLang ) )
}
}
// MARK: – G l o b a l s y n c
var globalSyncSection : some View {
Section {
Toggle ( isOn : allSyncedBinding ) {
Label {
VStack ( alignment : . leading , spacing : 2 ) {
Text ( L10n . t ( " settings.sync_all " , appLang ) )
Text ( L10n . t ( " settings.sync_all.desc " , appLang ) )
. font ( . caption ) . foregroundStyle ( . secondary )
}
} icon : {
Image ( systemName : " arrow.triangle.2.circlepath " ) . foregroundStyle ( . teal )
}
}
. tint ( Color . accentColor )
}
}
private var allSyncedBinding : Binding < Bool > {
Binding (
get : { SettingsSync . syncableKeys . allSatisfy { syncFlags [ $0 ] = = true } } ,
set : { on in SettingsSync . setAllSynced ( on , api : api ) ; syncFlags = SettingsSync . flags ( ) }
)
}
// MARK: – P r o f i l ( a c c o u n t i d e n t i t y — n o t s y n c e d )
var profilSection : some View {
Section ( L10n . t ( " settings.nav.profile " , appLang ) ) {
@@ -119,28 +193,7 @@ struct SettingsView: View {
}
}
// MARK: – B e n a c h r i c h t i g u n g e n
var benachrichtigungenSection : some View {
Section {
Picker ( ReminderOptions . defaultTitle ( appLang ) , selection : $ defaultReminderMinutes ) {
Text ( ReminderOptions . off ( appLang ) ) . tag ( - 1 )
ForEach ( ReminderOptions . all , id : \ . self ) { m in
Text ( ReminderOptions . label ( m , appLang ) ) . tag ( m )
}
}
. onChange ( of : defaultReminderMinutes ) { _ , _ in
SettingsSync . push ( api : api )
NotificationCenter . default . post ( name : . rescheduleReminders , object : nil )
}
} header : {
Text ( ReminderOptions . sectionTitle ( appLang ) )
} footer : {
Text ( ReminderOptions . defaultFooter ( appLang ) ) . font ( . caption )
}
}
// MARK: – P r i v a t s p h ä r e
// MARK: – P r i v a t s p h ä r e ( a c c o u n t — n o t s y n c e d )
var privatsphaereSection : some View {
Section {
@@ -158,22 +211,7 @@ struct SettingsView: View {
}
}
// MARK: – S t a n d a r d t e r m i n d a u e r
var termindauerSection : some View {
Section {
Picker ( L10n . t ( " settings.default_duration " , appLang ) , selection : $ defaultEventDurationMinutes ) {
ForEach ( [ 15 , 30 , 45 , 60 , 90 , 120 , 240 ] , id : \ . self ) { m in
Text ( ReminderOptions . durationLabel ( m , appLang ) ) . tag ( m )
}
}
. onChange ( of : defaultEventDurationMinutes ) { _ , _ in SettingsSync . push ( api : api ) }
} header : {
Text ( L10n . t ( " settings.calview " , appLang ) )
}
}
// MARK: – G e t e i l t e r K a l e n d e r
// MARK: – G e t e i l t e r K a l e n d e r ( a c c o u n t — n o t s y n c e d )
var geteilterKalenderSection : some View {
Section {
@@ -193,6 +231,170 @@ struct SettingsView: View {
}
}
// MARK: – T e r m i n e ( s y n c e d )
var termineSection : some View {
Section ( L10n . t ( " settings.calview " , appLang ) ) {
HStack ( spacing : 12 ) {
syncIcon ( " default_event_duration_minutes " )
Text ( L10n . t ( " settings.default_duration " , appLang ) )
Spacer ( )
Picker ( " " , selection : $ defaultEventDurationMinutes ) {
ForEach ( [ 15 , 30 , 45 , 60 , 90 , 120 , 240 ] , id : \ . self ) { m in
Text ( ReminderOptions . durationLabel ( m , appLang ) ) . tag ( m )
}
}
. pickerStyle ( . menu ) . labelsHidden ( )
}
HStack ( spacing : 12 ) {
syncIcon ( " default_reminder_minutes " )
Text ( ReminderOptions . defaultTitle ( appLang ) )
Spacer ( )
Picker ( " " , selection : $ defaultReminderMinutes ) {
Text ( ReminderOptions . off ( appLang ) ) . tag ( - 1 )
ForEach ( ReminderOptions . all , id : \ . self ) { m in
Text ( ReminderOptions . label ( m , appLang ) ) . tag ( m )
}
}
. pickerStyle ( . menu ) . labelsHidden ( )
. onChange ( of : defaultReminderMinutes ) { _ , _ in
SettingsSync . push ( api : api )
NotificationCenter . default . post ( name : . rescheduleReminders , object : nil )
}
}
}
}
// MARK: – A n s i c h t ( s y n c e d )
var ansichtSection : some View {
Section ( L10n . t ( " settings.appearance " , appLang ) ) {
HStack ( spacing : 12 ) {
syncIcon ( " default_view " )
Text ( L10n . t ( " settings.defaultview " , appLang ) )
Spacer ( )
Picker ( " " , selection : $ defaultView ) {
Text ( L10n . t ( " view.month " , appLang ) ) . tag ( " month " )
Text ( L10n . t ( " view.week " , appLang ) ) . tag ( " week " )
Text ( L10n . t ( " view.day " , appLang ) ) . tag ( " day " )
Text ( L10n . t ( " view.quarter " , appLang ) ) . tag ( " quarter " )
Text ( L10n . t ( " view.agenda " , appLang ) ) . tag ( " agenda " )
}
. pickerStyle ( . menu ) . labelsHidden ( )
}
HStack ( spacing : 12 ) {
syncIcon ( " week_start_day " )
Text ( L10n . t ( " settings.firstweekday " , appLang ) )
Spacer ( )
Picker ( " " , selection : $ weekStartDay ) {
Text ( L10n . t ( " settings.monday " , appLang ) ) . tag ( " monday " )
Text ( L10n . t ( " settings.sunday " , appLang ) ) . tag ( " sunday " )
}
. pickerStyle ( . menu ) . labelsHidden ( )
}
HStack ( spacing : 12 ) {
syncIcon ( " dim_past_events " )
Toggle ( L10n . t ( " settings.dimpast " , appLang ) , isOn : $ dimPastEvents ) . tint ( Color . accentColor )
}
HStack ( spacing : 12 ) {
syncIcon ( " month_view_paged " )
Toggle ( L10n . t ( " settings.month_paged " , appLang ) , isOn : $ monthViewPaged ) . tint ( Color . accentColor )
}
HStack ( spacing : 12 ) {
syncIcon ( " hour_height " )
Text ( L10n . t ( " settings.hourheight " , appLang ) )
Spacer ( )
Picker ( " " , selection : $ hourHeight ) {
Text ( L10n . t ( " settings.hourheight.compact " , appLang ) ) . tag ( 28 )
Text ( L10n . t ( " settings.hourheight.normal " , appLang ) ) . tag ( 44 )
Text ( L10n . t ( " settings.hourheight.comfort " , appLang ) ) . tag ( 60 )
Text ( L10n . t ( " settings.hourheight.large " , appLang ) ) . tag ( 80 )
}
. pickerStyle ( . menu ) . labelsHidden ( )
}
}
}
// MARK: – F a r b e n ( s y n c e d )
var farbenSection : some View {
Section ( L10n . t ( " settings.colors " , appLang ) ) {
colorRow ( " primary_color " , " primaryColor " , L10n . t ( " settings.color.primary " , appLang ) , $ primaryHex )
colorRow ( " accent_color " , " accentColor " , L10n . t ( " settings.color.accent " , appLang ) , $ accentHex )
colorRow ( " today_color " , " todayColor " , L10n . t ( " settings.color.today " , appLang ) , $ todayHex )
colorRow ( " text_color " , " textColor " , L10n . t ( " settings.color.text " , appLang ) , $ textHex )
colorRow ( " bg_color " , " backgroundColor " , L10n . t ( " settings.color.background " , appLang ) , $ bgHex )
colorRow ( " line_color " , " lineColor " , L10n . t ( " settings.color.line " , appLang ) , $ lineHex )
colorRow ( " month_divider_color " , " monthDividerColor " , L10n . t ( " settings.color.divider " , appLang ) , $ dividerHex )
colorRow ( " month_label_color " , " monthLabelColor " , L10n . t ( " settings.color.label " , appLang ) , $ labelHex )
}
}
// MARK: – C a c h e ( s y n c e d )
var cacheSection : some View {
Section {
HStack ( spacing : 12 ) {
syncIcon ( " cache_months " )
Text ( L10n . t ( " settings.cache.range " , appLang ) )
Spacer ( )
Picker ( " " , selection : $ cacheMonths ) {
Text ( L10n . t ( " settings.cache.1m " , appLang ) ) . tag ( 1 )
Text ( L10n . t ( " settings.cache.3m " , appLang ) ) . tag ( 3 )
Text ( L10n . t ( " settings.cache.6m " , appLang ) ) . tag ( 6 )
Text ( L10n . t ( " settings.cache.1y " , appLang ) ) . tag ( 12 )
}
. pickerStyle ( . menu ) . labelsHidden ( )
}
} header : {
Text ( L10n . t ( " settings.cache.header " , appLang ) )
} footer : {
Text ( L10n . t ( " settings.cache.footer " , appLang ) ) . font ( . caption )
}
}
// MARK: – G e r ä t ( d e v i c e - l o c a l : l a n g u a g e , c o n t r a s t , l i q u i d g l a s s )
var geraetSection : some View {
Section {
Picker ( L10n . t ( " settings.language " , appLang ) , selection : $ appLang ) {
Text ( L10n . t ( " lang.system " , appLang ) ) . tag ( " system " )
Text ( L10n . t ( " lang.german " , appLang ) ) . tag ( " de " )
Text ( L10n . t ( " lang.english " , appLang ) ) . tag ( " en " )
}
Picker ( L10n . t ( " settings.textcontrast " , appLang ) , selection : $ textContrast ) {
Text ( L10n . t ( " settings.contrast.dark " , appLang ) ) . tag ( 1 )
Text ( L10n . t ( " settings.contrast.medium " , appLang ) ) . tag ( 2 )
Text ( L10n . t ( " settings.contrast.bright " , appLang ) ) . tag ( 3 )
Text ( L10n . t ( " settings.contrast.max " , appLang ) ) . tag ( 4 )
}
Picker ( L10n . t ( " settings.linecontrast " , appLang ) , selection : $ lineContrast ) {
Text ( L10n . t ( " settings.linecontrast.barely " , appLang ) ) . tag ( 1 )
Text ( L10n . t ( " settings.linecontrast.subtle " , appLang ) ) . tag ( 2 )
Text ( L10n . t ( " settings.linecontrast.normal " , appLang ) ) . tag ( 3 )
Text ( L10n . t ( " settings.linecontrast.strong " , appLang ) ) . tag ( 4 )
}
Toggle ( isOn : $ liquidGlass ) {
Label {
VStack ( alignment : . leading , spacing : 2 ) {
Text ( L10n . t ( " settings.liquidglass " , appLang ) )
Text ( L10n . t ( " settings.liquidglass.desc " , appLang ) )
. font ( . caption ) . foregroundStyle ( . secondary )
}
} icon : {
Image ( systemName : " sparkles " ) . foregroundStyle ( . blue )
}
}
. tint ( Color . accentColor )
} header : {
Text ( L10n . t ( " settings.device " , appLang ) )
} footer : {
Text ( L10n . t ( " settings.device.footer " , appLang ) ) . font ( . caption )
}
}
// MARK: – P r o f i l e l o a d / s a v e
private func loadProfile ( ) async {
if let p = try ? await api . getProfile ( ) {
displayName = p . displayName ? ? p . username
@@ -223,252 +425,4 @@ struct SettingsView: View {
profileMsg = error . localizedDescription
}
}
// MARK: – L i q u i d G l a s s
var liquidGlassSection : some View {
Section {
Toggle ( isOn : $ liquidGlass ) {
Label {
VStack ( alignment : . leading , spacing : 2 ) {
Text ( L10n . t ( " settings.liquidglass " , appLang ) )
Text ( L10n . t ( " settings.liquidglass.desc " , appLang ) )
. font ( . caption )
. foregroundStyle ( . secondary )
}
} icon : {
Image ( systemName : " sparkles " )
. foregroundStyle ( . blue )
}
}
. tint ( Color . accentColor )
Toggle ( isOn : $ settingsSync ) {
Label {
VStack ( alignment : . leading , spacing : 2 ) {
Text ( L10n . t ( " settings.sync " , appLang ) )
Text ( L10n . t ( " settings.sync.desc " , appLang ) )
. font ( . caption )
. foregroundStyle ( . secondary )
}
} icon : {
Image ( systemName : " arrow.triangle.2.circlepath " )
. foregroundStyle ( . teal )
}
}
. tint ( Color . accentColor )
} header : {
Text ( L10n . t ( " settings.appdesign " , appLang ) )
} footer : {
Text ( L10n . t ( " settings.sync.footer " , appLang ) )
. font ( . caption )
}
}
// MARK: – C a c h e
var cacheSection : some View {
Section {
VStack ( alignment : . leading , spacing : 10 ) {
Label {
VStack ( alignment : . leading , spacing : 2 ) {
Text ( L10n . t ( " settings.cache.title " , appLang ) )
Text ( L10n . t ( " settings.cache.desc " , appLang ) )
. font ( . caption )
. foregroundStyle ( . secondary )
}
} icon : {
Image ( systemName : " arrow.down.circle " )
. foregroundStyle ( . green )
}
Picker ( L10n . t ( " settings.cache.range " , appLang ) , selection : $ cacheMonths ) {
Text ( L10n . t ( " settings.cache.1m " , appLang ) ) . tag ( 1 )
Text ( L10n . t ( " settings.cache.3m " , appLang ) ) . tag ( 3 )
Text ( L10n . t ( " settings.cache.6m " , appLang ) ) . tag ( 6 )
Text ( L10n . t ( " settings.cache.1y " , appLang ) ) . tag ( 12 )
}
. pickerStyle ( . segmented )
}
. padding ( . vertical , 4 )
} header : {
Text ( L10n . t ( " settings.cache.header " , appLang ) )
} footer : {
Text ( L10n . t ( " settings.cache.footer " , appLang ) )
. font ( . caption )
}
}
// MARK: – S p r a c h e
var spracheSection : some View {
Section ( L10n . t ( " settings.language " , appLang ) ) {
Picker ( L10n . t ( " settings.language " , appLang ) , selection : $ appLang ) {
Text ( L10n . t ( " lang.system " , appLang ) ) . tag ( " system " )
Text ( L10n . t ( " lang.german " , appLang ) ) . tag ( " de " )
Text ( L10n . t ( " lang.english " , appLang ) ) . tag ( " en " )
}
}
}
// MARK: – F a r b e n
var farbenSection : some View {
Section ( L10n . t ( " settings.colors " , appLang ) ) {
ColorPickerRow ( label : L10n . t ( " settings.color.primary " , appLang ) , hex : $ primaryHex )
ColorPickerRow ( label : L10n . t ( " settings.color.accent " , appLang ) , hex : $ accentHex )
ColorPickerRow ( label : L10n . t ( " settings.color.today " , appLang ) , hex : $ todayHex )
ColorPickerRow ( label : L10n . t ( " settings.color.text " , appLang ) , hex : $ textHex )
ColorPickerRow ( label : L10n . t ( " settings.color.background " , appLang ) , hex : $ bgHex )
ColorPickerRow ( label : L10n . t ( " settings.color.line " , appLang ) , hex : $ lineHex )
ColorPickerRow ( label : L10n . t ( " settings.color.divider " , appLang ) , hex : $ dividerHex )
ColorPickerRow ( label : L10n . t ( " settings.color.label " , appLang ) , hex : $ labelHex )
}
}
// MARK: – S c h r i f t k o n t r a s t
var schriftSection : some View {
Section {
VStack ( alignment : . leading , spacing : 10 ) {
Text ( L10n . t ( " settings.textcontrast " , appLang ) )
. font ( . headline )
Text ( L10n . t ( " settings.textcontrast.desc " , appLang ) )
. font ( . caption )
. foregroundStyle ( . secondary )
ContrastSelector (
value : $ textContrast ,
options : [
( 1 , L10n . t ( " settings.contrast.dark " , appLang ) ) ,
( 2 , L10n . t ( " settings.contrast.medium " , appLang ) ) ,
( 3 , L10n . t ( " settings.contrast.bright " , appLang ) ) ,
( 4 , L10n . t ( " settings.contrast.max " , appLang ) )
]
)
}
. padding ( . vertical , 4 )
}
}
// MARK: – L i n i e n k o n t r a s t
var linienSection : some View {
Section {
VStack ( alignment : . leading , spacing : 10 ) {
Text ( L10n . t ( " settings.linecontrast " , appLang ) )
. font ( . headline )
Text ( L10n . t ( " settings.linecontrast.desc " , appLang ) )
. font ( . caption )
. foregroundStyle ( . secondary )
ContrastSelector (
value : $ lineContrast ,
options : [
( 1 , L10n . t ( " settings.linecontrast.barely " , appLang ) ) ,
( 2 , L10n . t ( " settings.linecontrast.subtle " , appLang ) ) ,
( 3 , L10n . t ( " settings.linecontrast.normal " , appLang ) ) ,
( 4 , L10n . t ( " settings.linecontrast.strong " , appLang ) )
]
)
}
. padding ( . vertical , 4 )
}
}
// MARK: – A n s i c h t
var ansichtSection : some View {
Section ( L10n . t ( " settings.calview " , appLang ) ) {
Picker ( L10n . t ( " settings.defaultview " , appLang ) , selection : $ defaultView ) {
Text ( L10n . t ( " view.month " , appLang ) ) . tag ( " month " )
Text ( L10n . t ( " view.week " , appLang ) ) . tag ( " week " )
Text ( L10n . t ( " view.day " , appLang ) ) . tag ( " day " )
Text ( L10n . t ( " view.quarter " , appLang ) ) . tag ( " quarter " )
Text ( L10n . t ( " view.agenda " , appLang ) ) . tag ( " agenda " )
}
Picker ( L10n . t ( " settings.firstweekday " , appLang ) , selection : $ weekStartDay ) {
Text ( L10n . t ( " settings.monday " , appLang ) ) . tag ( " monday " )
Text ( L10n . t ( " settings.sunday " , appLang ) ) . tag ( " sunday " )
}
Toggle ( L10n . t ( " settings.dimpast " , appLang ) , isOn : $ dimPastEvents )
. tint ( Color . accentColor )
Toggle ( L10n . t ( " settings.month_paged " , appLang ) , isOn : $ monthViewPaged )
. tint ( Color . accentColor )
}
}
// MARK: – S t u n d e n h ö h e
var stundenSection : some View {
Section {
VStack ( alignment : . leading , spacing : 10 ) {
Text ( L10n . t ( " settings.hourheight " , appLang ) )
. font ( . headline )
Text ( L10n . t ( " settings.hourheight.desc " , appLang ) )
. font ( . caption )
. foregroundStyle ( . secondary )
ContrastSelector (
value : $ hourHeight ,
options : [
( 28 , L10n . t ( " settings.hourheight.compact " , appLang ) ) ,
( 44 , L10n . t ( " settings.hourheight.normal " , appLang ) ) ,
( 60 , L10n . t ( " settings.hourheight.comfort " , appLang ) ) ,
( 80 , L10n . t ( " settings.hourheight.large " , appLang ) )
]
)
}
. padding ( . vertical , 4 )
}
}
}
// MARK: – R e u s a b l e C o m p o n e n t s
struct ColorPickerRow : View {
let label : String
@ Binding var hex : String
var color : Binding < Color > {
Binding (
get : { Color ( hex : hex ) } ,
set : { hex = $0 . toHex ( ) }
)
}
var body : some View {
HStack {
Text ( label )
Spacer ( )
ColorPicker ( " " , selection : color , supportsOpacity : false )
. labelsHidden ( )
Text ( hex . uppercased ( ) )
. font ( . system ( . caption , design : . monospaced ) )
. foregroundStyle ( . secondary )
. frame ( width : 68 , alignment : . trailing )
}
}
}
struct ContrastSelector < T : Hashable & Equatable > : View {
@ Binding var value : T
let options : [ ( T , String ) ]
var body : some View {
HStack ( spacing : 8 ) {
ForEach ( Array ( options . enumerated ( ) ) , id : \ . offset ) { _ , opt in
Button {
value = opt . 0
} label : {
Text ( opt . 1 )
. font ( . caption . weight ( . medium ) )
. frame ( maxWidth : . infinity )
. padding ( . vertical , 8 )
. background ( value = = opt . 0 ? Color . accentColor : Color ( . systemGray5 ) )
. foregroundStyle ( value = = opt . 0 ? . white : . primary )
. clipShape ( RoundedRectangle ( cornerRadius : 8 ) )
}
. buttonStyle ( . plain )
}
}
}
}