Skip to content

Commit c7b3a3c

Browse files
authored
Merge pull request #11 from sipeed/feat/optimize_ui_and_config
* optimize_ui_and_config
2 parents f1c82bf + 59ead59 commit c7b3a3c

4 files changed

Lines changed: 308 additions & 337 deletions

File tree

lib/src/core/service_manager.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class ServiceManager extends ChangeNotifier {
6464
AppThemeMode get currentThemeMode => _currentThemeMode;
6565

6666
String get webUrl => 'http://$_host:$_port';
67+
String get host => _host;
68+
int get port => _port;
6769
String get binaryPath => _binaryPath;
6870
String get arguments => _arguments;
6971
bool get publicMode => _publicMode;

lib/src/ui/config_page.dart

Lines changed: 45 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'dart:convert';
21
import 'dart:io';
32
import 'package:flutter/material.dart';
43
import 'package:flutter/services.dart';
@@ -9,7 +8,6 @@ import 'package:file_picker/file_picker.dart';
98
import 'package:url_launcher/url_launcher.dart';
109
import 'package:picoclaw_flutter_ui/src/core/app_theme.dart';
1110
import 'package:remixicon/remixicon.dart';
12-
import 'package:picoclaw_flutter_ui/src/core/picoclaw_channel.dart';
1311

1412
const 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

Comments
 (0)