diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index eb86b37..03f035b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -10,20 +10,11 @@ PODS: - image_picker (0.0.1): - Flutter - OrderedSet (5.0.0) - - video_player (0.0.1): - - Flutter - - wakelock (0.0.1): - - Flutter - - webview_flutter (0.0.1): - - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - - video_player (from `.symlinks/plugins/video_player/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) - - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) SPEC REPOS: trunk: @@ -36,21 +27,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_inappwebview/ios" image_picker: :path: ".symlinks/plugins/image_picker/ios" - video_player: - :path: ".symlinks/plugins/video_player/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" - webview_flutter: - :path: ".symlinks/plugins/webview_flutter/ios" SPEC CHECKSUMS: Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 image_picker: 50e7c7ff960e5f58faa4d1f4af84a771c671bc4a OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e - wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f - webview_flutter: 9f491a9b5a66f2573946a389b2677987b0ff8c0b PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c diff --git a/lib/src/models/callbacks/did_html_change_listener.dart b/lib/src/models/callbacks/did_html_change_listener.dart new file mode 100644 index 0000000..4b43236 --- /dev/null +++ b/lib/src/models/callbacks/did_html_change_listener.dart @@ -0,0 +1,3 @@ +class DidHtmlChangeListener { + didHtmlChange(bool didHtmlChange) {} +} diff --git a/lib/src/models/callbacks/get_current_html_callback.dart b/lib/src/models/callbacks/get_current_html_callback.dart new file mode 100644 index 0000000..b026824 --- /dev/null +++ b/lib/src/models/callbacks/get_current_html_callback.dart @@ -0,0 +1,3 @@ +class GetCurrentHtmlCallback { + htmlRetrieved(String html) {} +} diff --git a/lib/src/models/callbacks/html_changed_listener.dart b/lib/src/models/callbacks/html_changed_listener.dart new file mode 100644 index 0000000..3c4dff8 --- /dev/null +++ b/lib/src/models/callbacks/html_changed_listener.dart @@ -0,0 +1,3 @@ +class HtmlChangedListener { + htmlChangedAsync(String html) {} +} diff --git a/lib/src/models/callbacks/loaded_listener.dart b/lib/src/models/callbacks/loaded_listener.dart new file mode 100644 index 0000000..6fa4077 --- /dev/null +++ b/lib/src/models/callbacks/loaded_listener.dart @@ -0,0 +1,3 @@ +class LoadedListener { + editorLoaded() {} +} diff --git a/lib/src/models/rich_editor_options.dart b/lib/src/models/rich_editor_options.dart index 0e52364..44f226b 100644 --- a/lib/src/models/rich_editor_options.dart +++ b/lib/src/models/rich_editor_options.dart @@ -9,6 +9,7 @@ class RichEditorOptions { String? placeholder; String? baseFontFamily; BarPosition? barPosition; + bool? enableVideo; RichEditorOptions({ Color? backgroundColor, @@ -17,6 +18,7 @@ class RichEditorOptions { String? placeholder, String? baseFontFamily, BarPosition? barPosition, + bool? enableVideo = true, }) { this.backgroundColor = backgroundColor; this.baseTextColor = baseTextColor; @@ -24,5 +26,6 @@ class RichEditorOptions { this.placeholder = placeholder; this.baseFontFamily = baseFontFamily; this.barPosition = barPosition; + this.enableVideo = enableVideo; } } diff --git a/lib/src/rendering/rich_editor.dart b/lib/src/rendering/rich_editor.dart index d85119f..b66cd7b 100644 --- a/lib/src/rendering/rich_editor.dart +++ b/lib/src/rendering/rich_editor.dart @@ -10,7 +10,6 @@ import 'package:rich_editor/src/models/rich_editor_options.dart'; import 'package:rich_editor/src/services/local_server.dart'; import 'package:rich_editor/src/utils/javascript_executor_base.dart'; import 'package:rich_editor/src/widgets/editor_tool_bar.dart'; -// import 'package:webview_flutter/webview_flutter.dart'; class RichEditor extends StatefulWidget { final String? value; @@ -35,8 +34,6 @@ class RichEditorState extends State { final Key _mapKey = UniqueKey(); String assetPath = 'packages/rich_editor/assets/editor/editor.html'; - // InAppWebViewController? webViewController; - int port = 5321; String html = ''; LocalServer? localServer; @@ -94,6 +91,7 @@ class RichEditorState extends State { child: EditorToolBar( getImageUrl: widget.getImageUrl, javascriptExecutor: javascriptExecutor, + enableVideo: widget.editorOptions!.enableVideo, ), ), Expanded( @@ -114,9 +112,11 @@ class RichEditorState extends State { } }, onLoadStop: (controller, link) async { - javascriptExecutor.init(_controller!); - await _setInitialValues(); - _addJSListener(); + if (link!.path != 'blank') { + javascriptExecutor.init(_controller!); + await _setInitialValues(); + _addJSListener(); + } }, // javascriptMode: JavascriptMode.unrestricted, // gestureNavigationEnabled: false, @@ -138,6 +138,7 @@ class RichEditorState extends State { child: EditorToolBar( getImageUrl: widget.getImageUrl, javascriptExecutor: javascriptExecutor, + enableVideo: widget.editorOptions!.enableVideo, ), ), ], diff --git a/lib/src/utils/javascript_executor_base.dart b/lib/src/utils/javascript_executor_base.dart index f1d5728..8868850 100644 --- a/lib/src/utils/javascript_executor_base.dart +++ b/lib/src/utils/javascript_executor_base.dart @@ -3,6 +3,9 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:rich_editor/src/extensions/extensions.dart'; +import 'package:rich_editor/src/models/callbacks/did_html_change_listener.dart'; +import 'package:rich_editor/src/models/callbacks/html_changed_listener.dart'; +import 'package:rich_editor/src/models/callbacks/loaded_listener.dart'; import 'package:rich_editor/src/models/editor_state.dart'; import 'package:rich_editor/src/models/enum/command_name.dart'; @@ -19,9 +22,27 @@ class JavascriptExecutorBase { String defaultEncoding = "UTF-8"; String? htmlField = ""; + var didHtmlChange = false; + Map commandStates = {}; + List> commandStatesChangedListeners = + >[]; + + List didHtmlChangeListeners = + []; + + List htmlChangedListeners = []; + + // protected val fireHtmlChangedListenersQueue = AsyncProducerConsumerQueue(1) { html -> + // fireHtmlChangedListeners(html) + // } + + bool isLoaded = false; + + List loadedListeners = []; + init(InAppWebViewController? controller) { _controller = controller; } @@ -42,8 +63,8 @@ class JavascriptExecutorBase { getCurrentHtml() async { String? html = await executeJavascript('getEncodedHtml()'); - String? decodedHtml = Uri.decodeFull(html!); - if (decodedHtml.startsWith('"') && decodedHtml.endsWith('"')) { + String? decodedHtml = decodeHtml(html!); + if (decodedHtml!.startsWith('"') && decodedHtml.endsWith('"')) { decodedHtml = decodedHtml.substring(1, decodedHtml.length - 1); } return decodedHtml; @@ -247,11 +268,11 @@ class JavascriptExecutorBase { await executeJavascript("setInputEnabled($inputEnabled);"); } - static decodeHtml(String html) { + decodeHtml(String html) { return Uri.decodeFull(html); } - static encodeHtml(String html) { + encodeHtml(String html) { return Uri.encodeFull(html); } @@ -295,9 +316,9 @@ class JavascriptExecutorBase { bool didHtmlChange, Map commandStates) { if (this.didHtmlChange != didHtmlChange) { this.didHtmlChange = didHtmlChange; - // didHtmlChangeListeners.forEach { - // it.didHtmlChange(didHtmlChange); - // } + didHtmlChangeListeners.forEach((element) { + element.didHtmlChange(didHtmlChange); + }); } handleRetrievedCommandStates(commandStates); @@ -307,37 +328,91 @@ class JavascriptExecutorBase { determineDerivedCommandStates(commandStates); this.commandStates = commandStates; - - // commandStatesChangedListeners.forEach { - // it.invoke(this.commandStates) - // } + commandStatesChangedListeners.forEach((element) { + element = this.commandStates; + }); } determineDerivedCommandStates(Map commandStates) { - // commandStates[CommandName.FORMATBLOCK]?.let { - // formatCommandState - // -> - // commandStates.put(CommandName.H1, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h1"))) - // commandStates.put(CommandName.H2, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h2"))) - // commandStates.put(CommandName.H3, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h3"))) - // commandStates.put(CommandName.H4, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h4"))) - // commandStates.put(CommandName.H5, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h5"))) - // commandStates.put(CommandName.H6, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h6"))) - // commandStates.put(CommandName.P, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "p"))) - // commandStates.put(CommandName.PRE, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "pre"))) - // commandStates.put(CommandName.BR, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, ""))) - // commandStates.put(CommandName.BLOCKQUOTE, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "blockquote"))) - // } - // - // commandStates[CommandName.INSERTHTML]?.let { insertHtmlState -> - // commandStates.put(CommandName.INSERTLINK, insertHtmlState) - // commandStates.put(CommandName.INSERTIMAGE, insertHtmlState) - // commandStates.put(CommandName.INSERTCHECKBOX, insertHtmlState) - // } + if (commandStates[CommandName.FORMATBLOCK] != null) { + var formatCommandState = commandStates[CommandName.FORMATBLOCK]; + commandStates.update( + CommandName.H1, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "h1")), + ); + commandStates.update( + CommandName.H2, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "h2"))); + commandStates.update( + CommandName.H3, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "h3")), + ); + commandStates.update( + CommandName.H4, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "h4")), + ); + commandStates.update( + CommandName.H5, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "h5")), + ); + commandStates.update( + CommandName.H6, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "h6")), + ); + commandStates.update( + CommandName.P, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "p")), + ); + commandStates.update( + CommandName.PRE, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "pre")), + ); + commandStates.update( + CommandName.BR, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "")), + ); + commandStates.update( + CommandName.BLOCKQUOTE, + (val) => CommandState(formatCommandState!.executable, + isFormatActivated(formatCommandState, "blockquote")), + ); + } + + if (commandStates[CommandName.INSERTHTML] != null) { + CommandState? insertHtmlState = commandStates[CommandName.INSERTHTML]; + commandStates.update(CommandName.INSERTLINK, (val) => insertHtmlState!); + commandStates.update(CommandName.INSERTIMAGE, (val) => insertHtmlState!); + commandStates.update( + CommandName.INSERTCHECKBOX, (val) => insertHtmlState!); + } } String isFormatActivated(CommandState formatCommandState, String format) { return (formatCommandState.value == format) .toString(); // rich_text_editor.js reports boolean values as string, so we also have to convert it to string } + + addCommandStatesChangedListener( + Map commandStates) { + commandStatesChangedListeners.add(commandStates); + + // listener.invoke(commandStates); + } + + addDidHtmlChangeListener(DidHtmlChangeListener listener) { + didHtmlChangeListeners.add(listener); + } + + addHtmlChangedListener(HtmlChangedListener listener) { + htmlChangedListeners.add(listener); + } } diff --git a/lib/src/widgets/editor_tool_bar.dart b/lib/src/widgets/editor_tool_bar.dart index 30c0bc0..3d4d2f0 100644 --- a/lib/src/widgets/editor_tool_bar.dart +++ b/lib/src/widgets/editor_tool_bar.dart @@ -16,11 +16,13 @@ class EditorToolBar extends StatelessWidget { final Function(File image)? getImageUrl; final Function(File video)? getVideoUrl; final JavascriptExecutorBase javascriptExecutor; + final bool? enableVideo; EditorToolBar({ this.getImageUrl, this.getVideoUrl, required this.javascriptExecutor, + this.enableVideo, }); @override @@ -85,6 +87,14 @@ class EditorToolBar extends StatelessWidget { } }, ), + Visibility( + visible: false, + child: TabButton( + tooltip: 'Insert video', + icon: Icons.video_call_sharp, + onTap: () async {}, + ), + ), TabButton( tooltip: 'Underline', icon: Icons.format_underline,