main.dart 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:example/plugin/underscore_to_italic.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/services.dart';
  6. import 'package:flutter_localizations/flutter_localizations.dart';
  7. import 'package:google_fonts/google_fonts.dart';
  8. import 'package:path_provider/path_provider.dart';
  9. import 'package:appflowy_editor/appflowy_editor.dart';
  10. import 'expandable_floating_action_button.dart';
  11. void main() {
  12. runApp(const MyApp());
  13. }
  14. class MyApp extends StatelessWidget {
  15. const MyApp({Key? key}) : super(key: key);
  16. @override
  17. Widget build(BuildContext context) {
  18. return MaterialApp(
  19. localizationsDelegates: const [
  20. GlobalMaterialLocalizations.delegate,
  21. GlobalCupertinoLocalizations.delegate,
  22. GlobalWidgetsLocalizations.delegate,
  23. AppFlowyEditorLocalizations.delegate,
  24. ],
  25. supportedLocales: const [Locale('en', 'US')],
  26. debugShowCheckedModeBanner: false,
  27. theme: ThemeData(
  28. primarySwatch: Colors.blue,
  29. ),
  30. home: const MyHomePage(title: 'AppFlowyEditor Example'),
  31. );
  32. }
  33. }
  34. class MyHomePage extends StatefulWidget {
  35. const MyHomePage({Key? key, required this.title}) : super(key: key);
  36. final String title;
  37. @override
  38. State<MyHomePage> createState() => _MyHomePageState();
  39. }
  40. class _MyHomePageState extends State<MyHomePage> {
  41. int _pageIndex = 0;
  42. EditorState? _editorState;
  43. bool darkMode = false;
  44. EditorStyle _editorStyle = EditorStyle.defaultStyle();
  45. Future<String>? _jsonString;
  46. @override
  47. Widget build(BuildContext context) {
  48. return Scaffold(
  49. extendBodyBehindAppBar: true,
  50. body: _buildEditor(context),
  51. floatingActionButton: _buildExpandableFab(),
  52. );
  53. }
  54. Widget _buildEditor(BuildContext context) {
  55. if (_jsonString != null) {
  56. return _buildEditorWithJsonString(_jsonString!);
  57. }
  58. if (_pageIndex == 0) {
  59. return _buildEditorWithJsonString(
  60. rootBundle.loadString('assets/example.json'),
  61. );
  62. } else if (_pageIndex == 1) {
  63. return _buildEditorWithJsonString(
  64. rootBundle.loadString('assets/big_document.json'),
  65. );
  66. } else if (_pageIndex == 2) {
  67. return _buildEditorWithJsonString(
  68. Future.value(
  69. jsonEncode(EditorState.empty().document.toJson()),
  70. ),
  71. );
  72. }
  73. throw UnimplementedError();
  74. }
  75. Widget _buildEditorWithJsonString(Future<String> jsonString) {
  76. return FutureBuilder<String>(
  77. future: jsonString,
  78. builder: (_, snapshot) {
  79. if (snapshot.hasData &&
  80. snapshot.connectionState == ConnectionState.done) {
  81. _editorState ??= EditorState(
  82. document: StateTree.fromJson(
  83. Map<String, Object>.from(
  84. json.decode(snapshot.data!),
  85. ),
  86. ),
  87. );
  88. _editorState!.logConfiguration
  89. ..level = LogLevel.all
  90. ..handler = (message) {
  91. debugPrint(message);
  92. };
  93. _editorState!.operationStream.listen((event) {
  94. debugPrint('Operation: ${event.toJson()}');
  95. });
  96. return Container(
  97. color: darkMode ? Colors.black : Colors.white,
  98. width: MediaQuery.of(context).size.width,
  99. child: AppFlowyEditor(
  100. editorState: _editorState!,
  101. editorStyle: _editorStyle,
  102. shortcutEvents: [
  103. underscoreToItalic,
  104. ],
  105. ),
  106. );
  107. } else {
  108. return const Center(
  109. child: CircularProgressIndicator(),
  110. );
  111. }
  112. },
  113. );
  114. }
  115. Widget _buildExpandableFab() {
  116. return ExpandableFab(
  117. distance: 112.0,
  118. children: [
  119. ActionButton(
  120. icon: const Icon(Icons.abc),
  121. onPressed: () => _switchToPage(0),
  122. ),
  123. ActionButton(
  124. icon: const Icon(Icons.abc),
  125. onPressed: () => _switchToPage(1),
  126. ),
  127. ActionButton(
  128. icon: const Icon(Icons.abc),
  129. onPressed: () => _switchToPage(2),
  130. ),
  131. ActionButton(
  132. icon: const Icon(Icons.print),
  133. onPressed: () => _exportDocument(_editorState!),
  134. ),
  135. ActionButton(
  136. icon: const Icon(Icons.import_export),
  137. onPressed: () => _importDocument(),
  138. ),
  139. ActionButton(
  140. icon: const Icon(Icons.color_lens),
  141. onPressed: () {
  142. setState(() {
  143. _editorStyle =
  144. darkMode ? EditorStyle.defaultStyle() : _customizedStyle();
  145. darkMode = !darkMode;
  146. });
  147. },
  148. ),
  149. ],
  150. );
  151. }
  152. void _exportDocument(EditorState editorState) async {
  153. final document = editorState.document.toJson();
  154. final json = jsonEncode(document);
  155. final directory = await getTemporaryDirectory();
  156. final path = directory.path;
  157. final file = File('$path/editor.json');
  158. await file.writeAsString(json);
  159. if (mounted) {
  160. ScaffoldMessenger.of(context).showSnackBar(
  161. SnackBar(
  162. content: Text('The document is saved to the ${file.path}'),
  163. ),
  164. );
  165. }
  166. }
  167. void _importDocument() async {
  168. final directory = await getTemporaryDirectory();
  169. final path = directory.path;
  170. final file = File('$path/editor.json');
  171. setState(() {
  172. _editorState = null;
  173. _jsonString = file.readAsString();
  174. });
  175. }
  176. void _switchToPage(int pageIndex) {
  177. if (pageIndex != _pageIndex) {
  178. setState(() {
  179. _editorState = null;
  180. _pageIndex = pageIndex;
  181. });
  182. }
  183. }
  184. EditorStyle _customizedStyle() {
  185. final editorStyle = EditorStyle.defaultStyle();
  186. return editorStyle.copyWith(
  187. cursorColor: Colors.white,
  188. selectionColor: Colors.blue.withOpacity(0.3),
  189. textStyle: editorStyle.textStyle.copyWith(
  190. defaultTextStyle: GoogleFonts.poppins().copyWith(
  191. color: Colors.white,
  192. fontSize: 14.0,
  193. ),
  194. defaultPlaceholderTextStyle: GoogleFonts.poppins().copyWith(
  195. color: Colors.white.withOpacity(0.5),
  196. fontSize: 14.0,
  197. ),
  198. bold: const TextStyle(fontWeight: FontWeight.w900),
  199. code: TextStyle(
  200. fontStyle: FontStyle.italic,
  201. color: Colors.red[300],
  202. backgroundColor: Colors.grey.withOpacity(0.3),
  203. ),
  204. highlightColorHex: '0x6FFFEB3B',
  205. ),
  206. pluginStyles: {
  207. 'text/quote': builtInPluginStyle
  208. ..update(
  209. 'textStyle',
  210. (_) {
  211. return (EditorState editorState, Node node) {
  212. return TextStyle(
  213. color: Colors.blue[200],
  214. fontStyle: FontStyle.italic,
  215. fontSize: 12.0,
  216. );
  217. };
  218. },
  219. ),
  220. },
  221. );
  222. }
  223. }