mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #1495 from LucasXu0/customize_font_size
Customize Font Size In AppFlowy #1479
This commit is contained in:
@ -40,6 +40,12 @@
|
|||||||
"markdown": "Markdown",
|
"markdown": "Markdown",
|
||||||
"copyLink": "Copy Link"
|
"copyLink": "Copy Link"
|
||||||
},
|
},
|
||||||
|
"moreAction": {
|
||||||
|
"small": "small",
|
||||||
|
"medium": "medium",
|
||||||
|
"large": "large",
|
||||||
|
"fontSize": "Font Size"
|
||||||
|
},
|
||||||
"disclosureAction": {
|
"disclosureAction": {
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
@ -1,29 +1,19 @@
|
|||||||
library document_plugin;
|
library document_plugin;
|
||||||
|
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
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/cubit/document_appearance_cubit.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/plugins/util.dart';
|
||||||
import 'package:app_flowy/startup/plugin/plugin.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/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/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: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-folder/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import 'document_page.dart';
|
|
||||||
|
|
||||||
class DocumentPluginBuilder extends PluginBuilder {
|
class DocumentPluginBuilder extends PluginBuilder {
|
||||||
@override
|
@override
|
||||||
Plugin build(dynamic data) {
|
Plugin build(dynamic data) {
|
||||||
@ -49,6 +39,8 @@ class DocumentPluginBuilder extends PluginBuilder {
|
|||||||
|
|
||||||
class DocumentPlugin extends Plugin<int> {
|
class DocumentPlugin extends Plugin<int> {
|
||||||
late PluginType _pluginType;
|
late PluginType _pluginType;
|
||||||
|
final DocumentAppearanceCubit _documentAppearanceCubit =
|
||||||
|
DocumentAppearanceCubit();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final ViewPluginNotifier notifier;
|
final ViewPluginNotifier notifier;
|
||||||
@ -59,10 +51,22 @@ class DocumentPlugin extends Plugin<int> {
|
|||||||
Key? key,
|
Key? key,
|
||||||
}) : notifier = ViewPluginNotifier(view: view) {
|
}) : notifier = ViewPluginNotifier(view: view) {
|
||||||
_pluginType = pluginType;
|
_pluginType = pluginType;
|
||||||
|
_documentAppearanceCubit.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PluginDisplay get display => DocumentPluginDisplay(notifier: notifier);
|
void dispose() {
|
||||||
|
_documentAppearanceCubit.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PluginDisplay get display {
|
||||||
|
return DocumentPluginDisplay(
|
||||||
|
notifier: notifier,
|
||||||
|
documentAppearanceCubit: _documentAppearanceCubit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PluginType get ty => _pluginType;
|
PluginType get ty => _pluginType;
|
||||||
@ -75,8 +79,13 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
|
|||||||
final ViewPluginNotifier notifier;
|
final ViewPluginNotifier notifier;
|
||||||
ViewPB get view => notifier.view;
|
ViewPB get view => notifier.view;
|
||||||
int? deletedViewIndex;
|
int? deletedViewIndex;
|
||||||
|
DocumentAppearanceCubit documentAppearanceCubit;
|
||||||
|
|
||||||
DocumentPluginDisplay({required this.notifier, Key? key});
|
DocumentPluginDisplay({
|
||||||
|
required this.notifier,
|
||||||
|
required this.documentAppearanceCubit,
|
||||||
|
Key? key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildWidget(PluginContext context) {
|
Widget buildWidget(PluginContext context) {
|
||||||
@ -88,10 +97,17 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return DocumentPage(
|
return BlocProvider.value(
|
||||||
view: view,
|
value: documentAppearanceCubit,
|
||||||
onDeleted: () => context.onDeleted(view, deletedViewIndex),
|
child: BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
|
||||||
key: ValueKey(view.id),
|
builder: (_, state) {
|
||||||
|
return DocumentPage(
|
||||||
|
view: view,
|
||||||
|
onDeleted: () => context.onDeleted(view, deletedViewIndex),
|
||||||
|
key: ValueKey(view.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,133 +115,19 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
|
|||||||
Widget get leftBarItem => ViewLeftBarItem(view: view);
|
Widget get leftBarItem => ViewLeftBarItem(view: view);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? get rightBarItem => DocumentShareButton(view: view);
|
Widget? get rightBarItem {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
DocumentShareButton(view: view),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
BlocProvider.value(
|
||||||
|
value: documentAppearanceCubit,
|
||||||
|
child: const DocumentMoreButton(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<NavigationItem> get navigationItems => [this];
|
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? leftIcon(Color iconColor) => null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get name => inner.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
extension QuestionBubbleExtension on ShareAction {
|
|
||||||
String get name {
|
|
||||||
switch (this) {
|
|
||||||
case ShareAction.markdown:
|
|
||||||
return LocaleKeys.shareAction_markdown.tr();
|
|
||||||
case ShareAction.copyLink:
|
|
||||||
return LocaleKeys.shareAction_copyLink.tr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import 'package:app_flowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
const _baseFontSize = 14.0;
|
|
||||||
|
|
||||||
EditorStyle customEditorTheme(BuildContext context) {
|
EditorStyle customEditorTheme(BuildContext context) {
|
||||||
|
final documentStyle =
|
||||||
|
context.watch<DocumentAppearanceCubit>().documentAppearance;
|
||||||
var editorStyle = Theme.of(context).brightness == Brightness.dark
|
var editorStyle = Theme.of(context).brightness == Brightness.dark
|
||||||
? EditorStyle.dark
|
? EditorStyle.dark
|
||||||
: EditorStyle.light;
|
: EditorStyle.light;
|
||||||
@ -11,14 +13,15 @@ EditorStyle customEditorTheme(BuildContext context) {
|
|||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
textStyle: editorStyle.textStyle?.copyWith(
|
textStyle: editorStyle.textStyle?.copyWith(
|
||||||
fontFamily: 'poppins',
|
fontFamily: 'poppins',
|
||||||
fontSize: _baseFontSize,
|
fontSize: documentStyle.fontSize,
|
||||||
),
|
),
|
||||||
placeholderTextStyle: editorStyle.placeholderTextStyle?.copyWith(
|
placeholderTextStyle: editorStyle.placeholderTextStyle?.copyWith(
|
||||||
fontFamily: 'poppins',
|
fontFamily: 'poppins',
|
||||||
fontSize: _baseFontSize,
|
fontSize: documentStyle.fontSize,
|
||||||
),
|
),
|
||||||
bold: editorStyle.bold?.copyWith(
|
bold: editorStyle.bold?.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w600,
|
||||||
|
fontFamily: 'poppins-Bold',
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
);
|
);
|
||||||
@ -26,6 +29,9 @@ EditorStyle customEditorTheme(BuildContext context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Iterable<ThemeExtension<dynamic>> customPluginTheme(BuildContext context) {
|
Iterable<ThemeExtension<dynamic>> customPluginTheme(BuildContext context) {
|
||||||
|
final documentStyle =
|
||||||
|
context.watch<DocumentAppearanceCubit>().documentAppearance;
|
||||||
|
final baseFontSize = documentStyle.fontSize;
|
||||||
const basePadding = 12.0;
|
const basePadding = 12.0;
|
||||||
var headingPluginStyle = Theme.of(context).brightness == Brightness.dark
|
var headingPluginStyle = Theme.of(context).brightness == Brightness.dark
|
||||||
? HeadingPluginStyle.dark
|
? HeadingPluginStyle.dark
|
||||||
@ -33,15 +39,15 @@ Iterable<ThemeExtension<dynamic>> customPluginTheme(BuildContext context) {
|
|||||||
headingPluginStyle = headingPluginStyle.copyWith(
|
headingPluginStyle = headingPluginStyle.copyWith(
|
||||||
textStyle: (EditorState editorState, Node node) {
|
textStyle: (EditorState editorState, Node node) {
|
||||||
final headingToFontSize = {
|
final headingToFontSize = {
|
||||||
'h1': _baseFontSize + 12,
|
'h1': baseFontSize + 12,
|
||||||
'h2': _baseFontSize + 8,
|
'h2': baseFontSize + 8,
|
||||||
'h3': _baseFontSize + 4,
|
'h3': baseFontSize + 4,
|
||||||
'h4': _baseFontSize,
|
'h4': baseFontSize,
|
||||||
'h5': _baseFontSize,
|
'h5': baseFontSize,
|
||||||
'h6': _baseFontSize,
|
'h6': baseFontSize,
|
||||||
};
|
};
|
||||||
final fontSize =
|
final fontSize =
|
||||||
headingToFontSize[node.attributes.heading] ?? _baseFontSize;
|
headingToFontSize[node.attributes.heading] ?? baseFontSize;
|
||||||
return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600);
|
return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600);
|
||||||
},
|
},
|
||||||
padding: (EditorState editorState, Node node) {
|
padding: (EditorState editorState, Node node) {
|
||||||
@ -57,10 +63,28 @@ Iterable<ThemeExtension<dynamic>> customPluginTheme(BuildContext context) {
|
|||||||
return EdgeInsets.only(bottom: padding);
|
return EdgeInsets.only(bottom: padding);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
var numberListPluginStyle = Theme.of(context).brightness == Brightness.dark
|
||||||
|
? NumberListPluginStyle.dark
|
||||||
|
: NumberListPluginStyle.light;
|
||||||
|
|
||||||
|
numberListPluginStyle = numberListPluginStyle.copyWith(
|
||||||
|
icon: (_, textNode) {
|
||||||
|
const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0);
|
||||||
|
return Container(
|
||||||
|
padding: iconPadding,
|
||||||
|
child: Text(
|
||||||
|
'${textNode.attributes.number.toString()}.',
|
||||||
|
style: customEditorTheme(context).textStyle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
final pluginTheme = Theme.of(context).brightness == Brightness.dark
|
final pluginTheme = Theme.of(context).brightness == Brightness.dark
|
||||||
? darkPlguinStyleExtension
|
? darkPlguinStyleExtension
|
||||||
: lightPlguinStyleExtension;
|
: lightPlguinStyleExtension;
|
||||||
return pluginTheme.toList()
|
return pluginTheme.toList()
|
||||||
..removeWhere((element) => element is HeadingPluginStyle)
|
..removeWhere((element) =>
|
||||||
..add(headingPluginStyle);
|
element is HeadingPluginStyle || element is NumberListPluginStyle)
|
||||||
|
..add(headingPluginStyle)
|
||||||
|
..add(numberListPluginStyle);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
const String _kDocumentAppearenceFontSize = 'kDocumentAppearenceFontSize';
|
||||||
|
|
||||||
|
class DocumentAppearance {
|
||||||
|
const DocumentAppearance({
|
||||||
|
required this.fontSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double fontSize;
|
||||||
|
// Will be supported...
|
||||||
|
// final String fontName;
|
||||||
|
|
||||||
|
DocumentAppearance copyWith({double? fontSize}) {
|
||||||
|
return DocumentAppearance(
|
||||||
|
fontSize: fontSize ?? this.fontSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
|
||||||
|
DocumentAppearanceCubit() : super(const DocumentAppearance(fontSize: 14.0));
|
||||||
|
|
||||||
|
late DocumentAppearance documentAppearance;
|
||||||
|
|
||||||
|
void fetch() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final fontSize = prefs.getDouble(_kDocumentAppearenceFontSize) ?? 14.0;
|
||||||
|
documentAppearance = DocumentAppearance(fontSize: fontSize);
|
||||||
|
emit(documentAppearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sync(DocumentAppearance documentAppearance) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
prefs.setDouble(_kDocumentAppearenceFontSize, documentAppearance.fontSize);
|
||||||
|
this.documentAppearance = documentAppearance;
|
||||||
|
emit(documentAppearance);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
import 'package:app_flowy/plugins/document/presentation/more/cubit/document_appearance_cubit.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';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class FontSizeSwitcher extends StatefulWidget {
|
||||||
|
const FontSizeSwitcher({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FontSizeSwitcher> createState() => _FontSizeSwitcherState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FontSizeSwitcherState extends State<FontSizeSwitcher> {
|
||||||
|
final List<bool> _selectedFontSizes = [false, true, false];
|
||||||
|
final List<Tuple2<String, double>> _fontSizes = [
|
||||||
|
Tuple2(LocaleKeys.moreAction_small.tr(), 12.0),
|
||||||
|
Tuple2(LocaleKeys.moreAction_medium.tr(), 14.0),
|
||||||
|
Tuple2(LocaleKeys.moreAction_large.tr(), 18.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
final fontSize =
|
||||||
|
context.read<DocumentAppearanceCubit>().documentAppearance.fontSize;
|
||||||
|
final index = _fontSizes.indexWhere((element) => element.item2 == fontSize);
|
||||||
|
_updateSelectedFontSize(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
FlowyText.semibold(
|
||||||
|
LocaleKeys.moreAction_fontSize.tr(),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 5,
|
||||||
|
),
|
||||||
|
ToggleButtons(
|
||||||
|
isSelected: _selectedFontSizes,
|
||||||
|
onPressed: (int index) {
|
||||||
|
_updateSelectedFontSize(index);
|
||||||
|
_sync(index);
|
||||||
|
},
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||||
|
selectedBorderColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
selectedColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
fillColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minHeight: 40.0,
|
||||||
|
minWidth: 80.0,
|
||||||
|
),
|
||||||
|
children: _fontSizes
|
||||||
|
.map((e) => Text(
|
||||||
|
e.item1,
|
||||||
|
style: TextStyle(fontSize: e.item2),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSelectedFontSize(int index) {
|
||||||
|
setState(() {
|
||||||
|
for (int i = 0; i < _selectedFontSizes.length; i++) {
|
||||||
|
_selectedFontSizes[i] = i == index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sync(int index) {
|
||||||
|
if (index < 0 || index >= _fontSizes.length) return;
|
||||||
|
final fontSize = _fontSizes[index].item2;
|
||||||
|
final cubit = context.read<DocumentAppearanceCubit>();
|
||||||
|
final documentAppearance = cubit.documentAppearance;
|
||||||
|
cubit.sync(documentAppearance.copyWith(fontSize: fontSize));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import 'package:app_flowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
|
import 'package:app_flowy/plugins/document/presentation/more/font_size_switcher.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class DocumentMoreButton extends StatelessWidget {
|
||||||
|
const DocumentMoreButton({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopupMenuButton<int>(
|
||||||
|
offset: const Offset(0, 30),
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 1,
|
||||||
|
enabled: false,
|
||||||
|
child: BlocProvider.value(
|
||||||
|
value: context.read<DocumentAppearanceCubit>(),
|
||||||
|
child: const FontSizeSwitcher(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
child: svgWidget(
|
||||||
|
'editor/details',
|
||||||
|
size: const Size(18, 18),
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
Widget? icon(Color iconColor) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => inner.name;
|
||||||
|
}
|
@ -84,6 +84,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
key: iconKey,
|
key: iconKey,
|
||||||
child: icon,
|
child: icon,
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await widget.editorState.formatTextToCheckbox(
|
await widget.editorState.formatTextToCheckbox(
|
||||||
widget.editorState,
|
widget.editorState,
|
||||||
|
@ -72,7 +72,7 @@ class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
|
|||||||
child: FlowyRichText(
|
child: FlowyRichText(
|
||||||
key: _richTextKey,
|
key: _richTextKey,
|
||||||
textNode: widget.textNode,
|
textNode: widget.textNode,
|
||||||
textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle),
|
textSpanDecorator: (textSpan) => textSpan,
|
||||||
placeholderTextSpanDecorator: (textSpan) =>
|
placeholderTextSpanDecorator: (textSpan) =>
|
||||||
textSpan.updateTextStyle(textStyle),
|
textSpan.updateTextStyle(textStyle),
|
||||||
lineHeight: widget.editorState.editorStyle.lineHeight,
|
lineHeight: widget.editorState.editorStyle.lineHeight,
|
||||||
|
@ -255,7 +255,9 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||||||
highlightCallback: (editorState) => _allSatisfy(
|
highlightCallback: (editorState) => _allSatisfy(
|
||||||
editorState,
|
editorState,
|
||||||
BuiltInAttributeKey.backgroundColor,
|
BuiltInAttributeKey.backgroundColor,
|
||||||
(value) => value != null,
|
(value) {
|
||||||
|
return value != null && value != '0x00000000'; // transparent color;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
handler: (editorState, context) => formatHighlight(
|
handler: (editorState, context) => formatHighlight(
|
||||||
editorState,
|
editorState,
|
||||||
|
@ -1,15 +1,26 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
Widget svgWidget(String name, {Color? color}) {
|
|
||||||
final Widget svg = SvgPicture.asset('assets/images/$name.svg', color: color);
|
|
||||||
|
|
||||||
return svg;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget svgWithSize(String name, Size size) {
|
Widget svgWithSize(String name, Size size) {
|
||||||
return SizedBox.fromSize(
|
return SizedBox.fromSize(
|
||||||
size: size,
|
size: size,
|
||||||
child: svgWidget(name),
|
child: svgWidget(name),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget svgWidget(String name, {Size? size, Color? color}) {
|
||||||
|
if (size != null) {
|
||||||
|
return SizedBox.fromSize(
|
||||||
|
size: size,
|
||||||
|
child: _svgWidget(name, color: color),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return _svgWidget(name, color: color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _svgWidget(String name, {Color? color}) {
|
||||||
|
final Widget svg = SvgPicture.asset('assets/images/$name.svg', color: color);
|
||||||
|
|
||||||
|
return svg;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user