追加webdav重新登录的button,还在测试边界情况
This commit is contained in:
@@ -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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user