fix(settings): save all config atomically and strip trailing slash from API URL
- Save API URL and sound paths in a single load-modify-write cycle to prevent race conditions that wiped previously saved config - Strip trailing slashes from API URL to avoid double-slash 404 errors - Make AppConfigService.saveConfig public for batch config writes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,14 +57,27 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
}
|
||||
|
||||
Future<void> _saveUrl() async {
|
||||
final url = _controller.text.trim();
|
||||
final url = _controller.text.trim().replaceAll(RegExp(r'/+$'), '');
|
||||
if (url.isEmpty) {
|
||||
_showSnackBar('请输入 API 地址', isError: true);
|
||||
return;
|
||||
}
|
||||
setState(() => _saving = true);
|
||||
// Load config once, apply all changes, then save once to avoid race conditions
|
||||
final configService = AppConfigService();
|
||||
await configService.setString('api_url', url);
|
||||
final config = await configService.loadConfig();
|
||||
config['api_url'] = url;
|
||||
if (_successPath != null) config['sound_success'] = _successPath;
|
||||
else config.remove('sound_success');
|
||||
if (_failurePath != null) config['sound_failure'] = _failurePath;
|
||||
else config.remove('sound_failure');
|
||||
if (_beepPath != null) config['sound_beep'] = _beepPath;
|
||||
else config.remove('sound_beep');
|
||||
if (_errorPath != null) config['sound_error'] = _errorPath;
|
||||
else config.remove('sound_error');
|
||||
if (_alertPath != null) config['sound_alert'] = _alertPath;
|
||||
else config.remove('sound_alert');
|
||||
await configService.saveConfig(config);
|
||||
setState(() => _saving = false);
|
||||
if (mounted) {
|
||||
_showSnackBar('设置已保存');
|
||||
@@ -91,18 +104,6 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
);
|
||||
if (result != null && result.files.single.path != null) {
|
||||
final path = result.files.single.path!;
|
||||
switch (key) {
|
||||
case 'success':
|
||||
await _soundService.setSuccessPath(path);
|
||||
case 'failure':
|
||||
await _soundService.setFailurePath(path);
|
||||
case 'beep':
|
||||
await _soundService.setBeepPath(path);
|
||||
case 'error':
|
||||
await _soundService.setErrorPath(path);
|
||||
case 'alert':
|
||||
await _soundService.setAlertPath(path);
|
||||
}
|
||||
setState(() {
|
||||
switch (key) {
|
||||
case 'success':
|
||||
@@ -121,18 +122,6 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
}
|
||||
|
||||
Future<void> _clearSound(String key) async {
|
||||
switch (key) {
|
||||
case 'success':
|
||||
await _soundService.setSuccessPath(null);
|
||||
case 'failure':
|
||||
await _soundService.setFailurePath(null);
|
||||
case 'beep':
|
||||
await _soundService.setBeepPath(null);
|
||||
case 'error':
|
||||
await _soundService.setErrorPath(null);
|
||||
case 'alert':
|
||||
await _soundService.setAlertPath(null);
|
||||
}
|
||||
setState(() {
|
||||
switch (key) {
|
||||
case 'success':
|
||||
|
||||
@@ -38,11 +38,11 @@ class AppConfigService {
|
||||
Future<void> setString(String key, String value) async {
|
||||
final config = await loadConfig();
|
||||
config[key.toString()] = value.toString();
|
||||
await _saveConfig(config);
|
||||
await saveConfig(config);
|
||||
}
|
||||
|
||||
/// 完整写入 JSON 文件
|
||||
Future<void> _saveConfig(Map<String, dynamic> config) async {
|
||||
Future<void> saveConfig(Map<String, dynamic> config) async {
|
||||
try {
|
||||
final file = File(await _filePath);
|
||||
await file.writeAsString(jsonEncode(config));
|
||||
|
||||
Reference in New Issue
Block a user