mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support scaling text on mobile (#4690)
This commit is contained in:
parent
f1831d07af
commit
7802c75d7c
@ -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';
|
||||
}
|
||||
|
@ -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(),
|
||||
],
|
||||
);
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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<CommandShortcutEvent> commandShortcutEvents = [
|
||||
|
@ -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<AppearanceSettingsCubit>().state.textScaleFactor,
|
||||
);
|
||||
}
|
||||
|
||||
@ -131,6 +134,8 @@ class EditorStyleCustomizer {
|
||||
textSpanDecorator: customizeAttributeDecorator,
|
||||
mobileDragHandleBallSize: const Size.square(12.0),
|
||||
magnifierSize: const Size(144, 96),
|
||||
textScaleFactor:
|
||||
context.watch<AppearanceSettingsCubit>().state.textScaleFactor,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -78,13 +78,15 @@ class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
|
||||
? 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,
|
||||
|
@ -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<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
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
|
||||
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<DocumentAppearanceCubit>()
|
||||
.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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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<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(
|
||||
text: FlowyText.regular(
|
||||
LocaleKeys.moreAction_fontSize.tr(),
|
||||
|
@ -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<ApplicationWidget> {
|
||||
// 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> appTheme(String themeName) async {
|
||||
|
@ -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<AppearanceSettingsState> {
|
||||
appearanceSettings.documentSetting.selectionColor,
|
||||
),
|
||||
),
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
) {
|
||||
readTextScaleFactor();
|
||||
}
|
||||
|
||||
final AppearanceSettingsPB _appearanceSettings;
|
||||
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
|
||||
/// with the AppTheme named [themeName].
|
||||
Future<void> 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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -333,6 +333,7 @@
|
||||
"dark": "Dark Mode",
|
||||
"system": "Adapt to System"
|
||||
},
|
||||
"fontScaleFactor": "Font Scale Factor",
|
||||
"documentSettings": {
|
||||
"cursorColor": "Document cursor color",
|
||||
"selectionColor": "Document selection color",
|
||||
|
Loading…
Reference in New Issue
Block a user