document.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. library document_plugin;
  2. import 'package:app_flowy/generated/locale_keys.g.dart';
  3. import 'package:app_flowy/plugins/util.dart';
  4. import 'package:app_flowy/startup/plugin/plugin.dart';
  5. import 'package:app_flowy/startup/startup.dart';
  6. import 'package:app_flowy/workspace/application/appearance.dart';
  7. import 'package:app_flowy/plugins/doc/application/share_bloc.dart';
  8. import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
  9. import 'package:app_flowy/workspace/presentation/home/toast.dart';
  10. import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
  11. import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
  12. import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
  13. import 'package:clipboard/clipboard.dart';
  14. import 'package:dartz/dartz.dart' as dartz;
  15. import 'package:easy_localization/easy_localization.dart';
  16. import 'package:flowy_infra/size.dart';
  17. import 'package:flowy_infra_ui/flowy_infra_ui.dart';
  18. import 'package:flowy_infra_ui/widget/rounded_button.dart';
  19. import 'package:flowy_sdk/log.dart';
  20. import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
  21. import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
  22. import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
  23. import 'package:flutter/material.dart';
  24. import 'package:flutter_bloc/flutter_bloc.dart';
  25. import 'package:provider/provider.dart';
  26. import 'document_page.dart';
  27. class DocumentPluginBuilder extends PluginBuilder {
  28. @override
  29. Plugin build(dynamic data) {
  30. if (data is ViewPB) {
  31. return DocumentPlugin(pluginType: pluginType, view: data);
  32. } else {
  33. throw FlowyPluginException.invalidData;
  34. }
  35. }
  36. @override
  37. String get menuName => LocaleKeys.document_menuName.tr();
  38. @override
  39. PluginType get pluginType => PluginType.editor;
  40. @override
  41. ViewDataTypePB get dataType => ViewDataTypePB.Text;
  42. }
  43. class DocumentPlugin extends Plugin<int> {
  44. late PluginType _pluginType;
  45. @override
  46. final ViewPluginNotifier notifier;
  47. DocumentPlugin({
  48. required PluginType pluginType,
  49. required ViewPB view,
  50. Key? key,
  51. }) : notifier = ViewPluginNotifier(view: view) {
  52. _pluginType = pluginType;
  53. }
  54. @override
  55. PluginDisplay get display => DocumentPluginDisplay(notifier: notifier);
  56. @override
  57. PluginType get ty => _pluginType;
  58. @override
  59. PluginId get id => notifier.view.id;
  60. }
  61. class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
  62. final ViewPluginNotifier notifier;
  63. ViewPB get view => notifier.view;
  64. DocumentPluginDisplay({required this.notifier, Key? key});
  65. @override
  66. Widget buildWidget(PluginContext context) => DocumentPage(
  67. view: view,
  68. onDeleted: () => context.onDeleted(view),
  69. key: ValueKey(view.id),
  70. );
  71. @override
  72. Widget get leftBarItem => ViewLeftBarItem(view: view);
  73. @override
  74. Widget? get rightBarItem => DocumentShareButton(view: view);
  75. @override
  76. List<NavigationItem> get navigationItems => [this];
  77. }
  78. class DocumentShareButton extends StatelessWidget {
  79. final ViewPB view;
  80. DocumentShareButton({Key? key, required this.view})
  81. : super(key: ValueKey(view.hashCode));
  82. @override
  83. Widget build(BuildContext context) {
  84. double buttonWidth = 60;
  85. return BlocProvider(
  86. create: (context) => getIt<DocShareBloc>(param1: view),
  87. child: BlocListener<DocShareBloc, DocShareState>(
  88. listener: (context, state) {
  89. state.map(
  90. initial: (_) {},
  91. loading: (_) {},
  92. finish: (state) {
  93. state.successOrFail.fold(
  94. _handleExportData,
  95. _handleExportError,
  96. );
  97. },
  98. );
  99. },
  100. child: BlocBuilder<DocShareBloc, DocShareState>(
  101. builder: (context, state) {
  102. return ChangeNotifierProvider.value(
  103. value: Provider.of<AppearanceSettingModel>(context, listen: true),
  104. child: Selector<AppearanceSettingModel, Locale>(
  105. selector: (ctx, notifier) => notifier.locale,
  106. builder: (ctx, _, child) => ConstrainedBox(
  107. constraints: const BoxConstraints.expand(
  108. height: 30,
  109. // minWidth: buttonWidth,
  110. width: 100,
  111. ),
  112. child: RoundedTextButton(
  113. title: LocaleKeys.shareAction_buttonText.tr(),
  114. fontSize: 12,
  115. borderRadius: Corners.s6Border,
  116. color: Colors.lightBlue,
  117. onPressed: () => _showActionList(
  118. context, Offset(-(buttonWidth / 2), 10)),
  119. ),
  120. ),
  121. ),
  122. );
  123. },
  124. ),
  125. ),
  126. );
  127. }
  128. void _handleExportData(ExportDataPB exportData) {
  129. switch (exportData.exportType) {
  130. case ExportType.Link:
  131. break;
  132. case ExportType.Markdown:
  133. FlutterClipboard.copy(exportData.data)
  134. .then((value) => Log.info('copied to clipboard'));
  135. break;
  136. case ExportType.Text:
  137. break;
  138. }
  139. }
  140. void _handleExportError(FlowyError error) {}
  141. void _showActionList(BuildContext context, Offset offset) {
  142. final actionList = ShareActions(onSelected: (result) {
  143. result.fold(() {}, (action) {
  144. switch (action) {
  145. case ShareAction.markdown:
  146. context
  147. .read<DocShareBloc>()
  148. .add(const DocShareEvent.shareMarkdown());
  149. showMessageToast(
  150. 'Exported to: ${LocaleKeys.notifications_export_path.tr()}');
  151. break;
  152. case ShareAction.copyLink:
  153. NavigatorAlertDialog(
  154. title: LocaleKeys.shareAction_workInProgress.tr())
  155. .show(context);
  156. break;
  157. }
  158. });
  159. });
  160. actionList.show(
  161. context,
  162. anchorDirection: AnchorDirection.bottomWithCenterAligned,
  163. anchorOffset: offset,
  164. );
  165. }
  166. }
  167. class ShareActions with ActionList<ShareActionWrapper>, FlowyOverlayDelegate {
  168. final Function(dartz.Option<ShareAction>) onSelected;
  169. final _items =
  170. ShareAction.values.map((action) => ShareActionWrapper(action)).toList();
  171. ShareActions({required this.onSelected});
  172. @override
  173. double get maxWidth => 130;
  174. @override
  175. double get itemHeight => 22;
  176. @override
  177. List<ShareActionWrapper> get items => _items;
  178. @override
  179. void Function(dartz.Option<ShareActionWrapper> p1) get selectCallback =>
  180. (result) {
  181. result.fold(
  182. () => onSelected(dartz.none()),
  183. (wrapper) => onSelected(
  184. dartz.some(wrapper.inner),
  185. ),
  186. );
  187. };
  188. @override
  189. FlowyOverlayDelegate? get delegate => this;
  190. @override
  191. void didRemove() => onSelected(dartz.none());
  192. }
  193. enum ShareAction {
  194. markdown,
  195. copyLink,
  196. }
  197. class ShareActionWrapper extends ActionItem {
  198. final ShareAction inner;
  199. ShareActionWrapper(this.inner);
  200. @override
  201. Widget? icon(Color iconColor) => null;
  202. @override
  203. String get name => inner.name;
  204. }
  205. extension QuestionBubbleExtension on ShareAction {
  206. String get name {
  207. switch (this) {
  208. case ShareAction.markdown:
  209. return LocaleKeys.shareAction_markdown.tr();
  210. case ShareAction.copyLink:
  211. return LocaleKeys.shareAction_copyLink.tr();
  212. }
  213. }
  214. }