初步确定基础外观

This commit is contained in:
2026-08-14 23:30:00 +08:00
parent b997391500
commit 15683d0bab
6 changed files with 374 additions and 115 deletions
+31 -40
View File
@@ -1,45 +1,36 @@
# Miscellaneous # ============================================================
*.class # frpc-console .gitignore
*.log # ============================================================
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related # ---------- 构建产物 ----------
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/ /build/
/coverage/ frpc-console
frpc-console.exe
*.exe
*.test
# Symbolication related # ---------- 运行时生成 ----------
app.*.symbols *.db
*.db-journal
frpc.log
frpc.pid
frpc.toml
# Obfuscation related # ---------- IDE ----------
app.*.map.json .idea/
.vscode/
*.swp
*.swo
*~
.dart_tool
.idea
# Android Studio will place build artifacts here # ---------- OS 垃圾 ----------
/android/app/debug .DS_Store
/android/app/profile Thumbs.db
/android/app/release desktop.ini
1.txt
# ---------- 临时文件 ----------
/tmp/
*.tmp
+5 -5
View File
@@ -15,20 +15,20 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.qt_player" applicationId = "com.example.qt_player"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
// ⬇️ 把 ndk 配置放在这里(defaultConfig 内部)
ndk {
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
}
} }
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
} }
} }
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/app/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/app/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/app/src/main/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/app/src/main/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/app/src/main/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/app/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Flutter for Android" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
+228 -29
View File
@@ -1,54 +1,253 @@
// lib/pages/home_page.dart
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 '../widgets/mini_player_bar.dart'; import '../widgets/mini_player_bar.dart';
import 'playlist_page.dart';
import 'settings_page.dart';
class HomePage extends StatefulWidget { class HomePage extends StatelessWidget {
const HomePage({super.key}); const HomePage({super.key});
@override // 收藏数据(从你的 ASCII 图提取)
State<HomePage> createState() => _HomePageState(); final List<Map<String, String>> _favorites = const [
} {'title': '暧昧', 'artist': '王菲', 'format': 'flac', 'tag': 'openlist'},
{'title': '清平调(独唱版)', 'artist': '王菲', 'format': 'flac', 'tag': 'openlist'},
class _HomePageState extends State<HomePage> { {
int _currentIndex = 0; 'title': 'Rain in the Park',
final List<Widget> _pages = const [ 'artist': 'Marika Takeuchi',
PlaylistPage(), 'format': 'flac',
SettingsPage(), 'tag': 'openlist'
},
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 监听播放状态,只要有歌就显示迷你条
final audioService = context.watch<AudioService>(); final audioService = context.watch<AudioService>();
final showMiniBar = audioService.currentSong != null; final showMiniBar = audioService.currentSong != null;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFF0E1211),
// ---------- 主体:可滚动内容 ----------
body: Column( body: Column(
children: [ children: [
Expanded( Expanded(
child: IndexedStack( child: SingleChildScrollView(
index: _currentIndex, padding: const EdgeInsets.symmetric(horizontal: 20),
children: _pages, child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 48),
// ---------- 顶部标题栏 ----------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'清听',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
IconButton(
icon: const Icon(Icons.menu, color: Colors.white54),
onPressed: () {
// 后续:菜单入口
},
),
],
),
const SizedBox(height: 28),
// ---------- 【媒体库】区域 ----------
const Text(
'媒体库',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Color(0xFFB8D4D0),
),
),
const SizedBox(height: 12),
// ---- WebDAV 连接状态卡片 ----
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1A2A2A),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0xFF2A4A4A), width: 0.5),
),
child: Row(
children: [
const Icon(Icons.cloud_outlined,
color: Color(0xFFB8D4D0), size: 24),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'WebDAV',
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w500),
),
Text(
'username@nas', // 后续替换为真实用户名
style: TextStyle(
fontSize: 13, color: Colors.grey[400]),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF4CAF50).withOpacity(0.15),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'已连接',
style: TextStyle(
fontSize: 12, color: Color(0xFF4CAF50)),
),
),
],
),
),
const SizedBox(height: 20),
// ---- 三个功能入口(横向三列) ----
Row(
children: [
_buildEntryCard(Icons.music_note, '本地音乐'),
const SizedBox(width: 12),
_buildEntryCard(Icons.history, '最近播放'),
const SizedBox(width: 12),
_buildEntryCard(Icons.playlist_play, '歌单列表'),
],
),
const SizedBox(height: 32),
// ---------- 【我的收藏】区域 ----------
Row(
children: [
const Icon(Icons.favorite,
color: Color(0xFFB8D4D0), size: 20),
const SizedBox(width: 8),
const Text(
'我的收藏',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Color(0xFFB8D4D0),
),
),
const Spacer(),
TextButton(
onPressed: () {},
child: const Text(
'查看全部',
style: TextStyle(fontSize: 13, color: Colors.grey),
),
),
],
),
const SizedBox(height: 8),
// ---- 收藏列表 ----
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _favorites.length,
separatorBuilder: (_, __) => const Divider(
color: Colors.white10,
height: 1,
),
itemBuilder: (context, index) {
final song = _favorites[index];
return ListTile(
contentPadding: EdgeInsets.zero,
leading: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFF2A3332),
borderRadius: BorderRadius.circular(6),
),
child: const Icon(Icons.music_note,
color: Colors.white38, size: 20),
),
title: Text(
'${song['title']} - ${song['artist']}.${song['format']}',
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w400),
),
subtitle: Text(
song['tag']!,
style:
TextStyle(fontSize: 12, color: Colors.grey[500]),
),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF2A4A4A).withOpacity(0.4),
borderRadius: BorderRadius.circular(12),
),
child: Text(
song['tag']!,
style: const TextStyle(
fontSize: 11, color: Color(0xFF7C9A9E)),
),
),
onTap: () {
// 后续:播放该歌曲
},
);
},
),
const SizedBox(height: 80), // 底部留空,避免被播放栏遮挡
],
),
), ),
), ),
if (showMiniBar) const MiniPlayerBar(), // 底部迷你播放器 // ---------- 底部全局播放控制栏(固定) ----------
], if (showMiniBar) const MiniPlayerBar(),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
backgroundColor: const Color(0xFF1A1F1E),
selectedItemColor: const Color(0xFFB8D4D0),
unselectedItemColor: Colors.grey[600],
onTap: (index) => setState(() => _currentIndex = index),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.music_note), label: '歌单'),
BottomNavigationBarItem(
icon: Icon(Icons.settings_outlined), label: '设置'),
], ],
), ),
); );
} }
// 功能入口卡片(横向三列共用)
Widget _buildEntryCard(IconData icon, String label) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: const Color(0xFF1A1F1E),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[800]!, width: 0.5),
),
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () {
// 后续跳转
},
child: Column(
children: [
Icon(icon, size: 28, color: const Color(0xFFB8D4D0)),
const SizedBox(height: 6),
Text(
label,
style:
const TextStyle(fontSize: 13, fontWeight: FontWeight.w400),
),
],
),
),
),
);
}
} }
+64 -41
View File
@@ -1,7 +1,7 @@
// lib/widgets/mini_player_bar.dart
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 'full_player_page.dart';
class MiniPlayerBar extends StatelessWidget { class MiniPlayerBar extends StatelessWidget {
const MiniPlayerBar({super.key}); const MiniPlayerBar({super.key});
@@ -11,50 +11,73 @@ class MiniPlayerBar extends StatelessWidget {
final service = context.watch<AudioService>(); final service = context.watch<AudioService>();
final song = service.currentSong!; final song = service.currentSong!;
return GestureDetector( return Container(
onTap: () => Navigator.push( height: 64,
context, decoration: BoxDecoration(
MaterialPageRoute(builder: (_) => const FullPlayerPage()), color: const Color(0xFF1A1F1E),
border:
const Border(top: BorderSide(color: Colors.white10, width: 0.5)),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: const Offset(0, -2)),
],
), ),
child: Container( child: Row(
height: 64, children: [
decoration: const BoxDecoration( const SizedBox(width: 12),
color: Color(0xFF1A1F1E), // ---- 封面方图 ----
border: Border(top: BorderSide(color: Colors.white10, width: 0.5)), Container(
), width: 44,
child: Row( height: 44,
children: [ decoration: BoxDecoration(
const SizedBox(width: 12), color: const Color(0xFF2A3332),
Container( borderRadius: BorderRadius.circular(6),
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFF2A3332),
borderRadius: BorderRadius.circular(6),
),
child: const Icon(Icons.music_note, color: Colors.white38),
), ),
const SizedBox(width: 12), child:
Expanded( const Icon(Icons.music_note, color: Colors.white38, size: 22),
child: Column( ),
mainAxisAlignment: MainAxisAlignment.center, const SizedBox(width: 12),
crossAxisAlignment: CrossAxisAlignment.start, // ---- 歌曲信息 ----
children: [ Expanded(
Text(song.title, child: Column(
style: const TextStyle( mainAxisAlignment: MainAxisAlignment.center,
fontSize: 14, fontWeight: FontWeight.w500)), crossAxisAlignment: CrossAxisAlignment.start,
Text(song.artist, children: [
style: TextStyle(fontSize: 12, color: Colors.grey[400])), Text(
], song.title,
), style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
song.artist,
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
), ),
IconButton( ),
icon: Icon(service.isPlaying ? Icons.pause : Icons.play_arrow), // ---- 控制按钮:播放/暂停 + 展开列表 ----
onPressed: () => context.read<AudioService>().togglePlay(), IconButton(
icon: Icon(
service.isPlaying ? Icons.pause : Icons.play_arrow,
color: Colors.white,
), ),
const SizedBox(width: 8), onPressed: () => context.read<AudioService>().togglePlay(),
], ),
), IconButton(
icon:
const Icon(Icons.playlist_play_outlined, color: Colors.white54),
onPressed: () {
// 后续:展开当前播放列表
},
),
const SizedBox(width: 4),
],
), ),
); );
} }
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>