import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:file_picker/file_picker.dart'; import 'package:pad_scanner/services/api_service.dart'; import 'package:pad_scanner/services/sound_service.dart'; class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @override State createState() => _SettingsPageState(); } class _SettingsPageState extends State { final _controller = TextEditingController(); final _apiService = ApiService(); final _soundService = SoundService(); bool _saving = false; bool _testing = false; String? _successPath; String? _failurePath; @override void initState() { super.initState(); _loadUrl(); _loadSoundPaths(); } Future _loadUrl() async { final prefs = await SharedPreferences.getInstance(); final url = prefs.getString('api_url') ?? ''; _controller.text = url; } Future _loadSoundPaths() async { final s = await _soundService.getSuccessPath(); final f = await _soundService.getFailurePath(); if (mounted) { setState(() { _successPath = s; _failurePath = f; }); } } Future _saveUrl() async { final url = _controller.text.trim(); if (url.isEmpty) { _showSnackBar('请输入 API 地址', isError: true); return; } setState(() => _saving = true); final prefs = await SharedPreferences.getInstance(); await prefs.setString('api_url', url); setState(() => _saving = false); if (mounted) { _showSnackBar('设置已保存'); } } Future _testConnection() async { final url = _controller.text.trim(); if (url.isEmpty) { _showSnackBar('请先输入 API 地址', isError: true); return; } setState(() => _testing = true); final ok = await _apiService.testConnection(url); setState(() => _testing = false); if (mounted) { _showSnackBar( ok ? '连接成功' : '连接失败,请检查地址和网络', isError: !ok, ); } } Future _pickSound(bool isSuccess) async { final result = await FilePicker.pickFiles( type: FileType.audio, ); if (result != null && result.files.single.path != null) { final path = result.files.single.path!; if (isSuccess) { await _soundService.setSuccessPath(path); } else { await _soundService.setFailurePath(path); } setState(() { if (isSuccess) { _successPath = path; } else { _failurePath = path; } }); } } Future _clearSound(bool isSuccess) async { if (isSuccess) { await _soundService.setSuccessPath(null); } else { await _soundService.setFailurePath(null); } setState(() { if (isSuccess) { _successPath = null; } else { _failurePath = null; } }); } Future _previewSound(String path) async { await _soundService.play(path); } String _fileName(String? path) { if (path == null) return '未设置'; return path.split('/').last; } void _showSnackBar(String message, {bool isError = false}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: isError ? Colors.red.shade700 : Colors.green.shade700, duration: const Duration(seconds: 2), ), ); } @override void dispose() { _controller.dispose(); _soundService.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('设置')), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ---- API 地址 ---- const Text('API 服务器地址', style: TextStyle(fontSize: 14)), const SizedBox(height: 8), TextField( controller: _controller, decoration: const InputDecoration( hintText: 'http://192.168.1.100:8000', border: OutlineInputBorder(), ), keyboardType: TextInputType.url, ), const SizedBox(height: 20), ElevatedButton.icon( onPressed: _saving ? null : _saveUrl, icon: _saving ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save), label: const Text('保存设置'), ), const SizedBox(height: 8), OutlinedButton.icon( onPressed: _testing ? null : _testConnection, icon: _testing ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.wifi), label: const Text('测试连接'), ), const SizedBox(height: 24), // ---- 铃声设置 ---- const Divider(), const SizedBox(height: 8), const Text('铃声设置', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 16), // 上架成功铃声 _buildSoundRow( label: '上架成功铃声', path: _successPath, onPick: () => _pickSound(true), onPreview: _successPath != null ? () => _previewSound(_successPath!) : null, onClear: _successPath != null ? () => _clearSound(true) : null, ), const SizedBox(height: 12), // 上架失败铃声 _buildSoundRow( label: '上架失败铃声', path: _failurePath, onPick: () => _pickSound(false), onPreview: _failurePath != null ? () => _previewSound(_failurePath!) : null, onClear: _failurePath != null ? () => _clearSound(false) : null, ), ], ), ), ); } Widget _buildSoundRow({ required String label, required String? path, required VoidCallback onPick, required VoidCallback? onPreview, required VoidCallback? onClear, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 14)), const SizedBox(height: 6), Row( children: [ Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade400), borderRadius: BorderRadius.circular(8), ), child: Text( _fileName(path), style: TextStyle( fontSize: 13, color: path != null ? Colors.black87 : Colors.grey, ), overflow: TextOverflow.ellipsis, ), ), ), const SizedBox(width: 8), IconButton( icon: const Icon(Icons.folder_open), tooltip: '选择文件', onPressed: onPick, ), if (onPreview != null) IconButton( icon: const Icon(Icons.play_arrow), tooltip: '试听', onPressed: onPreview, ), if (onClear != null) IconButton( icon: const Icon(Icons.clear, size: 20), tooltip: '清除', onPressed: onClear, ), ], ), ], ); } }