feat(flutter): 增加 debug 服务地址编辑器
This commit is contained in:
1 parent
eab5d3e230
commit
c259b0b543
9 files changed
+298
-5
No files matched your search
@@ -80,7 +80,12 @@ class AppConfig {
|
||||
Future<bool> Function(String baseUrl)? probe,
|
||||
String? fallbackBaseUrl,
|
||||
}) async {
|
||||
for (final candidate in _probeCandidates) {
|
||||
final candidates = <String>[
|
||||
if (_runtimeBaseUrl.isNotEmpty) _runtimeBaseUrl,
|
||||
for (final candidate in _probeCandidates)
|
||||
if (candidate != _runtimeBaseUrl) candidate,
|
||||
];
|
||||
for (final candidate in candidates) {
|
||||
final result = probe == null
|
||||
? await _probeBaseUrl(candidate)
|
||||
: await _runInjectedProbe(candidate, probe);
|
||||
|
||||
@@ -69,6 +69,7 @@ abstract final class ServerEndpoint {
|
||||
preferenceKey,
|
||||
normalized,
|
||||
);
|
||||
AppConfig.applyRuntimeBaseUrl(normalized);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
|
||||
@@ -14,9 +14,10 @@ const TextStyle _debugStyle = TextStyle(
|
||||
|
||||
/// 仅 debug 展示当前服务地址与目录请求的原始错误。
|
||||
class CatalogDebugInfo extends StatelessWidget {
|
||||
const CatalogDebugInfo({super.key, required this.error});
|
||||
const CatalogDebugInfo({super.key, required this.error, this.onEditEndpoint});
|
||||
|
||||
final String? error;
|
||||
final VoidCallback? onEditEndpoint;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -41,6 +42,12 @@ class CatalogDebugInfo extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
style: _debugStyle,
|
||||
),
|
||||
if (onEditEndpoint != null)
|
||||
TextButton(
|
||||
key: const Key('catalog-debug-endpoint-edit'),
|
||||
onPressed: onEditEndpoint,
|
||||
child: const Text('设置服务地址'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../config/app_config.dart';
|
||||
import '../config/server_endpoint.dart';
|
||||
|
||||
const String _title = '设置服务地址';
|
||||
const String _save = '保存';
|
||||
const String _cancel = '取消';
|
||||
const String _invalid = '请输入合法地址,例如 http://192.168.1.10:8081';
|
||||
const String _saveFailed = '服务地址保存失败';
|
||||
const String _localNetworkHint = '手机需与电脑同一 Wi-Fi,并在 iOS 设置-隐私与安全性-本地网络 中允许本应用';
|
||||
|
||||
/// Debug 下编辑服务地址,保存后重新探测并加载目录。
|
||||
Future<void> showCatalogEndpointEditor(
|
||||
BuildContext context, {
|
||||
required Future<void> Function() reload,
|
||||
Future<bool> Function(String value)? save,
|
||||
Future<void> Function()? probe,
|
||||
}) async {
|
||||
if (!kDebugMode) return;
|
||||
final value = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (_) => _CatalogEndpointDialog(initialValue: AppConfig.apiBaseUrl),
|
||||
);
|
||||
if (value == null) return;
|
||||
final saveEndpoint = save ?? (input) => ServerEndpoint.save(input);
|
||||
if (!await saveEndpoint(value)) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text(_saveFailed)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
await (probe ?? () => AppConfig.probeAndApplyBaseUrl())();
|
||||
await reload();
|
||||
}
|
||||
|
||||
class _CatalogEndpointDialog extends StatefulWidget {
|
||||
const _CatalogEndpointDialog({required this.initialValue});
|
||||
|
||||
final String initialValue;
|
||||
|
||||
@override
|
||||
State<_CatalogEndpointDialog> createState() => _CatalogEndpointDialogState();
|
||||
}
|
||||
|
||||
class _CatalogEndpointDialogState extends State<_CatalogEndpointDialog> {
|
||||
late final TextEditingController _controller = TextEditingController(
|
||||
text: widget.initialValue,
|
||||
);
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _submit() {
|
||||
final normalized = ServerEndpoint.normalize(_controller.text);
|
||||
if (normalized == null) {
|
||||
setState(() => _error = _invalid);
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop(normalized);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text(_title),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
key: const Key('catalog-debug-endpoint-input'),
|
||||
controller: _controller,
|
||||
keyboardType: TextInputType.url,
|
||||
textInputAction: TextInputAction.done,
|
||||
autocorrect: false,
|
||||
onSubmitted: (_) => _submit(),
|
||||
decoration: const InputDecoration(
|
||||
labelText: '服务地址',
|
||||
hintText: 'http://IP:端口',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
_localNetworkHint,
|
||||
key: Key('catalog-debug-endpoint-hint'),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
if (_error != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_error!,
|
||||
key: const Key('catalog-debug-endpoint-error'),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text(_cancel),
|
||||
),
|
||||
FilledButton(
|
||||
key: const Key('catalog-debug-endpoint-save'),
|
||||
onPressed: _submit,
|
||||
child: const Text(_save),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user