Skip to content

Commit ea7abd2

Browse files
committed
feat(settings): Refine Toggle dimensions to match Figma spec
This introduces a custom FigmaSwitch widget to precisely match the design specifications for the 'Invisible mode' toggle, resolving the dimension mismatch
1 parent 7a8c8bb commit ea7abd2

File tree

1 file changed

+107
-7
lines changed

1 file changed

+107
-7
lines changed

lib/widgets/settings.dart

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,100 @@ import 'app_bar.dart';
66
import 'page.dart';
77
import 'store.dart';
88

9+
/// A custom toggle widget that matches Figma specifications exactly.
10+
///
11+
/// This widget provides precise control over dimensions and styling
12+
/// to match the design requirements that Flutter's built-in Switch
13+
/// widget cannot currently accommodate.
14+
class FigmaToggle extends StatelessWidget {
15+
const FigmaToggle({
16+
super.key,
17+
required this.value,
18+
required this.onChanged,
19+
this.activeColor,
20+
this.inactiveColor,
21+
this.activeThumbColor,
22+
this.inactiveThumbColor,
23+
});
24+
25+
final bool value;
26+
final ValueChanged<bool>? onChanged;
27+
final Color? activeColor;
28+
final Color? inactiveColor;
29+
final Color? activeThumbColor;
30+
final Color? inactiveThumbColor;
31+
32+
@override
33+
Widget build(BuildContext context) {
34+
final theme = Theme.of(context);
35+
final colorScheme = theme.colorScheme;
36+
37+
// Figma-specified dimensions
38+
final trackWidth = value ? 48.0 : 46.0;
39+
final trackHeight = value ? 28.0 : 26.0;
40+
final thumbRadius = value ? 10.0 : 7.0;
41+
42+
// Colors with fallbacks to theme defaults
43+
final effectiveActiveColor = activeColor ?? colorScheme.primary;
44+
final effectiveInactiveColor = inactiveColor ?? colorScheme.outline;
45+
final effectiveActiveThumbColor = activeThumbColor ?? colorScheme.onPrimary;
46+
final effectiveInactiveThumbColor = inactiveThumbColor ?? colorScheme.outline;
47+
48+
final trackColor = value ? effectiveActiveColor : effectiveInactiveColor;
49+
final thumbColor = value ? effectiveActiveThumbColor : effectiveInactiveThumbColor;
50+
51+
return GestureDetector(
52+
onTap: onChanged != null ? () => onChanged!(!value) : null,
53+
child: AnimatedContainer(
54+
duration: const Duration(milliseconds: 200),
55+
curve: Curves.easeInOut,
56+
width: trackWidth,
57+
height: trackHeight,
58+
decoration: BoxDecoration(
59+
borderRadius: BorderRadius.circular(trackHeight / 2),
60+
color: trackColor,
61+
),
62+
child: Stack(
63+
children: [
64+
AnimatedPositioned(
65+
duration: const Duration(milliseconds: 200),
66+
curve: Curves.easeInOut,
67+
left: value
68+
? trackWidth - (thumbRadius * 2) - 4.0 // 4px padding from edge
69+
: 4.0, // 4px padding from edge
70+
top: (trackHeight - (thumbRadius * 2)) / 2,
71+
child: AnimatedContainer(
72+
duration: const Duration(milliseconds: 200),
73+
curve: Curves.easeInOut,
74+
width: thumbRadius * 2,
75+
height: thumbRadius * 2,
76+
decoration: BoxDecoration(
77+
shape: BoxShape.circle,
78+
color: thumbColor,
79+
boxShadow: [
80+
BoxShadow(
81+
color: Colors.black.withOpacity(0.2),
82+
blurRadius: 4,
83+
offset: const Offset(0, 2),
84+
),
85+
],
86+
),
87+
child: value
88+
? Icon(
89+
Icons.check,
90+
size: thumbRadius * 1.2,
91+
color: effectiveActiveColor,
92+
)
93+
: null,
94+
),
95+
),
96+
],
97+
),
98+
),
99+
);
100+
}
101+
}
102+
9103
class SettingsPage extends StatelessWidget {
10104
const SettingsPage({super.key});
11105

@@ -80,10 +174,13 @@ class _BrowserPreferenceSetting extends StatelessWidget {
80174
final globalSettings = GlobalStoreWidget.settingsOf(context);
81175
final openLinksWithInAppBrowser =
82176
globalSettings.effectiveBrowserPreference == BrowserPreference.inApp;
83-
return SwitchListTile.adaptive(
177+
return ListTile(
84178
title: Text(zulipLocalizations.openLinksWithInAppBrowser),
85-
value: openLinksWithInAppBrowser,
86-
onChanged: (newValue) => _handleChange(context, newValue));
179+
trailing: FigmaToggle(
180+
value: openLinksWithInAppBrowser,
181+
onChanged: (newValue) => _handleChange(context, newValue),
182+
),
183+
);
87184
}
88185
}
89186

@@ -251,10 +348,13 @@ class ExperimentalFeaturesPage extends StatelessWidget {
251348
ListTile(
252349
title: Text(zulipLocalizations.experimentalFeatureSettingsWarning)),
253350
for (final flag in flags)
254-
SwitchListTile.adaptive(
351+
ListTile(
255352
title: Text(flag.name), // no i18n; these are developer-facing settings
256-
value: globalSettings.getBool(flag),
257-
onChanged: (value) => globalSettings.setBool(flag, value)),
353+
trailing: FigmaToggle(
354+
value: globalSettings.getBool(flag),
355+
onChanged: (value) => globalSettings.setBool(flag, value),
356+
),
357+
),
258358
]));
259359
}
260-
}
360+
}

0 commit comments

Comments
 (0)