diff --git a/android/app/build/intermediates/apk/debug/app-debug.apk b/android/app/build/intermediates/apk/debug/app-debug.apk index 8972d84..e910071 100644 Binary files a/android/app/build/intermediates/apk/debug/app-debug.apk and b/android/app/build/intermediates/apk/debug/app-debug.apk differ diff --git a/android/app/build/intermediates/assets/debug/flutter_assets/assets/img/like.png b/android/app/build/intermediates/assets/debug/flutter_assets/assets/img/like.png new file mode 100644 index 0000000..ca505ab Binary files /dev/null and b/android/app/build/intermediates/assets/debug/flutter_assets/assets/img/like.png differ diff --git a/android/app/build/intermediates/assets/debug/flutter_assets/assets/img/unlike.png b/android/app/build/intermediates/assets/debug/flutter_assets/assets/img/unlike.png new file mode 100644 index 0000000..6d1fb29 Binary files /dev/null and b/android/app/build/intermediates/assets/debug/flutter_assets/assets/img/unlike.png differ diff --git a/android/app/build/intermediates/compressed_assets/debug/out/assets/flutter_assets/assets/img/like.png.jar b/android/app/build/intermediates/compressed_assets/debug/out/assets/flutter_assets/assets/img/like.png.jar new file mode 100644 index 0000000..2eacbf9 Binary files /dev/null and b/android/app/build/intermediates/compressed_assets/debug/out/assets/flutter_assets/assets/img/like.png.jar differ diff --git a/android/app/build/intermediates/compressed_assets/debug/out/assets/flutter_assets/assets/img/unlike.png.jar b/android/app/build/intermediates/compressed_assets/debug/out/assets/flutter_assets/assets/img/unlike.png.jar new file mode 100644 index 0000000..1b31f3f Binary files /dev/null and b/android/app/build/intermediates/compressed_assets/debug/out/assets/flutter_assets/assets/img/unlike.png.jar differ diff --git a/android/app/build/intermediates/flutter/debug/flutter_assets/assets/img/like.png b/android/app/build/intermediates/flutter/debug/flutter_assets/assets/img/like.png new file mode 100644 index 0000000..ca505ab Binary files /dev/null and b/android/app/build/intermediates/flutter/debug/flutter_assets/assets/img/like.png differ diff --git a/android/app/build/intermediates/flutter/debug/flutter_assets/assets/img/unlike.png b/android/app/build/intermediates/flutter/debug/flutter_assets/assets/img/unlike.png new file mode 100644 index 0000000..6d1fb29 Binary files /dev/null and b/android/app/build/intermediates/flutter/debug/flutter_assets/assets/img/unlike.png differ diff --git a/lib/view/main_tab_view/main_tab_view.dart b/lib/view/main_tab_view/main_tab_view.dart index a26a2e5..2fb6dad 100644 --- a/lib/view/main_tab_view/main_tab_view.dart +++ b/lib/view/main_tab_view/main_tab_view.dart @@ -7,6 +7,7 @@ import 'package:music_player_miao/view/user/user_view.dart'; import '../../common/audio_player_controller.dart'; import '../home_view.dart'; import '../release_view.dart'; +import '../song_recommendation_view.dart'; // 迷你播放器组件 class MiniPlayer extends StatelessWidget { @@ -365,6 +366,7 @@ class _MainTabViewState extends State with SingleTickerProviderStat children: const [ HomeView(), RankView(), + SongRecommendationView(), ReleaseView(), UserView() ], diff --git a/lib/view/song_recommendation_view.dart b/lib/view/song_recommendation_view.dart new file mode 100644 index 0000000..b4422a5 --- /dev/null +++ b/lib/view/song_recommendation_view.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'dart:math' as math; +import '../view_model/song_view_model.dart'; + +class SongRecommendationView extends StatefulWidget { + const SongRecommendationView({super.key}); + + @override + State createState() => _SongRecommendationViewState(); +} + +class _SongRecommendationViewState extends State { + final SongViewModel songVM = Get.put(SongViewModel()); + bool isLoading = false; + + @override + void initState() { + super.initState(); + _loadRecommendedSongs(); + } + + Future _loadRecommendedSongs() async { + setState(() { + isLoading = true; + }); + await Future.delayed(const Duration(seconds: 1)); + setState(() { + isLoading = false; + }); + } + + double _getCircleSize(double relevance) { + return 100.0; + } + + Map _getCirclePosition(int index, int totalItems, Size screenSize) { + final radius = math.min(screenSize.width, screenSize.height) * 0.28; + final angle = (index * 2 * math.pi / totalItems) - math.pi / 2; + return { + 'left': radius * math.cos(angle), + 'top': radius * math.sin(angle), + }; + } + + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + final safeAreaPadding = MediaQuery.of(context).padding; + + return Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/img/app_bg.png"), + fit: BoxFit.cover, + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, + body: SafeArea( + child: Stack( + children: [ + Positioned( + top: 20, + left: 0, + right: 0, + child: Center( + child: Text( + '知音推荐', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black.withOpacity(0.8), + ), + ), + ), + ), + Center( + child: Stack( + alignment: Alignment.center, + children: [ + if (!isLoading) + Obx(() => Stack( + children: songVM.recommendedSongs.asMap().entries.map((entry) { + final index = entry.key; + final song = entry.value; + final relevance = (song['relevance'] as num?)?.toDouble() ?? 0.5; + final size = _getCircleSize(relevance); + final position = _getCirclePosition( + index, + songVM.recommendedSongs.length, + screenSize, + ); + + return Positioned( + left: screenSize.width / 2 + position['left']! - size / 2, + top: screenSize.height / 2 + position['top']! - size / 2 - safeAreaPadding.top, + child: Container( + width: size, + height: size, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(size / 2), + boxShadow: [ + BoxShadow( + color: Colors.blue.withOpacity(0.1), + blurRadius: 15, + spreadRadius: 5, + ), + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${(relevance * 100).toInt()}%', + style: TextStyle( + color: Colors.blue[300], + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + (song['title'] as String?) ?? '', + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.black87, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + const SizedBox(height: 2), + Text( + (song['artist'] as String?) ?? '', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), + ), + ); + }).toList(), + )), + + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.blue.withOpacity(0.3), + blurRadius: 15, + spreadRadius: 5, + ), + ], + ), + child: IconButton( + icon: Icon( + isLoading ? Icons.hourglass_empty : Icons.refresh, + color: Colors.white, + size: 30, + ), + onPressed: () { + if (!isLoading) { + _loadRecommendedSongs(); + } + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/view_model/song_view_model.dart b/lib/view_model/song_view_model.dart new file mode 100644 index 0000000..219338d --- /dev/null +++ b/lib/view_model/song_view_model.dart @@ -0,0 +1,55 @@ +import 'package:get/get.dart'; + +class SongViewModel extends GetxController { + final recommendedSongs = [ + { + "id": 1, + "title": "背对背拥抱", + "artist": "林俊杰", + "relevance": 0.9, + "coverPath": "https://i.scdn.co/image/ab67616d0000b273b9659e2caa82191d633d6363" + }, + { + "id": 2, + "title": "Alone", + "artist": "Jon Caryl", + "relevance": 0.8, + "coverPath": "https://cdns-images.dzcdn.net/images/cover/7c99f6bb157544db8775430007bb7979/264x264.jpg" + }, + { + "id": 3, + "title": "Poyga", + "artist": "Konsta & Shokir", + "relevance": 0.7, + "coverPath": "https://is3-ssl.mzstatic.com/image/thumb/Music112/v4/9f/a7/98/9fa798ea-25fc-f447-196a-c9f8bc894669/cover.jpg/600x600bf-60.jpg" + }, + { + "id": 4, + "title": "光年之外", + "artist": "邓紫棋", + "relevance": 0.85, + "coverPath": "https://i.scdn.co/image/ab67616d0000b273b9659e2caa82191d633d6363" + }, + { + "id": 5, + "title": "起风了", + "artist": "买辣椒也用券", + "relevance": 0.75, + "coverPath": "https://cdns-images.dzcdn.net/images/cover/7c99f6bb157544db8775430007bb7979/264x264.jpg" + }, + { + "id": 6, + "title": "晴天", + "artist": "周杰伦", + "relevance": 0.95, + "coverPath": "https://i.scdn.co/image/ab67616d0000b273b9659e2caa82191d633d6363" + }, + ].obs; + + // 模拟加载推荐歌曲的方法 + Future>> loadRecommendedSongs() async { + // 模拟网络延迟 + await Future.delayed(const Duration(seconds: 1)); + return recommendedSongs; + } +}