diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index 92904a7..78ed2cf 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -99,7 +99,7 @@ class ProxyServer { ///检查是否监听端口 没有监听则启动 Future startForCheck() async { try { - var socket = await Socket.connect('127.0.0.1', port, timeout: const Duration(milliseconds: 100)); + var socket = await Socket.connect('127.0.0.1', port, timeout: const Duration(milliseconds: 150)); socket.close(); } catch (e) { await start(); diff --git a/lib/ui/component/cert_hash.dart b/lib/ui/component/cert_hash.dart index b0cc3d5..2b96bae 100644 --- a/lib/ui/component/cert_hash.dart +++ b/lib/ui/component/cert_hash.dart @@ -80,7 +80,7 @@ class _CertHashPageState extends State { FilledButton.icon( onPressed: () { getSubjectName(); - FocusScope.of(context).requestFocus(FocusNode()); + FocusScope.of(context).unfocus(); }, style: buttonStyle, icon: const Icon(Icons.play_arrow_rounded), diff --git a/lib/ui/component/js_run.dart b/lib/ui/component/js_run.dart index 439dbf3..50a38e8 100644 --- a/lib/ui/component/js_run.dart +++ b/lib/ui/component/js_run.dart @@ -2,10 +2,13 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; -import 'package:flutter_js/flutter_js.dart'; -import 'package:highlight/languages/javascript.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_highlight/themes/monokai-sublime.dart'; +import 'package:flutter_js/flutter_js.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:highlight/languages/javascript.dart'; import 'package:proxypin/network/components/js/file.dart'; import 'package:proxypin/network/components/js/md5.dart'; @@ -19,21 +22,31 @@ class JavaScript extends StatefulWidget { } class _JavaScriptState extends State { - static JavascriptRuntime flutterJs = getJavascriptRuntime(); + //重置环境 + static bool resetEnvironment = true; + + static JavascriptRuntime? flutterJs; late CodeController code; List outLines = []; + ScrollController inputScrollController = ScrollController(); + ScrollController outputScrollController = ScrollController(); + + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); - + if (resetEnvironment || flutterJs == null) { + flutterJs = getJavascriptRuntime(); + } // register channel callback - final channelCallbacks = JavascriptRuntime.channelFunctionsRegistered[flutterJs.getEngineInstanceId()]; + final channelCallbacks = JavascriptRuntime.channelFunctionsRegistered[flutterJs!.getEngineInstanceId()]; channelCallbacks!["ConsoleLog"] = consoleLog; - Md5Bridge.registerMd5(flutterJs); - FileBridge.registerFile(flutterJs); + Md5Bridge.registerMd5(flutterJs!); + FileBridge.registerFile(flutterJs!); code = CodeController(language: javascript, text: 'console.log("Hello, World!")'); } @@ -41,6 +54,12 @@ class _JavaScriptState extends State { @override void dispose() { code.dispose(); + inputScrollController.dispose(); + outputScrollController.dispose(); + if (resetEnvironment) { + flutterJs?.dispose(); + flutterJs = null; + } super.dispose(); } @@ -48,17 +67,18 @@ class _JavaScriptState extends State { var level = args.removeAt(0); String output = args.join(' '); if (level == 'info') level = 'warn'; - outLines.add(Text(output, style: TextStyle(color: level == 'error' ? Colors.red : Colors.white, fontSize: 13))); setState(() { + outLines.add(Text(output, style: TextStyle(color: level == 'error' ? Colors.red : Colors.white, fontSize: 13))); print(outLines); }); } @override Widget build(BuildContext context) { + Color primaryColor = Theme.of(context).colorScheme.primary; return Scaffold( - appBar: AppBar(title: const Text("JavaScript", style: TextStyle(fontSize: 16)), centerTitle: true), resizeToAvoidBottomInset: false, + appBar: AppBar(title: const Text("JavaScript", style: TextStyle(fontSize: 16)), centerTitle: true), body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, @@ -82,15 +102,16 @@ class _JavaScriptState extends State { onPressed: () async { outLines.clear(); //失去焦点 - FocusScope.of(context).requestFocus(FocusNode()); - var jsResult = await flutterJs.evaluateAsync(code.text); + FocusScope.of(context).unfocus(); + var jsResult = await flutterJs!.evaluateAsync(code.text); if (jsResult.isPromise || jsResult.rawResult is Future) { - jsResult = await flutterJs.handlePromise(jsResult); + jsResult = await flutterJs!.handlePromise(jsResult); } - if (jsResult.isError) { - outLines.add(Text(jsResult.toString(), style: const TextStyle(color: Colors.red, fontSize: 13))); - setState(() {}); + setState(() { + outLines + .add(Text(jsResult.toString(), style: const TextStyle(color: Colors.red, fontSize: 13))); + }); } }, icon: const Icon(Icons.play_arrow_rounded), @@ -103,22 +124,85 @@ class _JavaScriptState extends State { height: 320, child: CodeTheme( data: CodeThemeData(styles: monokaiSublimeTheme), - child: SingleChildScrollView( - child: CodeField( - minLines: 16, - textStyle: const TextStyle(fontSize: 12), - controller: code, - gutterStyle: const GutterStyle(width: 50, margin: 0), - )))), + child: Scrollbar( + controller: inputScrollController, + thumbVisibility: true, + interactive: true, + trackVisibility: true, + thickness: 8, + child: SingleChildScrollView( + controller: inputScrollController, + scrollDirection: Axis.vertical, + child: CodeField( + minLines: 16, + background: Colors.grey.shade800, + padding: const EdgeInsets.only(right: 10), + textStyle: const TextStyle(fontSize: 13), + controller: code, + enableSuggestions: true, + onTapOutside: (event) => FocusScope.of(context).unfocus(), + gutterStyle: const GutterStyle(width: 50, margin: 0), + ))))), const SizedBox(height: 10), - TextButton(onPressed: () {}, child: const Text("Output:", style: TextStyle(fontSize: 16))), + Row(children: [ + Text("Output:", style: TextStyle(fontSize: 16, color: primaryColor, fontWeight: FontWeight.w500)), + const SizedBox(width: 15), + //copy + IconButton( + icon: Icon(Icons.copy, color: primaryColor, size: 18), + onPressed: () { + Clipboard.setData(ClipboardData(text: outLines.join("\n"))); + FlutterToastr.show(localizations.copied, context, duration: 3); + }), + ]), Expanded( child: Container( width: double.infinity, padding: const EdgeInsets.all(10), - color: Colors.black, - child: SingleChildScrollView( - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: outLines)))), + color: Colors.grey.shade800, + child: Scrollbar( + controller: outputScrollController, + thumbVisibility: true, + trackVisibility: true, + child: SingleChildScrollView( + controller: outputScrollController, + child: SelectionArea( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: outLines)))))), ])); } } + +// Create a new widget for the fullscreen CodeField +class FullScreenCodeField extends StatelessWidget { + final CodeController code; + + FullScreenCodeField({required this.code}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("FullScreen Code Editor"), + actions: [ + IconButton( + icon: Icon(Icons.close), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + body: Expanded( + child: CodeTheme( + data: CodeThemeData(styles: monokaiSublimeTheme), + child: CodeField( + background: Colors.grey.shade800, + minLines: 50, + textStyle: const TextStyle(fontSize: 12), + controller: code, + gutterStyle: const GutterStyle(width: 50, margin: 0), + ), + ), + )); + } +} diff --git a/lib/ui/launch/launch.dart b/lib/ui/launch/launch.dart index 328df81..d9f1439 100644 --- a/lib/ui/launch/launch.dart +++ b/lib/ui/launch/launch.dart @@ -106,6 +106,12 @@ class _SocketLaunchState extends State with WindowListener, Widget @override void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + if (widget.proxyServer.isRunning) { + widget.proxyServer.startForCheck(); + } + } + if (state == AppLifecycleState.detached) { logger.d('AppLifecycleState.detached'); widget.onStop?.call(); diff --git a/lib/ui/mobile/setting/script.dart b/lib/ui/mobile/setting/script.dart index 5b8d6a3..66b1626 100644 --- a/lib/ui/mobile/setting/script.dart +++ b/lib/ui/mobile/setting/script.dart @@ -470,7 +470,11 @@ class _ScriptEditState extends State { CodeTheme( data: CodeThemeData(styles: monokaiSublimeTheme), child: SingleChildScrollView( - child: CodeField(textStyle: const TextStyle(fontSize: 14), controller: script))) + child: CodeField( + textStyle: const TextStyle(fontSize: 13), + enableSuggestions: true, + onTapOutside: (event) => FocusScope.of(context).unfocus(), + controller: script))) ], )))); } diff --git a/pubspec.yaml b/pubspec.yaml index 9911c25..6c260c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,10 @@ dependencies: share_plus: ^10.1.1 brotli: ^0.6.0 flutter_js: ^0.8.1 - flutter_code_editor: ^0.3.2 + flutter_code_editor: + git: + url: https://github.com/wanghongenpin/flutter-code-editor.git + ref: secure-keyboard flutter_desktop_context_menu: ^0.2.0 file_picker: ^8.1.3 file_selector: ^1.0.3