页面层级与返回逻辑实现完成,并且去掉web相关代码支持
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
// lib/pages/home_page.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../services/audio_service.dart';
|
||||
@@ -7,7 +6,6 @@ import '../services/playback_service.dart';
|
||||
import '../widgets/magnetic_scroll_physics.dart';
|
||||
import 'webdav_setup_page.dart';
|
||||
import 'webdav_file_list_page.dart';
|
||||
import 'dart:async';
|
||||
|
||||
// ==================================================
|
||||
// 歌曲数据模型
|
||||
@@ -171,7 +169,6 @@ class _HomePageState extends State<HomePage> {
|
||||
return (offset / _mediaLibraryHeight).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
// 媒体库透明度(快速淡出)
|
||||
double get _mediaOpacity {
|
||||
final p = _progress;
|
||||
if (p <= 0.3) {
|
||||
@@ -181,7 +178,6 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
// 列表展开程度
|
||||
double get _listReveal {
|
||||
if (_progress <= 0.7) return 0.0;
|
||||
return (_progress - 0.7) / 0.3;
|
||||
@@ -204,7 +200,6 @@ class _HomePageState extends State<HomePage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ========== 高度测量(带重试限制) ==========
|
||||
void _measureMediaLibraryHeight() {
|
||||
final renderBox =
|
||||
_mediaLibraryKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
@@ -231,12 +226,10 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 滚动监听 ==========
|
||||
void _onScroll() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
// ========== 初始化 WebDAV ==========
|
||||
Future<void> _initWebDAV() async {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
@@ -249,14 +242,10 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ⭐ 核心:播放方法(UI 优先响应,然后真正播放)
|
||||
// ============================================================
|
||||
void _playSong(SongItem song) async {
|
||||
try {
|
||||
final url = WebDAVService.instance.getFileUrl(song.path);
|
||||
|
||||
// 1️⃣ 先更新 UI(立即响应)
|
||||
final audioService = context.read<AudioService>();
|
||||
audioService.playSong(Song(
|
||||
id: song.path,
|
||||
@@ -265,11 +254,9 @@ class _HomePageState extends State<HomePage> {
|
||||
url: url,
|
||||
));
|
||||
|
||||
// 2️⃣ 动态获取认证头
|
||||
final headers = await WebDAVService.instance.getAuthHeaders();
|
||||
|
||||
if (headers.isEmpty) {
|
||||
print('⚠️ 未获取到认证头,请检查 WebDAV 登录状态');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
@@ -281,10 +268,6 @@ class _HomePageState extends State<HomePage> {
|
||||
return;
|
||||
}
|
||||
|
||||
print('🎵 [收藏列表] 播放 URL: $url');
|
||||
print('📋 [收藏列表] 认证头已设置: ${headers.keys}');
|
||||
|
||||
// 3️⃣ 播放
|
||||
await PlaybackService().play(url, headers: headers);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
@@ -298,7 +281,6 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 重置磁吸状态 ==========
|
||||
void _resetSnapState() {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(0.0);
|
||||
@@ -306,21 +288,15 @@ class _HomePageState extends State<HomePage> {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Build
|
||||
// ================================================================
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isConnected = WebDAVService.instance.isConnected;
|
||||
final username = WebDAVService.instance.username ?? '点击连接';
|
||||
final audioService = context.watch<AudioService>();
|
||||
final showMiniBar = audioService.currentSong != null;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF0E1211),
|
||||
body: Column(
|
||||
children: [
|
||||
// ---- 固定标题 "清听" ----
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
@@ -344,8 +320,6 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ---- 滚动区域 ----
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
@@ -356,7 +330,6 @@ class _HomePageState extends State<HomePage> {
|
||||
)
|
||||
: const ClampingScrollPhysics(),
|
||||
slivers: [
|
||||
// ---- 媒体库 ----
|
||||
SliverToBoxAdapter(
|
||||
child: Opacity(
|
||||
opacity: _mediaOpacity,
|
||||
@@ -551,8 +524,6 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ---- "我的收藏" Sticky Header ----
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _StickyHeaderDelegate(
|
||||
@@ -578,13 +549,11 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ---- 收藏列表 ----
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: showMiniBar ? 80.0 : 20.0,
|
||||
bottom: 20,
|
||||
),
|
||||
sliver: _isLoading
|
||||
? const SliverFillRemaining(
|
||||
|
||||
+28
-24
@@ -1,19 +1,10 @@
|
||||
// lib/pages/player_page.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../services/audio_service.dart';
|
||||
import '../main.dart';
|
||||
import 'playlist_page.dart';
|
||||
|
||||
class PlayerPage extends StatefulWidget {
|
||||
final VoidCallback onClose;
|
||||
final VoidCallback onOpenPlaylist;
|
||||
|
||||
const PlayerPage({
|
||||
super.key,
|
||||
required this.onClose,
|
||||
required this.onOpenPlaylist,
|
||||
});
|
||||
const PlayerPage({super.key});
|
||||
|
||||
@override
|
||||
State<PlayerPage> createState() => _PlayerPageState();
|
||||
@@ -37,22 +28,33 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
if (song == null) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF0E1211),
|
||||
body: Center(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: const Text(
|
||||
'正在播放',
|
||||
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
Icon(Icons.music_off, size: 64, color: Colors.grey),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'没有正在播放的歌曲',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: widget.onClose,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFB8D4D0),
|
||||
foregroundColor: Colors.black87,
|
||||
),
|
||||
child: const Text('返回'),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'请先在首页点击一首歌曲',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -83,7 +85,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white),
|
||||
onPressed: widget.onClose,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: const Text(
|
||||
'正在播放',
|
||||
@@ -279,8 +281,8 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFB8D4D0),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFB8D4D0),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
@@ -301,7 +303,9 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
padding: const EdgeInsets.all(12),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: widget.onOpenPlaylist,
|
||||
onPressed: () {
|
||||
navigatorKey.currentState?.pushNamed('/playlist');
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.playlist_play_outlined,
|
||||
color: Color(0xFFB8D4D0),
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
// lib/pages/playlist_page.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../services/audio_service.dart';
|
||||
|
||||
class PlaylistPage extends StatelessWidget {
|
||||
final VoidCallback onClose;
|
||||
|
||||
const PlaylistPage({
|
||||
super.key,
|
||||
required this.onClose,
|
||||
});
|
||||
const PlaylistPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -33,7 +27,7 @@ class PlaylistPage extends StatelessWidget {
|
||||
foregroundColor: Colors.white,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new),
|
||||
onPressed: onClose,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
@@ -60,7 +54,7 @@ class PlaylistPage extends StatelessWidget {
|
||||
onPressed: () {
|
||||
service.clearQueue();
|
||||
Navigator.pop(context);
|
||||
onClose();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text(
|
||||
'清空',
|
||||
@@ -146,7 +140,7 @@ class PlaylistPage extends StatelessWidget {
|
||||
: null,
|
||||
onTap: () {
|
||||
service.setQueue(queue, startIndex: index);
|
||||
onClose();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
// lib/pages/shell_page.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../services/audio_service.dart';
|
||||
import '../widgets/global_mini_player.dart';
|
||||
import 'home_page.dart';
|
||||
import 'player_page.dart';
|
||||
import 'playlist_page.dart';
|
||||
|
||||
class ShellPage extends StatefulWidget {
|
||||
const ShellPage({super.key});
|
||||
|
||||
@override
|
||||
State<ShellPage> createState() => _ShellPageState();
|
||||
}
|
||||
|
||||
class _ShellPageState extends State<ShellPage> {
|
||||
// ✅ 从 PlayerPage 打开 PlaylistPage
|
||||
void _openPlaylistFromPlayer() {
|
||||
Navigator.pushNamed(context, '/playlist');
|
||||
}
|
||||
|
||||
// ✅ 关闭 PlayerPage
|
||||
void _closePlayer() {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
// ✅ 关闭 PlaylistPage
|
||||
void _closePlaylist() {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF0E1211),
|
||||
body: Stack(
|
||||
children: [
|
||||
// ---- 页面内容(Navigator) ----
|
||||
Navigator(
|
||||
initialRoute: '/',
|
||||
onGenerateRoute: (settings) {
|
||||
switch (settings.name) {
|
||||
case '/':
|
||||
return _buildPageRoute(
|
||||
const HomePage(),
|
||||
settings: settings,
|
||||
);
|
||||
case '/player':
|
||||
return _buildPageRoute(
|
||||
PlayerPage(
|
||||
onClose: _closePlayer,
|
||||
onOpenPlaylist: _openPlaylistFromPlayer,
|
||||
),
|
||||
settings: settings,
|
||||
fromBottom: true,
|
||||
);
|
||||
case '/playlist':
|
||||
return _buildPageRoute(
|
||||
PlaylistPage(
|
||||
onClose: _closePlaylist,
|
||||
),
|
||||
settings: settings,
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
// ---- MiniPlayer(覆盖在最上层) ----
|
||||
const Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: GlobalMiniPlayer(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PageRouteBuilder _buildPageRoute(
|
||||
Widget page, {
|
||||
required RouteSettings settings,
|
||||
bool fromBottom = false,
|
||||
}) {
|
||||
return PageRouteBuilder(
|
||||
settings: settings,
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
if (fromBottom) {
|
||||
// 播放页:从底部滑入
|
||||
final offset = Tween<Offset>(
|
||||
begin: const Offset(0, 1),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutCubic,
|
||||
));
|
||||
return SlideTransition(position: offset, child: child);
|
||||
} else {
|
||||
// 其他页面:淡入
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 300),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user