Pārlūkot izejas kodu

feat: Customize Font Size In AppFlowy #1479

Lucas.Xu 2 gadi atpakaļ
vecāks
revīzija
0ba26e0a84

+ 6 - 0
frontend/app_flowy/assets/translations/en.json

@@ -40,6 +40,12 @@
     "markdown": "Markdown",
     "copyLink": "Copy Link"
   },
+  "moreAction": {
+    "small": "small",
+    "medium": "medium",
+    "large": "large",
+    "fontSize": "Font Size"
+  },
   "disclosureAction": {
     "rename": "Rename",
     "delete": "Delete",

+ 52 - 134
frontend/app_flowy/lib/plugins/document/document.dart

@@ -1,28 +1,17 @@
 library document_plugin;
 
 import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:app_flowy/plugins/document/document_page.dart';
+import 'package:app_flowy/plugins/document/presentation/more/more_button.dart';
+import 'package:app_flowy/plugins/document/presentation/share/share_button.dart';
 import 'package:app_flowy/plugins/util.dart';
 import 'package:app_flowy/startup/plugin/plugin.dart';
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/plugins/document/application/share_bloc.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
-import 'package:app_flowy/workspace/presentation/home/toast.dart';
 import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
-import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
-import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
-import 'package:appflowy_popover/appflowy_popover.dart';
-import 'package:clipboard/clipboard.dart';
 import 'package:easy_localization/easy_localization.dart';
-import 'package:file_picker/file_picker.dart';
-import 'package:flowy_infra_ui/widget/rounded_button.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-import 'document_page.dart';
+import 'package:provider/provider.dart';
 
 class DocumentPluginBuilder extends PluginBuilder {
   @override
@@ -47,8 +36,20 @@ class DocumentPluginBuilder extends PluginBuilder {
   ViewDataFormatPB get dataFormatType => ViewDataFormatPB.TreeFormat;
 }
 
+class DocumentStyle with ChangeNotifier {
+  DocumentStyle();
+
+  double _fontSize = 14.0;
+  double get fontSize => _fontSize;
+  set fontSize(double fontSize) {
+    _fontSize = fontSize;
+    notifyListeners();
+  }
+}
+
 class DocumentPlugin extends Plugin<int> {
   late PluginType _pluginType;
+  late final DocumentStyle _documentStyle;
 
   @override
   final ViewPluginNotifier notifier;
@@ -59,10 +60,22 @@ class DocumentPlugin extends Plugin<int> {
     Key? key,
   }) : notifier = ViewPluginNotifier(view: view) {
     _pluginType = pluginType;
+    _documentStyle = DocumentStyle();
   }
 
   @override
-  PluginDisplay get display => DocumentPluginDisplay(notifier: notifier);
+  void dispose() {
+    _documentStyle.dispose();
+    super.dispose();
+  }
+
+  @override
+  PluginDisplay get display {
+    return DocumentPluginDisplay(
+      notifier: notifier,
+      documentStyle: _documentStyle,
+    );
+  }
 
   @override
   PluginType get ty => _pluginType;
@@ -75,8 +88,13 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
   final ViewPluginNotifier notifier;
   ViewPB get view => notifier.view;
   int? deletedViewIndex;
+  DocumentStyle documentStyle;
 
-  DocumentPluginDisplay({required this.notifier, Key? key});
+  DocumentPluginDisplay({
+    required this.notifier,
+    required this.documentStyle,
+    Key? key,
+  });
 
   @override
   Widget buildWidget(PluginContext context) {
@@ -88,10 +106,13 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
       });
     });
 
-    return DocumentPage(
-      view: view,
-      onDeleted: () => context.onDeleted(view, deletedViewIndex),
-      key: ValueKey(view.id),
+    return ChangeNotifierProvider.value(
+      value: documentStyle,
+      child: DocumentPage(
+        view: view,
+        onDeleted: () => context.onDeleted(view, deletedViewIndex),
+        key: ValueKey(view.id),
+      ),
     );
   }
 
@@ -99,124 +120,21 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
   Widget get leftBarItem => ViewLeftBarItem(view: view);
 
   @override
-  Widget? get rightBarItem => DocumentShareButton(view: view);
-
-  @override
-  List<NavigationItem> get navigationItems => [this];
-}
-
-class DocumentShareButton extends StatelessWidget {
-  final ViewPB view;
-  DocumentShareButton({Key? key, required this.view})
-      : super(key: ValueKey(view.hashCode));
-
-  @override
-  Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (context) => getIt<DocShareBloc>(param1: view),
-      child: BlocListener<DocShareBloc, DocShareState>(
-        listener: (context, state) {
-          state.map(
-            initial: (_) {},
-            loading: (_) {},
-            finish: (state) {
-              state.successOrFail.fold(
-                _handleExportData,
-                _handleExportError,
-              );
-            },
-          );
-        },
-        child: BlocBuilder<DocShareBloc, DocShareState>(
-          builder: (context, state) => ConstrainedBox(
-            constraints: const BoxConstraints.expand(
-              height: 30,
-              width: 100,
-            ),
-            child: ShareActionList(view: view),
-          ),
+  Widget? get rightBarItem {
+    return Row(
+      children: [
+        DocumentShareButton(view: view),
+        const SizedBox(width: 10),
+        ChangeNotifierProvider.value(
+          value: documentStyle,
+          child: const DocumentMoreButton(),
         ),
-      ),
+      ],
     );
   }
 
-  void _handleExportData(ExportDataPB exportData) {
-    switch (exportData.exportType) {
-      case ExportType.Link:
-        break;
-      case ExportType.Markdown:
-        FlutterClipboard.copy(exportData.data)
-            .then((value) => Log.info('copied to clipboard'));
-        break;
-      case ExportType.Text:
-        break;
-    }
-  }
-
-  void _handleExportError(FlowyError error) {}
-}
-
-class ShareActionList extends StatelessWidget {
-  const ShareActionList({
-    Key? key,
-    required this.view,
-  }) : super(key: key);
-
-  final ViewPB view;
-
-  @override
-  Widget build(BuildContext context) {
-    final docShareBloc = context.read<DocShareBloc>();
-    return PopoverActionList<ShareActionWrapper>(
-      direction: PopoverDirection.bottomWithCenterAligned,
-      actions: ShareAction.values
-          .map((action) => ShareActionWrapper(action))
-          .toList(),
-      buildChild: (controller) {
-        return RoundedTextButton(
-          title: LocaleKeys.shareAction_buttonText.tr(),
-          onPressed: () => controller.show(),
-        );
-      },
-      onSelected: (action, controller) async {
-        switch (action.inner) {
-          case ShareAction.markdown:
-            final exportPath = await FilePicker.platform.saveFile(
-              dialogTitle: '',
-              fileName: '${view.name}.md',
-            );
-            if (exportPath != null) {
-              docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
-              showMessageToast('Exported to: $exportPath');
-            }
-            break;
-          case ShareAction.copyLink:
-            NavigatorAlertDialog(
-                    title: LocaleKeys.shareAction_workInProgress.tr())
-                .show(context);
-            break;
-        }
-        controller.close();
-      },
-    );
-  }
-}
-
-enum ShareAction {
-  markdown,
-  copyLink,
-}
-
-class ShareActionWrapper extends ActionCell {
-  final ShareAction inner;
-
-  ShareActionWrapper(this.inner);
-
   @override
-  Widget? icon(Color iconColor) => null;
-
-  @override
-  String get name => inner.name;
+  List<NavigationItem> get navigationItems => [this];
 }
 
 extension QuestionBubbleExtension on ShareAction {

+ 14 - 11
frontend/app_flowy/lib/plugins/document/editor_styles.dart

@@ -1,9 +1,10 @@
+import 'package:app_flowy/plugins/document/document.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flutter/material.dart';
-
-const _baseFontSize = 14.0;
+import 'package:provider/provider.dart';
 
 EditorStyle customEditorTheme(BuildContext context) {
+  final documentStyle = context.watch<DocumentStyle>();
   var editorStyle = Theme.of(context).brightness == Brightness.dark
       ? EditorStyle.dark
       : EditorStyle.light;
@@ -11,11 +12,11 @@ EditorStyle customEditorTheme(BuildContext context) {
     padding: const EdgeInsets.all(0),
     textStyle: editorStyle.textStyle?.copyWith(
       fontFamily: 'poppins',
-      fontSize: _baseFontSize,
+      fontSize: documentStyle.fontSize,
     ),
     placeholderTextStyle: editorStyle.placeholderTextStyle?.copyWith(
       fontFamily: 'poppins',
-      fontSize: _baseFontSize,
+      fontSize: documentStyle.fontSize,
     ),
     bold: editorStyle.bold?.copyWith(
       fontWeight: FontWeight.w500,
@@ -26,22 +27,24 @@ EditorStyle customEditorTheme(BuildContext context) {
 }
 
 Iterable<ThemeExtension<dynamic>> customPluginTheme(BuildContext context) {
+  final documentStyle = context.watch<DocumentStyle>();
   const basePadding = 12.0;
   var headingPluginStyle = Theme.of(context).brightness == Brightness.dark
       ? HeadingPluginStyle.dark
       : HeadingPluginStyle.light;
   headingPluginStyle = headingPluginStyle.copyWith(
     textStyle: (EditorState editorState, Node node) {
+      final baseFontSize = documentStyle.fontSize;
       final headingToFontSize = {
-        'h1': _baseFontSize + 12,
-        'h2': _baseFontSize + 8,
-        'h3': _baseFontSize + 4,
-        'h4': _baseFontSize,
-        'h5': _baseFontSize,
-        'h6': _baseFontSize,
+        'h1': baseFontSize + 12,
+        'h2': baseFontSize + 8,
+        'h3': baseFontSize + 4,
+        'h4': baseFontSize,
+        'h5': baseFontSize,
+        'h6': baseFontSize,
       };
       final fontSize =
-          headingToFontSize[node.attributes.heading] ?? _baseFontSize;
+          headingToFontSize[node.attributes.heading] ?? baseFontSize;
       return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600);
     },
     padding: (EditorState editorState, Node node) {

+ 54 - 0
frontend/app_flowy/lib/plugins/document/presentation/more/font_size_switcher.dart

@@ -0,0 +1,54 @@
+import 'package:app_flowy/plugins/document/document.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flutter/material.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:provider/provider.dart';
+
+class FontSizeSwitcher extends StatefulWidget {
+  const FontSizeSwitcher({
+    super.key,
+    // required this.documentStyle,
+  });
+
+  // final DocumentStyle documentStyle;
+
+  @override
+  State<FontSizeSwitcher> createState() => _FontSizeSwitcherState();
+}
+
+class _FontSizeSwitcherState extends State<FontSizeSwitcher> {
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      children: [
+        const FlowyText.semibold(LocaleKeys.moreAction_fontSize),
+        Row(
+          crossAxisAlignment: CrossAxisAlignment.center,
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            _buildFontSizeSwitchButton(LocaleKeys.moreAction_small, 12.0),
+            _buildFontSizeSwitchButton(LocaleKeys.moreAction_medium, 14.0),
+            _buildFontSizeSwitchButton(LocaleKeys.moreAction_large, 18.0),
+          ],
+        )
+      ],
+    );
+  }
+
+  Widget _buildFontSizeSwitchButton(String name, double fontSize) {
+    return Center(
+      child: TextButton(
+        onPressed: () {
+          final x = Provider.of<DocumentStyle>(context, listen: false);
+          x;
+          Provider.of<DocumentStyle>(context, listen: false).fontSize =
+              fontSize;
+        },
+        child: Text(
+          name,
+          style: TextStyle(fontSize: fontSize),
+        ),
+      ),
+    );
+  }
+}

+ 30 - 0
frontend/app_flowy/lib/plugins/document/presentation/more/more_button.dart

@@ -0,0 +1,30 @@
+import 'package:app_flowy/plugins/document/presentation/more/font_size_switcher.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flutter/material.dart';
+
+class DocumentMoreButton extends StatelessWidget {
+  const DocumentMoreButton({
+    Key? key,
+    // required this.documentStyle,
+  }) : super(key: key);
+
+  // final DocumentStyle documentStyle;
+
+  @override
+  Widget build(BuildContext context) {
+    return PopupMenuButton<int>(
+      itemBuilder: (context) {
+        return [
+          const PopupMenuItem(
+            value: 1,
+            enabled: false,
+            child: FontSizeSwitcher(
+                // documentStyle: documentStyle,
+                ),
+          )
+        ];
+      },
+      child: svgWithSize('editor/details', const Size(18, 18)),
+    );
+  }
+}

+ 133 - 0
frontend/app_flowy/lib/plugins/document/presentation/share/share_button.dart

@@ -0,0 +1,133 @@
+library document_plugin;
+
+import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/plugins/document/application/share_bloc.dart';
+import 'package:app_flowy/workspace/presentation/home/toast.dart';
+import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:clipboard/clipboard.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flowy_infra_ui/widget/rounded_button.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class DocumentShareButton extends StatelessWidget {
+  final ViewPB view;
+  DocumentShareButton({Key? key, required this.view})
+      : super(key: ValueKey(view.hashCode));
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) => getIt<DocShareBloc>(param1: view),
+      child: BlocListener<DocShareBloc, DocShareState>(
+        listener: (context, state) {
+          state.map(
+            initial: (_) {},
+            loading: (_) {},
+            finish: (state) {
+              state.successOrFail.fold(
+                _handleExportData,
+                _handleExportError,
+              );
+            },
+          );
+        },
+        child: BlocBuilder<DocShareBloc, DocShareState>(
+          builder: (context, state) => ConstrainedBox(
+            constraints: const BoxConstraints.expand(
+              height: 30,
+              width: 100,
+            ),
+            child: ShareActionList(view: view),
+          ),
+        ),
+      ),
+    );
+  }
+
+  void _handleExportData(ExportDataPB exportData) {
+    switch (exportData.exportType) {
+      case ExportType.Link:
+        break;
+      case ExportType.Markdown:
+        FlutterClipboard.copy(exportData.data)
+            .then((value) => Log.info('copied to clipboard'));
+        break;
+      case ExportType.Text:
+        break;
+    }
+  }
+
+  void _handleExportError(FlowyError error) {}
+}
+
+class ShareActionList extends StatelessWidget {
+  const ShareActionList({
+    Key? key,
+    required this.view,
+  }) : super(key: key);
+
+  final ViewPB view;
+
+  @override
+  Widget build(BuildContext context) {
+    final docShareBloc = context.read<DocShareBloc>();
+    return PopoverActionList<ShareActionWrapper>(
+      direction: PopoverDirection.bottomWithCenterAligned,
+      actions: ShareAction.values
+          .map((action) => ShareActionWrapper(action))
+          .toList(),
+      buildChild: (controller) {
+        return RoundedTextButton(
+          title: LocaleKeys.shareAction_buttonText.tr(),
+          onPressed: () => controller.show(),
+        );
+      },
+      onSelected: (action, controller) async {
+        switch (action.inner) {
+          case ShareAction.markdown:
+            final exportPath = await FilePicker.platform.saveFile(
+              dialogTitle: '',
+              fileName: '${view.name}.md',
+            );
+            if (exportPath != null) {
+              docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
+              showMessageToast('Exported to: $exportPath');
+            }
+            break;
+          case ShareAction.copyLink:
+            NavigatorAlertDialog(
+                    title: LocaleKeys.shareAction_workInProgress.tr())
+                .show(context);
+            break;
+        }
+        controller.close();
+      },
+    );
+  }
+}
+
+enum ShareAction {
+  markdown,
+  copyLink,
+}
+
+class ShareActionWrapper extends ActionCell {
+  final ShareAction inner;
+
+  ShareActionWrapper(this.inner);
+
+  @override
+  Widget? icon(Color iconColor) => null;
+
+  @override
+  String get name => inner.name;
+}