追加webdav重新登录的button,还在测试边界情况

This commit is contained in:
2026-08-27 21:52:45 +08:00
parent 90aa7d10e9
commit bca1288bd1
5 changed files with 151 additions and 13 deletions
+63
View File
@@ -0,0 +1,63 @@
// lib/base/base_state.dart
import 'package:flutter/material.dart';
/// 所有页面的基类,提供安全的 setState 封装
///
/// 使用方式:
/// 1. 将页面的 State 类从 `State<YourPage>` 改为 `BaseState<YourPage>`
/// 2. 将所有 `setState(() { ... })` 替换为 `safeSetState(() { ... })`
abstract class BaseState<T extends StatefulWidget> extends State<T> {
/// 安全的 setState,自动检查 mounted
///
/// 用法:
/// ```dart
/// safeSetState(() {
/// _isLoading = false;
/// });
/// ```
void safeSetState(VoidCallback fn) {
if (mounted) {
setState(fn);
}
}
/// 安全执行异步操作,完成后自动刷新
///
/// 用法:
/// ```dart
/// await safeAsync(
/// () => _loadData(), // 异步操作
/// () => _isLoading = false, // 完成后执行的 setState
/// );
/// ```
Future<void> safeAsync(
Future<void> Function() action,
VoidCallback onComplete, {
VoidCallback? onError,
}) async {
try {
await action();
if (mounted) {
setState(onComplete);
}
} catch (e) {
debugPrint('⚠️ [BaseState] safeAsync error: $e');
if (mounted) {
setState(onComplete);
}
onError?.call();
}
}
/// 显示 SnackBar(封装,确保 mounted
void showSafeSnackBar(String message, {Color? backgroundColor}) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: backgroundColor ?? Colors.grey,
duration: const Duration(seconds: 2),
),
);
}
}
+67 -1
View File
@@ -7,6 +7,7 @@ import '../widgets/magnetic_scroll_physics.dart';
import 'webdav_setup_page.dart'; import 'webdav_setup_page.dart';
import 'webdav_file_list_page.dart'; import 'webdav_file_list_page.dart';
import '../constants/ui_constants.dart'; import '../constants/ui_constants.dart';
import '../base/base_state.dart';
// ================================================== // ==================================================
// 歌曲数据模型 // 歌曲数据模型
@@ -149,7 +150,7 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState(); State<HomePage> createState() => _HomePageState();
} }
class _HomePageState extends State<HomePage> { class _HomePageState extends BaseState<HomePage> {
List<SongItem> _favorites = []; List<SongItem> _favorites = [];
bool _isLoading = true; bool _isLoading = true;
@@ -282,6 +283,52 @@ class _HomePageState extends State<HomePage> {
} }
} }
// ⭐ 退出/重新登录对话框
void _showReauthDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: const Color(0xFF1A1F1E),
title: const Text(
'重新登录 WebDAV',
style: TextStyle(color: Colors.white),
),
content: const Text(
'退出当前账号并重新登录?',
style: TextStyle(color: Colors.grey),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
await WebDAVService.instance.clearCredentials();
setState(() {
_favorites = [];
_isLoading = false;
});
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const WebDAVSetupPage(),
),
);
}
},
child: const Text(
'重新登录',
style: TextStyle(color: Colors.redAccent),
),
),
],
),
);
}
void _resetSnapState() { void _resetSnapState() {
if (_scrollController.hasClients) { if (_scrollController.hasClients) {
_scrollController.jumpTo(0.0); _scrollController.jumpTo(0.0);
@@ -413,6 +460,25 @@ class _HomePageState extends State<HomePage> {
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
// ⭐ 退出按钮(已连接时显示)
if (isConnected) ...[
const Spacer(),
IconButton(
icon: const Icon(
Icons.logout,
color: Colors.grey,
size: 18,
),
tooltip: '重新登录',
onPressed: _showReauthDialog,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
),
const SizedBox(width: 4),
],
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
+2 -1
View File
@@ -2,6 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
import '../base/base_state.dart';
class PlayerPage extends StatefulWidget { class PlayerPage extends StatefulWidget {
final VoidCallback? onClose; final VoidCallback? onClose;
@@ -17,7 +18,7 @@ class PlayerPage extends StatefulWidget {
State<PlayerPage> createState() => _PlayerPageState(); State<PlayerPage> createState() => _PlayerPageState();
} }
class _PlayerPageState extends State<PlayerPage> { class _PlayerPageState extends BaseState<PlayerPage> {
double? _dragProgress; double? _dragProgress;
String _formatDuration(Duration d) { String _formatDuration(Duration d) {
+17 -10
View File
@@ -7,6 +7,7 @@ import '../services/webdav_service.dart';
import '../services/playback_service.dart'; import '../services/playback_service.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
import '../constants/ui_constants.dart'; import '../constants/ui_constants.dart';
import '../base/base_state.dart';
class WebDAVFileListPage extends StatefulWidget { class WebDAVFileListPage extends StatefulWidget {
final String currentPath; final String currentPath;
@@ -17,7 +18,7 @@ class WebDAVFileListPage extends StatefulWidget {
State<WebDAVFileListPage> createState() => _WebDAVFileListPageState(); State<WebDAVFileListPage> createState() => _WebDAVFileListPageState();
} }
class _WebDAVFileListPageState extends State<WebDAVFileListPage> { class _WebDAVFileListPageState extends BaseState<WebDAVFileListPage> {
List<WebDAVItem> _items = []; List<WebDAVItem> _items = [];
bool _isLoading = true; bool _isLoading = true;
String _errorMessage = ''; String _errorMessage = '';
@@ -43,7 +44,7 @@ class _WebDAVFileListPageState extends State<WebDAVFileListPage> {
// 加载目录 // 加载目录
// ============================================================ // ============================================================
Future<void> _loadDirectory() async { Future<void> _loadDirectory() async {
setState(() { safeSetState(() {
_isLoading = true; _isLoading = true;
_errorMessage = ''; _errorMessage = '';
}); });
@@ -51,15 +52,21 @@ class _WebDAVFileListPageState extends State<WebDAVFileListPage> {
try { try {
final items = final items =
await WebDAVService.instance.listDirectory(path: _currentPath); await WebDAVService.instance.listDirectory(path: _currentPath);
setState(() { if (mounted) {
_items = items; // ⭐ 加这个
_isLoading = false; setState(() {
}); _items = items;
_isLoading = false;
});
}
} catch (e) { } catch (e) {
setState(() { if (mounted) {
_errorMessage = '加载失败: $e'; // ⭐ 加这个
_isLoading = false; setState(() {
}); _errorMessage = '加载失败: $e';
_isLoading = false;
});
}
} }
} }
+2 -1
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../services/webdav_service.dart'; import '../services/webdav_service.dart';
import 'webdav_file_list_page.dart'; import 'webdav_file_list_page.dart';
import '../base/base_state.dart';
class WebDAVSetupPage extends StatefulWidget { class WebDAVSetupPage extends StatefulWidget {
const WebDAVSetupPage({super.key}); const WebDAVSetupPage({super.key});
@@ -9,7 +10,7 @@ class WebDAVSetupPage extends StatefulWidget {
State<WebDAVSetupPage> createState() => _WebDAVSetupPageState(); State<WebDAVSetupPage> createState() => _WebDAVSetupPageState();
} }
class _WebDAVSetupPageState extends State<WebDAVSetupPage> { class _WebDAVSetupPageState extends BaseState<WebDAVSetupPage> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _baseUrlController = TextEditingController(); final _baseUrlController = TextEditingController();
final _usernameController = TextEditingController(); final _usernameController = TextEditingController();