feat: support scaling text on mobile (#4690)

This commit is contained in:
Lucas.Xu 2024-02-22 08:50:24 +07:00 committed by GitHub
parent f1831d07af
commit 7802c75d7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 218 additions and 78 deletions

View File

@ -51,4 +51,11 @@ class KVKeys {
static const String kAppflowyCloudBaseURL = 'kAppFlowyCloudBaseURL'; static const String kAppflowyCloudBaseURL = 'kAppFlowyCloudBaseURL';
static const String kSupabaseURL = 'kSupbaseURL'; static const String kSupabaseURL = 'kSupbaseURL';
static const String kSupabaseAnonKey = 'kSupabaseAnonKey'; 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';
} }

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart'; 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/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:appflowy/mobile/presentation/setting/appearance/theme_setting.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -18,6 +19,7 @@ class AppearanceSettingGroup extends StatelessWidget {
settingItemList: const [ settingItemList: const [
ThemeSetting(), ThemeSetting(),
FontSetting(), FontSetting(),
TextScaleSetting(),
RTLSetting(), RTLSetting(),
], ],
); );

View File

@ -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<AppearanceSettingsCubit>().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<AppearanceSettingsCubit>()
.setTextScaleFactor(newTextScaleFactor);
},
);
},
);
},
);
}
}

View File

@ -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/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_configuration.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'; 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:collection/collection.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.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:flutter_bloc/flutter_bloc.dart';
final List<CommandShortcutEvent> commandShortcutEvents = [ final List<CommandShortcutEvent> commandShortcutEvents = [

View File

@ -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/plugins/inline_actions/inline_actions_menu.dart';
import 'package:appflowy/util/google_font_family_extension.dart'; import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.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/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -83,6 +84,8 @@ class EditorStyleCustomizer {
), ),
), ),
textSpanDecorator: customizeAttributeDecorator, textSpanDecorator: customizeAttributeDecorator,
textScaleFactor:
context.watch<AppearanceSettingsCubit>().state.textScaleFactor,
); );
} }
@ -131,6 +134,8 @@ class EditorStyleCustomizer {
textSpanDecorator: customizeAttributeDecorator, textSpanDecorator: customizeAttributeDecorator,
mobileDragHandleBallSize: const Size.square(12.0), mobileDragHandleBallSize: const Size.square(12.0),
magnifierSize: const Size(144, 96), magnifierSize: const Size(144, 96),
textScaleFactor:
context.watch<AppearanceSettingsCubit>().state.textScaleFactor,
); );
} }

View File

@ -78,13 +78,15 @@ class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
? Color(int.parse(selectionColorString)) ? Color(int.parse(selectionColorString))
: null; : null;
final textScaleFactor = prefs.getDouble(KVKeys.textScaleFactor) ?? 1.0;
if (isClosed) { if (isClosed) {
return; return;
} }
emit( emit(
state.copyWith( state.copyWith(
fontSize: fontSize, fontSize: fontSize * textScaleFactor,
fontFamily: fontFamily, fontFamily: fontFamily,
cursorColor: cursorColor, cursorColor: cursorColor,
selectionColor: selectionColor, selectionColor: selectionColor,

View File

@ -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/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.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 { class FontSizeStepper extends StatefulWidget {
const FontSizeStepper({super.key}); 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<double> onChanged;
final int divisions;
@override
State<FontSizeStepper> createState() => _FontSizeStepperState();
}
class _FontSizeStepperState extends State<FontSizeStepper> {
late double _value = widget.value.clamp(
widget.minimumValue,
widget.maximumValue,
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>( return Padding(
builder: (context, state) { padding: const EdgeInsets.symmetric(horizontal: 10),
return Padding( child: Row(
padding: const EdgeInsets.symmetric(horizontal: 10), children: [
child: Row( const FlowyText('A', fontSize: 14),
children: [ const HSpace(6),
const FlowyText('A', fontSize: 14), Expanded(
const HSpace(6), child: SliderTheme(
Expanded( data: Theme.of(context).sliderTheme.copyWith(
child: SliderTheme( showValueIndicator: ShowValueIndicator.never,
data: Theme.of(context).sliderTheme.copyWith( thumbShape: const RoundSliderThumbShape(
showValueIndicator: ShowValueIndicator.never, enabledThumbRadius: 8,
thumbShape: const RoundSliderThumbShape( ),
enabledThumbRadius: 8, overlayShape: const RoundSliderOverlayShape(
), overlayRadius: 16,
overlayShape: const RoundSliderOverlayShape( ),
overlayRadius: 16,
),
),
child: Slider(
value: state.fontSize,
min: 10,
max: 24,
divisions: 8,
onChanged: (fontSize) => context
.read<DocumentAppearanceCubit>()
.syncFontSize(fontSize),
), ),
), 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),
],
),
); );
} }
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/plugins/document/presentation/more/font_size_slider.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.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/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.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 { class DocumentMoreButton extends StatelessWidget {
const DocumentMoreButton({super.key}); const DocumentMoreButton({super.key});
@ -24,7 +25,23 @@ class DocumentMoreButton extends StatelessWidget {
direction: PopoverDirection.leftWithCenterAligned, direction: PopoverDirection.leftWithCenterAligned,
constraints: const BoxConstraints(maxHeight: 40, maxWidth: 240), constraints: const BoxConstraints(maxHeight: 40, maxWidth: 240),
offset: const Offset(-10, 0), offset: const Offset(-10, 0),
popupBuilder: (context) => const FontSizeStepper(), popupBuilder: (context) {
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
builder: (context, state) {
return FontSizeStepper(
minimumValue: 10,
maximumValue: 24,
value: state.fontSize,
divisions: 8,
onChanged: (newFontSize) {
context
.read<DocumentAppearanceCubit>()
.syncFontSize(newFontSize);
},
);
},
);
},
child: FlowyButton( child: FlowyButton(
text: FlowyText.regular( text: FlowyText.regular(
LocaleKeys.moreAction_fontSize.tr(), LocaleKeys.moreAction_fontSize.tr(),

View File

@ -1,8 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/startup/startup.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:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.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:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -186,7 +185,7 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
// use the 1.0 as the textScaleFactor to avoid the text size // use the 1.0 as the textScaleFactor to avoid the text size
// affected by the system setting. // affected by the system setting.
data: MediaQuery.of(context).copyWith( data: MediaQuery.of(context).copyWith(
textScaler: const TextScaler.linear(1), textScaler: TextScaler.linear(state.textScaleFactor),
), ),
child: overlayManagerBuilder(context, child), child: overlayManagerBuilder(context, child),
), ),
@ -238,24 +237,11 @@ class AppGlobals {
} }
class ApplicationBlocObserver extends BlocObserver { 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 @override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) { void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
Log.debug(error); Log.debug(error);
super.onError(bloc, error, stackTrace); super.onError(bloc, error, stackTrace);
} }
// @override
// void onEvent(Bloc bloc, Object? event) {
// Log.debug("$event");
// super.onEvent(bloc, event);
// }
} }
Future<AppTheme> appTheme(String themeName) async { Future<AppTheme> appTheme(String themeName) async {

View File

@ -1,5 +1,7 @@
import 'dart:async'; 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/startup/startup.dart';
import 'package:appflowy/user/application/user_settings_service.dart'; import 'package:appflowy/user/application/user_settings_service.dart';
import 'package:appflowy/util/color_to_hex_string.dart'; import 'package:appflowy/util/color_to_hex_string.dart';
@ -58,12 +60,38 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
appearanceSettings.documentSetting.selectionColor, appearanceSettings.documentSetting.selectionColor,
), ),
), ),
1.0,
), ),
); ) {
readTextScaleFactor();
}
final AppearanceSettingsPB _appearanceSettings; final AppearanceSettingsPB _appearanceSettings;
final DateTimeSettingsPB _dateTimeSettings; final DateTimeSettingsPB _dateTimeSettings;
Future<void> setTextScaleFactor(double textScaleFactor) async {
// only saved in local storage, this value is not synced across devices
await getIt<KeyValueStorage>().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<void> readTextScaleFactor() async {
final textScaleFactor = await getIt<KeyValueStorage>().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 /// Update selected theme in the user's settings and emit an updated state
/// with the AppTheme named [themeName]. /// with the AppTheme named [themeName].
Future<void> setTheme(String themeName) async { Future<void> setTheme(String themeName) async {
@ -347,6 +375,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
required String timezoneId, required String timezoneId,
required Color? documentCursorColor, required Color? documentCursorColor,
required Color? documentSelectionColor, required Color? documentSelectionColor,
required double textScaleFactor,
}) = _AppearanceSettingsState; }) = _AppearanceSettingsState;
factory AppearanceSettingsState.initial( factory AppearanceSettingsState.initial(
@ -364,6 +393,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
String timezoneId, String timezoneId,
Color? documentCursorColor, Color? documentCursorColor,
Color? documentSelectionColor, Color? documentSelectionColor,
double textScaleFactor,
) { ) {
return AppearanceSettingsState( return AppearanceSettingsState(
appTheme: appTheme, appTheme: appTheme,
@ -380,6 +410,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
timezoneId: timezoneId, timezoneId: timezoneId,
documentCursorColor: documentCursorColor, documentCursorColor: documentCursorColor,
documentSelectionColor: documentSelectionColor, documentSelectionColor: documentSelectionColor,
textScaleFactor: textScaleFactor,
); );
} }

View File

@ -49,20 +49,24 @@ class FlowySvg extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final iconColor = color ?? Theme.of(context).iconTheme.color; final iconColor = color ?? Theme.of(context).iconTheme.color;
final textScaleFactor = MediaQuery.textScalerOf(context).scale(1);
return SizedBox( return Transform.scale(
width: size?.width, scale: textScaleFactor,
height: size?.height, child: SizedBox(
child: SvgPicture.asset(
_normalized(),
width: size?.width, width: size?.width,
height: size?.height, height: size?.height,
colorFilter: iconColor != null && blendMode != null child: SvgPicture.asset(
? ColorFilter.mode( _normalized(),
iconColor, width: size?.width,
blendMode!, height: size?.height,
) colorFilter: iconColor != null && blendMode != null
: null, ? ColorFilter.mode(
iconColor,
blendMode!,
)
: null,
),
), ),
); );
} }

View File

@ -53,8 +53,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: f38328d ref: "1715ed4"
resolved-ref: f38328d9e52be89b8036ae0ad3460ce9d6cc5be7 resolved-ref: "1715ed45490e0a432fa1bbfcb2f1693471632ff7"
url: "https://github.com/AppFlowy-IO/appflowy-editor.git" url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git source: git
version: "2.3.2" version: "2.3.2"

View File

@ -165,7 +165,7 @@ dependency_overrides:
appflowy_editor: appflowy_editor:
git: git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "f38328d" ref: "1715ed4"
uuid: ^4.1.0 uuid: ^4.1.0

View File

@ -333,6 +333,7 @@
"dark": "Dark Mode", "dark": "Dark Mode",
"system": "Adapt to System" "system": "Adapt to System"
}, },
"fontScaleFactor": "Font Scale Factor",
"documentSettings": { "documentSettings": {
"cursorColor": "Document cursor color", "cursorColor": "Document cursor color",
"selectionColor": "Document selection color", "selectionColor": "Document selection color",