feat: Customize Font Size In AppFlowy #1479

This commit is contained in:
Lucas.Xu 2022-11-28 10:37:37 +08:00
parent 462daee934
commit 0ba26e0a84
6 changed files with 291 additions and 147 deletions

View File

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

View File

@ -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,126 +120,23 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
Widget get leftBarItem => ViewLeftBarItem(view: view);
@override
Widget? get rightBarItem => DocumentShareButton(view: view);
Widget? get rightBarItem {
return Row(
children: [
DocumentShareButton(view: view),
const SizedBox(width: 10),
ChangeNotifierProvider.value(
value: documentStyle,
child: const DocumentMoreButton(),
),
],
);
}
@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),
),
),
),
);
}
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;
}
extension QuestionBubbleExtension on ShareAction {
String get name {
switch (this) {

View File

@ -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) {

View File

@ -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),
),
),
);
}
}

View File

@ -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)),
);
}
}

View File

@ -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;
}