From 643e558d64742f9bcf46a537784506677009fa33 Mon Sep 17 00:00:00 2001 From: alphaisx Date: Fri, 29 May 2026 15:09:51 +0100 Subject: [PATCH 1/7] fixed focusing issue within flutter app --- lib/flutter_summernote.dart | 50 ++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/lib/flutter_summernote.dart b/lib/flutter_summernote.dart index dbabfb1..a6ef74f 100644 --- a/lib/flutter_summernote.dart +++ b/lib/flutter_summernote.dart @@ -100,7 +100,6 @@ class FlutterSummernoteState extends State { setHint(''); } - setFullContainer(); if (widget.value != null) { setText(widget.value!); } @@ -108,6 +107,16 @@ class FlutterSummernoteState extends State { final String contentBase64 = base64Encode(const Utf8Encoder().convert(_page)); + + await _webViewController!.enableZoom(false); + await _webViewController!.setBackgroundColor( + const Color(0x00000000), + ); + if (Platform.isAndroid) { + await _webViewController!.runJavaScript( + "document.body.style.webkitUserSelect='auto';" + ); + } _webViewController! .loadRequest(Uri.parse('data:text/html;base64,$contentBase64')); } @@ -236,10 +245,10 @@ class FlutterSummernoteState extends State { } /// [setFullContainer] to set full summernote form - void setFullContainer() { - _webViewController! - .runJavaScript('\$("#summernote").summernote("fullscreen.toggle");'); - } + // void setFullContainer() { + // _webViewController! + // .runJavaScript('\$("#summernote").summernote("fullscreen.toggle");'); + // } /// [setFocus] to focus summernote form void setFocus() { @@ -315,14 +324,37 @@ class FlutterSummernoteState extends State { -
+
From be34087f3a2fab3f171a89e2bc361b90d3564373 Mon Sep 17 00:00:00 2001 From: alphaisx Date: Fri, 29 May 2026 15:15:30 +0100 Subject: [PATCH 2/7] Update jQuery selector syntax in callbacks Refactor jQuery selector syntax for consistency. --- lib/flutter_summernote.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/flutter_summernote.dart b/lib/flutter_summernote.dart index a6ef74f..8174d9b 100644 --- a/lib/flutter_summernote.dart +++ b/lib/flutter_summernote.dart @@ -333,12 +333,12 @@ class FlutterSummernoteState extends State { popover: {$popover}, callbacks: { onInit: function () { - $('.note-editable').attr( + \$('.note-editable').attr( 'contenteditable', 'true' ); - $('.note-editable').on( + \$('.note-editable').on( 'focus', function () { document.activeElement.blur(); @@ -346,7 +346,7 @@ class FlutterSummernoteState extends State { } ); - $('.note-editable').on( + \$('.note-editable').on( 'click', function () { $(this).focus(); From 112386133d6a3c1e97d535607150129eea584b99 Mon Sep 17 00:00:00 2001 From: alphaisx Date: Fri, 29 May 2026 15:25:14 +0100 Subject: [PATCH 3/7] Refactor WebViewController usage and cleanup code --- lib/flutter_summernote.dart | 48 ++++++++++++++----------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/lib/flutter_summernote.dart b/lib/flutter_summernote.dart index 8174d9b..448086d 100644 --- a/lib/flutter_summernote.dart +++ b/lib/flutter_summernote.dart @@ -53,18 +53,8 @@ class FlutterSummernoteState extends State { final _imagePicker = ImagePicker(); late bool _hasAttachment; - late final WebViewController? _webViewController; - - void handleRequest(HttpRequest request) { - try { - if (request.method == 'GET' && - request.uri.queryParameters['query'] == 'getRawTeXHTML') { - } else {} - } catch (e) { - debugPrint('Exception in handleRequest: $e'); - } - } - + late final WebViewController _webViewController; + @override void initState() { super.initState(); @@ -74,8 +64,8 @@ class FlutterSummernoteState extends State { _webViewController = WebViewController(); - _webViewController!.setJavaScriptMode(JavaScriptMode.unrestricted); - _webViewController!.addJavaScriptChannel('GetTextSummernote', + _webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); + _webViewController.addJavaScriptChannel('GetTextSummernote', onMessageReceived: (JavaScriptMessage message) { String isi = message.message; if (isi.isEmpty || @@ -108,12 +98,12 @@ class FlutterSummernoteState extends State { final String contentBase64 = base64Encode(const Utf8Encoder().convert(_page)); - await _webViewController!.enableZoom(false); - await _webViewController!.setBackgroundColor( + await _webViewController.enableZoom(false); + await _webViewController.setBackgroundColor( const Color(0x00000000), ); if (Platform.isAndroid) { - await _webViewController!.runJavaScript( + await _webViewController.runJavaScript( "document.body.style.webkitUserSelect='auto';" ); } @@ -123,9 +113,7 @@ class FlutterSummernoteState extends State { @override void dispose() { - if (_webViewController != null) { - _webViewController = null; - } + // Don't reassign late final variables super.dispose(); } @@ -176,7 +164,7 @@ class FlutterSummernoteState extends State { ClipboardData data = await (Clipboard.getData(Clipboard.kTextPlain) as FutureOr); - String txtIsi = data.text! + String txtIsi = data!.text! .replaceAll("'", '\\"') .replaceAll('"', '\\"') .replaceAll('[', '\\[') @@ -186,7 +174,7 @@ class FlutterSummernoteState extends State { .replaceAll('\r', ' ') .replaceAll('\r\n', ' '); String txt = "\$('.note-editable').append( '$txtIsi');"; - _webViewController!.runJavaScript(txt); + _webViewController.runJavaScript(txt); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -220,13 +208,13 @@ class FlutterSummernoteState extends State { /// Call [getText] to get current value from summernote form Future getText() async { - await _webViewController?.runJavaScript( + await _webViewController.runJavaScript( 'setTimeout(function(){GetTextSummernote.postMessage(document.' 'getElementsByClassName(\'note-editable\')[0].innerHTML)}, 0);', ); return text; } - + /// Call [setText] to set current value in summernote form Future setText(String v) async { String txtIsi = v @@ -241,7 +229,7 @@ class FlutterSummernoteState extends State { String txt = 'document.getElementsByClassName(\'note-editable\')[0].innerHTML' ' = \'$txtIsi\';'; - _webViewController!.runJavaScript(txt); + _webViewController.runJavaScript(txt); } /// [setFullContainer] to set full summernote form @@ -252,18 +240,18 @@ class FlutterSummernoteState extends State { /// [setFocus] to focus summernote form void setFocus() { - _webViewController!.runJavaScript("\$('#summernote').summernote('focus');"); + _webViewController.runJavaScript("\$('#summernote').summernote('focus');"); } /// [setEmpty] called to reset summmernote form void setEmpty() { - _webViewController!.runJavaScript("\$('#summernote').summernote('reset');"); + _webViewController.runJavaScript("\$('#summernote').summernote('reset');"); } /// [setHint] to give placeholder void setHint(String? text) { String hint = '\$(".note-placeholder").html("$text");'; - _webViewController!.runJavaScript('setTimeout(function(){$hint}, 0);'); + _webViewController.runJavaScript('setTimeout(function(){$hint}, 0);'); } /// [widgetIcon] to simplify create a button icon with text @@ -437,7 +425,7 @@ class FlutterSummernoteState extends State { } /// [_addImage] to add image in summernote form - void _addImage(XFile image) async { + void _addImage(XFile image) { String filename = basename(image.path); List imageBytes = await image.readAsBytes(); String base64Image = @@ -445,6 +433,6 @@ class FlutterSummernoteState extends State { '${base64Encode(imageBytes)}" data-filename="$filename">'; String txt = "\$('.note-editable').append( '$base64Image');"; - _webViewController!.runJavaScript(txt); + _webViewController.runJavaScript(txt); } } From 9c04159f935f307ef397c2fec012f3a234684725 Mon Sep 17 00:00:00 2001 From: Alpha Date: Fri, 29 May 2026 15:39:02 +0100 Subject: [PATCH 4/7] feat: update dependencies and improve code structure - Refactored MyApp and MyHomePage constructors to use const constructors. - Updated FlutterSummernoteState to improve WebViewController usage. - Enhanced code readability by removing unnecessary null checks and using final where applicable. - Added file_selector plugin support for Linux and macOS. - Updated pubspec.yaml with new dependency versions and improved SDK constraints. - Added flutter_lldb_helper.py and flutter_lldbinit for better debugging support in iOS. --- .../Flutter/ephemeral/flutter_lldb_helper.py | 32 +++ .../ios/Flutter/ephemeral/flutter_lldbinit | 5 + example/lib/main.dart | 19 +- example/pubspec.lock | 86 +++++--- lib/flutter_summernote.dart | 46 ++-- .../.plugin_symlinks/file_selector_linux | 1 + .../.plugin_symlinks/image_picker_linux | 1 + linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 206 +++++++++++++----- pubspec.yaml | 14 +- 12 files changed, 293 insertions(+), 126 deletions(-) create mode 100644 example/ios/Flutter/ephemeral/flutter_lldb_helper.py create mode 100644 example/ios/Flutter/ephemeral/flutter_lldbinit create mode 120000 linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux create mode 120000 linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/example/lib/main.dart b/example/lib/main.dart index 6f7cfb3..a8142bf 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,10 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_summernote/flutter_summernote.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + // This widget is the root of your application. @override Widget build(BuildContext context) { @@ -14,22 +16,22 @@ class MyApp extends StatelessWidget { theme: ThemeData( primarySwatch: Colors.blue, ), - home: MyHomePage(title: 'Demo Flutter Summernote'), + home: const MyHomePage(title: 'Demo Flutter Summernote'), ); } } class MyHomePage extends StatefulWidget { - MyHomePage({required this.title}); + const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override - _MyHomePageState createState() => _MyHomePageState(); + MyHomePageState createState() => MyHomePageState(); } -class _MyHomePageState extends State { - GlobalKey _keyEditor = GlobalKey(); +class MyHomePageState extends State { + final GlobalKey _keyEditor = GlobalKey(); String result = ''; @override @@ -40,11 +42,12 @@ class _MyHomePageState extends State { elevation: 0, actions: [ IconButton( - icon: Icon(Icons.save), + icon: const Icon(Icons.save), onPressed: () async { final value = (await _keyEditor.currentState?.getText()); + if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar( - duration: Duration(seconds: 5), + duration: const Duration(seconds: 5), content: Text(value ?? '-'), )); }, diff --git a/example/pubspec.lock b/example/pubspec.lock index e8c9f0f..4609ebe 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,26 +21,26 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.19.1" cross_file: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -159,30 +159,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.5" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.17.0" mime: dependency: transitive description: @@ -195,10 +219,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: @@ -211,7 +235,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -224,18 +248,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -256,10 +280,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.7.10" typed_data: dependency: transitive description: @@ -272,10 +296,18 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" webview_flutter: dependency: transitive description: @@ -309,5 +341,5 @@ packages: source: hosted version: "3.0.5" sdks: - dart: ">=2.18.0 <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/flutter_summernote.dart b/lib/flutter_summernote.dart index 448086d..97290a5 100644 --- a/lib/flutter_summernote.dart +++ b/lib/flutter_summernote.dart @@ -54,7 +54,7 @@ class FlutterSummernoteState extends State { late bool _hasAttachment; late final WebViewController _webViewController; - + @override void initState() { super.initState(); @@ -82,7 +82,7 @@ class FlutterSummernoteState extends State { } }); - _webViewController! + _webViewController .setNavigationDelegate(NavigationDelegate(onPageFinished: (String url) { if (widget.hint != null) { setHint(widget.hint); @@ -98,16 +98,15 @@ class FlutterSummernoteState extends State { final String contentBase64 = base64Encode(const Utf8Encoder().convert(_page)); - await _webViewController.enableZoom(false); - await _webViewController.setBackgroundColor( + _webViewController.enableZoom(false); + _webViewController.setBackgroundColor( const Color(0x00000000), ); if (Platform.isAndroid) { - await _webViewController.runJavaScript( - "document.body.style.webkitUserSelect='auto';" - ); + _webViewController + .runJavaScript("document.body.style.webkitUserSelect='auto';"); } - _webViewController! + _webViewController .loadRequest(Uri.parse('data:text/html;base64,$contentBase64')); } @@ -129,7 +128,7 @@ class FlutterSummernoteState extends State { child: Column( children: [ Expanded( - child: WebViewWidget(key: _mapKey, controller: _webViewController!), + child: WebViewWidget(key: _mapKey, controller: _webViewController), ), Visibility( visible: widget.showBottomToolbar, @@ -153,9 +152,9 @@ class FlutterSummernoteState extends State { String data = await getText(); Clipboard.setData(ClipboardData(text: data)); }, - child: Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.center, - children: const [Icon(Icons.content_copy), Text('Copy')]), + children: [Icon(Icons.content_copy), Text('Copy')]), ), ), Expanded( @@ -164,7 +163,7 @@ class FlutterSummernoteState extends State { ClipboardData data = await (Clipboard.getData(Clipboard.kTextPlain) as FutureOr); - String txtIsi = data!.text! + String txtIsi = data.text! .replaceAll("'", '\\"') .replaceAll('"', '\\"') .replaceAll('[', '\\[') @@ -176,9 +175,9 @@ class FlutterSummernoteState extends State { String txt = "\$('.note-editable').append( '$txtIsi');"; _webViewController.runJavaScript(txt); }, - child: Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Icon(Icons.content_paste), Text('Paste'), ]), @@ -193,12 +192,9 @@ class FlutterSummernoteState extends State { Expanded( child: GestureDetector( onTap: () => _attach(context), - child: Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Icon(Icons.attach_file), - Text('Attach') - ]), + children: [Icon(Icons.attach_file), Text('Attach')]), ), )); } @@ -214,7 +210,7 @@ class FlutterSummernoteState extends State { ); return text; } - + /// Call [setText] to set current value in summernote form Future setText(String v) async { String txtIsi = v @@ -330,14 +326,14 @@ class FlutterSummernoteState extends State { 'focus', function () { document.activeElement.blur(); - $(this).focus(); + \$(this).focus(); } ); \$('.note-editable').on( 'click', function () { - $(this).focus(); + \$(this).focus(); } ); } @@ -396,7 +392,7 @@ class FlutterSummernoteState extends State { onTap: () async { Navigator.pop(context); final image = await _getImage(true); - if (image != null) _addImage(image); + if (image != null) await _addImage(image); }, ), ListTile( @@ -406,7 +402,7 @@ class FlutterSummernoteState extends State { onTap: () async { Navigator.pop(context); final image = await _getImage(false); - if (image != null) _addImage(image); + if (image != null) await _addImage(image); }, ), ]); @@ -425,7 +421,7 @@ class FlutterSummernoteState extends State { } /// [_addImage] to add image in summernote form - void _addImage(XFile image) { + Future _addImage(XFile image) async { String filename = basename(image.path); List imageBytes = await image.readAsBytes(); String base64Image = diff --git a/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux b/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux new file mode 120000 index 0000000..664c08c --- /dev/null +++ b/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux @@ -0,0 +1 @@ +C:/Users/owner/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_linux-0.9.4/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux b/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux new file mode 120000 index 0000000..b1b95ae --- /dev/null +++ b/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux @@ -0,0 +1 @@ +C:/Users/owner/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_linux-0.2.2/ \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..64a0ece 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..2db3c22 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..3529df2 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,10 @@ import FlutterMacOS import Foundation +import file_selector_macos +import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 966d328..c305d48 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,42 +21,74 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.19.1" cross_file: dependency: "direct main" description: name: cross_file - sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" url: "https://pub.dev" source: hosted - version: "0.3.3+4" + version: "0.3.5+2" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" flutter: dependency: "direct main" description: flutter @@ -66,10 +98,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "5.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -108,111 +140,151 @@ packages: dependency: "direct main" description: name: image_picker - sha256: f98d76672d309c8b7030c323b3394669e122d52b307d2bbd8d06bd70f5b2aabe + sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c url: "https://pub.dev" source: hosted - version: "0.8.6+1" + version: "0.8.9" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: b1cbfec0f5aef427a18eb573f5445af8c9c568626bf3388553e40c263d3f7368 + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.5+5" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "7d319fb74955ca46d9bf7011497860e3923bb67feebcf068f489311065863899" + sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" url: "https://pub.dev" source: hosted - version: "2.1.10" + version: "2.2.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "8ffb14b43713d7c43fb21299cc18181cc5b39bd3ea1cc427a085c6400fe5aa52" + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + url: "https://pub.dev" + source: hosted + version: "0.8.13+6" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" url: "https://pub.dev" source: hosted - version: "0.8.6+7" + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "7cef2f28f4f2fef99180f636c3d446b4ccbafd6ba0fad2adc9a80c4040f656b8" + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" url: "https://pub.dev" source: hosted - version: "2.6.2" - js: + version: "2.11.1" + image_picker_windows: dependency: transitive description: - name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.2.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "5.1.1" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.17.0" mime: dependency: "direct main" description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" path: dependency: "direct main" description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.8" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -225,18 +297,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -257,10 +329,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.7.10" typed_data: dependency: transitive description: @@ -273,42 +345,58 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" webview_flutter: dependency: "direct main" description: name: webview_flutter - sha256: f7ec234830f86d0ef2bd664e8460b0038b8c1a83ff076035cad74ac70273753c + sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9 url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.13.1" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: "5f49a6e5fc59e21fcec5e1bbcd401afbee9792a24a4f3d9cef9b5bb0cd1e3767" + sha256: ad5182eff9a550925330cb9f0cb038eddfdd5712aba8b77aa0f0400e50f6e688 url: "https://pub.dev" source: hosted - version: "3.2.4" + version: "4.12.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "8b2262dda5d26eabc600a7282a8c16a9473a0c765526afb0ffc33eef912f7968" + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.15.1" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "92e7e7fa468f1df597fb9d37bcf1f303175cbe147c4dbdf06ecc323d950116eb" + sha256: "82648217f537573e1ca9ae9952d3eacedca6ab5aee69dc84445fc763766dcea2" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.25.1" sdks: - dart: ">=2.18.0 <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2c6594d..3ff5887 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,19 +8,19 @@ documentation: https://summernote.org issue_tracker: https://github.com/chandrabezzo/flutter_summernote/issues environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.12.0 <4.0.0" dependencies: flutter: sdk: flutter - webview_flutter: ^4.0.2 - image_picker: ^0.8.6+1 - path: ^1.8.2 - mime: ^1.0.4 - cross_file: ^0.3.3+4 + webview_flutter: ^4.13.1 + image_picker: ^0.8.9 + path: ^1.8.3 + mime: ^1.0.5 + cross_file: ^0.3.4 dev_dependencies: - flutter_lints: ^2.0.1 + flutter_lints: ^5.0.0 flutter_test: sdk: flutter From 89fe381ebb3fbb0b2bb9f0a714d0328d6edc8703 Mon Sep 17 00:00:00 2001 From: alphaisx Date: Fri, 29 May 2026 15:59:26 +0100 Subject: [PATCH 5/7] Fix: Critical production issues - infinite recursion, null safety, race conditions, error handling --- lib/flutter_summernote.dart | 297 ++++++++++++++++++++++++------------ 1 file changed, 202 insertions(+), 95 deletions(-) diff --git a/lib/flutter_summernote.dart b/lib/flutter_summernote.dart index 97290a5..3edd343 100644 --- a/lib/flutter_summernote.dart +++ b/lib/flutter_summernote.dart @@ -52,6 +52,7 @@ class FlutterSummernoteState extends State { final Key _mapKey = UniqueKey(); final _imagePicker = ImagePicker(); late bool _hasAttachment; + bool _isLoading = true; late final WebViewController _webViewController; @@ -82,18 +83,31 @@ class FlutterSummernoteState extends State { } }); - _webViewController - .setNavigationDelegate(NavigationDelegate(onPageFinished: (String url) { - if (widget.hint != null) { - setHint(widget.hint); - } else { - setHint(''); - } - - if (widget.value != null) { - setText(widget.value!); - } - })); + _webViewController.setNavigationDelegate( + NavigationDelegate( + onPageFinished: (String url) { + if (widget.hint != null) { + setHint(widget.hint); + } else { + setHint(''); + } + + if (widget.value != null) { + setText(widget.value!); + } + + setState(() { + _isLoading = false; + }); + }, + onWebResourceError: (WebResourceError error) { + debugPrint('WebView error: ${error.description}'); + setState(() { + _isLoading = false; + }); + }, + ), + ); final String contentBase64 = base64Encode(const Utf8Encoder().convert(_page)); @@ -106,13 +120,16 @@ class FlutterSummernoteState extends State { _webViewController .runJavaScript("document.body.style.webkitUserSelect='auto';"); } + if (Platform.isIOS) { + _webViewController + .runJavaScript("document.body.style.webkitUserSelect='auto';"); + } _webViewController .loadRequest(Uri.parse('data:text/html;base64,$contentBase64')); } @override void dispose() { - // Don't reassign late final variables super.dispose(); } @@ -128,7 +145,18 @@ class FlutterSummernoteState extends State { child: Column( children: [ Expanded( - child: WebViewWidget(key: _mapKey, controller: _webViewController), + child: Stack( + children: [ + WebViewWidget( + key: _mapKey, + controller: _webViewController, + ), + if (_isLoading) + const Center( + child: CircularProgressIndicator(), + ), + ], + ), ), Visibility( visible: widget.showBottomToolbar, @@ -149,8 +177,18 @@ class FlutterSummernoteState extends State { Expanded( child: GestureDetector( onTap: () async { - String data = await getText(); - Clipboard.setData(ClipboardData(text: data)); + try { + String data = await getText(); + if (data.isNotEmpty) { + await Clipboard.setData(ClipboardData(text: data)); + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Copied to clipboard')), + ); + } + } catch (e) { + debugPrint('Error copying text: $e'); + } }, child: const Row( mainAxisAlignment: MainAxisAlignment.center, @@ -160,20 +198,36 @@ class FlutterSummernoteState extends State { Expanded( child: GestureDetector( onTap: () async { - ClipboardData data = await (Clipboard.getData(Clipboard.kTextPlain) - as FutureOr); - - String txtIsi = data.text! - .replaceAll("'", '\\"') - .replaceAll('"', '\\"') - .replaceAll('[', '\\[') - .replaceAll(']', '\\]') - .replaceAll('\n', '
') - .replaceAll('\n\n', '
') - .replaceAll('\r', ' ') - .replaceAll('\r\n', ' '); - String txt = "\$('.note-editable').append( '$txtIsi');"; - _webViewController.runJavaScript(txt); + try { + ClipboardData? data = + await Clipboard.getData(Clipboard.kTextPlain); + + if (data?.text == null || data!.text!.isEmpty) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Clipboard is empty')), + ); + return; + } + + String txtIsi = data.text! + .replaceAll("'", '\\"') + .replaceAll('"', '\\"') + .replaceAll('[', '\\[') + .replaceAll(']', '\\]') + .replaceAll('\n', '
') + .replaceAll('\n\n', '
') + .replaceAll('\r', ' ') + .replaceAll('\r\n', ' '); + String txt = "\$('.note-editable').append( '$txtIsi');"; + await _webViewController.runJavaScript(txt); + } catch (e) { + debugPrint('Error pasting text: $e'); + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error pasting: $e')), + ); + } }, child: const Row( mainAxisAlignment: MainAxisAlignment.center, @@ -186,7 +240,6 @@ class FlutterSummernoteState extends State { ]; if (_hasAttachment) { - //add attachment widget toolbar.insert( 0, Expanded( @@ -204,56 +257,93 @@ class FlutterSummernoteState extends State { /// Call [getText] to get current value from summernote form Future getText() async { - await _webViewController.runJavaScript( - 'setTimeout(function(){GetTextSummernote.postMessage(document.' - 'getElementsByClassName(\'note-editable\')[0].innerHTML)}, 0);', - ); - return text; + final Completer completer = Completer(); + + try { + await _webViewController.runJavaScript( + 'setTimeout(function(){GetTextSummernote.postMessage(document.' + 'getElementsByClassName(\'note-editable\')[0].innerHTML)}, 0);', + ); + + // Give callback a moment to populate text + await Future.delayed(const Duration(milliseconds: 100)); + completer.complete(text); + } catch (e) { + debugPrint('Error getting text: $e'); + completer.completeError(e); + } + + return completer.future; } /// Call [setText] to set current value in summernote form Future setText(String v) async { - String txtIsi = v - .replaceAll("'", '\\"') - .replaceAll('"', '\\"') - .replaceAll('[', '\\[') - .replaceAll(']', '\\]') - .replaceAll('\n', '
') - .replaceAll('\n\n', '
') - .replaceAll('\r', ' ') - .replaceAll('\r\n', ' '); - String txt = - 'document.getElementsByClassName(\'note-editable\')[0].innerHTML' - ' = \'$txtIsi\';'; - _webViewController.runJavaScript(txt); + try { + String txtIsi = v + .replaceAll("'", '\\"') + .replaceAll('"', '\\"') + .replaceAll('[', '\\[') + .replaceAll(']', '\\]') + .replaceAll('\n', '
') + .replaceAll('\n\n', '
') + .replaceAll('\r', ' ') + .replaceAll('\r\n', ' '); + String txt = + 'document.getElementsByClassName(\'note-editable\')[0].innerHTML' + ' = \'$txtIsi\';'; + await _webViewController.runJavaScript(txt); + } catch (e) { + debugPrint('Error setting text: $e'); + } } /// [setFullContainer] to set full summernote form - // void setFullContainer() { - // _webViewController! - // .runJavaScript('\$("#summernote").summernote("fullscreen.toggle");'); - // } + void setFullContainer() { + try { + _webViewController + .runJavaScript('\$("#summernote").summernote("fullscreen.toggle");'); + } catch (e) { + debugPrint('Error toggling fullscreen: $e'); + } + } /// [setFocus] to focus summernote form void setFocus() { - _webViewController.runJavaScript("\$('#summernote').summernote('focus');"); + try { + _webViewController + .runJavaScript("\$('#summernote').summernote('focus');"); + } catch (e) { + debugPrint('Error focusing editor: $e'); + } } /// [setEmpty] called to reset summmernote form void setEmpty() { - _webViewController.runJavaScript("\$('#summernote').summernote('reset');"); + try { + _webViewController + .runJavaScript("\$('#summernote').summernote('reset');"); + setState(() { + text = ''; + }); + } catch (e) { + debugPrint('Error resetting editor: $e'); + } } /// [setHint] to give placeholder void setHint(String? text) { - String hint = '\$(".note-placeholder").html("$text");'; - _webViewController.runJavaScript('setTimeout(function(){$hint}, 0);'); + try { + String hint = '\$(".note-placeholder").html("$text");'; + _webViewController.runJavaScript('setTimeout(function(){$hint}, 0);'); + } catch (e) { + debugPrint('Error setting hint: $e'); + } } /// [widgetIcon] to simplify create a button icon with text - Widget widgetIcon(IconData icon, String title, {Function? onTap}) { + Widget widgetIcon(IconData icon, String title, {VoidCallback? onTap}) { return InkWell( - onTap: onTap as void Function()?, + onTap: onTap, child: Row( children: [ Icon( @@ -317,25 +407,7 @@ class FlutterSummernoteState extends State { popover: {$popover}, callbacks: { onInit: function () { - \$('.note-editable').attr( - 'contenteditable', - 'true' - ); - - \$('.note-editable').on( - 'focus', - function () { - document.activeElement.blur(); - \$(this).focus(); - } - ); - - \$('.note-editable').on( - 'click', - function () { - \$(this).focus(); - } - ); + \$('.note-editable').attr('contenteditable', 'true'); } } }); @@ -391,8 +463,15 @@ class FlutterSummernoteState extends State { subtitle: const Text('Attach image from camera'), onTap: () async { Navigator.pop(context); - final image = await _getImage(true); - if (image != null) await _addImage(image); + try { + final image = await _getImage(true); + if (image != null) await _addImage(image); + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e')), + ); + } }, ), ListTile( @@ -401,8 +480,15 @@ class FlutterSummernoteState extends State { subtitle: const Text('Attach image from gallery'), onTap: () async { Navigator.pop(context); - final image = await _getImage(false); - if (image != null) await _addImage(image); + try { + final image = await _getImage(false); + if (image != null) await _addImage(image); + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e')), + ); + } }, ), ]); @@ -411,24 +497,45 @@ class FlutterSummernoteState extends State { /// [_getImage] to get image from summernote Future _getImage(bool fromCamera) async { - final picked = await _imagePicker.pickImage( - source: (fromCamera) ? ImageSource.camera : ImageSource.gallery); - if (picked != null) { - return XFile(picked.path); - } else { + try { + final picked = await _imagePicker.pickImage( + source: (fromCamera) ? ImageSource.camera : ImageSource.gallery); + if (picked != null) { + return XFile(picked.path); + } return null; + } catch (e) { + debugPrint('Error picking image: $e'); + rethrow; } } /// [_addImage] to add image in summernote form Future _addImage(XFile image) async { - String filename = basename(image.path); - List imageBytes = await image.readAsBytes(); - String base64Image = - ''; - - String txt = "\$('.note-editable').append( '$base64Image');"; - _webViewController.runJavaScript(txt); + try { + String filename = basename(image.path); + List imageBytes = await image.readAsBytes(); + + // Determine image type + String mimeType = 'image/png'; + if (filename.toLowerCase().endsWith('.jpg') || + filename.toLowerCase().endsWith('.jpeg')) { + mimeType = 'image/jpeg'; + } else if (filename.toLowerCase().endsWith('.gif')) { + mimeType = 'image/gif'; + } else if (filename.toLowerCase().endsWith('.webp')) { + mimeType = 'image/webp'; + } + + String base64Image = + ''; + + String txt = "\$('.note-editable').append( '$base64Image');"; + await _webViewController.runJavaScript(txt); + } catch (e) { + debugPrint('Error adding image: $e'); + rethrow; + } } } From 11ea45a1a9ac18757ce36eb491fcda0abb0ea21e Mon Sep 17 00:00:00 2001 From: Alpha Date: Fri, 29 May 2026 16:34:28 +0100 Subject: [PATCH 6/7] glorified --- example/pubspec.lock | 104 +++++++++++++++++++++++++++--------- lib/flutter_summernote.dart | 9 ++++ 2 files changed, 89 insertions(+), 24 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 4609ebe..2470279 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" url: "https://pub.dev" source: hosted - version: "0.3.3+4" + version: "0.3.5+2" cupertino_icons: dependency: "direct main" description: @@ -65,6 +65,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" flutter: dependency: "direct main" description: flutter @@ -115,10 +147,10 @@ packages: dependency: transitive description: name: image_picker - sha256: f98d76672d309c8b7030c323b3394669e122d52b307d2bbd8d06bd70f5b2aabe + sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c url: "https://pub.dev" source: hosted - version: "0.8.6+1" + version: "0.8.9" image_picker_android: dependency: transitive description: @@ -143,22 +175,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.6+7" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "7cef2f28f4f2fef99180f636c3d446b4ccbafd6ba0fad2adc9a80c4040f656b8" + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" url: "https://pub.dev" source: hosted - version: "2.6.2" - js: + version: "2.11.1" + image_picker_windows: dependency: transitive description: - name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.2.2" leak_tracker: dependency: transitive description: @@ -211,10 +259,10 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" path: dependency: transitive description: @@ -227,10 +275,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -308,38 +356,46 @@ packages: url: "https://pub.dev" source: hosted version: "15.2.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" webview_flutter: dependency: transitive description: name: webview_flutter - sha256: f7ec234830f86d0ef2bd664e8460b0038b8c1a83ff076035cad74ac70273753c + sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9 url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.13.1" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: "5f49a6e5fc59e21fcec5e1bbcd401afbee9792a24a4f3d9cef9b5bb0cd1e3767" + sha256: ad5182eff9a550925330cb9f0cb038eddfdd5712aba8b77aa0f0400e50f6e688 url: "https://pub.dev" source: hosted - version: "3.2.4" + version: "4.12.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "8b2262dda5d26eabc600a7282a8c16a9473a0c765526afb0ffc33eef912f7968" + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.15.1" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "92e7e7fa468f1df597fb9d37bcf1f303175cbe147c4dbdf06ecc323d950116eb" + sha256: "82648217f537573e1ca9ae9952d3eacedca6ab5aee69dc84445fc763766dcea2" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.25.1" sdks: - dart: ">=3.9.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/lib/flutter_summernote.dart b/lib/flutter_summernote.dart index 3edd343..c188d0d 100644 --- a/lib/flutter_summernote.dart +++ b/lib/flutter_summernote.dart @@ -96,9 +96,18 @@ class FlutterSummernoteState extends State { setText(widget.value!); } + // Ensure webview can receive focus + FocusManager.instance.primaryFocus?.unfocus(); + debugPrint('webview focusing by alpha'); + _webViewController + .runJavaScript("document.body.style.webkitUserSelect='auto';"); + setState(() { _isLoading = false; }); + + setFullContainer(); + setFocus(); }, onWebResourceError: (WebResourceError error) { debugPrint('WebView error: ${error.description}'); From b86f64dbf74e6167a2d9577be9f04352c361f46e Mon Sep 17 00:00:00 2001 From: Alpha Date: Fri, 29 May 2026 17:44:48 +0100 Subject: [PATCH 7/7] {commit_message} --- lib/flutter_summernote.dart | 128 +++++++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/lib/flutter_summernote.dart b/lib/flutter_summernote.dart index c188d0d..36d7bb7 100644 --- a/lib/flutter_summernote.dart +++ b/lib/flutter_summernote.dart @@ -53,19 +53,35 @@ class FlutterSummernoteState extends State { final _imagePicker = ImagePicker(); late bool _hasAttachment; bool _isLoading = true; + bool _webViewHasFocus = false; late final WebViewController _webViewController; + // Add FocusNode for managing focus + late FocusNode _focusNode; @override void initState() { super.initState(); + _focusNode = FocusNode(); // Initialize FocusNode + _focusNode.addListener(_handleFocusChange); // Listen to focus changes + _page = _initPage(widget.customToolbar, widget.customPopover); _hasAttachment = widget.hasAttachment; _webViewController = WebViewController(); _webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); + // Add JavaScript channel to handle webview focus events + _webViewController.addJavaScriptChannel('FocusHandler', + onMessageReceived: (JavaScriptMessage message) { + if (message.message == 'focused') { + setState(() { + _webViewHasFocus = true; + }); + _focusNode.requestFocus(); + } + }); _webViewController.addJavaScriptChannel('GetTextSummernote', onMessageReceived: (JavaScriptMessage message) { String isi = message.message; @@ -92,22 +108,17 @@ class FlutterSummernoteState extends State { setHint(''); } + setFullContainer(); if (widget.value != null) { setText(widget.value!); } - // Ensure webview can receive focus - FocusManager.instance.primaryFocus?.unfocus(); - debugPrint('webview focusing by alpha'); - _webViewController - .runJavaScript("document.body.style.webkitUserSelect='auto';"); + // Inject JavaScript to handle focus events + _injectFocusHandling(); setState(() { _isLoading = false; }); - - setFullContainer(); - setFocus(); }, onWebResourceError: (WebResourceError error) { debugPrint('WebView error: ${error.description}'); @@ -139,46 +150,89 @@ class FlutterSummernoteState extends State { @override void dispose() { + _focusNode.removeListener(_handleFocusChange); + _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - return Container( - height: widget.height ?? MediaQuery.of(context).size.height, - decoration: widget.decoration ?? - BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(4)), - border: Border.all(color: const Color(0xffececec), width: 1), - ), - child: Column( - children: [ - Expanded( - child: Stack( - children: [ - WebViewWidget( - key: _mapKey, - controller: _webViewController, + return Focus( + focusNode: _focusNode, + onKeyEvent: (node, event) { + // Ensure keyboard input goes to webview + if (_webViewHasFocus) { + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: GestureDetector( + onTap: () { + _focusNode.requestFocus(); + setFocus(); + }, + child: Container( + height: widget.height ?? MediaQuery.of(context).size.height, + decoration: widget.decoration ?? + BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(4)), + border: Border.all(color: const Color(0xffececec), width: 1), + ), + child: Column( + children: [ + Expanded( + child: Stack( + children: [ + WebViewWidget( + key: _mapKey, + controller: _webViewController, + ), + if (_isLoading) + const Center( + child: CircularProgressIndicator(), + ), + ], ), - if (_isLoading) - const Center( - child: CircularProgressIndicator(), - ), - ], - ), + ), + Visibility( + visible: widget.showBottomToolbar, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row(children: _generateBottomToolbar(context)), + ), + ) + ], ), - Visibility( - visible: widget.showBottomToolbar, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row(children: _generateBottomToolbar(context)), - ), - ) - ], + ), ), ); } + /// Handle focus changes from FocusNode + void _handleFocusChange() { + if (_focusNode.hasFocus) { + _webViewHasFocus = true; + setFocus(); + } + } + + /// Inject JavaScript to handle webview focus events + Future _injectFocusHandling() async { + await _webViewController.runJavaScript(''' + document.addEventListener('focusin', function(e) { + if (e.target.closest('.note-editable')) { + FocusHandler.postMessage('focused'); + } + }); + + document.addEventListener('focusout', function(e) { + if (e.target.closest('.note-editable')) { + window._webviewHasFocus = false; + } + }); + '''); + } + /// Method [_generateBottomToolbar] to render bottom toolbar that declared /// by user List _generateBottomToolbar(BuildContext context) {