1.首页增加下拉刷新
2.修改下载状态监听方式,实现全局同步 3.修复搜索无结果时页面报错 4.歌单页面点击播放全部和随机是播放当前歌单列表
This commit is contained in:
parent
64047ed78a
commit
da21720c3c
@ -10,18 +10,16 @@ class MyMarqueeText extends StatelessWidget {
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.textStyle,
|
||||
this.enable = true,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final TextStyle textStyle;
|
||||
final bool enable;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Marquee(
|
||||
delay: Duration.zero,
|
||||
duration: Duration(seconds: enable ? 16 : 0),
|
||||
duration: const Duration(seconds: 40),
|
||||
pause: Duration.zero,
|
||||
gap: 80,
|
||||
child: Text(text, style: textStyle),
|
||||
|
||||
@ -5,38 +5,41 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class BaseEasyRefresh extends StatelessWidget {
|
||||
final EasyRefreshController? controller;
|
||||
final bool noMoreRefresh;
|
||||
final bool noMoreLoad;
|
||||
final bool refreshOnStart;
|
||||
final bool resetAfterRefresh;
|
||||
final Header? header;
|
||||
final Footer? footer;
|
||||
final FutureOr Function()? onRefresh;
|
||||
final FutureOr Function()? onLoad;
|
||||
final Widget child;
|
||||
final ScrollController? scrollController;
|
||||
final Widget Function(BuildContext context, ScrollPhysics physics) childBuilder;
|
||||
|
||||
const BaseEasyRefresh({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.noMoreRefresh = false,
|
||||
this.noMoreLoad = false,
|
||||
this.refreshOnStart = false,
|
||||
this.resetAfterRefresh = true,
|
||||
this.header,
|
||||
this.footer,
|
||||
required this.child,
|
||||
this.onRefresh,
|
||||
this.onLoad,
|
||||
this.scrollController,
|
||||
required this.childBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EasyRefresh(
|
||||
return EasyRefresh.builder(
|
||||
refreshOnStart: refreshOnStart,
|
||||
resetAfterRefresh: resetAfterRefresh,
|
||||
controller: controller,
|
||||
header: header,
|
||||
footer: footer,
|
||||
header: header ?? const ClassicHeader(),
|
||||
footer: footer ?? const ClassicFooter(),
|
||||
onRefresh: onRefresh,
|
||||
onLoad: onLoad,
|
||||
child: child,
|
||||
scrollController: scrollController,
|
||||
triggerAxis: Axis.vertical,
|
||||
childBuilder: childBuilder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,11 +18,15 @@ class ViewStateWidget extends StatelessWidget {
|
||||
required this.viewState,
|
||||
required this.child,
|
||||
this.cpiBgColor,
|
||||
this.showTryAgain = false,
|
||||
this.onTapTryAgain,
|
||||
});
|
||||
|
||||
final ViewState viewState;
|
||||
final Widget child;
|
||||
final Color? cpiBgColor;
|
||||
final bool showTryAgain;
|
||||
final Function()? onTapTryAgain;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -32,7 +36,10 @@ class ViewStateWidget extends StatelessWidget {
|
||||
case ViewState.loading:
|
||||
return loadingView(backgroundColor: cpiBgColor);
|
||||
case ViewState.empty:
|
||||
return AppConfig.appSideEnum == AppSideEnum.sideA ? emptyViewA() : emptyViewB();
|
||||
return AppConfig.appSideEnum == AppSideEnum.sideA ? emptyViewA() : emptyViewB(
|
||||
showTryAgain: showTryAgain,
|
||||
onTapTryAgain: onTapTryAgain,
|
||||
);
|
||||
case ViewState.error:
|
||||
return errorView();
|
||||
}
|
||||
@ -46,14 +53,14 @@ Widget loadingView({
|
||||
}) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
strokeWidth: 2.w,
|
||||
color: AppConfig.appSideEnum == AppSideEnum.sideA ? color : seedColor,
|
||||
backgroundColor: backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 空视图
|
||||
/// 空视图A
|
||||
Widget emptyViewA({String? msg, Color? textColor}) {
|
||||
return Center(
|
||||
child: Text(
|
||||
@ -67,13 +74,55 @@ Widget emptyViewA({String? msg, Color? textColor}) {
|
||||
);
|
||||
}
|
||||
|
||||
/// 空视图2
|
||||
Widget emptyViewB() {
|
||||
/// 空视图B
|
||||
Widget emptyViewB({
|
||||
required bool showTryAgain,
|
||||
Function()? onTapTryAgain,
|
||||
}) {
|
||||
return Center(
|
||||
child: Image.asset(
|
||||
Assets.sideBEmpty,
|
||||
width: 180.w,
|
||||
height: 160.h,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
Assets.sideBEmpty,
|
||||
width: 180.w,
|
||||
height: 120.h,
|
||||
),
|
||||
if (showTryAgain) ...[
|
||||
Text(
|
||||
'An error occurred\nSorry, please try again later',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: const Color(0x73FFFFFF),
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
GestureDetector(
|
||||
onTap: onTapTryAgain,
|
||||
child: Container(
|
||||
width: 122.w,
|
||||
height: 35.h,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 1.w,
|
||||
color: seedColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(40).r,
|
||||
),
|
||||
child: Text(
|
||||
'Try again',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: seedColor,
|
||||
fontSize: 16.sp,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
6
lib/data/cache/music_cache_manager.dart
vendored
6
lib/data/cache/music_cache_manager.dart
vendored
@ -3,6 +3,7 @@
|
||||
// Description: 自定义音乐缓存管理器
|
||||
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
class MusicCacheManager {
|
||||
static const key = 'musicCacheKey';
|
||||
@ -19,7 +20,8 @@ class MusicCacheManager {
|
||||
}
|
||||
|
||||
/// 检查是否有缓存
|
||||
static Future<FileInfo?> checkCache(String videoId) async {
|
||||
return await instance.getFileFromCache(getCacheKey(videoId));
|
||||
static Future<FileInfo?> checkCache(String? videoId) async {
|
||||
if (ObjUtil.isEmpty(videoId)) return null;
|
||||
return await instance.getFileFromCache(getCacheKey(videoId!));
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,8 @@ class PlaylistModel extends HiveObject {
|
||||
String? coverUrl;
|
||||
@HiveField(6)
|
||||
String? subtitle;
|
||||
@HiveField(7)
|
||||
String? musicType;
|
||||
|
||||
PlaylistModel({
|
||||
required this.id,
|
||||
@ -38,6 +40,7 @@ class PlaylistModel extends HiveObject {
|
||||
this.params,
|
||||
this.coverUrl,
|
||||
this.subtitle,
|
||||
this.musicType,
|
||||
});
|
||||
|
||||
PlaylistModel copyWith({
|
||||
@ -48,6 +51,7 @@ class PlaylistModel extends HiveObject {
|
||||
String? params,
|
||||
String? coverUrl,
|
||||
String? subtitle,
|
||||
String? musicType,
|
||||
}) =>
|
||||
PlaylistModel(
|
||||
id: id,
|
||||
@ -57,6 +61,7 @@ class PlaylistModel extends HiveObject {
|
||||
params: params ?? this.params,
|
||||
coverUrl: coverUrl ?? this.coverUrl,
|
||||
subtitle: subtitle ?? this.subtitle,
|
||||
musicType: musicType ?? this.musicType,
|
||||
);
|
||||
|
||||
factory PlaylistModel.fromJson(String str) => PlaylistModel.fromMap(json.decode(str));
|
||||
@ -71,6 +76,7 @@ class PlaylistModel extends HiveObject {
|
||||
params: json["params"],
|
||||
coverUrl: json["coverUrl"],
|
||||
subtitle: json["subtitle"],
|
||||
musicType: json["musicType"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
@ -81,5 +87,6 @@ class PlaylistModel extends HiveObject {
|
||||
"params": params,
|
||||
"coverUrl": coverUrl,
|
||||
"subtitle": subtitle,
|
||||
"musicType": musicType,
|
||||
};
|
||||
}
|
||||
|
||||
@ -61,8 +61,6 @@ class DioClient {
|
||||
/// 请求
|
||||
Future request<T>(
|
||||
String path, {
|
||||
bool showLoading = false,
|
||||
bool showToast = false,
|
||||
required RequestMethod requestMethod,
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
@ -72,6 +70,8 @@ class DioClient {
|
||||
T Function(Map<String, dynamic>)? formJson,
|
||||
required Function(T? result) success,
|
||||
Function(BaseError baseError)? fail,
|
||||
bool showLoading = false,
|
||||
bool showToast = false,
|
||||
}) async {
|
||||
try {
|
||||
BaseEasyLoading.loading(show: showLoading);
|
||||
|
||||
@ -31,13 +31,21 @@ class CollectPlaylistsBox {
|
||||
}
|
||||
|
||||
/// 添加数据
|
||||
Future<int> add({required String id, required String title, String? params, String? coverUrl, String? subtitle}) async {
|
||||
Future<int> add({
|
||||
required String id,
|
||||
required String title,
|
||||
String? params,
|
||||
String? coverUrl,
|
||||
String? subtitle,
|
||||
String? musicType,
|
||||
}) async {
|
||||
return await _box.add(PlaylistModel(
|
||||
id: id,
|
||||
title: title,
|
||||
params: params,
|
||||
coverUrl: coverUrl,
|
||||
subtitle: subtitle,
|
||||
musicType: musicType,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -92,16 +92,14 @@ class PlaylistsBox {
|
||||
}
|
||||
|
||||
/// 删除歌曲
|
||||
Future<bool> removeMusic(String id, String videoId) async {
|
||||
Future<void> removeMusic(String id, String videoId) async {
|
||||
final playlistModel = _box.get(id);
|
||||
if (playlistModel != null && playlistModel.musicList != null) {
|
||||
if (playlistModel.musicList!.firstWhereOrNull((e) => e.videoId == videoId) != null) {
|
||||
playlistModel.musicList!.removeWhere((e) => e.videoId == videoId);
|
||||
_box.put(id, playlistModel);
|
||||
return true;
|
||||
await _box.put(id, playlistModel);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 获取当前列表的封面
|
||||
|
||||
@ -17,16 +17,17 @@ import 'package:tone_snap/utils/local_path_util.dart';
|
||||
import 'package:tone_snap/utils/log_util.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
class DownloadManager {
|
||||
static final DownloadManager _instance = DownloadManager._getInstance();
|
||||
class DownloadManager extends GetxController {
|
||||
static DownloadManager get to => Get.put(DownloadManager(), permanent: true);
|
||||
final downloadStateId = 'download_state_id';
|
||||
|
||||
factory DownloadManager() => _instance;
|
||||
MemoryTaskQueue? tq;
|
||||
|
||||
static MemoryTaskQueue? tq;
|
||||
List<MusicModel> downloadList = [];
|
||||
|
||||
List<Rx<MusicModel>> downloadList = [];
|
||||
|
||||
DownloadManager._getInstance() {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (tq == null) {
|
||||
tq ??= MemoryTaskQueue();
|
||||
tq!.maxConcurrent = 3; // no more than 5 tasks active at any one time
|
||||
@ -34,13 +35,13 @@ class DownloadManager {
|
||||
tq!.maxConcurrentByGroup = 3; // no more than three tasks from the same group active at the same time
|
||||
FileDownloader().addTaskQueue(tq!); // 'connects' the TaskQueue to the FileDownloader
|
||||
FileDownloader().updates.listen((update) async { // listen to updates as per usual
|
||||
Rx<MusicModel>? musicModel = downloadList.firstWhereOrNull((e) => e.value.videoId == update.task.taskId);
|
||||
MusicModel? musicModel = downloadList.firstWhereOrNull((e) => e.videoId == update.task.taskId);
|
||||
if (musicModel == null) return;
|
||||
|
||||
if (update.runtimeType == TaskStatusUpdate) {
|
||||
TaskStatus taskStatus = (update as TaskStatusUpdate).status;
|
||||
LogUtil.d('${update.task.filename},任务状态: $taskStatus');
|
||||
musicModel.update((fn) => fn?.taskStatus = taskStatus);
|
||||
LogUtil.d('${update.task.filename},任务状态: $taskStatus');
|
||||
musicModel.taskStatus = taskStatus;
|
||||
switch (taskStatus) {
|
||||
case TaskStatus.enqueued:
|
||||
break;
|
||||
@ -48,8 +49,8 @@ class DownloadManager {
|
||||
break;
|
||||
case TaskStatus.complete:
|
||||
LogUtil.d('音乐下载路径:${await update.task.filePath()}');
|
||||
musicModel.value.localPath = await update.task.filePath();
|
||||
OfflineBox().add(musicModel.value.copyWith());
|
||||
musicModel.localPath = await update.task.filePath();
|
||||
OfflineBox().add(musicModel);
|
||||
downloadList.remove(musicModel);
|
||||
BaseEasyLoading.toast('Download completed');
|
||||
if (Get.isRegistered<PersonalMusicLibraryController>()) {
|
||||
@ -78,77 +79,74 @@ class DownloadManager {
|
||||
}
|
||||
}
|
||||
if (update.runtimeType == TaskProgressUpdate) {
|
||||
LogUtil.d('${update.task.filename},下载进度: $update');
|
||||
musicModel.update((fn) => fn?.progress = (update as TaskProgressUpdate).progress);
|
||||
LogUtil.d('${update.task.filename},下载进度: $update');
|
||||
musicModel.progress = (update as TaskProgressUpdate).progress;
|
||||
}
|
||||
updateDownloadState();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 下载文件
|
||||
void downloadMusic(Rx<MusicModel>? musicModel) {
|
||||
if (musicModel == null) return;
|
||||
musicModel.update((fn) {
|
||||
fn?.taskStatus = TaskStatus.enqueued;
|
||||
fn?.cancelToken = CancelToken();
|
||||
});
|
||||
void downloadMusic(MusicModel? m) {
|
||||
if (m == null) return;
|
||||
MusicModel musicModel = m.copyWith();
|
||||
musicModel.taskStatus = TaskStatus.enqueued;
|
||||
musicModel.cancelToken = CancelToken();
|
||||
downloadList.add(musicModel);
|
||||
updateDownloadState();
|
||||
|
||||
_getMusicUrl(musicModel, (url, mimeType) async {
|
||||
if (ObjUtil.isEmpty(url)) {
|
||||
BaseEasyLoading.toast('Resource acquisition failed');
|
||||
musicModel.update((fn) => fn?.taskStatus = TaskStatus.failed);
|
||||
return;
|
||||
if (ObjUtil.isNotEmpty(url)) {
|
||||
String extension = mimeType ?? 'mp4';
|
||||
if (ObjUtil.isNotEmpty(mimeType)) {
|
||||
// 从 mimeType 中提取主类型和子类型
|
||||
String type = mimeType!.split(';')[0].trim();
|
||||
// 获取文件扩展名
|
||||
extension = type.split('/')[1];
|
||||
}
|
||||
final filename = '${musicModel.title}_${DateUtil.getNowTimestamp()}.$extension';
|
||||
final task = DownloadTask(
|
||||
taskId: musicModel.videoId,
|
||||
url: url!,
|
||||
filename: filename,
|
||||
directory: LocalPathUtil.getMusicDownloadDir(),
|
||||
updates: Updates.statusAndProgress,
|
||||
requiresWiFi: false,
|
||||
retries: 0,
|
||||
allowPause: false,
|
||||
metaData: '',
|
||||
);
|
||||
tq?.add(task);
|
||||
}
|
||||
String extension = mimeType ?? 'mp4';
|
||||
if (ObjUtil.isNotEmpty(mimeType)) {
|
||||
// 从 mimeType 中提取主类型和子类型
|
||||
String type = mimeType!.split(';')[0].trim();
|
||||
// 获取文件扩展名
|
||||
extension = type.split('/')[1];
|
||||
}
|
||||
final filename = '${musicModel.value.title}_${DateUtil.getNowTimestamp()}.$extension';
|
||||
final task = DownloadTask(
|
||||
taskId: musicModel.value.videoId,
|
||||
url: url!,
|
||||
filename: filename,
|
||||
directory: LocalPathUtil.getMusicDownloadDir(),
|
||||
updates: Updates.statusAndProgress,
|
||||
requiresWiFi: false,
|
||||
retries: 0,
|
||||
allowPause: false,
|
||||
metaData: '',
|
||||
);
|
||||
tq?.add(task);
|
||||
downloadList.add(musicModel);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _getMusicUrl(Rx<MusicModel> musicModel, Function(String? url, String? mimeType) onTap) async {
|
||||
Future<void> _getMusicUrl(MusicModel musicModel, Function(String? url, String? mimeType) onTap) async {
|
||||
PlayerModel? playerModel = await MusicApi.player(
|
||||
videoId: musicModel.value.videoId,
|
||||
cancelToken: musicModel.value.cancelToken,
|
||||
videoId: musicModel.videoId,
|
||||
cancelToken: musicModel.cancelToken,
|
||||
fail: (baseError) {
|
||||
if (baseError.code == DioExceptionType.cancel.index) {
|
||||
musicModel.update((fn) {
|
||||
fn?.taskStatus = TaskStatus.canceled;
|
||||
fn?.cancelToken = null;
|
||||
});
|
||||
musicModel.taskStatus = TaskStatus.canceled;
|
||||
musicModel.cancelToken = null;
|
||||
} else {
|
||||
musicModel.update((fn) {
|
||||
fn?.taskStatus = TaskStatus.failed;
|
||||
fn?.cancelToken = null;
|
||||
});
|
||||
musicModel.taskStatus = TaskStatus.failed;
|
||||
musicModel.cancelToken = null;
|
||||
}
|
||||
downloadList.remove(musicModel);
|
||||
updateDownloadState();
|
||||
}
|
||||
);
|
||||
if (playerModel != null) {
|
||||
if (ObjUtil.isEmpty(musicModel.value.coverUrl)) {
|
||||
if (ObjUtil.isEmpty(musicModel.coverUrl)) {
|
||||
var thumbnails = playerModel.videoDetails?.thumbnail?.thumbnails;
|
||||
if (thumbnails != null && thumbnails.isNotEmpty) {
|
||||
musicModel.value.coverUrl = thumbnails.last.url;
|
||||
musicModel.coverUrl = thumbnails.last.url;
|
||||
}
|
||||
}
|
||||
if (ObjUtil.isEmpty(musicModel.value.musicType)) {
|
||||
musicModel.value.musicType = playerModel.videoDetails?.musicVideoType;
|
||||
if (ObjUtil.isEmpty(musicModel.musicType)) {
|
||||
musicModel.musicType = playerModel.videoDetails?.musicVideoType;
|
||||
}
|
||||
var formats = playerModel.streamingData?.formats;
|
||||
if (formats != null && formats.isNotEmpty) {
|
||||
@ -157,9 +155,24 @@ class DownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
void cancelDownload(Rx<MusicModel>? musicModel) {
|
||||
if (musicModel == null || musicModel.value.videoId == null) return;
|
||||
musicModel.value.cancelToken?.cancel();
|
||||
FileDownloader().cancelTaskWithId(musicModel.value.videoId!);
|
||||
void cancelDownload(String? videoId) {
|
||||
if (ObjUtil.isEmpty(videoId)) return;
|
||||
final m = getMusicModel(videoId);
|
||||
if (m != null) {
|
||||
m.cancelToken?.cancel();
|
||||
if (m.videoId != null) {
|
||||
FileDownloader().cancelTaskWithId(m.videoId!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateDownloadState() {
|
||||
DownloadManager.to.update([downloadStateId]);
|
||||
}
|
||||
|
||||
MusicModel? getMusicModel(String? videoId) {
|
||||
if (ObjUtil.isEmpty(videoId)) return null;
|
||||
final m = downloadList.firstWhereOrNull((e) => e.videoId == videoId);
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ class MyApp extends StatelessWidget {
|
||||
MusicBar().hide();
|
||||
} else {
|
||||
if (Get.isRegistered<MusicPlayerController>()) {
|
||||
if (MusicPlayerController.to.getMusicModel()?.value.videoId != null) {
|
||||
if (MusicPlayerController.to.getMusicModel()?.videoId != null) {
|
||||
if (Get.isBottomSheetOpen != null && Get.isBottomSheetOpen!) {
|
||||
MusicBar().hide();
|
||||
} else {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tone_snap/components/base_easyloading.dart';
|
||||
import 'package:tone_snap/components/view_state_widget.dart';
|
||||
import 'package:tone_snap/data/api/music_api.dart';
|
||||
import 'package:tone_snap/data/models/browse_model.dart';
|
||||
import 'package:tone_snap/data/models/music_model.dart';
|
||||
import 'package:tone_snap/data/models/next_model.dart';
|
||||
import 'package:tone_snap/data/storage/collect_playlists_box.dart';
|
||||
import 'package:tone_snap/modules/sideb/collect_playlists/collect_playlists_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
||||
@ -16,6 +17,7 @@ class AlbumSongListController extends GetxController {
|
||||
var musicPlayerController = MusicPlayerController.to;
|
||||
var browseId = '';
|
||||
var params = '';
|
||||
var musicType = '';
|
||||
|
||||
/// 封面 url、标题 、描述
|
||||
var coverUrl = ''.obs;
|
||||
@ -25,7 +27,7 @@ class AlbumSongListController extends GetxController {
|
||||
|
||||
var viewState = ViewState.loading.obs;
|
||||
/// 专辑/歌单预览列表
|
||||
var musicList = <Rx<MusicModel>>[].obs;
|
||||
var musicList = <MusicModel>[].obs;
|
||||
|
||||
/// 是否收藏
|
||||
var isCollect = false.obs;
|
||||
@ -34,6 +36,7 @@ class AlbumSongListController extends GetxController {
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
Map<String, dynamic> arguments = Get.arguments;
|
||||
musicType = arguments['musicType'] ?? '';
|
||||
browseId = arguments['browseId'] ?? '';
|
||||
params = arguments['params'] ?? '';
|
||||
coverUrl.value = arguments['coverUrl'] ?? '';
|
||||
@ -129,7 +132,7 @@ class AlbumSongListController extends GetxController {
|
||||
if (playlistItemData != null) {
|
||||
musicModel.videoId = playlistItemData.videoId;
|
||||
}
|
||||
musicList.add(musicModel.obs);
|
||||
musicList.add(musicModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,66 +140,24 @@ class AlbumSongListController extends GetxController {
|
||||
}
|
||||
|
||||
/// 点击播放全部歌曲
|
||||
Future<void> onTapPlayAll(int type) async {
|
||||
Future<void> onTapPlayAll(bool isShuffle) async {
|
||||
if (musicList.isNotEmpty) {
|
||||
int index = 0;
|
||||
if (type == 1) {
|
||||
index = NumUtil.getRandomNumber(0, musicList.length);
|
||||
}
|
||||
List<MusicModel> playList = await _next(musicList[index].value.videoId, playlistId: musicList[index].value.playlistId);
|
||||
musicPlayerController.playMusic(playList[index].videoId, playList: playList);
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取播放列表
|
||||
Future<List<MusicModel>> _next(String? videoId, {String? playlistId}) async {
|
||||
List<MusicModel> playList = [];
|
||||
NextModel? model = await MusicApi.next(playlistId: playlistId, videoId: videoId, showLoading: true);
|
||||
if (model != null) {
|
||||
var tabs = model.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs;
|
||||
if (tabs != null && tabs.isNotEmpty) {
|
||||
for (var i = 0; i < tabs.length; ++i) {
|
||||
var o = tabs[i];
|
||||
if (i == 0) {
|
||||
var contents = o.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents;
|
||||
if (contents != null && contents.isNotEmpty) {
|
||||
for (var j = 0; j < contents.length; ++j) {
|
||||
var musicModel = MusicModel();
|
||||
var content = contents[j];
|
||||
if (content.playlistPanelVideoRenderer != null) {
|
||||
// 封面
|
||||
var thumbnails = content.playlistPanelVideoRenderer?.thumbnail?.thumbnails;
|
||||
if (thumbnails != null) {
|
||||
musicModel.coverUrl = thumbnails.last.url;
|
||||
}
|
||||
|
||||
// 标题
|
||||
var runs = content.playlistPanelVideoRenderer?.title?.runs;
|
||||
if (runs != null && runs.isNotEmpty) {
|
||||
musicModel.title = runs[0].text;
|
||||
}
|
||||
|
||||
// 副标题
|
||||
var subRuns = content.playlistPanelVideoRenderer?.longBylineText?.runs;
|
||||
if (subRuns != null && subRuns.isNotEmpty) {
|
||||
musicModel.subtitle = subRuns.map((e) => e.text).join();
|
||||
}
|
||||
|
||||
// videoId, playlistId
|
||||
var watchEndpoint = content.playlistPanelVideoRenderer?.navigationEndpoint?.watchEndpoint;
|
||||
if (watchEndpoint != null) {
|
||||
musicModel.videoId = watchEndpoint.videoId;
|
||||
musicModel.playlistId = watchEndpoint.playlistId;
|
||||
}
|
||||
playList.add(musicModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isShuffle && musicList.length > 1) {
|
||||
final n = musicList.indexWhere((e) => e.videoId == musicPlayerController.getMusicModel()?.videoId);
|
||||
if (n != -1) {
|
||||
index = NumUtil.getRandomNumberExcludingCurrent(0, musicList.length, n);
|
||||
} else {
|
||||
index = NumUtil.getRandomNumber(0, musicList.length);
|
||||
}
|
||||
}
|
||||
for (var o in musicList) {
|
||||
if (ObjUtil.isEmpty(o.coverUrl)) {
|
||||
o.coverUrl = coverUrl.value;
|
||||
}
|
||||
}
|
||||
musicPlayerController.playMusic(musicList[index].videoId, playList: musicList);
|
||||
}
|
||||
return playList;
|
||||
}
|
||||
|
||||
/// 点击收藏
|
||||
@ -212,6 +173,7 @@ class AlbumSongListController extends GetxController {
|
||||
params: params,
|
||||
coverUrl: coverUrl.value,
|
||||
subtitle: subtitle,
|
||||
musicType: musicType,
|
||||
);
|
||||
BaseEasyLoading.toast('Collected');
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:get/get.dart';
|
||||
import 'package:tone_snap/components/base_scrollbar.dart';
|
||||
import 'package:tone_snap/components/network_image_widget.dart';
|
||||
import 'package:tone_snap/components/view_state_widget.dart';
|
||||
import 'package:tone_snap/data/enum/music_type.dart';
|
||||
import 'package:tone_snap/generated/assets.dart';
|
||||
import 'package:tone_snap/modules/sideb/album_song_list/album_song_list_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
|
||||
@ -109,9 +110,9 @@ class AlbumSongListView extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', 0),
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', false),
|
||||
SizedBox(width: 10.w),
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAllRandom, 'Shuffle', 1),
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAllRandom, 'Shuffle', true),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -142,13 +143,13 @@ class AlbumSongListView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlayAll(String img, String label, int type) {
|
||||
Widget _buildPlayAll(String img, String label, bool isShuffle) {
|
||||
return IntrinsicWidth(
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.onTapPlayAll(type),
|
||||
onTap: () => controller.onTapPlayAll(isShuffle),
|
||||
child: Container(
|
||||
height: 32.h,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4).w,
|
||||
padding: const EdgeInsets.only(left: 4, right: 6).w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16).r,
|
||||
color: const Color(0x1A80F988),
|
||||
@ -163,7 +164,7 @@ class AlbumSongListView extends StatelessWidget {
|
||||
SizedBox(width: 4.w),
|
||||
Flexible(
|
||||
child: Visibility(
|
||||
visible: type == 0,
|
||||
visible: !isShuffle,
|
||||
replacement: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
@ -205,7 +206,9 @@ class AlbumSongListView extends StatelessWidget {
|
||||
itemBuilder: (context, index) {
|
||||
return MusicItem(
|
||||
musicModel: controller.musicList[index],
|
||||
onTapItem: () => controller.onTapItem(controller.musicList[index].value),
|
||||
showNumber: controller.musicType == MusicType.musicPageTypeAlbum.name,
|
||||
number: index + 1,
|
||||
onTapItem: () => controller.onTapItem(controller.musicList[index]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:tone_snap/components/base_easyloading.dart';
|
||||
@ -63,7 +62,7 @@ class MusicPlayerController extends GetxController {
|
||||
final List<String> _playHistory = [];
|
||||
|
||||
/// 播放列表
|
||||
var playlist = <Rx<MusicModel>>[].obs;
|
||||
var playlist = <MusicModel>[].obs;
|
||||
|
||||
/// 当前播放的歌曲索引
|
||||
var currentIndex = 0.obs;
|
||||
@ -83,18 +82,18 @@ class MusicPlayerController extends GetxController {
|
||||
}
|
||||
|
||||
void playMusic(String? videoId, {List<MusicModel>? playList, bool isCurrentPlaylist = false}) {
|
||||
if (videoId == null) return;
|
||||
if (ObjUtil.isEmpty(videoId)) return;
|
||||
if (!isCurrentPlaylist) {
|
||||
if (playList == null || playList.isEmpty) return;
|
||||
_playHistory.clear();
|
||||
playlist.value = playList.map((e) => e.obs).toList();
|
||||
playlist.value = playList.map((e) => e).toList();
|
||||
}
|
||||
Rx<MusicModel>? model = playlist.firstWhereOrNull((e) => e.value.videoId == videoId);
|
||||
MusicModel? model = playlist.firstWhereOrNull((e) => e.videoId == videoId);
|
||||
currentIndex.value = model != null ? playlist.indexOf(model) : 0;
|
||||
_startPlay();
|
||||
}
|
||||
|
||||
Rx<MusicModel>? getMusicModel() {
|
||||
MusicModel? getMusicModel() {
|
||||
return playlist.isNotEmpty ? playlist[currentIndex.value] : null;
|
||||
}
|
||||
|
||||
@ -104,7 +103,7 @@ class MusicPlayerController extends GetxController {
|
||||
resetPlaybackStatus();
|
||||
Get.currentRoute == AppRoutes.playPage ? MusicBar().hide() : MusicBar().show();
|
||||
try {
|
||||
MusicModel? model = OfflineBox().getList().firstWhereOrNull((e) => e.videoId == getMusicModel()!.value.videoId!);
|
||||
var model = OfflineBox().getList().firstWhereOrNull((e) => e.videoId == getMusicModel()?.videoId);
|
||||
if (model != null && ObjUtil.isNotEmpty(model.localPath)) {
|
||||
// 有下载
|
||||
LogUtil.d('读取下载路径=${model.localPath}');
|
||||
@ -115,7 +114,7 @@ class MusicPlayerController extends GetxController {
|
||||
await _player.setFilePath(model.localPath!);
|
||||
} else {
|
||||
// 无下载
|
||||
FileInfo? fileInfo = await MusicCacheManager.checkCache(getMusicModel()!.value.videoId!);
|
||||
var fileInfo = await MusicCacheManager.checkCache(getMusicModel()?.videoId);
|
||||
if (fileInfo != null) {
|
||||
// 有缓存
|
||||
LogUtil.d('读取缓存路径=${fileInfo.file.path}');
|
||||
@ -129,7 +128,7 @@ class MusicPlayerController extends GetxController {
|
||||
}
|
||||
await _player.setUrl(url!);
|
||||
// 同时启动缓存下载
|
||||
_cacheManager.downloadFile(url, key: MusicCacheManager.getCacheKey(getMusicModel()!.value.videoId!)).then((fileInfo) {
|
||||
_cacheManager.downloadFile(url, key: MusicCacheManager.getCacheKey(getMusicModel()!.videoId!)).then((fileInfo) {
|
||||
LogUtil.d('缓存下载路径=${fileInfo.file.path}');
|
||||
});
|
||||
}
|
||||
@ -146,7 +145,7 @@ class MusicPlayerController extends GetxController {
|
||||
|
||||
/// 获取当前歌曲的播放 url
|
||||
Future<String?> _getMusicUrl() async {
|
||||
PlayerModel? model = await MusicApi.player(videoId: playlist[currentIndex.value].value.videoId);
|
||||
PlayerModel? model = await MusicApi.player(videoId: playlist[currentIndex.value].videoId);
|
||||
if (model != null && model.streamingData != null) {
|
||||
var formats = model.streamingData?.formats;
|
||||
if (formats != null && playlist.isNotEmpty) {
|
||||
@ -260,7 +259,7 @@ class MusicPlayerController extends GetxController {
|
||||
bool historyExist = false;
|
||||
for (var i = _playHistory.length - 1; i >= 0; --i) {
|
||||
var history = _playHistory[i];
|
||||
Rx<MusicModel>? model = playlist.firstWhereOrNull((e) => e.value.videoId == history);
|
||||
MusicModel? model = playlist.firstWhereOrNull((e) => e.videoId == history);
|
||||
if (model != null) {
|
||||
currentIndex.value = playlist.indexOf(model);
|
||||
_playHistory.remove(history);
|
||||
@ -293,7 +292,7 @@ class MusicPlayerController extends GetxController {
|
||||
break;
|
||||
case PlayMode.random:
|
||||
// 记录当前播放的索引
|
||||
_playHistory.add(getMusicModel()!.value.videoId!);
|
||||
_playHistory.add(getMusicModel()!.videoId!);
|
||||
currentIndex.value = _getRandomNumber();
|
||||
_startPlay();
|
||||
break;
|
||||
|
||||
@ -14,11 +14,12 @@ class CustomPlaylistController extends GetxController {
|
||||
static CustomPlaylistController get to => Get.find<CustomPlaylistController>();
|
||||
var musicPlayerController = MusicPlayerController.to;
|
||||
late String playlistModelId;
|
||||
var playlistModel = Rx<PlaylistModel?>(null);
|
||||
var viewState = ViewState.normal.obs;
|
||||
var musicList = <Rx<MusicModel>>[].obs;
|
||||
late PlaylistModel playlistModel;
|
||||
var title = ''.obs;
|
||||
var showSearch = false.obs;
|
||||
var textEditingController = TextEditingController();
|
||||
var viewState = ViewState.normal.obs;
|
||||
var musicList = <MusicModel>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -34,21 +35,21 @@ class CustomPlaylistController extends GetxController {
|
||||
}
|
||||
|
||||
void getPlaylistMode() {
|
||||
playlistModel.value = PlaylistsBox().getPlaylistModel(playlistModelId);
|
||||
playlistModel.update((fn) {});
|
||||
_getList();
|
||||
if (PlaylistsBox().getPlaylistModel(playlistModelId) != null) {
|
||||
playlistModel = PlaylistsBox().getPlaylistModel(playlistModelId)!;
|
||||
title.value = playlistModel.title;
|
||||
_getList();
|
||||
}
|
||||
}
|
||||
|
||||
void _getList() {
|
||||
if (playlistModel.value != null && playlistModel.value!.musicList != null) {
|
||||
musicList.value = playlistModel.value!.musicList!.map((e) => e.obs).toList();
|
||||
}
|
||||
musicList.value = playlistModel.musicList?.toList() ?? <MusicModel>[];
|
||||
viewState.value = musicList.isNotEmpty ? ViewState.normal : ViewState.empty;
|
||||
}
|
||||
|
||||
String? getFirstCoverUrl() {
|
||||
if (musicList.isNotEmpty) {
|
||||
return musicList.first.value.coverUrl;
|
||||
return musicList.first.coverUrl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -56,18 +57,23 @@ class CustomPlaylistController extends GetxController {
|
||||
void onTapMore() {
|
||||
Get.bottomSheet(
|
||||
MorePlaylistBottomSheetView(
|
||||
playlistModel: playlistModel.value!,
|
||||
playlistModel: playlistModel,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onTapPlayAll(int type) async {
|
||||
Future<void> onTapPlayAll(bool isShuffle) async {
|
||||
if (musicList.isNotEmpty) {
|
||||
int index = 0;
|
||||
if (type == 1) {
|
||||
index = NumUtil.getRandomNumber(0, musicList.length);
|
||||
if (isShuffle && musicList.length > 1) {
|
||||
final n = musicList.indexWhere((e) => e.videoId == musicPlayerController.getMusicModel()?.videoId);
|
||||
if (n != -1) {
|
||||
index = NumUtil.getRandomNumberExcludingCurrent(0, musicList.length, n);
|
||||
} else {
|
||||
index = NumUtil.getRandomNumber(0, musicList.length);
|
||||
}
|
||||
}
|
||||
musicPlayerController.playMusic(musicList[index].value.videoId, playList: musicList.map((e) => e.value).toList());
|
||||
musicPlayerController.playMusic(musicList[index].videoId, playList: musicList);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,10 +90,10 @@ class CustomPlaylistController extends GetxController {
|
||||
|
||||
void onTapSearch(String keywords) {
|
||||
if (ObjUtil.isNotEmpty(keywords)) {
|
||||
final result = <Rx<MusicModel>>[].obs;
|
||||
playlistModel.value!.musicList!.map((e) {
|
||||
final result = <MusicModel>[];
|
||||
playlistModel.musicList!.map((e) {
|
||||
if (e.title!.toLowerCase().contains(keywords.trim().toLowerCase())) {
|
||||
result.add(e.obs);
|
||||
result.add(e);
|
||||
}
|
||||
}).toList();
|
||||
musicList.value = result;
|
||||
@ -99,7 +105,7 @@ class CustomPlaylistController extends GetxController {
|
||||
void onTapItem(MusicModel musicModel) {
|
||||
Get.toNamed(AppRoutes.playPage, arguments: {
|
||||
'videoId': musicModel.videoId,
|
||||
'playList': musicList.map((e) => e.value).toList(),
|
||||
'playList': musicList.toList(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ class CustomPlaylistView extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(top: 20.0).h,
|
||||
child: Obx(() {
|
||||
return MyMarqueeText(
|
||||
text: ObjUtil.getStr(controller.playlistModel.value!.title),
|
||||
text: ObjUtil.getStr(controller.title.value),
|
||||
textStyle: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18.sp,
|
||||
@ -115,7 +115,7 @@ class CustomPlaylistView extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 2).h,
|
||||
child: Text(
|
||||
'Created in: ${controller.playlistModel.value!.milliseconds != null ? DateUtil.formatDateMs(controller.playlistModel.value!.milliseconds!) : ''}',
|
||||
'Created in: ${controller.playlistModel.milliseconds != null ? DateUtil.formatDateMs(controller.playlistModel.milliseconds!) : ''}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
@ -138,9 +138,9 @@ class CustomPlaylistView extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', 0),
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', false),
|
||||
SizedBox(width: 10.w),
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAllRandom, 'Shuffle', 1),
|
||||
_buildPlayAll(Assets.sideBPlaylistPlayAllRandom, 'Shuffle', true),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -215,13 +215,13 @@ class CustomPlaylistView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlayAll(String img, String label, int type) {
|
||||
Widget _buildPlayAll(String img, String label, bool isShuffle) {
|
||||
return IntrinsicWidth(
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.onTapPlayAll(type),
|
||||
onTap: () => controller.onTapPlayAll(isShuffle),
|
||||
child: Container(
|
||||
height: 32.h,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4).w,
|
||||
padding: const EdgeInsets.only(left: 4, right: 6).w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16).r,
|
||||
color: const Color(0x1A80F988),
|
||||
@ -236,7 +236,7 @@ class CustomPlaylistView extends StatelessWidget {
|
||||
SizedBox(width: 4.w),
|
||||
Flexible(
|
||||
child: Visibility(
|
||||
visible: type == 0,
|
||||
visible: !isShuffle,
|
||||
replacement: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
@ -273,17 +273,15 @@ class CustomPlaylistView extends StatelessWidget {
|
||||
viewState: controller.viewState.value,
|
||||
child: BaseScrollbar(
|
||||
child: Obx(() {
|
||||
return Visibility(
|
||||
child: ListView.builder(
|
||||
itemCount: controller.musicList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return MusicItem(
|
||||
musicModel: controller.musicList[index],
|
||||
playlistModelId: controller.playlistModel.value!.id,
|
||||
onTapItem: () => controller.onTapItem(controller.musicList[index].value),
|
||||
);
|
||||
},
|
||||
),
|
||||
return ListView.builder(
|
||||
itemCount: controller.musicList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return MusicItem(
|
||||
musicModel: controller.musicList[index],
|
||||
playlistModelId: controller.playlistModelId,
|
||||
onTapItem: () => controller.onTapItem(controller.musicList[index]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
@ -291,41 +289,4 @@ class CustomPlaylistView extends StatelessWidget {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget _buildEmpty() {
|
||||
// return Column(
|
||||
// children: [
|
||||
// SizedBox(height: 40.h),
|
||||
// Text(
|
||||
// 'Nothing Yet',
|
||||
// style: TextStyle(
|
||||
// color: const Color(0x99FFFFFF),
|
||||
// fontSize: 14.sp,
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 16.h),
|
||||
// GestureDetector(
|
||||
// onTap: controller.onTapAddSongs,
|
||||
// child: Container(
|
||||
// width: 122.w,
|
||||
// height: 35.h,
|
||||
// alignment: Alignment.center,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(40).r,
|
||||
// border: Border.all(
|
||||
// color: seedColor,
|
||||
// ),
|
||||
// ),
|
||||
// child: Text(
|
||||
// 'Add Songs',
|
||||
// style: TextStyle(
|
||||
// color: seedColor,
|
||||
// fontSize: 16.sp,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tone_snap/components/view_state_widget.dart';
|
||||
import 'package:tone_snap/data/api/music_api.dart';
|
||||
@ -12,6 +14,10 @@ class HomeController extends GetxController {
|
||||
var viewState = ViewState.loading.obs;
|
||||
var groupList = <BrowseGroupModel>[].obs;
|
||||
String? visitorData;
|
||||
var refreshController = EasyRefreshController(
|
||||
controlFinishRefresh: true,
|
||||
);
|
||||
var isRefresh = false;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
@ -19,6 +25,23 @@ class HomeController extends GetxController {
|
||||
firstBrowse();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
refreshController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void onRefresh() {
|
||||
isRefresh = true;
|
||||
refreshController.callRefresh();
|
||||
firstBrowse();
|
||||
}
|
||||
|
||||
void onTapTryAgain() {
|
||||
viewState.value = ViewState.loading;
|
||||
firstBrowse();
|
||||
}
|
||||
|
||||
/// 首次请求
|
||||
Future<void> firstBrowse() async {
|
||||
Map<String, dynamic> queryParameters = {
|
||||
@ -27,8 +50,9 @@ class HomeController extends GetxController {
|
||||
};
|
||||
BrowseModel? browseModel = await MusicApi.browse<BrowseModel>(queryParameters: queryParameters, formJson: BrowseModel.fromMap);
|
||||
if (browseModel != null) {
|
||||
_extractAssemblyData(browseModel);
|
||||
refreshController.finishRefresh();
|
||||
|
||||
_extractAssemblyData(browseModel);
|
||||
// 获取 visitorData
|
||||
visitorData = browseModel.responseContext?.visitorData;
|
||||
|
||||
@ -41,6 +65,8 @@ class HomeController extends GetxController {
|
||||
clickTrackingParams = continuations[0].nextContinuationData?.clickTrackingParams;
|
||||
}
|
||||
_reBrowse(continuation: continuation, clickTrackingParams: clickTrackingParams);
|
||||
} else {
|
||||
refreshController.finishRefresh(IndicatorResult.fail);
|
||||
}
|
||||
viewState.value = groupList.isNotEmpty ? ViewState.normal : ViewState.empty;
|
||||
}
|
||||
@ -119,7 +145,7 @@ class HomeController extends GetxController {
|
||||
} else {
|
||||
// 获取副标题
|
||||
if (runs != null && runs.isNotEmpty) {
|
||||
musicModel.subtitle = runs.map((e) => e.text).join(' • ');
|
||||
musicModel.subtitle = runs.map((e) => e.text).join();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -154,6 +180,10 @@ class HomeController extends GetxController {
|
||||
}
|
||||
// 根据类型判断是否添加
|
||||
if (MusicTypeExtension.isThereAny(browseGroupModel.musicType)) {
|
||||
if (isRefresh) {
|
||||
groupList.clear();
|
||||
isRefresh = false;
|
||||
}
|
||||
groupList.add(browseGroupModel);
|
||||
}
|
||||
}
|
||||
@ -175,8 +205,9 @@ class HomeController extends GetxController {
|
||||
});
|
||||
}
|
||||
|
||||
void openAlbumSong(MusicModel musicModel) {
|
||||
void openAlbumSong(BrowseGroupModel browseGroupModel, MusicModel musicModel) {
|
||||
Get.toNamed(AppRoutes.albumSongList, arguments: {
|
||||
'musicType': browseGroupModel.musicType,
|
||||
'browseId': musicModel.browseId,
|
||||
'params': musicModel.params,
|
||||
'coverUrl': musicModel.coverUrl,
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tone_snap/components/base_scrollbar.dart';
|
||||
import 'package:tone_snap/components/refresh/base_easyrefresh.dart';
|
||||
import 'package:tone_snap/components/view_state_widget.dart';
|
||||
import 'package:tone_snap/data/enum/music_type.dart';
|
||||
import 'package:tone_snap/data/models/browse_group_model.dart';
|
||||
@ -97,30 +98,39 @@ class HomeView extends GetView<HomeController> {
|
||||
child: Obx(() {
|
||||
return ViewStateWidget(
|
||||
viewState: controller.viewState.value,
|
||||
child: BaseScrollbar(
|
||||
child: Obx(() {
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.only(bottom: 16).h,
|
||||
itemCount: controller.groupList.length,
|
||||
separatorBuilder: (context, index) => SizedBox(height: 16.h),
|
||||
itemBuilder: (context, index) {
|
||||
final browseGroupModel = controller.groupList[index];
|
||||
if (!MusicTypeExtension.isThereAny(browseGroupModel.musicType)) {
|
||||
return Container();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
_buildGroupTitle(ObjUtil.getStr(browseGroupModel.groupTitle)),
|
||||
if (browseGroupModel.musicType == MusicType.musicVideoTypeAtv.name) ...[
|
||||
_buildGroupAtv(browseGroupModel),
|
||||
] else ...[
|
||||
_buildGroupPlaylist(browseGroupModel),
|
||||
],
|
||||
],
|
||||
showTryAgain: true,
|
||||
onTapTryAgain: controller.onTapTryAgain,
|
||||
child: BaseEasyRefresh(
|
||||
controller: controller.refreshController,
|
||||
onRefresh: controller.onRefresh,
|
||||
childBuilder: (context, physics) {
|
||||
return BaseScrollbar(
|
||||
child: Obx(() {
|
||||
return ListView.separated(
|
||||
physics: physics,
|
||||
padding: const EdgeInsets.only(bottom: 16).h,
|
||||
itemCount: controller.groupList.length,
|
||||
separatorBuilder: (context, index) => SizedBox(height: 16.h),
|
||||
itemBuilder: (context, index) {
|
||||
final browseGroupModel = controller.groupList[index];
|
||||
if (!MusicTypeExtension.isThereAny(browseGroupModel.musicType)) {
|
||||
return Container();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
_buildGroupTitle(ObjUtil.getStr(browseGroupModel.groupTitle)),
|
||||
if (browseGroupModel.musicType == MusicType.musicVideoTypeAtv.name) ...[
|
||||
_buildGroupAtv(browseGroupModel),
|
||||
] else ...[
|
||||
_buildGroupPlaylist(browseGroupModel),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
@ -136,9 +146,9 @@ class HomeView extends GetView<HomeController> {
|
||||
itemCount: browseGroupModel.browseList != null ? browseGroupModel.browseList!.length : 0,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 0,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: 60.w / (1.sw - 48.w),
|
||||
childAspectRatio: 60.w / (1.sw - 50.w),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final musicModel = browseGroupModel.browseList![index];
|
||||
@ -163,7 +173,7 @@ class HomeView extends GetView<HomeController> {
|
||||
final musicModel = browseGroupModel.browseList![index];
|
||||
return BrowseItemAlbumSongList(
|
||||
musicModel: musicModel,
|
||||
onTap: () => controller.openAlbumSong(musicModel),
|
||||
onTap: () => controller.openAlbumSong(browseGroupModel, musicModel),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@ -7,7 +7,7 @@ import 'package:tone_snap/routes/app_routes.dart';
|
||||
class LoveSongsController extends GetxController {
|
||||
static LoveSongsController get to => Get.find<LoveSongsController>();
|
||||
var viewState = ViewState.loading.obs;
|
||||
var loveList = <Rx<MusicModel>>[].obs;
|
||||
var loveList = <MusicModel>[].obs;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
@ -16,7 +16,7 @@ class LoveSongsController extends GetxController {
|
||||
}
|
||||
|
||||
void getLoveList() {
|
||||
loveList.value = LoveSongsBox().getReversedList().map((e) => e.obs).toList();
|
||||
loveList.value = LoveSongsBox().getReversedList();
|
||||
viewState.value = loveList.isNotEmpty ? ViewState.normal : ViewState.empty;
|
||||
}
|
||||
|
||||
|
||||
@ -75,7 +75,7 @@ class LoveSongsView extends StatelessWidget {
|
||||
itemBuilder: (context, index) {
|
||||
return MusicItem(
|
||||
musicModel: controller.loveList[index],
|
||||
onTapItem: () => controller.onTapItem(controller.loveList[index].value),
|
||||
onTapItem: () => controller.onTapItem(controller.loveList[index]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -13,29 +13,30 @@ import 'package:tone_snap/modules/sideb/love_songs/love_songs_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/offline/offline_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/playlists/playlists_controller.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
class MoreBottomSheetController extends GetxController {
|
||||
late Rx<MusicModel> musicModel;
|
||||
late MusicModel musicModel;
|
||||
String? playlistModelId;
|
||||
var isLove = false.obs;
|
||||
|
||||
void setMusicModel(Rx<MusicModel> musicModel, {String? playlistModelId}) {
|
||||
void setMusicModel(MusicModel musicModel, {String? playlistModelId}) {
|
||||
this.musicModel = musicModel;
|
||||
this.playlistModelId = playlistModelId;
|
||||
_checkIsLove();
|
||||
}
|
||||
|
||||
void _checkIsLove() {
|
||||
isLove.value = LoveSongsBox().checkLove(musicModel.value.videoId);
|
||||
isLove.value = LoveSongsBox().checkLove(musicModel.videoId);
|
||||
}
|
||||
|
||||
void onTapLove() async {
|
||||
if (musicModel.value.videoId == null) return;
|
||||
if (ObjUtil.isEmpty(musicModel.videoId)) return;
|
||||
if (isLove.value) {
|
||||
await LoveSongsBox().delete(musicModel.value.videoId!);
|
||||
await LoveSongsBox().delete(musicModel.videoId!);
|
||||
BaseEasyLoading.toast('Removed');
|
||||
} else {
|
||||
await LoveSongsBox().add(musicModel.value.copyWith());
|
||||
await LoveSongsBox().add(musicModel.copyWith());
|
||||
BaseEasyLoading.toast('Collected');
|
||||
}
|
||||
_checkIsLove();
|
||||
@ -48,14 +49,14 @@ class MoreBottomSheetController extends GetxController {
|
||||
}
|
||||
|
||||
void onTapDownload() {
|
||||
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) {
|
||||
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
|
||||
Get.dialog(
|
||||
RemindDialog(
|
||||
content: 'Confirm to remove this song?',
|
||||
confirmOnTap: () async {
|
||||
Get.back();
|
||||
await OfflineBox().delete(musicModel.value.videoId!);
|
||||
musicModel.update((fn) => fn?.taskStatus = null);
|
||||
await OfflineBox().delete(musicModel.videoId!);
|
||||
DownloadManager.to.updateDownloadState();
|
||||
BaseEasyLoading.toast('Removed');
|
||||
if (Get.isRegistered<PersonalMusicLibraryController>()) {
|
||||
PersonalMusicLibraryController.to.refreshOffline();
|
||||
@ -67,11 +68,11 @@ class MoreBottomSheetController extends GetxController {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (musicModel.value.taskStatus == TaskStatus.enqueued
|
||||
|| musicModel.value.taskStatus == TaskStatus.running) {
|
||||
DownloadManager().cancelDownload(musicModel);
|
||||
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|
||||
|| DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
|
||||
DownloadManager.to.cancelDownload(musicModel.videoId);
|
||||
} else {
|
||||
DownloadManager().downloadMusic(musicModel);
|
||||
DownloadManager.to.downloadMusic(musicModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,23 +81,21 @@ class MoreBottomSheetController extends GetxController {
|
||||
Get.back();
|
||||
Get.bottomSheet(
|
||||
AddToPlaylistBottomSheetView(
|
||||
musicModel: musicModel.value,
|
||||
musicModel: musicModel,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onTapRemove() async {
|
||||
if (playlistModelId != null && musicModel.value.videoId != null) {
|
||||
bool result = await PlaylistsBox().removeMusic(playlistModelId!, musicModel.value.videoId!);
|
||||
if (result) {
|
||||
BaseEasyLoading.toast('Removed');
|
||||
Get.back();
|
||||
if (Get.isRegistered<PlaylistsController>()) {
|
||||
PlaylistsController.to.getList();
|
||||
}
|
||||
if (Get.isRegistered<CustomPlaylistController>()) {
|
||||
CustomPlaylistController.to.getPlaylistMode();
|
||||
}
|
||||
if (ObjUtil.isNotEmpty(playlistModelId) && ObjUtil.isNotEmpty(musicModel.videoId)) {
|
||||
await PlaylistsBox().removeMusic(playlistModelId!, musicModel.videoId!);
|
||||
BaseEasyLoading.toast('Removed');
|
||||
Get.back();
|
||||
if (Get.isRegistered<PlaylistsController>()) {
|
||||
PlaylistsController.to.getList();
|
||||
}
|
||||
if (Get.isRegistered<CustomPlaylistController>()) {
|
||||
CustomPlaylistController.to.getPlaylistMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:tone_snap/components/network_image_widget.dart';
|
||||
import 'package:tone_snap/data/models/music_model.dart';
|
||||
import 'package:tone_snap/data/storage/offline_box.dart';
|
||||
import 'package:tone_snap/generated/assets.dart';
|
||||
import 'package:tone_snap/global/download_manager.dart';
|
||||
import 'package:tone_snap/res/themes/app_colors.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
@ -17,7 +18,7 @@ class MoreBottomSheetView extends StatelessWidget {
|
||||
MoreBottomSheetView({super.key, required this.musicModel, this.playlistModelId});
|
||||
|
||||
final controller = Get.put(MoreBottomSheetController());
|
||||
final Rx<MusicModel> musicModel;
|
||||
final MusicModel musicModel;
|
||||
final String? playlistModelId;
|
||||
|
||||
@override
|
||||
@ -42,7 +43,7 @@ class MoreBottomSheetView extends StatelessWidget {
|
||||
_buildDownload(),
|
||||
_buildItem('Add to playlist', Assets.sideBAddToPlaylist, controller.onTapAddToPlaylist),
|
||||
// _buildItem('Add to queue', Assets.sideBAddToQueue, () {}),
|
||||
if (playlistModelId != null) ...[
|
||||
if (ObjUtil.isNotEmpty(playlistModelId)) ...[
|
||||
_buildItem('Remove from list', Assets.sideBMoreRemove, controller.onTapRemove),
|
||||
],
|
||||
_buildItem('Report', Assets.sideBReport, controller.onTapReport),
|
||||
@ -73,21 +74,19 @@ class MoreBottomSheetView extends StatelessWidget {
|
||||
SizedBox(height: 20.h),
|
||||
Row(
|
||||
children: [
|
||||
Obx(() {
|
||||
return NetworkImageWidget(
|
||||
url: musicModel.value.coverUrl,
|
||||
width: 50.w,
|
||||
height: 50.w,
|
||||
radius: 10.r,
|
||||
);
|
||||
}),
|
||||
NetworkImageWidget(
|
||||
url: musicModel.coverUrl,
|
||||
width: 50.w,
|
||||
height: 50.w,
|
||||
radius: 10.r,
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
ObjUtil.getStr(musicModel.value.title),
|
||||
ObjUtil.getStr(musicModel.title),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
@ -96,7 +95,7 @@ class MoreBottomSheetView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
ObjUtil.getStr(musicModel.value.subtitle),
|
||||
ObjUtil.getStr(musicModel.subtitle),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
@ -148,52 +147,58 @@ class MoreBottomSheetView extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
child: Obx(() {
|
||||
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) {
|
||||
return Image.asset(Assets.sideBDownloaded);
|
||||
} else {
|
||||
if (musicModel.value.taskStatus == TaskStatus.enqueued) {
|
||||
return CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else if (musicModel.value.taskStatus == TaskStatus.running) {
|
||||
return CircularProgressIndicator(
|
||||
value: musicModel.value.progress,
|
||||
backgroundColor: seedColor.withOpacity(0.2),
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
child: GetBuilder<DownloadManager>(
|
||||
id: DownloadManager.to.downloadStateId,
|
||||
builder: (_) {
|
||||
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
|
||||
return Image.asset(Assets.sideBDownloaded);
|
||||
} else {
|
||||
return Image.asset(Assets.sideBNotDownload1);
|
||||
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued) {
|
||||
return CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
|
||||
return CircularProgressIndicator(
|
||||
value: DownloadManager.to.getMusicModel(musicModel.videoId)?.progress,
|
||||
backgroundColor: seedColor.withOpacity(0.2),
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else {
|
||||
return Image.asset(Assets.sideBNotDownload1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
var text = '';
|
||||
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) {
|
||||
text = ' Removed';
|
||||
} else {
|
||||
if (musicModel.value.taskStatus == TaskStatus.enqueued
|
||||
|| musicModel.value.taskStatus == TaskStatus.running) {
|
||||
text = 'Cancel download';
|
||||
child: GetBuilder<DownloadManager>(
|
||||
id: DownloadManager.to.downloadStateId,
|
||||
builder: (_) {
|
||||
var text = '';
|
||||
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
|
||||
text = 'Removed';
|
||||
} else {
|
||||
text = 'Download';
|
||||
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|
||||
|| DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
|
||||
text = 'Cancel download';
|
||||
} else {
|
||||
text = 'Download';
|
||||
}
|
||||
}
|
||||
}
|
||||
return Text(
|
||||
text,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
);
|
||||
}),
|
||||
return Text(
|
||||
text,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -44,15 +44,15 @@ class MusicBarView extends StatelessWidget {
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 52.w,
|
||||
height: 52.w,
|
||||
width: 51.w,
|
||||
height: 51.w,
|
||||
child: Obx(() {
|
||||
int comparison = musicPlayerController.positionDuration.value.compareTo(musicPlayerController.totalDuration.value);
|
||||
double value = comparison < 0 ? (musicPlayerController.positionDuration.value.inSeconds / musicPlayerController.totalDuration.value.inSeconds).toDouble() : 0;
|
||||
return CircularProgressIndicator(
|
||||
value: value,
|
||||
backgroundColor: Colors.transparent,
|
||||
strokeWidth: 4.w,
|
||||
strokeWidth: 3.w,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
);
|
||||
}),
|
||||
@ -62,7 +62,7 @@ class MusicBarView extends StatelessWidget {
|
||||
child: FittedBox(
|
||||
fit: BoxFit.none,
|
||||
child: NetworkImageWidget(
|
||||
url: musicPlayerController.getMusicModel()?.value.coverUrl,
|
||||
url: musicPlayerController.getMusicModel()?.coverUrl,
|
||||
width: 48.w,
|
||||
height: 48.w,
|
||||
),
|
||||
@ -79,7 +79,7 @@ class MusicBarView extends StatelessWidget {
|
||||
children: [
|
||||
Obx(() {
|
||||
return MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.title),
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.title),
|
||||
textStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.sp,
|
||||
@ -90,7 +90,7 @@ class MusicBarView extends StatelessWidget {
|
||||
SizedBox(height: 4.h),
|
||||
Obx(() {
|
||||
return MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.subtitle),
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.subtitle),
|
||||
textStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12.sp,
|
||||
@ -100,7 +100,7 @@ class MusicBarView extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 13),
|
||||
SizedBox(width: 13.w),
|
||||
Obx(() {
|
||||
return GestureDetector(
|
||||
onTap: musicPlayerController.playPause,
|
||||
@ -111,7 +111,7 @@ class MusicBarView extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 26),
|
||||
SizedBox(width: 26.w),
|
||||
GestureDetector(
|
||||
onTap: musicPlayerController.nextTrack,
|
||||
child: Image.asset(
|
||||
|
||||
@ -7,7 +7,7 @@ import 'package:tone_snap/routes/app_routes.dart';
|
||||
class OfflineController extends GetxController {
|
||||
static OfflineController get to => Get.find<OfflineController>();
|
||||
var viewState = ViewState.loading.obs;
|
||||
var offlineList = <Rx<MusicModel>>[].obs;
|
||||
var offlineList = <MusicModel>[].obs;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
@ -16,7 +16,7 @@ class OfflineController extends GetxController {
|
||||
}
|
||||
|
||||
void getOfflineList() {
|
||||
offlineList.value = OfflineBox().getReversedList().map((e) => e.obs).toList();
|
||||
offlineList.value = OfflineBox().getReversedList();
|
||||
viewState.value = offlineList.isNotEmpty ? ViewState.normal : ViewState.empty;
|
||||
}
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ class OfflineView extends StatelessWidget {
|
||||
return MusicItem(
|
||||
musicModel: controller.offlineList[index],
|
||||
showDownload: false,
|
||||
onTapItem: () => controller.onTapItem(controller.offlineList[index].value),
|
||||
onTapItem: () => controller.onTapItem(controller.offlineList[index]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -10,9 +10,12 @@ import 'package:tone_snap/data/storage/offline_box.dart';
|
||||
import 'package:tone_snap/global/download_manager.dart';
|
||||
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
class PlayPageController extends GetxController {
|
||||
static PlayPageController get to => Get.find<PlayPageController>();
|
||||
var musicPlayerController = MusicPlayerController.to;
|
||||
final loveStateId = 'loveStateId';
|
||||
|
||||
@override
|
||||
void onReady() async {
|
||||
@ -85,30 +88,29 @@ class PlayPageController extends GetxController {
|
||||
|
||||
/// 加入/取消收藏
|
||||
Future<void> onTapLove() async {
|
||||
if (musicPlayerController.getMusicModel()?.value.videoId == null) return;
|
||||
final isLove = LoveSongsBox().checkLove(musicPlayerController.getMusicModel()!.value.videoId);
|
||||
if (ObjUtil.isEmpty(musicPlayerController.getMusicModel()?.videoId)) return;
|
||||
final isLove = LoveSongsBox().checkLove(musicPlayerController.getMusicModel()!.videoId);
|
||||
if (isLove) {
|
||||
await LoveSongsBox().delete(musicPlayerController.getMusicModel()!.value.videoId!);
|
||||
await LoveSongsBox().delete(musicPlayerController.getMusicModel()!.videoId!);
|
||||
BaseEasyLoading.toast('Removed');
|
||||
} else {
|
||||
await LoveSongsBox().add(musicPlayerController.getMusicModel()!.value.copyWith());
|
||||
await LoveSongsBox().add(musicPlayerController.getMusicModel()!.copyWith());
|
||||
BaseEasyLoading.toast('Collected');
|
||||
}
|
||||
if (Get.isRegistered<PersonalMusicLibraryController>()) {
|
||||
PersonalMusicLibraryController.to.refreshLoveSongs();
|
||||
}
|
||||
musicPlayerController.getMusicModel()!.update((fn) => fn?.isLove = LoveSongsBox().checkLove(musicPlayerController.getMusicModel()!.value.videoId));
|
||||
update([loveStateId]);
|
||||
}
|
||||
|
||||
void onTapDownload() {
|
||||
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.value.videoId)
|
||||
|| musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.complete) {
|
||||
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.videoId)) {
|
||||
Get.dialog(
|
||||
RemindDialog(
|
||||
content: 'Confirm to remove this song?',
|
||||
confirmOnTap: () async {
|
||||
await OfflineBox().delete(musicPlayerController.getMusicModel()!.value.videoId!);
|
||||
musicPlayerController.getMusicModel()!.update((fn) => fn?.taskStatus = null);
|
||||
await OfflineBox().delete(musicPlayerController.getMusicModel()!.videoId!);
|
||||
DownloadManager.to.updateDownloadState();
|
||||
BaseEasyLoading.toast('Removed');
|
||||
if (Get.isRegistered<PersonalMusicLibraryController>()) {
|
||||
PersonalMusicLibraryController.to.refreshOffline();
|
||||
@ -117,11 +119,11 @@ class PlayPageController extends GetxController {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.enqueued
|
||||
|| musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.running) {
|
||||
DownloadManager().cancelDownload(musicPlayerController.getMusicModel());
|
||||
if (DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.enqueued
|
||||
|| DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.running) {
|
||||
DownloadManager.to.cancelDownload(musicPlayerController.getMusicModel()?.videoId);
|
||||
} else {
|
||||
DownloadManager().downloadMusic(musicPlayerController.getMusicModel());
|
||||
DownloadManager.to.downloadMusic(musicPlayerController.getMusicModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import 'package:tone_snap/data/enum/play_mode.dart';
|
||||
import 'package:tone_snap/data/storage/love_songs_box.dart';
|
||||
import 'package:tone_snap/data/storage/offline_box.dart';
|
||||
import 'package:tone_snap/generated/assets.dart';
|
||||
import 'package:tone_snap/global/download_manager.dart';
|
||||
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/play_page/play_page_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
|
||||
@ -67,7 +68,7 @@ class PlayPageView extends StatelessWidget {
|
||||
children: [
|
||||
Obx(() {
|
||||
return NetworkImageWidget(
|
||||
url: musicPlayerController.getMusicModel()?.value.coverUrl,
|
||||
url: musicPlayerController.getMusicModel()?.coverUrl,
|
||||
width: 1.sw,
|
||||
height: 1.sh,
|
||||
noPlaceholder: true,
|
||||
@ -130,7 +131,7 @@ class PlayPageView extends StatelessWidget {
|
||||
Widget _buildCover() {
|
||||
return Obx(() {
|
||||
return NetworkImageWidget(
|
||||
url: musicPlayerController.getMusicModel()?.value.coverUrl,
|
||||
url: musicPlayerController.getMusicModel()?.coverUrl,
|
||||
width: 1.sw,
|
||||
height: 330.h,
|
||||
radius: 16.r,
|
||||
@ -150,7 +151,7 @@ class PlayPageView extends StatelessWidget {
|
||||
children: [
|
||||
Obx(() {
|
||||
return MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.title),
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.title),
|
||||
textStyle: TextStyle(
|
||||
color: const Color(0xD9FFFFFF),
|
||||
fontSize: 22.sp,
|
||||
@ -160,7 +161,7 @@ class PlayPageView extends StatelessWidget {
|
||||
SizedBox(height: 6.h),
|
||||
Obx(() {
|
||||
return MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.subtitle),
|
||||
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.subtitle),
|
||||
textStyle: TextStyle(
|
||||
color: const Color(0x99EEEEEE),
|
||||
fontSize: 12.sp,
|
||||
@ -181,13 +182,16 @@ class PlayPageView extends StatelessWidget {
|
||||
onTap: controller.onTapLove,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6).w,
|
||||
child: Obx(() {
|
||||
return Image.asset(
|
||||
LoveSongsBox().checkLove(musicPlayerController.getMusicModel()?.value.videoId) ? Assets.sideBLoveSolid : Assets.sideBLove,
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
);
|
||||
}),
|
||||
child: GetBuilder<PlayPageController>(
|
||||
id: PlayPageController.to.loveStateId,
|
||||
builder: (_) {
|
||||
return Image.asset(
|
||||
LoveSongsBox().checkLove(musicPlayerController.getMusicModel()?.videoId) ? Assets.sideBLoveSolid : Assets.sideBLove,
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -208,29 +212,31 @@ class PlayPageView extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
child: Obx(() {
|
||||
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.value.videoId)
|
||||
|| musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.complete) {
|
||||
return Image.asset(Assets.sideBDownloaded);
|
||||
} else {
|
||||
if (musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.enqueued) {
|
||||
return CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else if (musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.running) {
|
||||
return CircularProgressIndicator(
|
||||
value: musicPlayerController.getMusicModel()?.value.progress,
|
||||
backgroundColor: seedColor.withOpacity(0.2),
|
||||
valueColor:
|
||||
const AlwaysStoppedAnimation<Color>(seedColor),
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
child: GetBuilder<DownloadManager>(
|
||||
id: DownloadManager.to.downloadStateId,
|
||||
builder: (_) {
|
||||
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.videoId)) {
|
||||
return Image.asset(Assets.sideBDownloaded);
|
||||
} else {
|
||||
return Image.asset(Assets.sideBNotDownload1);
|
||||
if (DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.enqueued) {
|
||||
return CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else if (DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.running) {
|
||||
return CircularProgressIndicator(
|
||||
value: DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.progress,
|
||||
backgroundColor: seedColor.withOpacity(0.2),
|
||||
valueColor:
|
||||
const AlwaysStoppedAnimation<Color>(seedColor),
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else {
|
||||
return Image.asset(Assets.sideBNotDownload1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -250,15 +256,17 @@ class PlayPageView extends StatelessWidget {
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: seedColor,
|
||||
inactiveTrackColor: const Color(0x4DFFFFFF),
|
||||
disabledInactiveTrackColor: const Color(0x4DFFFFFF),
|
||||
secondaryActiveTrackColor: seedColor.withOpacity(0.3),
|
||||
trackHeight: 4.h,
|
||||
thumbColor: Colors.white,
|
||||
disabledThumbColor: Colors.white,
|
||||
thumbShape: RoundSliderThumbShape(
|
||||
disabledThumbRadius: 8.w,
|
||||
enabledThumbRadius: 8.w,
|
||||
),
|
||||
trackShape: FullWidthTrackShape(),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 10.w), // 扩大覆盖层半径
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 10.w),
|
||||
),
|
||||
child: Obx(() {
|
||||
return Slider(
|
||||
@ -272,6 +280,7 @@ class PlayPageView extends StatelessWidget {
|
||||
);
|
||||
}),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -380,9 +389,9 @@ class PlayPageView extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
child: const CircularProgressIndicator(
|
||||
child: CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 3,
|
||||
strokeWidth: 2.w,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -480,7 +489,7 @@ class PlayPageView extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildPlayListItem(int index) {
|
||||
final musicModel = musicPlayerController.playlist[index].value;
|
||||
final musicModel = musicPlayerController.playlist[index];
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
@ -503,13 +512,13 @@ class PlayPageView extends StatelessWidget {
|
||||
children: [
|
||||
Obx(() {
|
||||
return Visibility(
|
||||
visible: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId,
|
||||
visible: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId,
|
||||
replacement: Text(
|
||||
ObjUtil.getStr(musicModel.title),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId
|
||||
color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
|
||||
? seedColor
|
||||
: const Color(0xD9FFFFFF),
|
||||
fontSize: 14.sp,
|
||||
@ -518,7 +527,7 @@ class PlayPageView extends StatelessWidget {
|
||||
child: MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicModel.title),
|
||||
textStyle: TextStyle(
|
||||
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId
|
||||
color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
|
||||
? seedColor
|
||||
: const Color(0xD9FFFFFF),
|
||||
fontSize: 14.sp,
|
||||
@ -529,13 +538,13 @@ class PlayPageView extends StatelessWidget {
|
||||
SizedBox(height: 8.h),
|
||||
Obx(() {
|
||||
return Visibility(
|
||||
visible: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId,
|
||||
visible: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId,
|
||||
replacement: Text(
|
||||
ObjUtil.getStr(musicModel.subtitle),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId
|
||||
color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
|
||||
? seedColor
|
||||
: const Color(0x99FFFFFF),
|
||||
fontSize: 12.sp,
|
||||
@ -544,7 +553,7 @@ class PlayPageView extends StatelessWidget {
|
||||
child: MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicModel.subtitle),
|
||||
textStyle: TextStyle(
|
||||
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId
|
||||
color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
|
||||
? seedColor
|
||||
: const Color(0x99FFFFFF),
|
||||
fontSize: 12.sp,
|
||||
@ -557,7 +566,7 @@ class PlayPageView extends StatelessWidget {
|
||||
),
|
||||
Obx(() {
|
||||
return Visibility(
|
||||
visible: musicPlayerController.getMusicModel()?.value.videoId != musicModel.videoId,
|
||||
visible: musicPlayerController.getMusicModel()?.videoId != musicModel.videoId,
|
||||
child: ClipOval(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
@ -577,7 +586,7 @@ class PlayPageView extends StatelessWidget {
|
||||
);
|
||||
}),
|
||||
Obx(() {
|
||||
return SizedBox(width: musicPlayerController.getMusicModel()?.value.videoId != musicModel.videoId ? 12.w : 16.w);
|
||||
return SizedBox(width: musicPlayerController.getMusicModel()?.videoId != musicModel.videoId ? 12.w : 16.w);
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
||||
@ -124,7 +124,7 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
|
||||
await _cleanTab();
|
||||
searchResultViewState.value = ViewState.loading;
|
||||
await _searchPreviewResult(value);
|
||||
searchResultViewState.value = tabs.isNotEmpty ? ViewState.normal : ViewState.empty;
|
||||
searchResultViewState.value = tabs.length > 1 ? ViewState.normal : ViewState.empty;
|
||||
tabController.value = TabController(length: tabs.length, vsync: this);
|
||||
}
|
||||
|
||||
@ -257,12 +257,12 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < this.tabs.length; ++i) {
|
||||
if (i == 0) {
|
||||
pages.add(const SearchResultChildOptimumView());
|
||||
} else {
|
||||
pages.add(SearchResultChildView(searchResultTabBarModel: this.tabs[i]));
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < this.tabs.length; ++i) {
|
||||
if (i == 0) {
|
||||
pages.add(const SearchResultChildOptimumView());
|
||||
} else {
|
||||
pages.add(SearchResultChildView(searchResultTabBarModel: this.tabs[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import 'package:tone_snap/utils/obj_util.dart';
|
||||
class SearchResultChildController extends GetxController {
|
||||
var viewState = ViewState.loading.obs;
|
||||
var scrollController = ScrollController();
|
||||
var musicList = <Rx<MusicModel>>[].obs;
|
||||
var musicList = <MusicModel>[].obs;
|
||||
String? params;
|
||||
String? continuation;
|
||||
String? clickTrackingParams;
|
||||
@ -107,7 +107,7 @@ class SearchResultChildController extends GetxController {
|
||||
}
|
||||
}
|
||||
}
|
||||
musicList.value = list.map((e) => e.obs).toList();
|
||||
musicList.value = list;
|
||||
viewState.value = musicList.isNotEmpty ? ViewState.normal : ViewState.empty;
|
||||
}
|
||||
|
||||
@ -124,7 +124,6 @@ class SearchResultChildController extends GetxController {
|
||||
};
|
||||
refreshController.callLoad();
|
||||
SearchResultMoreModel? searchResultMoreModel = await MusicApi.search<SearchResultMoreModel>(queryParameters: queryParameters, formJson: SearchResultMoreModel.fromMap);
|
||||
final list = <MusicModel>[];
|
||||
if (searchResultMoreModel != null) {
|
||||
var continuations = searchResultMoreModel.continuationContents?.musicShelfContinuation?.continuations;
|
||||
if (continuations != null && continuations.isNotEmpty) {
|
||||
@ -166,11 +165,10 @@ class SearchResultChildController extends GetxController {
|
||||
musicModel.browseId = o.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId;
|
||||
musicModel.musicType = o.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType;
|
||||
}
|
||||
list.add(musicModel);
|
||||
musicList.add(musicModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
musicList.addAll(list.map((e) => e.obs).toList());
|
||||
if (searchResultMoreModel != null) {
|
||||
if (ObjUtil.isNotEmpty(continuation)) {
|
||||
refreshController.finishLoad(IndicatorResult.success);
|
||||
@ -197,6 +195,7 @@ class SearchResultChildController extends GetxController {
|
||||
'coverUrl': musicModel.coverUrl,
|
||||
'title': musicModel.title,
|
||||
'subtitle': musicModel.subtitle,
|
||||
'musicType': musicModel.musicType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,9 @@ import 'package:tone_snap/components/base_scrollbar.dart';
|
||||
import 'package:tone_snap/components/refresh/base_easyrefresh.dart';
|
||||
import 'package:tone_snap/components/view_state_widget.dart';
|
||||
import 'package:tone_snap/data/enum/music_type.dart';
|
||||
import 'package:tone_snap/data/models/search_result_tabbar_model.dart';
|
||||
import 'package:tone_snap/modules/sideb/widgets/music_item.dart';
|
||||
|
||||
import '../../../data/models/search_result_tabbar_model.dart';
|
||||
import 'search_result_child_controller.dart';
|
||||
|
||||
class SearchResultChildView extends GetView<SearchResultChildController> {
|
||||
@ -28,23 +28,26 @@ class SearchResultChildView extends GetView<SearchResultChildController> {
|
||||
child: BaseEasyRefresh(
|
||||
controller: controller.refreshController,
|
||||
onLoad: controller.onLoad,
|
||||
child: BaseScrollbar(
|
||||
scrollController: controller.scrollController,
|
||||
child: Obx(() {
|
||||
return ListView.builder(
|
||||
controller: controller.scrollController,
|
||||
itemCount: controller.musicList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return MusicItem(
|
||||
musicModel: controller.musicList[index],
|
||||
showDownload: controller.musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name,
|
||||
showMore: controller.musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name,
|
||||
onTapItem: () => controller.onTapItem(controller.musicList[index].value),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
childBuilder: (context, physics) {
|
||||
return BaseScrollbar(
|
||||
scrollController: controller.scrollController,
|
||||
child: Obx(() {
|
||||
return ListView.builder(
|
||||
physics: physics,
|
||||
controller: controller.scrollController,
|
||||
itemCount: controller.musicList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return MusicItem(
|
||||
musicModel: controller.musicList[index],
|
||||
showDownload: controller.musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
|
||||
showMore: controller.musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
|
||||
onTapItem: () => controller.onTapItem(controller.musicList[index]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@ -18,6 +18,7 @@ class SearchResultChildOptimumController extends GetxController {
|
||||
'coverUrl': musicModel.coverUrl,
|
||||
'title': musicModel.title,
|
||||
'subtitle': musicModel.subtitle,
|
||||
'musicType': musicModel.musicType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ class SearchResultChildOptimumView extends GetView<SearchResultChildOptimumContr
|
||||
itemCount: SearchResultController.to.tabs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final searchResultTabBarModel = SearchResultController.to.tabs[index];
|
||||
final musicList = searchResultTabBarModel.musicList?.map((e) => e.obs).toList();
|
||||
final musicList = searchResultTabBarModel.musicList;
|
||||
if (index == 0) {
|
||||
return Container();
|
||||
}
|
||||
@ -51,9 +51,9 @@ class SearchResultChildOptimumView extends GetView<SearchResultChildOptimumContr
|
||||
itemBuilder: (context, index) {
|
||||
return MusicItem(
|
||||
musicModel: musicList[index],
|
||||
showDownload: musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name,
|
||||
showMore: musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name,
|
||||
onTapItem: () => controller.onTapItem(musicList[index].value),
|
||||
showDownload: musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
|
||||
showMore: musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
|
||||
onTapItem: () => controller.onTapItem(musicList[index]),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@ -17,6 +17,7 @@ class BrowseItemAlbumSongList extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
width: 109.w,
|
||||
|
||||
@ -19,18 +19,21 @@ class BrowseItemAtv extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
height: 60.w,
|
||||
child: Row(
|
||||
children: [
|
||||
_buildCover(),
|
||||
_buildContent(),
|
||||
_buildMore(),
|
||||
],
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8).r,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
height: 60.w,
|
||||
child: Row(
|
||||
children: [
|
||||
_buildCover(),
|
||||
_buildContent(),
|
||||
_buildMore(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -80,20 +83,17 @@ class BrowseItemAtv extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildMore() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 12).w,
|
||||
child: ClipOval(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTapMore,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4).w,
|
||||
child: Image.asset(
|
||||
Assets.sideBMore,
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
),
|
||||
return ClipOval(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTapMore,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4).w,
|
||||
child: Image.asset(
|
||||
Assets.sideBMore,
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -104,7 +104,7 @@ class BrowseItemAtv extends StatelessWidget {
|
||||
void onTapMore() {
|
||||
Get.bottomSheet(
|
||||
MoreBottomSheetView(
|
||||
musicModel: musicModel.obs,
|
||||
musicModel: musicModel,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tone_snap/components/base_easyloading.dart';
|
||||
import 'package:tone_snap/components/dialog/remind_dialog.dart';
|
||||
import 'package:tone_snap/components/my_marquee_text.dart';
|
||||
import 'package:tone_snap/components/network_image_widget.dart';
|
||||
import 'package:tone_snap/data/models/music_model.dart';
|
||||
import 'package:tone_snap/data/storage/offline_box.dart';
|
||||
@ -14,6 +13,7 @@ import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart
|
||||
import 'package:tone_snap/modules/sideb/more_bottom_sheet/more_bottom_sheet_view.dart';
|
||||
import 'package:tone_snap/modules/sideb/offline/offline_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/widgets/music_item_marquee_text.dart';
|
||||
import 'package:tone_snap/res/themes/app_colors.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
@ -24,15 +24,19 @@ class MusicItem extends StatelessWidget {
|
||||
required this.onTapItem,
|
||||
this.showDownload = true,
|
||||
this.showMore = true,
|
||||
this.showNumber = false,
|
||||
this.number = 0,
|
||||
this.playlistModelId,
|
||||
});
|
||||
|
||||
final musicPlayerController = MusicPlayerController.to;
|
||||
|
||||
final Rx<MusicModel> musicModel;
|
||||
final MusicModel musicModel;
|
||||
final Function() onTapItem;
|
||||
final bool showDownload;
|
||||
final bool showMore;
|
||||
final bool showNumber;
|
||||
final int number;
|
||||
final String? playlistModelId;
|
||||
|
||||
@override
|
||||
@ -46,7 +50,11 @@ class MusicItem extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 18.w),
|
||||
_buildCover(),
|
||||
if (showNumber) ...[
|
||||
_buildNumber(),
|
||||
] else ...[
|
||||
_buildCover(),
|
||||
],
|
||||
SizedBox(width: 12.w),
|
||||
_buildContent(),
|
||||
SizedBox(width: 8.w),
|
||||
@ -66,15 +74,34 @@ class MusicItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNumber() {
|
||||
return Container(
|
||||
width: 60.w,
|
||||
height: 60.w,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10).w,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF242529),
|
||||
borderRadius: BorderRadius.circular(8).r,
|
||||
),
|
||||
child: Text(
|
||||
number.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCover() {
|
||||
return Obx(() {
|
||||
return NetworkImageWidget(
|
||||
url: musicModel.value.coverUrl,
|
||||
width: 60.w,
|
||||
height: 60.w,
|
||||
radius: 8.r,
|
||||
);
|
||||
});
|
||||
return NetworkImageWidget(
|
||||
url: musicModel.coverUrl,
|
||||
width: 60.w,
|
||||
height: 60.w,
|
||||
radius: 8.r,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
@ -83,48 +110,17 @@ class MusicItem extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(() {
|
||||
bool isCurrentPlayModel = ObjUtil.isNotEmpty(musicModel.value.videoId) && musicPlayerController.getMusicModel()?.value.videoId == musicModel.value.videoId;
|
||||
return Visibility(
|
||||
visible: isCurrentPlayModel,
|
||||
replacement: Text(
|
||||
ObjUtil.getStr(musicModel.value.title),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
),
|
||||
child: MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicModel.value.title),
|
||||
textStyle: TextStyle(
|
||||
color: seedColor,
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
),
|
||||
return MusicItemMarqueeText(
|
||||
text: musicModel.title,
|
||||
showMarquee: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 4.h),
|
||||
Obx(() {
|
||||
bool isCurrentPlayModel = ObjUtil.isNotEmpty(musicModel.value.videoId) && musicPlayerController.getMusicModel()?.value.videoId == musicModel.value.videoId;
|
||||
return Visibility(
|
||||
visible: isCurrentPlayModel,
|
||||
replacement: Text(
|
||||
ObjUtil.getStr(musicModel.value.subtitle),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: const Color(0x99FFFFFF),
|
||||
fontSize: 12.sp,
|
||||
),
|
||||
),
|
||||
child: MyMarqueeText(
|
||||
text: ObjUtil.getStr(musicModel.value.subtitle),
|
||||
textStyle: TextStyle(
|
||||
color: seedColor,
|
||||
fontSize: 12.sp,
|
||||
),
|
||||
),
|
||||
return MusicItemMarqueeText(
|
||||
text: musicModel.subtitle,
|
||||
isTitle: false,
|
||||
showMarquee: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
|
||||
);
|
||||
}),
|
||||
],
|
||||
@ -143,27 +139,30 @@ class MusicItem extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
child: Obx(() {
|
||||
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) {
|
||||
return Image.asset(Assets.sideBDownloaded);
|
||||
} else {
|
||||
if (musicModel.value.taskStatus == TaskStatus.enqueued) {
|
||||
return CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else if (musicModel.value.taskStatus == TaskStatus.running) {
|
||||
return CircularProgressIndicator(
|
||||
value: musicModel.value.progress,
|
||||
backgroundColor: seedColor.withOpacity(0.2),
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
child: GetBuilder<DownloadManager>(
|
||||
id: DownloadManager.to.downloadStateId,
|
||||
builder: (_) {
|
||||
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
|
||||
return Image.asset(Assets.sideBDownloaded);
|
||||
} else {
|
||||
return Image.asset(Assets.sideBNotDownload2);
|
||||
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued) {
|
||||
return CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
|
||||
return CircularProgressIndicator(
|
||||
value: DownloadManager.to.getMusicModel(musicModel.videoId)?.progress,
|
||||
backgroundColor: seedColor.withOpacity(0.2),
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else {
|
||||
return Image.asset(Assets.sideBNotDownload2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -191,13 +190,13 @@ class MusicItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
void onTapDownload() {
|
||||
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) {
|
||||
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
|
||||
Get.dialog(
|
||||
RemindDialog(
|
||||
content: 'Confirm to remove this song?',
|
||||
confirmOnTap: () async {
|
||||
await OfflineBox().delete(musicModel.value.videoId!);
|
||||
musicModel.update((fn) => fn?.taskStatus = null);
|
||||
await OfflineBox().delete(musicModel.videoId!);
|
||||
DownloadManager.to.updateDownloadState();
|
||||
BaseEasyLoading.toast('Removed');
|
||||
if (Get.isRegistered<PersonalMusicLibraryController>()) {
|
||||
PersonalMusicLibraryController.to.refreshOffline();
|
||||
@ -209,11 +208,11 @@ class MusicItem extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (musicModel.value.taskStatus == TaskStatus.enqueued
|
||||
|| musicModel.value.taskStatus == TaskStatus.running) {
|
||||
DownloadManager().cancelDownload(musicModel);
|
||||
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|
||||
|| DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
|
||||
DownloadManager.to.cancelDownload(musicModel.videoId);
|
||||
} else {
|
||||
DownloadManager().downloadMusic(musicModel);
|
||||
DownloadManager.to.downloadMusic(musicModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
lib/modules/sideb/widgets/music_item_marquee_text.dart
Normal file
51
lib/modules/sideb/widgets/music_item_marquee_text.dart
Normal file
@ -0,0 +1,51 @@
|
||||
// Author: fengshengxiong
|
||||
// Date: 2024/6/5
|
||||
// Description: 跑马灯
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:tone_snap/res/themes/app_colors.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
import 'package:widget_marquee/widget_marquee.dart';
|
||||
|
||||
class MusicItemMarqueeText extends StatelessWidget {
|
||||
const MusicItemMarqueeText({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.isTitle = true,
|
||||
this.showMarquee = false,
|
||||
});
|
||||
|
||||
final String? text;
|
||||
final bool isTitle;
|
||||
final bool showMarquee;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Visibility(
|
||||
visible: showMarquee,
|
||||
replacement: Text(
|
||||
ObjUtil.getStr(text),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: isTitle ? Colors.white : const Color(0x99FFFFFF),
|
||||
fontSize: isTitle ? 14.sp : 12.sp,
|
||||
),
|
||||
),
|
||||
child: Marquee(
|
||||
delay: Duration.zero,
|
||||
duration: const Duration(seconds: 40),
|
||||
pause: Duration.zero,
|
||||
gap: 80,
|
||||
child: Text(
|
||||
ObjUtil.getStr(text),
|
||||
style: TextStyle(
|
||||
color: seedColor,
|
||||
fontSize: isTitle ? 14.sp : 12.sp,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -78,6 +78,7 @@ class PlaylistItem extends StatelessWidget {
|
||||
'coverUrl': playlistModel.coverUrl,
|
||||
'title': playlistModel.title,
|
||||
'subtitle': playlistModel.subtitle,
|
||||
'musicType': playlistModel.musicType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user