From 7802c75d7c1b050baac6617880ce1dcf156da778 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 22 Feb 2024 08:50:24 +0700 Subject: [PATCH] feat: support scaling text on mobile (#4690) --- .../lib/core/config/kv_keys.dart | 7 ++ .../appearance/appearance_setting_group.dart | 2 + .../appearance/text_scale_setting.dart | 66 +++++++++++++ .../document/presentation/editor_page.dart | 5 +- .../document/presentation/editor_style.dart | 5 + .../more/cubit/document_appearance_cubit.dart | 4 +- .../presentation/more/font_size_slider.dart | 98 +++++++++++-------- .../presentation/more/more_button.dart | 23 ++++- .../lib/startup/tasks/app_widget.dart | 20 +--- .../settings/appearance/appearance_cubit.dart | 33 ++++++- .../packages/flowy_svg/lib/src/flowy_svg.dart | 26 ++--- frontend/appflowy_flutter/pubspec.lock | 4 +- frontend/appflowy_flutter/pubspec.yaml | 2 +- frontend/resources/translations/en.json | 1 + 14 files changed, 218 insertions(+), 78 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart diff --git a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart index 7f49f7ee28..4c4e4264c5 100644 --- a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart +++ b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart @@ -51,4 +51,11 @@ class KVKeys { static const String kAppflowyCloudBaseURL = 'kAppFlowyCloudBaseURL'; static const String kSupabaseURL = 'kSupbaseURL'; static const String kSupabaseAnonKey = 'kSupabaseAnonKey'; + + /// The key for saving the text scale factor. + /// + /// The value is a double string. + /// The value range is from 0.8 to 1.0. If it's greater than 1.0, it will cause + /// the text to be too large and not aligned with the icon + static const String textScaleFactor = 'textScaleFactor'; } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart index e926822fd4..abb31817e5 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/setting/appearance/rtl_setting.dart'; +import 'package:appflowy/mobile/presentation/setting/appearance/text_scale_setting.dart'; import 'package:appflowy/mobile/presentation/setting/appearance/theme_setting.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -18,6 +19,7 @@ class AppearanceSettingGroup extends StatelessWidget { settingItemList: const [ ThemeSetting(), FontSetting(), + TextScaleSetting(), RTLSetting(), ], ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart new file mode 100644 index 0000000000..1d32071701 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart @@ -0,0 +1,66 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/plugins/document/presentation/more/font_size_slider.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../setting.dart'; + +const int _divisions = 4; + +class TextScaleSetting extends StatelessWidget { + const TextScaleSetting({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textScaleFactor = + context.watch().state.textScaleFactor; + return MobileSettingItem( + name: LocaleKeys.settings_appearance_fontScaleFactor.tr(), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowyText( + // map the text scale factor to the 0-1 + // 0.8 - 0.0 + // 0.9 - 0.5 + // 1.0 - 1.0 + ((_divisions + 1) * textScaleFactor - _divisions) + .toStringAsFixed(2), + color: theme.colorScheme.onSurface, + ), + const Icon(Icons.chevron_right), + ], + ), + onTap: () { + showMobileBottomSheet( + context, + showHeader: true, + showDragHandle: true, + showDivider: false, + showCloseButton: false, + title: LocaleKeys.settings_appearance_fontScaleFactor.tr(), + builder: (context) { + return FontSizeStepper( + value: textScaleFactor, + minimumValue: 0.8, + maximumValue: 1.0, + divisions: _divisions, + onChanged: (newTextScaleFactor) { + context + .read() + .setTextScaleFactor(newTextScaleFactor); + }, + ); + }, + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 8b959daae4..fa48ecc2d4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -1,6 +1,3 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/plugins/document/application/doc_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_configuration.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart'; @@ -22,6 +19,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:collection/collection.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; final List commandShortcutEvents = [ diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart index f00d975fe2..06ac4e222b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -7,6 +7,7 @@ import 'package:appflowy/plugins/document/presentation/more/cubit/document_appea import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; import 'package:appflowy/util/google_font_family_extension.dart'; import 'package:appflowy/workspace/application/appearance_defaults.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:collection/collection.dart'; @@ -83,6 +84,8 @@ class EditorStyleCustomizer { ), ), textSpanDecorator: customizeAttributeDecorator, + textScaleFactor: + context.watch().state.textScaleFactor, ); } @@ -131,6 +134,8 @@ class EditorStyleCustomizer { textSpanDecorator: customizeAttributeDecorator, mobileDragHandleBallSize: const Size.square(12.0), magnifierSize: const Size(144, 96), + textScaleFactor: + context.watch().state.textScaleFactor, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/more/cubit/document_appearance_cubit.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/more/cubit/document_appearance_cubit.dart index b0db2174bf..f75fc3f7a7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/more/cubit/document_appearance_cubit.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/more/cubit/document_appearance_cubit.dart @@ -78,13 +78,15 @@ class DocumentAppearanceCubit extends Cubit { ? Color(int.parse(selectionColorString)) : null; + final textScaleFactor = prefs.getDouble(KVKeys.textScaleFactor) ?? 1.0; + if (isClosed) { return; } emit( state.copyWith( - fontSize: fontSize, + fontSize: fontSize * textScaleFactor, fontFamily: fontFamily, cursorColor: cursorColor, selectionColor: selectionColor, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/more/font_size_slider.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/more/font_size_slider.dart index 513833b82b..7a512b3b6b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/more/font_size_slider.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/more/font_size_slider.dart @@ -1,51 +1,71 @@ -import 'package:flutter/material.dart'; - -import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; -class FontSizeStepper extends StatelessWidget { - const FontSizeStepper({super.key}); +class FontSizeStepper extends StatefulWidget { + const FontSizeStepper({ + super.key, + required this.minimumValue, + required this.maximumValue, + required this.value, + required this.divisions, + required this.onChanged, + }); + + final double minimumValue; + final double maximumValue; + final double value; + final ValueChanged onChanged; + final int divisions; + + @override + State createState() => _FontSizeStepperState(); +} + +class _FontSizeStepperState extends State { + late double _value = widget.value.clamp( + widget.minimumValue, + widget.maximumValue, + ); @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Row( - children: [ - const FlowyText('A', fontSize: 14), - const HSpace(6), - Expanded( - child: SliderTheme( - data: Theme.of(context).sliderTheme.copyWith( - showValueIndicator: ShowValueIndicator.never, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 8, - ), - overlayShape: const RoundSliderOverlayShape( - overlayRadius: 16, - ), - ), - child: Slider( - value: state.fontSize, - min: 10, - max: 24, - divisions: 8, - onChanged: (fontSize) => context - .read() - .syncFontSize(fontSize), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + children: [ + const FlowyText('A', fontSize: 14), + const HSpace(6), + Expanded( + child: SliderTheme( + data: Theme.of(context).sliderTheme.copyWith( + showValueIndicator: ShowValueIndicator.never, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 8, + ), + overlayShape: const RoundSliderOverlayShape( + overlayRadius: 16, + ), ), - ), + child: Slider( + value: _value, + min: widget.minimumValue, + max: widget.maximumValue, + divisions: widget.divisions, + onChanged: (value) { + setState(() { + _value = value; + }); + + widget.onChanged(value); + }, ), - const HSpace(6), - const FlowyText('A', fontSize: 20), - ], + ), ), - ); - }, + const HSpace(6), + const FlowyText('A', fontSize: 20), + ], + ), ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/more/more_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/more/more_button.dart index 338e1bf429..5351643d19 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/more/more_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/more/more_button.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/more/font_size_slider.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -9,6 +8,8 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class DocumentMoreButton extends StatelessWidget { const DocumentMoreButton({super.key}); @@ -24,7 +25,23 @@ class DocumentMoreButton extends StatelessWidget { direction: PopoverDirection.leftWithCenterAligned, constraints: const BoxConstraints(maxHeight: 40, maxWidth: 240), offset: const Offset(-10, 0), - popupBuilder: (context) => const FontSizeStepper(), + popupBuilder: (context) { + return BlocBuilder( + builder: (context, state) { + return FontSizeStepper( + minimumValue: 10, + maximumValue: 24, + value: state.fontSize, + divisions: 8, + onChanged: (newFontSize) { + context + .read() + .syncFontSize(newFontSize); + }, + ); + }, + ); + }, child: FlowyButton( text: FlowyText.regular( LocaleKeys.moreAction_fontSize.tr(), diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index 2c9d66d43f..b1ecef58c0 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -1,8 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/startup/startup.dart'; @@ -21,6 +18,8 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -186,7 +185,7 @@ class _ApplicationWidgetState extends State { // use the 1.0 as the textScaleFactor to avoid the text size // affected by the system setting. data: MediaQuery.of(context).copyWith( - textScaler: const TextScaler.linear(1), + textScaler: TextScaler.linear(state.textScaleFactor), ), child: overlayManagerBuilder(context, child), ), @@ -238,24 +237,11 @@ class AppGlobals { } class ApplicationBlocObserver extends BlocObserver { - @override - void onTransition(Bloc bloc, Transition transition) { - // Log.debug("[current]: ${transition.currentState} \n\n[next]: ${transition.nextState}"); - // Log.debug("${transition.nextState}"); - super.onTransition(bloc, transition); - } - @override void onError(BlocBase bloc, Object error, StackTrace stackTrace) { Log.debug(error); super.onError(bloc, error, stackTrace); } - - // @override - // void onEvent(Bloc bloc, Object? event) { - // Log.debug("$event"); - // super.onEvent(bloc, event); - // } } Future appTheme(String themeName) async { diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart index 1bd6ad4666..756bb80097 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/user_settings_service.dart'; import 'package:appflowy/util/color_to_hex_string.dart'; @@ -58,12 +60,38 @@ class AppearanceSettingsCubit extends Cubit { appearanceSettings.documentSetting.selectionColor, ), ), + 1.0, ), - ); + ) { + readTextScaleFactor(); + } final AppearanceSettingsPB _appearanceSettings; final DateTimeSettingsPB _dateTimeSettings; + Future setTextScaleFactor(double textScaleFactor) async { + // only saved in local storage, this value is not synced across devices + await getIt().set( + KVKeys.textScaleFactor, + textScaleFactor.toString(), + ); + + // don't allow the text scale factor to be greater than 1.0, it will cause + // ui issues + emit(state.copyWith(textScaleFactor: textScaleFactor.clamp(0.7, 1.0))); + } + + Future readTextScaleFactor() async { + final textScaleFactor = await getIt().getWithFormat( + KVKeys.textScaleFactor, + (value) => double.parse(value), + ); + textScaleFactor.fold( + () => emit(state.copyWith(textScaleFactor: 1.0)), + (value) => emit(state.copyWith(textScaleFactor: value.clamp(0.7, 1.0))), + ); + } + /// Update selected theme in the user's settings and emit an updated state /// with the AppTheme named [themeName]. Future setTheme(String themeName) async { @@ -347,6 +375,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState { required String timezoneId, required Color? documentCursorColor, required Color? documentSelectionColor, + required double textScaleFactor, }) = _AppearanceSettingsState; factory AppearanceSettingsState.initial( @@ -364,6 +393,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState { String timezoneId, Color? documentCursorColor, Color? documentSelectionColor, + double textScaleFactor, ) { return AppearanceSettingsState( appTheme: appTheme, @@ -380,6 +410,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState { timezoneId: timezoneId, documentCursorColor: documentCursorColor, documentSelectionColor: documentSelectionColor, + textScaleFactor: textScaleFactor, ); } diff --git a/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart b/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart index 0ed3cd98ca..baf08538b8 100644 --- a/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart +++ b/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart @@ -49,20 +49,24 @@ class FlowySvg extends StatelessWidget { @override Widget build(BuildContext context) { final iconColor = color ?? Theme.of(context).iconTheme.color; + final textScaleFactor = MediaQuery.textScalerOf(context).scale(1); - return SizedBox( - width: size?.width, - height: size?.height, - child: SvgPicture.asset( - _normalized(), + return Transform.scale( + scale: textScaleFactor, + child: SizedBox( width: size?.width, height: size?.height, - colorFilter: iconColor != null && blendMode != null - ? ColorFilter.mode( - iconColor, - blendMode!, - ) - : null, + child: SvgPicture.asset( + _normalized(), + width: size?.width, + height: size?.height, + colorFilter: iconColor != null && blendMode != null + ? ColorFilter.mode( + iconColor, + blendMode!, + ) + : null, + ), ), ); } diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 48f4da5ac0..12c40fe635 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -53,8 +53,8 @@ packages: dependency: "direct main" description: path: "." - ref: f38328d - resolved-ref: f38328d9e52be89b8036ae0ad3460ce9d6cc5be7 + ref: "1715ed4" + resolved-ref: "1715ed45490e0a432fa1bbfcb2f1693471632ff7" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "2.3.2" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index bd2d712077..7457c552f4 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -165,7 +165,7 @@ dependency_overrides: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: "f38328d" + ref: "1715ed4" uuid: ^4.1.0 diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 7dc6b3918b..2f6c90c962 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -333,6 +333,7 @@ "dark": "Dark Mode", "system": "Adapt to System" }, + "fontScaleFactor": "Font Scale Factor", "documentSettings": { "cursorColor": "Document cursor color", "selectionColor": "Document selection color",