@@ -34,7 +34,7 @@ class FigmaToggle extends StatelessWidget {
34
34
final theme = Theme .of (context);
35
35
final colorScheme = theme.colorScheme;
36
36
37
- // Figma-specified dimensions
37
+ // Exact Figma-specified dimensions
38
38
final trackWidth = value ? 48.0 : 46.0 ;
39
39
final trackHeight = value ? 28.0 : 26.0 ;
40
40
final thumbRadius = value ? 10.0 : 7.0 ;
@@ -48,6 +48,13 @@ class FigmaToggle extends StatelessWidget {
48
48
final trackColor = value ? effectiveActiveColor : effectiveInactiveColor;
49
49
final thumbColor = value ? effectiveActiveThumbColor : effectiveInactiveThumbColor;
50
50
51
+ // Calculate thumb positioning with proper padding
52
+ final thumbDiameter = thumbRadius * 2 ;
53
+ final horizontalPadding = 4.0 ;
54
+ final thumbLeftPosition = value
55
+ ? trackWidth - thumbDiameter - horizontalPadding
56
+ : horizontalPadding;
57
+
51
58
return GestureDetector (
52
59
onTap: onChanged != null ? () => onChanged !(! value) : null ,
53
60
child: AnimatedContainer (
@@ -64,21 +71,19 @@ class FigmaToggle extends StatelessWidget {
64
71
AnimatedPositioned (
65
72
duration: const Duration (milliseconds: 200 ),
66
73
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 ,
74
+ left: thumbLeftPosition,
75
+ top: (trackHeight - thumbDiameter) / 2 ,
71
76
child: AnimatedContainer (
72
77
duration: const Duration (milliseconds: 200 ),
73
78
curve: Curves .easeInOut,
74
- width: thumbRadius * 2 ,
75
- height: thumbRadius * 2 ,
79
+ width: thumbDiameter ,
80
+ height: thumbDiameter ,
76
81
decoration: BoxDecoration (
77
82
shape: BoxShape .circle,
78
83
color: thumbColor,
79
84
boxShadow: [
80
85
BoxShadow (
81
- color: Colors .black.withOpacity ( 0.2 ),
86
+ color: Colors .black.withValues (alpha : 0.2 ),
82
87
blurRadius: 4 ,
83
88
offset: const Offset (0 , 2 ),
84
89
),
@@ -105,26 +110,35 @@ class SettingsPage extends StatelessWidget {
105
110
106
111
static AccountRoute <void > buildRoute ({required BuildContext context}) {
107
112
return MaterialAccountWidgetRoute (
108
- context: context, page: const SettingsPage ());
113
+ context: context,
114
+ page: const SettingsPage (),
115
+ );
109
116
}
110
117
111
118
@override
112
119
Widget build (BuildContext context) {
113
120
final zulipLocalizations = ZulipLocalizations .of (context);
114
121
return Scaffold (
115
122
appBar: ZulipAppBar (
116
- title: Text (zulipLocalizations.settingsPageTitle)),
117
- body: Column (children: [
118
- const _ThemeSetting (),
119
- const _BrowserPreferenceSetting (),
120
- const _VisitFirstUnreadSetting (),
121
- const _MarkReadOnScrollSetting (),
122
- if (GlobalSettingsStore .experimentalFeatureFlags.isNotEmpty)
123
- ListTile (
124
- title: Text (zulipLocalizations.experimentalFeatureSettingsPageTitle),
125
- onTap: () => Navigator .push (context,
126
- ExperimentalFeaturesPage .buildRoute ()))
127
- ]));
123
+ title: Text (zulipLocalizations.settingsPageTitle),
124
+ ),
125
+ body: Column (
126
+ children: [
127
+ const _ThemeSetting (),
128
+ const _BrowserPreferenceSetting (),
129
+ const _VisitFirstUnreadSetting (),
130
+ const _MarkReadOnScrollSetting (),
131
+ if (GlobalSettingsStore .experimentalFeatureFlags.isNotEmpty)
132
+ ListTile (
133
+ title: Text (zulipLocalizations.experimentalFeatureSettingsPageTitle),
134
+ onTap: () => Navigator .push (
135
+ context,
136
+ ExperimentalFeaturesPage .buildRoute (),
137
+ ),
138
+ ),
139
+ ],
140
+ ),
141
+ );
128
142
}
129
143
}
130
144
@@ -143,18 +157,24 @@ class _ThemeSetting extends StatelessWidget {
143
157
return Column (
144
158
children: [
145
159
ListTile (title: Text (zulipLocalizations.themeSettingTitle)),
146
- for (final themeSettingOption in [null , ...ThemeSetting .values])
147
- RadioListTile <ThemeSetting ?>.adaptive (
148
- title: Text (ThemeSetting .displayName (
149
- themeSetting: themeSettingOption,
150
- zulipLocalizations: zulipLocalizations)),
151
- value: themeSettingOption,
152
- // TODO(#1545) stop using the deprecated members
153
- // ignore: deprecated_member_use
154
- groupValue: globalSettings.themeSetting,
155
- // ignore: deprecated_member_use
156
- onChanged: (newValue) => _handleChange (context, newValue)),
157
- ]);
160
+ RadioGroup <ThemeSetting ?>(
161
+ groupValue: globalSettings.themeSetting,
162
+ onChanged: (newValue) => _handleChange (context, newValue),
163
+ child: Column (
164
+ children: [
165
+ for (final themeSettingOption in [null , ...ThemeSetting .values])
166
+ RadioListTile <ThemeSetting ?>.adaptive (
167
+ title: Text (ThemeSetting .displayName (
168
+ themeSetting: themeSettingOption,
169
+ zulipLocalizations: zulipLocalizations,
170
+ )),
171
+ value: themeSettingOption,
172
+ ),
173
+ ],
174
+ ),
175
+ ),
176
+ ],
177
+ );
158
178
}
159
179
}
160
180
@@ -165,7 +185,8 @@ class _BrowserPreferenceSetting extends StatelessWidget {
165
185
final globalSettings = GlobalStoreWidget .settingsOf (context);
166
186
globalSettings.setBrowserPreference (
167
187
newOpenLinksWithInAppBrowser ? BrowserPreference .inApp
168
- : BrowserPreference .external );
188
+ : BrowserPreference .external ,
189
+ );
169
190
}
170
191
171
192
@override
@@ -194,9 +215,14 @@ class _VisitFirstUnreadSetting extends StatelessWidget {
194
215
return ListTile (
195
216
title: Text (zulipLocalizations.initialAnchorSettingTitle),
196
217
subtitle: Text (VisitFirstUnreadSettingPage ._valueDisplayName (
197
- globalSettings.visitFirstUnread, zulipLocalizations: zulipLocalizations)),
198
- onTap: () => Navigator .push (context,
199
- VisitFirstUnreadSettingPage .buildRoute ()));
218
+ globalSettings.visitFirstUnread,
219
+ zulipLocalizations: zulipLocalizations,
220
+ )),
221
+ onTap: () => Navigator .push (
222
+ context,
223
+ VisitFirstUnreadSettingPage .buildRoute (),
224
+ ),
225
+ );
200
226
}
201
227
}
202
228
@@ -207,7 +233,8 @@ class VisitFirstUnreadSettingPage extends StatelessWidget {
207
233
return MaterialWidgetRoute (page: const VisitFirstUnreadSettingPage ());
208
234
}
209
235
210
- static String _valueDisplayName (VisitFirstUnreadSetting value, {
236
+ static String _valueDisplayName (
237
+ VisitFirstUnreadSetting value, {
211
238
required ZulipLocalizations zulipLocalizations,
212
239
}) {
213
240
return switch (value) {
@@ -221,7 +248,7 @@ class VisitFirstUnreadSettingPage extends StatelessWidget {
221
248
}
222
249
223
250
void _handleChange (BuildContext context, VisitFirstUnreadSetting ? value) {
224
- if (value == null ) return ; // TODO(log); can this actually happen? how?
251
+ if (value == null ) return ;
225
252
final globalSettings = GlobalStoreWidget .settingsOf (context);
226
253
globalSettings.setVisitFirstUnread (value);
227
254
}
@@ -232,19 +259,28 @@ class VisitFirstUnreadSettingPage extends StatelessWidget {
232
259
final globalSettings = GlobalStoreWidget .settingsOf (context);
233
260
return Scaffold (
234
261
appBar: AppBar (title: Text (zulipLocalizations.initialAnchorSettingTitle)),
235
- body: Column (children: [
236
- ListTile (title: Text (zulipLocalizations.initialAnchorSettingDescription)),
237
- for (final value in VisitFirstUnreadSetting .values)
238
- RadioListTile .adaptive (
239
- title: Text (_valueDisplayName (value,
240
- zulipLocalizations: zulipLocalizations)),
241
- value: value,
242
- // TODO(#1545) stop using the deprecated members
243
- // ignore: deprecated_member_use
262
+ body: Column (
263
+ children: [
264
+ ListTile (title: Text (zulipLocalizations.initialAnchorSettingDescription)),
265
+ RadioGroup <VisitFirstUnreadSetting >(
244
266
groupValue: globalSettings.visitFirstUnread,
245
- // ignore: deprecated_member_use
246
- onChanged: (newValue) => _handleChange (context, newValue)),
247
- ]));
267
+ onChanged: (newValue) => _handleChange (context, newValue),
268
+ child: Column (
269
+ children: [
270
+ for (final value in VisitFirstUnreadSetting .values)
271
+ RadioListTile .adaptive (
272
+ title: Text (_valueDisplayName (
273
+ value,
274
+ zulipLocalizations: zulipLocalizations,
275
+ )),
276
+ value: value,
277
+ ),
278
+ ],
279
+ ),
280
+ ),
281
+ ],
282
+ ),
283
+ );
248
284
}
249
285
}
250
286
@@ -258,9 +294,14 @@ class _MarkReadOnScrollSetting extends StatelessWidget {
258
294
return ListTile (
259
295
title: Text (zulipLocalizations.markReadOnScrollSettingTitle),
260
296
subtitle: Text (MarkReadOnScrollSettingPage ._valueDisplayName (
261
- globalSettings.markReadOnScroll, zulipLocalizations: zulipLocalizations)),
262
- onTap: () => Navigator .push (context,
263
- MarkReadOnScrollSettingPage .buildRoute ()));
297
+ globalSettings.markReadOnScroll,
298
+ zulipLocalizations: zulipLocalizations,
299
+ )),
300
+ onTap: () => Navigator .push (
301
+ context,
302
+ MarkReadOnScrollSettingPage .buildRoute (),
303
+ ),
304
+ );
264
305
}
265
306
}
266
307
@@ -271,7 +312,8 @@ class MarkReadOnScrollSettingPage extends StatelessWidget {
271
312
return MaterialWidgetRoute (page: const MarkReadOnScrollSettingPage ());
272
313
}
273
314
274
- static String _valueDisplayName (MarkReadOnScrollSetting value, {
315
+ static String _valueDisplayName (
316
+ MarkReadOnScrollSetting value, {
275
317
required ZulipLocalizations zulipLocalizations,
276
318
}) {
277
319
return switch (value) {
@@ -284,7 +326,8 @@ class MarkReadOnScrollSettingPage extends StatelessWidget {
284
326
};
285
327
}
286
328
287
- static String ? _valueDescription (MarkReadOnScrollSetting value, {
329
+ static String ? _valueDescription (
330
+ MarkReadOnScrollSetting value, {
288
331
required ZulipLocalizations zulipLocalizations,
289
332
}) {
290
333
return switch (value) {
@@ -296,7 +339,7 @@ class MarkReadOnScrollSettingPage extends StatelessWidget {
296
339
}
297
340
298
341
void _handleChange (BuildContext context, MarkReadOnScrollSetting ? value) {
299
- if (value == null ) return ; // TODO(log); can this actually happen? how?
342
+ if (value == null ) return ;
300
343
final globalSettings = GlobalStoreWidget .settingsOf (context);
301
344
globalSettings.setMarkReadOnScroll (value);
302
345
}
@@ -307,24 +350,35 @@ class MarkReadOnScrollSettingPage extends StatelessWidget {
307
350
final globalSettings = GlobalStoreWidget .settingsOf (context);
308
351
return Scaffold (
309
352
appBar: AppBar (title: Text (zulipLocalizations.markReadOnScrollSettingTitle)),
310
- body: Column (children: [
311
- ListTile (title: Text (zulipLocalizations.markReadOnScrollSettingDescription)),
312
- for (final value in MarkReadOnScrollSetting .values)
313
- RadioListTile .adaptive (
314
- title: Text (_valueDisplayName (value,
315
- zulipLocalizations: zulipLocalizations)),
316
- subtitle: () {
317
- final result = _valueDescription (value,
318
- zulipLocalizations: zulipLocalizations);
319
- return result == null ? null : Text (result);
320
- }(),
321
- value: value,
322
- // TODO(#1545) stop using the deprecated members
323
- // ignore: deprecated_member_use
353
+ body: Column (
354
+ children: [
355
+ ListTile (title: Text (zulipLocalizations.markReadOnScrollSettingDescription)),
356
+ RadioGroup <MarkReadOnScrollSetting >(
324
357
groupValue: globalSettings.markReadOnScroll,
325
- // ignore: deprecated_member_use
326
- onChanged: (newValue) => _handleChange (context, newValue)),
327
- ]));
358
+ onChanged: (newValue) => _handleChange (context, newValue),
359
+ child: Column (
360
+ children: [
361
+ for (final value in MarkReadOnScrollSetting .values)
362
+ RadioListTile .adaptive (
363
+ title: Text (_valueDisplayName (
364
+ value,
365
+ zulipLocalizations: zulipLocalizations,
366
+ )),
367
+ subtitle: () {
368
+ final result = _valueDescription (
369
+ value,
370
+ zulipLocalizations: zulipLocalizations,
371
+ );
372
+ return result == null ? null : Text (result);
373
+ }(),
374
+ value: value,
375
+ ),
376
+ ],
377
+ ),
378
+ ),
379
+ ],
380
+ ),
381
+ );
328
382
}
329
383
}
330
384
@@ -343,18 +397,23 @@ class ExperimentalFeaturesPage extends StatelessWidget {
343
397
assert (flags.isNotEmpty);
344
398
return Scaffold (
345
399
appBar: AppBar (
346
- title: Text (zulipLocalizations.experimentalFeatureSettingsPageTitle)),
347
- body: Column (children: [
348
- ListTile (
349
- title: Text (zulipLocalizations.experimentalFeatureSettingsWarning)),
350
- for (final flag in flags)
400
+ title: Text (zulipLocalizations.experimentalFeatureSettingsPageTitle),
401
+ ),
402
+ body: Column (
403
+ children: [
351
404
ListTile (
352
- title: Text (flag.name), // no i18n; these are developer-facing settings
353
- trailing: FigmaToggle (
354
- value: globalSettings.getBool (flag),
355
- onChanged: (value) => globalSettings.setBool (flag, value),
356
- ),
405
+ title: Text (zulipLocalizations.experimentalFeatureSettingsWarning),
357
406
),
358
- ]));
407
+ for (final flag in flags)
408
+ ListTile (
409
+ title: Text (flag.name), // no i18n; these are developer-facing settings
410
+ trailing: FigmaToggle (
411
+ value: globalSettings.getBool (flag),
412
+ onChanged: (value) => globalSettings.setBool (flag, value),
413
+ ),
414
+ ),
415
+ ],
416
+ ),
417
+ );
359
418
}
360
419
}
0 commit comments