@@ -6,6 +6,100 @@ import 'app_bar.dart';
6
6
import 'page.dart' ;
7
7
import 'store.dart' ;
8
8
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
+
9
103
class SettingsPage extends StatelessWidget {
10
104
const SettingsPage ({super .key});
11
105
@@ -80,10 +174,13 @@ class _BrowserPreferenceSetting extends StatelessWidget {
80
174
final globalSettings = GlobalStoreWidget .settingsOf (context);
81
175
final openLinksWithInAppBrowser =
82
176
globalSettings.effectiveBrowserPreference == BrowserPreference .inApp;
83
- return SwitchListTile . adaptive (
177
+ return ListTile (
84
178
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
+ );
87
184
}
88
185
}
89
186
@@ -251,10 +348,13 @@ class ExperimentalFeaturesPage extends StatelessWidget {
251
348
ListTile (
252
349
title: Text (zulipLocalizations.experimentalFeatureSettingsWarning)),
253
350
for (final flag in flags)
254
- SwitchListTile . adaptive (
351
+ ListTile (
255
352
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
+ ),
258
358
]));
259
359
}
260
- }
360
+ }
0 commit comments