Skip to content

Commit cf07b69

Browse files
committed
feat: add text scaling support
Text scaling settings and UI adaption. Closes: #324
1 parent 3c074bf commit cf07b69

File tree

14 files changed

+140
-10
lines changed

14 files changed

+140
-10
lines changed

lib/app.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
55
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
66
import 'package:flutter_localizations/flutter_localizations.dart';
77
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
8+
import 'package:tsdm_client/constants/layout.dart';
89
import 'package:tsdm_client/extensions/build_context.dart';
910
import 'package:tsdm_client/features/authentication/repository/authentication_repository.dart';
1011
import 'package:tsdm_client/features/cache/bloc/image_cache_trigger_cubit.dart';
@@ -428,6 +429,9 @@ class _AppState extends State<App> with WindowListener, LoggerMixin {
428429
final accentColor = themeState.accentColor;
429430
final themeModeIndex = themeState.themeModeIndex;
430431
final fontFamily = themeState.fontFamily;
432+
final textScaleFactor = context.select<SettingsBloc, double>(
433+
(bloc) => bloc.state.settingsMap.textScaleFactor,
434+
);
431435

432436
final lightTheme = AppTheme.makeLight(context, seedColor: accentColor, fontFamily: fontFamily);
433437
final darkTheme = AppTheme.makeDark(context, seedColor: accentColor, fontFamily: fontFamily);
@@ -442,6 +446,15 @@ class _AppState extends State<App> with WindowListener, LoggerMixin {
442446
darkTheme: darkTheme,
443447
themeMode: ThemeMode.values[themeModeIndex],
444448
scaffoldMessengerKey: snackbarKey,
449+
builder: (context, child) {
450+
final data = MediaQuery.of(context);
451+
return MediaQuery(
452+
data: data.copyWith(
453+
textScaler: TextScaler.linear(textScaleFactor)..clamp(minScaleFactor: 0.7, maxScaleFactor: 1.5),
454+
),
455+
child: child ?? sizedBoxEmpty,
456+
);
457+
},
445458
);
446459
},
447460
),

lib/features/homepage/widgets/pin_section.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class PinSection extends StatelessWidget with LoggerMixin {
6262
return Column(children: listTileList);
6363
}
6464

65-
Widget _buildSection(BuildContext context) {
65+
Widget _buildSection(BuildContext context, double textScaleFactor) {
6666
final ret = <Widget>[];
6767

6868
final count = pinnedThreadGroup.length;
@@ -95,9 +95,9 @@ class PinSection extends StatelessWidget with LoggerMixin {
9595

9696
return GridView(
9797
physics: const NeverScrollableScrollPhysics(),
98-
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
99-
maxCrossAxisExtent: 600,
100-
mainAxisExtent: 700,
98+
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
99+
maxCrossAxisExtent: 670,
100+
mainAxisExtent: 700 + math.max(25 * ((textScaleFactor - 1) / 0.1), 0),
101101
mainAxisSpacing: 12,
102102
crossAxisSpacing: 12,
103103
),
@@ -108,6 +108,7 @@ class PinSection extends StatelessWidget with LoggerMixin {
108108

109109
@override
110110
Widget build(BuildContext context) {
111-
return _buildSection(context);
111+
final textScaleFactor = context.select<SettingsBloc, double>((bloc) => bloc.state.settingsMap.textScaleFactor);
112+
return _buildSection(context, textScaleFactor);
112113
}
113114
}

lib/features/homepage/widgets/widgets.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:math' as math;
23

34
import 'package:flutter/material.dart';
45
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -9,6 +10,7 @@ import 'package:tsdm_client/extensions/map.dart';
910
import 'package:tsdm_client/extensions/string.dart';
1011
import 'package:tsdm_client/features/homepage/bloc/homepage_bloc.dart';
1112
import 'package:tsdm_client/features/homepage/models/models.dart';
13+
import 'package:tsdm_client/features/settings/bloc/settings_bloc.dart';
1214
import 'package:tsdm_client/routes/screen_paths.dart';
1315
import 'package:tsdm_client/utils/logger.dart';
1416
import 'package:tsdm_client/widgets/cached_image/cached_image.dart';

lib/features/settings/repositories/settings_repository.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ final class SettingsRepository with LoggerMixin {
140140
autoClearImageCacheDuration: s.extract(_SK.autoClearImageCacheDuration),
141141
collapseAppBarWhenScroll: s.extract(_SK.collapseAppBarWhenScroll),
142142
threadFloorInteractionMode: s.extract(_SK.threadFloorInteractionMode),
143+
textScaleFactor: s.extract(_SK.textScaleFactor),
143144
);
144145
}
145146

lib/features/settings/view/settings_page.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'package:tsdm_client/features/settings/widgets/check_in_dialog.dart';
2525
import 'package:tsdm_client/features/settings/widgets/clear_cache_bottom_sheet.dart';
2626
import 'package:tsdm_client/features/settings/widgets/color_picker_dialog.dart';
2727
import 'package:tsdm_client/features/settings/widgets/font_family_dialog.dart';
28+
import 'package:tsdm_client/features/settings/widgets/font_scale_dialog.dart';
2829
import 'package:tsdm_client/features/settings/widgets/language_dialog.dart';
2930
import 'package:tsdm_client/features/settings/widgets/proxy_settings_dialog.dart';
3031
import 'package:tsdm_client/features/settings/widgets/select_thread_floor_interaction_mode_dialog.dart';
@@ -143,6 +144,9 @@ class _SettingsPageState extends State<SettingsPage> {
143144
/// App wide font family
144145
final fontFamily = state.settingsMap.fontFamily;
145146

147+
// App wide text scale factor.
148+
final textScaleFactor = state.settingsMap.textScaleFactor;
149+
146150
return [
147151
SectionTitleText(tr.title),
148152
// Theme mode
@@ -345,6 +349,23 @@ class _SettingsPageState extends State<SettingsPage> {
345349
context.read<SettingsBloc>().add(SettingsValueChanged(SettingsKeys.fontFamily, selectedFont));
346350
},
347351
),
352+
353+
/// Text scale factor.
354+
SectionListTile(
355+
leading: const Icon(Icons.text_increase_outlined),
356+
title: Text(tr.textScaleFactor.title),
357+
onTap: () async {
358+
final selectedScale = await showDialog<double>(
359+
context: context,
360+
builder: (_) => RootPage(DialogPaths.textScalePicker, TextScaleDialog(textScaleFactor)),
361+
);
362+
if (!context.mounted || selectedScale == null) {
363+
return;
364+
}
365+
366+
context.read<SettingsBloc>().add(SettingsValueChanged(SettingsKeys.textScaleFactor, selectedScale));
367+
},
368+
),
348369
];
349370
}
350371

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:go_router/go_router.dart';
3+
import 'package:tsdm_client/i18n/strings.g.dart';
4+
import 'package:tsdm_client/widgets/custom_alert_dialog.dart';
5+
6+
/// Dialog to show available text scale options as a chooser.
7+
class TextScaleDialog extends StatefulWidget {
8+
/// Constructor.
9+
const TextScaleDialog(this.initialScale, {super.key});
10+
11+
/// Text scale factor when enter this widget.
12+
final double initialScale;
13+
14+
@override
15+
State<TextScaleDialog> createState() => _TextScaleDialogState();
16+
}
17+
18+
class _TextScaleDialogState extends State<TextScaleDialog> {
19+
late double _currentScale;
20+
21+
@override
22+
void initState() {
23+
super.initState();
24+
_currentScale = widget.initialScale;
25+
}
26+
27+
@override
28+
Widget build(BuildContext context) {
29+
final tr = context.t.settingsPage.appearanceSection.textScaleFactor;
30+
31+
return CustomAlertDialog.sync(
32+
title: Text(tr.dialogTitle),
33+
content: Column(
34+
spacing: 12,
35+
children: [
36+
Text(_currentScale.toString()),
37+
Slider(
38+
autofocus: true,
39+
// Since flutter 3.29
40+
// ignore: deprecated_member_use
41+
year2023: false,
42+
min: 0.7,
43+
max: 1.5,
44+
divisions: 8,
45+
value: _currentScale,
46+
onChanged: (v) => setState(() => _currentScale = double.parse(v.toStringAsFixed(2))),
47+
),
48+
],
49+
),
50+
actions: [TextButton(onPressed: () => context.pop(_currentScale), child: Text(context.t.general.ok))],
51+
);
52+
}
53+
}

lib/features/thread/v1/view/thread_page.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:math' as math;
2+
13
import 'package:collection/collection.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -12,6 +14,7 @@ import 'package:tsdm_client/features/authentication/repository/authentication_re
1214
import 'package:tsdm_client/features/forum/models/models.dart';
1315
import 'package:tsdm_client/features/jump_page/cubit/jump_page_cubit.dart';
1416
import 'package:tsdm_client/features/need_login/view/need_login_page.dart';
17+
import 'package:tsdm_client/features/settings/bloc/settings_bloc.dart';
1518
import 'package:tsdm_client/features/settings/repositories/settings_repository.dart';
1619
import 'package:tsdm_client/features/thread/v1/bloc/thread_bloc.dart';
1720
import 'package:tsdm_client/features/thread/v1/repository/thread_repository.dart';
@@ -125,7 +128,7 @@ class _ThreadPageState extends State<ThreadPage> with SingleTickerProviderStateM
125128

126129
final _replyBarController = ReplyBarController();
127130

128-
Widget _buildBreadcrumbsRow(ThreadState state) {
131+
Widget _buildBreadcrumbsRow(ThreadState state, double extraHeight) {
129132
final infoTextStyle = Theme.of(
130133
context,
131134
).textTheme.labelLarge?.copyWith(color: Theme.of(context).colorScheme.outline);
@@ -165,7 +168,7 @@ class _ThreadPageState extends State<ThreadPage> with SingleTickerProviderStateM
165168
child: DefaultTextStyle.merge(
166169
style: infoTextStyle,
167170
child: SizedBox(
168-
height: 20,
171+
height: 20 + extraHeight,
169172
child: ListView(
170173
scrollDirection: Axis.horizontal,
171174
reverse: true,
@@ -398,6 +401,9 @@ class _ThreadPageState extends State<ThreadPage> with SingleTickerProviderStateM
398401
builder: (context, state) {
399402
// Update jump page state.
400403
context.read<JumpPageCubit>().setPageInfo(totalPages: state.totalPages, currentPage: state.currentPage);
404+
final textScaleExtraBreadHeight = context.select<SettingsBloc, double>(
405+
(bloc) => 1 * math.max(0, (bloc.state.settingsMap.textScaleFactor - 1) / 0.1),
406+
);
401407

402408
final title = widget.title ?? state.title;
403409
// Reset jump page state when every build.
@@ -425,7 +431,10 @@ class _ThreadPageState extends State<ThreadPage> with SingleTickerProviderStateM
425431
resizeToAvoidBottomInset: false,
426432
appBar: ListAppBar(
427433
title: title,
428-
bottom: PreferredSize(preferredSize: const Size.fromHeight(20), child: _buildBreadcrumbsRow(state)),
434+
bottom: PreferredSize(
435+
preferredSize: Size.fromHeight(20 + textScaleExtraBreadHeight),
436+
child: _buildBreadcrumbsRow(state, textScaleExtraBreadHeight),
437+
),
429438
showReverseOrderAction: true,
430439
onJumpPage: (pageNumber) async {
431440
if (!mounted) {

lib/i18n/en.i18n.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@
160160
"fontFamily": {
161161
"title": "Font family",
162162
"dialogTitle": "Choose font"
163+
},
164+
"textScaleFactor": {
165+
"title": "Text scale",
166+
"dialogTitle": "Text scale factor"
163167
}
164168
},
165169
"windowSection": {

lib/i18n/zh-CN.i18n.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@
160160
"fontFamily": {
161161
"title": "字体",
162162
"dialogTitle": "选择字体"
163+
},
164+
"textScaleFactor": {
165+
"title": "文字缩放",
166+
"dialogTitle": "文字缩放系数"
163167
}
164168
},
165169
"windowSection": {

lib/i18n/zh-TW.i18n.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@
160160
"fontFamily": {
161161
"title": "字體",
162162
"dialogTitle": "選擇字體"
163+
},
164+
"textScaleFactor": {
165+
"title": "文字縮放",
166+
"dialogTitle": "文字縮放係數"
163167
}
164168
},
165169
"windowSection": {

0 commit comments

Comments
 (0)