mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-21 22:29:08 +02:00
[client] Add light mode with system, light, and dark theme options (#7344)
* desktop UI light mode * Theme review fixes plus macOS window outline fix * Windows runtime chrome re-theming plus apply serialization * Windows chrome threading and theme event ordering fixes * Darken toggle and setting sidebar text * resolve theme appearance, apply on UI thread * read theme once per window * Re-assert Windows dark opt-in after SetTheme * split app-wide GTK theming from per-window chrome * Update Wails dependency and checksums * KDE tray icon panel fix * Five review fixes: theme ordering, cgo dedup, KDE panel resolution * Path guard hardening, toggle contrast, windows comment * non-vacuous escape tests * Default view edits * Polish settings nav, controls, borders, and disc * Profiles settings boarder, modals, and buttons * Additional edits based on feedback * Switch colors away from slight blue hue * Update missing lang * Fix vertical tab active view
This commit is contained in:
@@ -49,10 +49,34 @@ func (v ViewMode) IsValid() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Theme is the preferred UI appearance: follow the OS ("system") or force
|
||||
// "light"/"dark".
|
||||
type Theme string
|
||||
|
||||
const (
|
||||
ThemeSystem Theme = "system"
|
||||
ThemeLight Theme = "light"
|
||||
ThemeDark Theme = "dark"
|
||||
)
|
||||
|
||||
// DefaultTheme applies when no file exists or its theme is empty/unknown.
|
||||
const DefaultTheme = ThemeSystem
|
||||
|
||||
var ErrUnsupportedTheme = errors.New("unsupported theme")
|
||||
|
||||
func (t Theme) IsValid() bool {
|
||||
switch t {
|
||||
case ThemeSystem, ThemeLight, ThemeDark:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UIPreferences is rewritten in full on every change; there are no partial updates.
|
||||
type UIPreferences struct {
|
||||
Language i18n.LanguageCode `json:"language"`
|
||||
ViewMode ViewMode `json:"viewMode"`
|
||||
Theme Theme `json:"theme"`
|
||||
OnboardingCompleted bool `json:"onboardingCompleted"`
|
||||
// AutostartInitialized records that the one-time autostart default
|
||||
// decision has run for this OS user. It only ever transitions to true
|
||||
@@ -105,7 +129,7 @@ func NewStore(validator LanguageValidator, emitter Emitter) (*Store, error) {
|
||||
path: path,
|
||||
validator: validator,
|
||||
emitter: emitter,
|
||||
current: UIPreferences{ViewMode: DefaultViewMode},
|
||||
current: UIPreferences{ViewMode: DefaultViewMode, Theme: DefaultTheme},
|
||||
}
|
||||
|
||||
if err := s.load(); err != nil {
|
||||
@@ -146,6 +170,30 @@ func (s *Store) SetViewMode(mode ViewMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTheme validates, persists, and broadcasts. No-op if unchanged.
|
||||
func (s *Store) SetTheme(theme Theme) error {
|
||||
if !theme.IsValid() {
|
||||
return fmt.Errorf("%w: %q", ErrUnsupportedTheme, theme)
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
if s.current.Theme == theme {
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
next := s.current
|
||||
next.Theme = theme
|
||||
if err := s.persistLocked(next); err != nil {
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("persist preferences: %w", err)
|
||||
}
|
||||
s.current = next
|
||||
s.mu.Unlock()
|
||||
|
||||
s.broadcast(next)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetOnboardingCompleted persists the welcome-window dismissal. No-op if unchanged.
|
||||
func (s *Store) SetOnboardingCompleted(done bool) error {
|
||||
s.mu.Lock()
|
||||
@@ -288,6 +336,9 @@ func (s *Store) load() error {
|
||||
if !loaded.ViewMode.IsValid() {
|
||||
loaded.ViewMode = DefaultViewMode
|
||||
}
|
||||
if !loaded.Theme.IsValid() {
|
||||
loaded.Theme = DefaultTheme
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.current = loaded
|
||||
|
||||
Reference in New Issue
Block a user