From e20ce9052abc81e012320913c0ba3b537a8d3d06 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 9 Nov 2022 15:36:30 +0800 Subject: [PATCH] feat: implement theme customizer showcase --- .../example/lib/home_page.dart | 86 +++++++- .../appflowy_editor/example/lib/main.dart | 206 ------------------ .../example/lib/pages/simple_editor.dart | 3 + .../lib/src/core/document/document.dart | 4 + .../lib/src/render/style/editor_style.dart | 7 + .../lib/src/service/editor_service.dart | 3 +- 6 files changed, 95 insertions(+), 214 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/home_page.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/home_page.dart index 6310839629..abb7344943 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/home_page.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/home_page.dart @@ -7,6 +7,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:universal_html/html.dart' as html; enum ExportFileType { @@ -39,15 +40,28 @@ class _HomePageState extends State { final _scaffoldKey = GlobalKey(); late WidgetBuilder _widgetBuilder; late EditorState _editorState; + late Future _jsonString; + ThemeData _themeData = ThemeData.light().copyWith( + extensions: [ + ...lightEditorStyleExtension, + ...lightPlguinStyleExtension, + ], + ); @override void initState() { super.initState(); - _widgetBuilder = (context) { - _editorState = EditorState.empty(); - return AppFlowyEditor(editorState: EditorState.empty()); - }; + _jsonString = Future.value( + jsonEncode(EditorState.empty().document.toJson()), + ); + _widgetBuilder = (context) => SimpleEditor( + jsonString: _jsonString, + themeData: _themeData, + onEditorStateChange: (editorState) { + _editorState = editorState; + }, + ); } @override @@ -108,8 +122,27 @@ class _HomePageState extends State { // Theme Demo _buildSeparator(context, 'Theme Demo'), - _buildListTile(context, 'Bulit In Dark Mode', () {}), - _buildListTile(context, 'Custom Theme', () {}), + _buildListTile(context, 'Bulit In Dark Mode', () { + _jsonString = Future.value( + jsonEncode(_editorState.document.toJson()).toString(), + ); + setState(() { + _themeData = ThemeData.dark().copyWith( + extensions: [ + ...darkEditorStyleExtension, + ...darkPlguinStyleExtension, + ], + ); + }); + }), + _buildListTile(context, 'Custom Theme', () { + _jsonString = Future.value( + jsonEncode(_editorState.document.toJson()).toString(), + ); + setState(() { + _themeData = _customizeEditorTheme(context); + }); + }), ], ), ); @@ -165,10 +198,12 @@ class _HomePageState extends State { } void _loadEditor(BuildContext context, Future jsonString) { + _jsonString = jsonString; setState( () { _widgetBuilder = (context) => SimpleEditor( - jsonString: jsonString, + jsonString: _jsonString, + themeData: _themeData, onEditorStateChange: (editorState) { _editorState = editorState; }, @@ -245,4 +280,41 @@ class _HomePageState extends State { _loadEditor(context, Future.value(jsonString)); } } + + ThemeData _customizeEditorTheme(BuildContext context) { + final dark = EditorStyle.dark; + final editorStyle = dark.copyWith( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 150), + cursorColor: Colors.blue.shade600, + selectionColor: Colors.yellow.shade600.withOpacity(0.5), + textStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.grey, + ), + placeholderTextStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.grey.shade500, + ), + code: dark.code?.copyWith( + backgroundColor: Colors.lightBlue.shade200, + fontStyle: FontStyle.italic, + ), + highlightColorHex: '0x60FF0000', // red + ); + + final quote = QuotedTextPluginStyle.dark.copyWith( + textStyle: (_, __) => GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.blue.shade400, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w700, + ), + ); + + return Theme.of(context).copyWith(extensions: [ + editorStyle, + ...darkPlguinStyleExtension, + quote, + ]); + } } diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart index 990de0fe9a..b98cec364a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -1,24 +1,10 @@ -import 'dart:convert'; -import 'dart:io'; - import 'package:example/home_page.dart'; -import 'package:example/plugin/editor_theme.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:example/plugin/code_block_node_widget.dart'; -import 'package:example/plugin/horizontal_rule_node_widget.dart'; -import 'package:example/plugin/tex_block_node_widget.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:universal_html/html.dart' as html; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'expandable_floating_action_button.dart'; - void main() { runApp(const MyApp()); } @@ -51,200 +37,8 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int _pageIndex = 0; - EditorState? _editorState; - bool darkMode = false; - Future? _jsonString; - - ThemeData? _editorThemeData; - @override Widget build(BuildContext context) { return const HomePage(); } - - Widget _buildEditor(BuildContext context) { - if (_jsonString != null) { - return _buildEditorWithJsonString(_jsonString!); - } - if (_pageIndex == 0) { - return _buildEditorWithJsonString( - rootBundle.loadString('assets/example.json'), - ); - } else if (_pageIndex == 1) { - return _buildEditorWithJsonString( - Future.value( - jsonEncode(EditorState.empty().document.toJson()), - ), - ); - } - throw UnimplementedError(); - } - - Widget _buildEditorWithJsonString(Future jsonString) { - return FutureBuilder( - future: jsonString, - builder: (_, snapshot) { - if (snapshot.hasData && - snapshot.connectionState == ConnectionState.done) { - _editorState ??= EditorState( - document: Document.fromJson( - Map.from( - json.decode(snapshot.data!), - ), - ), - ); - _editorState!.logConfiguration - ..level = LogLevel.all - ..handler = (message) { - debugPrint(message); - }; - _editorState!.transactionStream.listen((event) { - debugPrint('Transaction: ${event.toJson()}'); - }); - _editorThemeData ??= Theme.of(context).copyWith(extensions: [ - if (darkMode) ...darkEditorStyleExtension, - if (darkMode) ...darkPlguinStyleExtension, - if (!darkMode) ...lightEditorStyleExtension, - if (!darkMode) ...lightPlguinStyleExtension, - ]); - return Container( - color: darkMode ? Colors.black : Colors.white, - width: MediaQuery.of(context).size.width, - child: AppFlowyEditor( - editorState: _editorState!, - editable: true, - autoFocus: _editorState!.document.isEmpty, - themeData: _editorThemeData, - customBuilders: { - 'text/code_block': CodeBlockNodeWidgetBuilder(), - 'tex': TeXBlockNodeWidgetBuidler(), - 'horizontal_rule': HorizontalRuleWidgetBuilder(), - }, - shortcutEvents: [ - enterInCodeBlock, - ignoreKeysInCodeBlock, - insertHorizontalRule, - ], - selectionMenuItems: [ - codeBlockMenuItem, - teXBlockMenuItem, - horizontalRuleMenuItem, - ], - ), - ); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - ); - } - - Widget _buildExpandableFab(BuildContext context) { - return FloatingActionButton(onPressed: () { - Scaffold.of(context).openDrawer(); - }); - return ExpandableFab( - distance: 112.0, - children: [ - ActionButton( - icon: const Icon(Icons.abc), - onPressed: () => _switchToPage(0), - ), - ActionButton( - icon: const Icon(Icons.abc), - onPressed: () => _switchToPage(1), - ), - ActionButton( - icon: const Icon(Icons.print), - onPressed: () => _exportDocument(_editorState!), - ), - ActionButton( - icon: const Icon(Icons.import_export), - onPressed: () async => await _importDocument(), - ), - ActionButton( - icon: const Icon(Icons.dark_mode), - onPressed: () { - setState(() { - darkMode = !darkMode; - }); - }, - ), - ActionButton( - icon: const Icon(Icons.color_lens), - onPressed: () { - setState(() { - _editorThemeData = customizeEditorTheme(context); - darkMode = true; - }); - }, - ), - ], - ); - } - - void _exportDocument(EditorState editorState) async { - final document = editorState.document.toJson(); - final json = jsonEncode(document); - if (kIsWeb) { - final blob = html.Blob([json], 'text/plain', 'native'); - html.AnchorElement( - href: html.Url.createObjectUrlFromBlob(blob).toString(), - ) - ..setAttribute('download', 'editor.json') - ..click(); - } else { - final directory = await getTemporaryDirectory(); - final path = directory.path; - final file = File('$path/editor.json'); - await file.writeAsString(json); - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('The document is saved to the ${file.path}'), - ), - ); - } - } - } - - Future _importDocument() async { - if (kIsWeb) { - final result = await FilePicker.platform.pickFiles( - allowMultiple: false, - allowedExtensions: ['json'], - type: FileType.custom, - ); - final bytes = result?.files.first.bytes; - if (bytes != null) { - final jsonString = const Utf8Decoder().convert(bytes); - setState(() { - _editorState = null; - _jsonString = Future.value(jsonString); - }); - } - } else { - final directory = await getTemporaryDirectory(); - final path = '${directory.path}/editor.json'; - final file = File(path); - setState(() { - _editorState = null; - _jsonString = file.readAsString(); - }); - } - } - - void _switchToPage(int pageIndex) { - if (pageIndex != _pageIndex) { - setState(() { - _editorThemeData = null; - _editorState = null; - _pageIndex = pageIndex; - }); - } - } } diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart index 291274de6a..5fa772d11f 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart @@ -7,10 +7,12 @@ class SimpleEditor extends StatelessWidget { const SimpleEditor({ super.key, required this.jsonString, + required this.themeData, required this.onEditorStateChange, }); final Future jsonString; + final ThemeData themeData; final void Function(EditorState editorState) onEditorStateChange; @override @@ -30,6 +32,7 @@ class SimpleEditor extends StatelessWidget { onEditorStateChange(editorState); return AppFlowyEditor( editorState: editorState, + themeData: themeData, autoFocus: editorState.document.isEmpty, ); } else { diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart index 3cf5b837b9..2994896b58 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart @@ -115,6 +115,10 @@ class Document { return true; } + if (root.children.length > 1) { + return false; + } + final node = root.children.first; if (node is TextNode && (node.delta.isEmpty || node.delta.toPlainText().isEmpty)) { diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart index 30e4465bf8..93305bb31e 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart @@ -11,6 +11,7 @@ Iterable> get darkEditorStyleExtension => [ class EditorStyle extends ThemeExtension { // Editor styles final EdgeInsets? padding; + final Color? backgroundColor; final Color? cursorColor; final Color? selectionColor; @@ -39,6 +40,7 @@ class EditorStyle extends ThemeExtension { EditorStyle({ required this.padding, + required this.backgroundColor, required this.cursorColor, required this.selectionColor, required this.selectionMenuBackgroundColor, @@ -63,6 +65,7 @@ class EditorStyle extends ThemeExtension { @override EditorStyle copyWith({ EdgeInsets? padding, + Color? backgroundColor, Color? cursorColor, Color? selectionColor, Color? selectionMenuBackgroundColor, @@ -84,6 +87,7 @@ class EditorStyle extends ThemeExtension { }) { return EditorStyle( padding: padding ?? this.padding, + backgroundColor: backgroundColor ?? this.backgroundColor, cursorColor: cursorColor ?? this.cursorColor, selectionColor: selectionColor ?? this.selectionColor, selectionMenuBackgroundColor: @@ -120,6 +124,7 @@ class EditorStyle extends ThemeExtension { } return EditorStyle( padding: EdgeInsets.lerp(padding, other.padding, t), + backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t), cursorColor: Color.lerp(cursorColor, other.cursorColor, t), textPadding: EdgeInsets.lerp(textPadding, other.textPadding, t), selectionColor: Color.lerp(selectionColor, other.selectionColor, t), @@ -155,6 +160,7 @@ class EditorStyle extends ThemeExtension { static final light = EditorStyle( padding: const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0), + backgroundColor: Colors.white, cursorColor: const Color(0xFF00BCF0), selectionColor: const Color.fromARGB(53, 111, 201, 231), selectionMenuBackgroundColor: const Color(0xFFFFFFFF), @@ -184,6 +190,7 @@ class EditorStyle extends ThemeExtension { ); static final dark = light.copyWith( + backgroundColor: Colors.black, textStyle: const TextStyle(fontSize: 16.0, color: Colors.white), placeholderTextStyle: TextStyle( fontSize: 16.0, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart index 2180534756..9d58eb066c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart @@ -119,7 +119,8 @@ class _AppFlowyEditorState extends State { data: widget.themeData, child: AppFlowyScroll( key: editorState.service.scrollServiceKey, - child: Padding( + child: Container( + color: editorStyle.backgroundColor, padding: editorStyle.padding!, child: AppFlowySelection( key: editorState.service.selectionServiceKey,