Add accessibility settings preferences
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"git.julianfamily.org/keepassgo/vault"
|
||||
)
|
||||
|
||||
const (
|
||||
displayDensityDense = "dense"
|
||||
displayDensityComfortable = "comfortable"
|
||||
|
||||
contrastStandard = "standard"
|
||||
contrastHigh = "high"
|
||||
|
||||
keyboardFocusStandard = "standard"
|
||||
keyboardFocusProminent = "prominent"
|
||||
)
|
||||
|
||||
type accessibilityPreferences struct {
|
||||
DisplayDensity string
|
||||
Contrast string
|
||||
ReducedMotion bool
|
||||
KeyboardFocus string
|
||||
}
|
||||
|
||||
type settingsDraft struct {
|
||||
Accessibility accessibilityPreferences
|
||||
}
|
||||
|
||||
type choiceSpec struct {
|
||||
Click *widget.Clickable
|
||||
Label string
|
||||
Active bool
|
||||
}
|
||||
|
||||
func defaultAccessibilityPreferences() accessibilityPreferences {
|
||||
return accessibilityPreferences{
|
||||
DisplayDensity: displayDensityForDenseLayout(true),
|
||||
Contrast: contrastStandard,
|
||||
KeyboardFocus: keyboardFocusStandard,
|
||||
}
|
||||
}
|
||||
|
||||
func displayDensityForDenseLayout(dense bool) string {
|
||||
if dense {
|
||||
return displayDensityDense
|
||||
}
|
||||
return displayDensityComfortable
|
||||
}
|
||||
|
||||
func normalizeAccessibilityPreferences(prefs accessibilityPreferences) accessibilityPreferences {
|
||||
normalized := defaultAccessibilityPreferences()
|
||||
switch prefs.DisplayDensity {
|
||||
case displayDensityDense, displayDensityComfortable:
|
||||
normalized.DisplayDensity = prefs.DisplayDensity
|
||||
}
|
||||
switch prefs.Contrast {
|
||||
case contrastStandard, contrastHigh:
|
||||
normalized.Contrast = prefs.Contrast
|
||||
}
|
||||
switch prefs.KeyboardFocus {
|
||||
case keyboardFocusStandard, keyboardFocusProminent:
|
||||
normalized.KeyboardFocus = prefs.KeyboardFocus
|
||||
}
|
||||
normalized.ReducedMotion = prefs.ReducedMotion
|
||||
return normalized
|
||||
}
|
||||
|
||||
func (u *ui) applyAccessibilityPreferences(prefs accessibilityPreferences) {
|
||||
normalized := normalizeAccessibilityPreferences(prefs)
|
||||
u.denseLayout = normalized.DisplayDensity == displayDensityDense
|
||||
u.accessibilityPrefs = normalized
|
||||
}
|
||||
|
||||
func (u *ui) loadSettingsDraft() {
|
||||
u.settingsDraft = settingsDraft{
|
||||
Accessibility: accessibilityPreferences{
|
||||
DisplayDensity: displayDensityForDenseLayout(u.denseLayout),
|
||||
Contrast: u.accessibilityPrefs.Contrast,
|
||||
ReducedMotion: u.accessibilityPrefs.ReducedMotion,
|
||||
KeyboardFocus: u.accessibilityPrefs.KeyboardFocus,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ui) saveSecuritySettingsAction() error {
|
||||
settings := vault.SecuritySettings{
|
||||
Cipher: strings.TrimSpace(u.securityCipher.Text()),
|
||||
KDF: strings.TrimSpace(u.securityKDF.Text()),
|
||||
}
|
||||
if err := u.state.ConfigureSecurity(settings); err != nil {
|
||||
return err
|
||||
}
|
||||
if u.settingsDraft.Accessibility.DisplayDensity == displayDensityForDenseLayout(u.denseLayout) {
|
||||
u.settingsDraft.Accessibility.DisplayDensity = displayDensityForDenseLayout(u.settingsDenseLayout.Value)
|
||||
}
|
||||
u.settingsDenseLayout.Value = u.settingsDraft.Accessibility.DisplayDensity == displayDensityDense
|
||||
u.applySettingsFormToPreferences()
|
||||
u.applyAccessibilityPreferences(u.settingsDraft.Accessibility)
|
||||
u.saveUIPreferences()
|
||||
u.securityDialogOpen = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ui) showStatusMessage(message string) {
|
||||
u.state.StatusMessage = message
|
||||
if u.accessibilityPrefs.ReducedMotion {
|
||||
u.statusExpiresAt = time.Time{}
|
||||
return
|
||||
}
|
||||
u.statusExpiresAt = u.now().Add(u.statusBannerTTL)
|
||||
}
|
||||
|
||||
func (u *ui) settingsPreferenceCard(gtx layout.Context, title, detail string, body layout.Widget) layout.Dimensions {
|
||||
return sectionCard(gtx, u.theme, title, detail, body)
|
||||
}
|
||||
|
||||
func settingsSummaryCard(gtx layout.Context, th *material.Theme, title, body string) layout.Dimensions {
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(th, unit.Sp(12), title)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(th, unit.Sp(13), body)
|
||||
lbl.Color = th.Palette.Fg
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ui) settingsChoiceRow(gtx layout.Context, choices ...choiceSpec) layout.Dimensions {
|
||||
children := make([]layout.FlexChild, 0, len(choices)*2)
|
||||
for i, choice := range choices {
|
||||
if i > 0 {
|
||||
children = append(children, layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout))
|
||||
}
|
||||
current := choice
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return syncChoiceButton(gtx, u.theme, current.Click, current.Label, current.Active)
|
||||
}))
|
||||
}
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, children...)
|
||||
}
|
||||
|
||||
type listRowColors struct {
|
||||
Title color.NRGBA
|
||||
Meta color.NRGBA
|
||||
Secondary color.NRGBA
|
||||
Divider color.NRGBA
|
||||
Fill color.NRGBA
|
||||
Edge color.NRGBA
|
||||
}
|
||||
|
||||
func (u *ui) listRowColors(selected, focused, recycleBin bool) listRowColors {
|
||||
colors := listRowColors{
|
||||
Title: accentColor,
|
||||
Meta: color.NRGBA{R: 61, G: 60, B: 56, A: 255},
|
||||
Secondary: mutedColor,
|
||||
Divider: color.NRGBA{R: 225, G: 219, B: 210, A: 255},
|
||||
Fill: color.NRGBA{R: 231, G: 239, B: 235, A: 255},
|
||||
Edge: color.NRGBA{R: 69, G: 118, B: 97, A: 255},
|
||||
}
|
||||
if selected {
|
||||
colors.Title = color.NRGBA{R: 19, G: 57, B: 43, A: 255}
|
||||
colors.Meta = color.NRGBA{R: 31, G: 53, B: 44, A: 255}
|
||||
colors.Secondary = color.NRGBA{R: 72, G: 88, B: 80, A: 255}
|
||||
colors.Divider = color.NRGBA{R: 173, G: 196, B: 184, A: 255}
|
||||
colors.Fill = color.NRGBA{R: 212, G: 228, B: 220, A: 255}
|
||||
colors.Edge = color.NRGBA{R: 46, G: 106, B: 82, A: 255}
|
||||
}
|
||||
if recycleBin {
|
||||
colors.Fill = color.NRGBA{R: 244, G: 229, B: 219, A: 255}
|
||||
colors.Edge = color.NRGBA{R: 133, G: 65, B: 41, A: 255}
|
||||
}
|
||||
if focused && !selected {
|
||||
colors.Meta = color.NRGBA{R: 49, G: 74, B: 63, A: 255}
|
||||
colors.Secondary = color.NRGBA{R: 86, G: 102, B: 95, A: 255}
|
||||
colors.Divider = color.NRGBA{R: 190, G: 208, B: 199, A: 255}
|
||||
}
|
||||
if u.accessibilityPrefs.Contrast == contrastHigh {
|
||||
colors.Meta = color.NRGBA{R: 39, G: 39, B: 36, A: 255}
|
||||
colors.Secondary = color.NRGBA{R: 58, G: 57, B: 52, A: 255}
|
||||
if focused || selected {
|
||||
colors.Fill = color.NRGBA{R: 211, G: 228, B: 219, A: 255}
|
||||
colors.Edge = color.NRGBA{R: 16, G: 60, B: 44, A: 255}
|
||||
}
|
||||
}
|
||||
if u.accessibilityPrefs.KeyboardFocus == keyboardFocusProminent && focused && !selected {
|
||||
colors.Fill = color.NRGBA{R: 220, G: 234, B: 226, A: 255}
|
||||
colors.Edge = color.NRGBA{R: 20, G: 74, B: 55, A: 255}
|
||||
}
|
||||
if recycleBin && (focused || selected) && u.accessibilityPrefs.Contrast == contrastHigh {
|
||||
colors.Fill = color.NRGBA{R: 242, G: 223, B: 209, A: 255}
|
||||
colors.Edge = color.NRGBA{R: 116, G: 43, B: 19, A: 255}
|
||||
}
|
||||
return colors
|
||||
}
|
||||
Reference in New Issue
Block a user