1- import 'dart:convert' ;
21import 'dart:io' ;
32import 'package:flutter/material.dart' ;
43import 'package:flutter/services.dart' ;
@@ -9,7 +8,6 @@ import 'package:file_picker/file_picker.dart';
98import 'package:url_launcher/url_launcher.dart' ;
109import 'package:picoclaw_flutter_ui/src/core/app_theme.dart' ;
1110import 'package:remixicon/remixicon.dart' ;
12- import 'package:picoclaw_flutter_ui/src/core/picoclaw_channel.dart' ;
1311
1412const String _githubRepoUrl = 'https://github.com/sipeed/picoclaw_fui' ;
1513
@@ -35,21 +33,11 @@ class _ConfigPageState extends State<ConfigPage> {
3533 final _browseFocusNode = FocusNode ();
3634 final _checkFocusNode = FocusNode ();
3735 final _argsFocusNode = FocusNode ();
38- final _saveFocusNode = FocusNode ();
3936 final List <FocusNode > _themeFocusNodes = [];
4037
4138 @override
4239 void initState () {
4340 super .initState ();
44- final service = context.read <ServiceManager >();
45- // 从实际配置获取地址和端口
46- // 如果publicMode为true,host显示0.0.0.0
47- _hostController.text = service.publicMode
48- ? '0.0.0.0'
49- : service.webUrl.replaceAll ('http://' , '' ).split (':' ).first;
50- _portController.text = service.webUrl.split (':' ).last;
51- _pathController.text = service.binaryPath;
52- _argsController.text = service.arguments;
5341 _loadConfig ();
5442
5543 // Initialize theme focus nodes
@@ -59,53 +47,16 @@ class _ConfigPageState extends State<ConfigPage> {
5947 }
6048
6149 Future <void > _loadConfig () async {
62- try {
63- String configStr;
64- if (Platform .isAndroid) {
65- configStr = await PicoClawChannel .getConfig ();
66- } else {
67- final file = File ('config.json' );
68- if (await file.exists ()) {
69- configStr = await file.readAsString ();
70- } else {
71- configStr = '' ;
72- }
73- }
74-
75- if (configStr.isEmpty) {
76- return ;
77- }
78-
79- final config = jsonDecode (configStr) as Map <String , dynamic >;
80-
81- // 从config.json读取gateway配置并更新UI
82- final gateway = config['gateway' ] as Map <String , dynamic >? ;
83- if (gateway != null && mounted) {
84- final service = context.read <ServiceManager >();
85- final port = gateway['port' ] as int ? ;
86-
87- // 如果publicMode为true,显示0.0.0.0,否则显示config.json中的host
88- if (service.publicMode) {
89- setState (() {
90- _hostController.text = '0.0.0.0' ;
91- });
92- } else {
93- final host = gateway['host' ] as String ? ;
94- if (host != null && host.isNotEmpty) {
95- setState (() {
96- _hostController.text = host;
97- });
98- }
99- }
100-
101- if (port != null && port > 0 ) {
102- setState (() {
103- _portController.text = port.toString ();
104- });
105- }
106- }
107- } catch (e) {
108- // Ignore config loading errors
50+ // 统一从 ServiceManager 加载配置,所有平台使用相同方式
51+ final service = context.read <ServiceManager >();
52+ if (mounted) {
53+ setState (() {
54+ // 如果publicMode为true,显示0.0.0.0,否则显示ServiceManager中的host
55+ _hostController.text = service.publicMode ? '0.0.0.0' : service.host;
56+ _portController.text = service.port.toString ();
57+ _pathController.text = service.binaryPath;
58+ _argsController.text = service.arguments;
59+ });
10960 }
11061 }
11162
@@ -124,14 +75,32 @@ class _ConfigPageState extends State<ConfigPage> {
12475 _browseFocusNode.dispose ();
12576 _checkFocusNode.dispose ();
12677 _argsFocusNode.dispose ();
127- _saveFocusNode.dispose ();
12878 for (final node in _themeFocusNodes) {
12979 node.dispose ();
13080 }
13181
13282 super .dispose ();
13383 }
13484
85+ Future <void > _saveConfig () async {
86+ final service = context.read <ServiceManager >();
87+ final port = int .tryParse (_portController.text);
88+
89+ if (port != null ) {
90+ final String ? binaryArg = (Platform .isWindows || Platform .isAndroid)
91+ ? null
92+ : _pathController.text;
93+
94+ await service.updateConfig (
95+ _hostController.text,
96+ port,
97+ binaryPath: binaryArg,
98+ arguments: _argsController.text,
99+ publicMode: service.publicMode,
100+ );
101+ }
102+ }
103+
135104 Future <void > _pickFile () async {
136105 FilePickerResult ? result = await FilePicker .platform.pickFiles (
137106 type: FileType .custom,
@@ -140,6 +109,7 @@ class _ConfigPageState extends State<ConfigPage> {
140109
141110 if (result != null ) {
142111 _pathController.text = result.files.single.path ?? '' ;
112+ await _saveConfig ();
143113 }
144114 }
145115
@@ -214,6 +184,7 @@ class _ConfigPageState extends State<ConfigPage> {
214184 enabled: ! service.publicMode,
215185 nextFocusNode: _portFocusNode,
216186 prevFocusNode: _publicModeFocusNode,
187+ onSubmitted: _saveConfig,
217188 ),
218189 const SizedBox (height: 16 ),
219190
@@ -227,6 +198,7 @@ class _ConfigPageState extends State<ConfigPage> {
227198 ? _pathFocusNode
228199 : _argsFocusNode,
229200 prevFocusNode: _hostFocusNode,
201+ onSubmitted: _saveConfig,
230202 ),
231203 const SizedBox (height: 16 ),
232204
@@ -240,6 +212,7 @@ class _ConfigPageState extends State<ConfigPage> {
240212 label: l10n.binaryPath,
241213 nextFocusNode: _browseFocusNode,
242214 prevFocusNode: _portFocusNode,
215+ onSubmitted: _saveConfig,
243216 ),
244217 ),
245218 const SizedBox (width: 8 ),
@@ -354,80 +327,16 @@ class _ConfigPageState extends State<ConfigPage> {
354327 focusNode: _argsFocusNode,
355328 label: l10n.arguments,
356329 hint: l10n.argumentsHint,
357- nextFocusNode: _saveFocusNode,
330+ nextFocusNode: _themeFocusNodes.isNotEmpty
331+ ? _themeFocusNodes.first
332+ : _argsFocusNode,
358333 prevFocusNode: (! Platform .isWindows && ! Platform .isAndroid)
359334 ? _checkFocusNode
360335 : _portFocusNode,
336+ onSubmitted: _saveConfig,
361337 ),
362338 const SizedBox (height: 24 ),
363339
364- // Save button
365- Builder (
366- builder: (ctx) {
367- final cs = Theme .of (ctx).colorScheme;
368- return FocusableButton (
369- focusNode: _saveFocusNode,
370- onPressed: () async {
371- final port = int .tryParse (_portController.text);
372- if (port != null ) {
373- final messenger = ScaffoldMessenger .of (context);
374- final savedL10n = AppLocalizations .of (context)! ;
375- final String ? binaryArg =
376- (Platform .isWindows || Platform .isAndroid)
377- ? null
378- : _pathController.text;
379-
380- await service.updateConfig (
381- _hostController.text,
382- port,
383- binaryPath: binaryArg,
384- arguments: _argsController.text,
385- publicMode: service.publicMode,
386- );
387-
388- if (mounted) {
389- messenger.showSnackBar (
390- SnackBar (content: Text (savedL10n.save)),
391- );
392- final code = service.lastErrorCode;
393- if (code != null ) {
394- String msg;
395- final l = savedL10n;
396- if (code == 'core.binary_missing' ) {
397- msg = l.coreBinaryMissing;
398- } else if (code == 'core.start_failed' ) {
399- msg = l.coreStartFailed;
400- } else if (code == 'core.stop_failed' ) {
401- msg = l.coreStopFailed;
402- } else {
403- msg = l.coreUnknownError (code);
404- }
405- messenger.showSnackBar (SnackBar (content: Text (msg)));
406- }
407- }
408- }
409- },
410- nextFocusNode: _themeFocusNodes.isNotEmpty
411- ? _themeFocusNodes.first
412- : _saveFocusNode,
413- prevFocusNode: _argsFocusNode,
414- style: ElevatedButton .styleFrom (
415- backgroundColor: cs.secondary,
416- foregroundColor: cs.onSecondary,
417- padding: const EdgeInsets .symmetric (
418- horizontal: 20 ,
419- vertical: 16 ,
420- ),
421- shape: RoundedRectangleBorder (
422- borderRadius: BorderRadius .circular (10 ),
423- ),
424- elevation: 2 ,
425- ),
426- child: Text (l10n.save),
427- );
428- },
429- ),
430-
431340 // Theme selection
432341 const SizedBox (height: 32 ),
433342 const Divider (),
@@ -469,7 +378,7 @@ class _ConfigPageState extends State<ConfigPage> {
469378 .requestFocus ();
470379 }
471380 },
472- onArrowUp: () => _saveFocusNode .requestFocus (),
381+ onArrowUp: () => _argsFocusNode .requestFocus (),
473382 );
474383 }).toList (),
475384 ),
@@ -493,6 +402,7 @@ class FocusableTextField extends StatefulWidget {
493402 final bool enabled;
494403 final FocusNode nextFocusNode;
495404 final FocusNode prevFocusNode;
405+ final VoidCallback ? onSubmitted;
496406
497407 const FocusableTextField ({
498408 super .key,
@@ -504,6 +414,7 @@ class FocusableTextField extends StatefulWidget {
504414 this .enabled = true ,
505415 required this .nextFocusNode,
506416 required this .prevFocusNode,
417+ this .onSubmitted,
507418 });
508419
509420 @override
@@ -588,8 +499,12 @@ class _FocusableTextFieldState extends State<FocusableTextField> {
588499 keyboardType: widget.keyboardType,
589500 enabled: widget.enabled,
590501 onEditingComplete: () {
502+ widget.onSubmitted? .call ();
591503 widget.nextFocusNode.requestFocus ();
592504 },
505+ onSubmitted: (_) {
506+ widget.onSubmitted? .call ();
507+ },
593508 textInputAction: TextInputAction .next,
594509 ),
595510 ),
@@ -788,9 +703,7 @@ class _PublicModeToggleState extends State<PublicModeToggle> {
788703 padding: const EdgeInsets .all (8 ),
789704 decoration: BoxDecoration (
790705 color: (widget.isPublicMode || _isFocused)
791- ? Theme .of (
792- context,
793- ).colorScheme.secondary.withAlpha (40 )
706+ ? Theme .of (context).colorScheme.secondary.withAlpha (40 )
794707 : null ,
795708 borderRadius: BorderRadius .circular (8 ),
796709 ),
0 commit comments