1.首页增加下拉刷新

2.修改下载状态监听方式,实现全局同步
3.修复搜索无结果时页面报错
4.歌单页面点击播放全部和随机是播放当前歌单列表
This commit is contained in:
fengshengxiong 2024-08-06 15:52:07 +08:00
parent 64047ed78a
commit da21720c3c
36 changed files with 679 additions and 559 deletions

View File

@ -10,18 +10,16 @@ class MyMarqueeText extends StatelessWidget {
super.key, super.key,
required this.text, required this.text,
required this.textStyle, required this.textStyle,
this.enable = true,
}); });
final String text; final String text;
final TextStyle textStyle; final TextStyle textStyle;
final bool enable;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Marquee( return Marquee(
delay: Duration.zero, delay: Duration.zero,
duration: Duration(seconds: enable ? 16 : 0), duration: const Duration(seconds: 40),
pause: Duration.zero, pause: Duration.zero,
gap: 80, gap: 80,
child: Text(text, style: textStyle), child: Text(text, style: textStyle),

View File

@ -5,38 +5,41 @@ import 'package:flutter/material.dart';
class BaseEasyRefresh extends StatelessWidget { class BaseEasyRefresh extends StatelessWidget {
final EasyRefreshController? controller; final EasyRefreshController? controller;
final bool noMoreRefresh;
final bool noMoreLoad;
final bool refreshOnStart; final bool refreshOnStart;
final bool resetAfterRefresh;
final Header? header; final Header? header;
final Footer? footer; final Footer? footer;
final FutureOr Function()? onRefresh; final FutureOr Function()? onRefresh;
final FutureOr Function()? onLoad; final FutureOr Function()? onLoad;
final Widget child; final ScrollController? scrollController;
final Widget Function(BuildContext context, ScrollPhysics physics) childBuilder;
const BaseEasyRefresh({ const BaseEasyRefresh({
super.key, super.key,
this.controller, this.controller,
this.noMoreRefresh = false,
this.noMoreLoad = false,
this.refreshOnStart = false, this.refreshOnStart = false,
this.resetAfterRefresh = true,
this.header, this.header,
this.footer, this.footer,
required this.child,
this.onRefresh, this.onRefresh,
this.onLoad, this.onLoad,
this.scrollController,
required this.childBuilder,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return EasyRefresh( return EasyRefresh.builder(
refreshOnStart: refreshOnStart, refreshOnStart: refreshOnStart,
resetAfterRefresh: resetAfterRefresh,
controller: controller, controller: controller,
header: header, header: header ?? const ClassicHeader(),
footer: footer, footer: footer ?? const ClassicFooter(),
onRefresh: onRefresh, onRefresh: onRefresh,
onLoad: onLoad, onLoad: onLoad,
child: child, scrollController: scrollController,
triggerAxis: Axis.vertical,
childBuilder: childBuilder,
); );
} }
} }

View File

@ -18,11 +18,15 @@ class ViewStateWidget extends StatelessWidget {
required this.viewState, required this.viewState,
required this.child, required this.child,
this.cpiBgColor, this.cpiBgColor,
this.showTryAgain = false,
this.onTapTryAgain,
}); });
final ViewState viewState; final ViewState viewState;
final Widget child; final Widget child;
final Color? cpiBgColor; final Color? cpiBgColor;
final bool showTryAgain;
final Function()? onTapTryAgain;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -32,7 +36,10 @@ class ViewStateWidget extends StatelessWidget {
case ViewState.loading: case ViewState.loading:
return loadingView(backgroundColor: cpiBgColor); return loadingView(backgroundColor: cpiBgColor);
case ViewState.empty: case ViewState.empty:
return AppConfig.appSideEnum == AppSideEnum.sideA ? emptyViewA() : emptyViewB(); return AppConfig.appSideEnum == AppSideEnum.sideA ? emptyViewA() : emptyViewB(
showTryAgain: showTryAgain,
onTapTryAgain: onTapTryAgain,
);
case ViewState.error: case ViewState.error:
return errorView(); return errorView();
} }
@ -46,14 +53,14 @@ Widget loadingView({
}) { }) {
return Center( return Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 3, strokeWidth: 2.w,
color: AppConfig.appSideEnum == AppSideEnum.sideA ? color : seedColor, color: AppConfig.appSideEnum == AppSideEnum.sideA ? color : seedColor,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
), ),
); );
} }
/// /// A
Widget emptyViewA({String? msg, Color? textColor}) { Widget emptyViewA({String? msg, Color? textColor}) {
return Center( return Center(
child: Text( child: Text(
@ -67,13 +74,55 @@ Widget emptyViewA({String? msg, Color? textColor}) {
); );
} }
/// 2 /// B
Widget emptyViewB() { Widget emptyViewB({
required bool showTryAgain,
Function()? onTapTryAgain,
}) {
return Center( return Center(
child: Image.asset( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
Assets.sideBEmpty, Assets.sideBEmpty,
width: 180.w, width: 180.w,
height: 160.h, 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,
),
),
),
),
],
],
), ),
); );
} }

View File

@ -3,6 +3,7 @@
// Description: // Description:
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:tone_snap/utils/obj_util.dart';
class MusicCacheManager { class MusicCacheManager {
static const key = 'musicCacheKey'; static const key = 'musicCacheKey';
@ -19,7 +20,8 @@ class MusicCacheManager {
} }
/// ///
static Future<FileInfo?> checkCache(String videoId) async { static Future<FileInfo?> checkCache(String? videoId) async {
return await instance.getFileFromCache(getCacheKey(videoId)); if (ObjUtil.isEmpty(videoId)) return null;
return await instance.getFileFromCache(getCacheKey(videoId!));
} }
} }

View File

@ -29,6 +29,8 @@ class PlaylistModel extends HiveObject {
String? coverUrl; String? coverUrl;
@HiveField(6) @HiveField(6)
String? subtitle; String? subtitle;
@HiveField(7)
String? musicType;
PlaylistModel({ PlaylistModel({
required this.id, required this.id,
@ -38,6 +40,7 @@ class PlaylistModel extends HiveObject {
this.params, this.params,
this.coverUrl, this.coverUrl,
this.subtitle, this.subtitle,
this.musicType,
}); });
PlaylistModel copyWith({ PlaylistModel copyWith({
@ -48,6 +51,7 @@ class PlaylistModel extends HiveObject {
String? params, String? params,
String? coverUrl, String? coverUrl,
String? subtitle, String? subtitle,
String? musicType,
}) => }) =>
PlaylistModel( PlaylistModel(
id: id, id: id,
@ -57,6 +61,7 @@ class PlaylistModel extends HiveObject {
params: params ?? this.params, params: params ?? this.params,
coverUrl: coverUrl ?? this.coverUrl, coverUrl: coverUrl ?? this.coverUrl,
subtitle: subtitle ?? this.subtitle, subtitle: subtitle ?? this.subtitle,
musicType: musicType ?? this.musicType,
); );
factory PlaylistModel.fromJson(String str) => PlaylistModel.fromMap(json.decode(str)); factory PlaylistModel.fromJson(String str) => PlaylistModel.fromMap(json.decode(str));
@ -71,6 +76,7 @@ class PlaylistModel extends HiveObject {
params: json["params"], params: json["params"],
coverUrl: json["coverUrl"], coverUrl: json["coverUrl"],
subtitle: json["subtitle"], subtitle: json["subtitle"],
musicType: json["musicType"],
); );
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
@ -81,5 +87,6 @@ class PlaylistModel extends HiveObject {
"params": params, "params": params,
"coverUrl": coverUrl, "coverUrl": coverUrl,
"subtitle": subtitle, "subtitle": subtitle,
"musicType": musicType,
}; };
} }

View File

@ -61,8 +61,6 @@ class DioClient {
/// ///
Future request<T>( Future request<T>(
String path, { String path, {
bool showLoading = false,
bool showToast = false,
required RequestMethod requestMethod, required RequestMethod requestMethod,
dynamic data, dynamic data,
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
@ -72,6 +70,8 @@ class DioClient {
T Function(Map<String, dynamic>)? formJson, T Function(Map<String, dynamic>)? formJson,
required Function(T? result) success, required Function(T? result) success,
Function(BaseError baseError)? fail, Function(BaseError baseError)? fail,
bool showLoading = false,
bool showToast = false,
}) async { }) async {
try { try {
BaseEasyLoading.loading(show: showLoading); BaseEasyLoading.loading(show: showLoading);

View File

@ -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( return await _box.add(PlaylistModel(
id: id, id: id,
title: title, title: title,
params: params, params: params,
coverUrl: coverUrl, coverUrl: coverUrl,
subtitle: subtitle, subtitle: subtitle,
musicType: musicType,
)); ));
} }

View File

@ -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); final playlistModel = _box.get(id);
if (playlistModel != null && playlistModel.musicList != null) { if (playlistModel != null && playlistModel.musicList != null) {
if (playlistModel.musicList!.firstWhereOrNull((e) => e.videoId == videoId) != null) { if (playlistModel.musicList!.firstWhereOrNull((e) => e.videoId == videoId) != null) {
playlistModel.musicList!.removeWhere((e) => e.videoId == videoId); playlistModel.musicList!.removeWhere((e) => e.videoId == videoId);
_box.put(id, playlistModel); await _box.put(id, playlistModel);
return true;
} }
} }
return false;
} }
/// ///

View File

@ -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/log_util.dart';
import 'package:tone_snap/utils/obj_util.dart'; import 'package:tone_snap/utils/obj_util.dart';
class DownloadManager { class DownloadManager extends GetxController {
static final DownloadManager _instance = DownloadManager._getInstance(); 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 = []; @override
void onInit() {
DownloadManager._getInstance() { super.onInit();
if (tq == null) { if (tq == null) {
tq ??= MemoryTaskQueue(); tq ??= MemoryTaskQueue();
tq!.maxConcurrent = 3; // no more than 5 tasks active at any one time 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 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().addTaskQueue(tq!); // 'connects' the TaskQueue to the FileDownloader
FileDownloader().updates.listen((update) async { // listen to updates as per usual 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 (musicModel == null) return;
if (update.runtimeType == TaskStatusUpdate) { if (update.runtimeType == TaskStatusUpdate) {
TaskStatus taskStatus = (update as TaskStatusUpdate).status; TaskStatus taskStatus = (update as TaskStatusUpdate).status;
LogUtil.d('${update.task.filename},任务状态: $taskStatus'); LogUtil.d('${update.task.filename}任务状态: $taskStatus');
musicModel.update((fn) => fn?.taskStatus = taskStatus); musicModel.taskStatus = taskStatus;
switch (taskStatus) { switch (taskStatus) {
case TaskStatus.enqueued: case TaskStatus.enqueued:
break; break;
@ -48,8 +49,8 @@ class DownloadManager {
break; break;
case TaskStatus.complete: case TaskStatus.complete:
LogUtil.d('音乐下载路径:${await update.task.filePath()}'); LogUtil.d('音乐下载路径:${await update.task.filePath()}');
musicModel.value.localPath = await update.task.filePath(); musicModel.localPath = await update.task.filePath();
OfflineBox().add(musicModel.value.copyWith()); OfflineBox().add(musicModel);
downloadList.remove(musicModel); downloadList.remove(musicModel);
BaseEasyLoading.toast('Download completed'); BaseEasyLoading.toast('Download completed');
if (Get.isRegistered<PersonalMusicLibraryController>()) { if (Get.isRegistered<PersonalMusicLibraryController>()) {
@ -78,26 +79,25 @@ class DownloadManager {
} }
} }
if (update.runtimeType == TaskProgressUpdate) { if (update.runtimeType == TaskProgressUpdate) {
LogUtil.d('${update.task.filename},下载进度: $update'); LogUtil.d('${update.task.filename}下载进度: $update');
musicModel.update((fn) => fn?.progress = (update as TaskProgressUpdate).progress); musicModel.progress = (update as TaskProgressUpdate).progress;
} }
updateDownloadState();
}); });
} }
} }
/// ///
void downloadMusic(Rx<MusicModel>? musicModel) { void downloadMusic(MusicModel? m) {
if (musicModel == null) return; if (m == null) return;
musicModel.update((fn) { MusicModel musicModel = m.copyWith();
fn?.taskStatus = TaskStatus.enqueued; musicModel.taskStatus = TaskStatus.enqueued;
fn?.cancelToken = CancelToken(); musicModel.cancelToken = CancelToken();
}); downloadList.add(musicModel);
updateDownloadState();
_getMusicUrl(musicModel, (url, mimeType) async { _getMusicUrl(musicModel, (url, mimeType) async {
if (ObjUtil.isEmpty(url)) { if (ObjUtil.isNotEmpty(url)) {
BaseEasyLoading.toast('Resource acquisition failed');
musicModel.update((fn) => fn?.taskStatus = TaskStatus.failed);
return;
}
String extension = mimeType ?? 'mp4'; String extension = mimeType ?? 'mp4';
if (ObjUtil.isNotEmpty(mimeType)) { if (ObjUtil.isNotEmpty(mimeType)) {
// mimeType // mimeType
@ -105,9 +105,9 @@ class DownloadManager {
// //
extension = type.split('/')[1]; extension = type.split('/')[1];
} }
final filename = '${musicModel.value.title}_${DateUtil.getNowTimestamp()}.$extension'; final filename = '${musicModel.title}_${DateUtil.getNowTimestamp()}.$extension';
final task = DownloadTask( final task = DownloadTask(
taskId: musicModel.value.videoId, taskId: musicModel.videoId,
url: url!, url: url!,
filename: filename, filename: filename,
directory: LocalPathUtil.getMusicDownloadDir(), directory: LocalPathUtil.getMusicDownloadDir(),
@ -118,37 +118,35 @@ class DownloadManager {
metaData: '', metaData: '',
); );
tq?.add(task); 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( PlayerModel? playerModel = await MusicApi.player(
videoId: musicModel.value.videoId, videoId: musicModel.videoId,
cancelToken: musicModel.value.cancelToken, cancelToken: musicModel.cancelToken,
fail: (baseError) { fail: (baseError) {
if (baseError.code == DioExceptionType.cancel.index) { if (baseError.code == DioExceptionType.cancel.index) {
musicModel.update((fn) { musicModel.taskStatus = TaskStatus.canceled;
fn?.taskStatus = TaskStatus.canceled; musicModel.cancelToken = null;
fn?.cancelToken = null;
});
} else { } else {
musicModel.update((fn) { musicModel.taskStatus = TaskStatus.failed;
fn?.taskStatus = TaskStatus.failed; musicModel.cancelToken = null;
fn?.cancelToken = null;
});
} }
downloadList.remove(musicModel);
updateDownloadState();
} }
); );
if (playerModel != null) { if (playerModel != null) {
if (ObjUtil.isEmpty(musicModel.value.coverUrl)) { if (ObjUtil.isEmpty(musicModel.coverUrl)) {
var thumbnails = playerModel.videoDetails?.thumbnail?.thumbnails; var thumbnails = playerModel.videoDetails?.thumbnail?.thumbnails;
if (thumbnails != null && thumbnails.isNotEmpty) { if (thumbnails != null && thumbnails.isNotEmpty) {
musicModel.value.coverUrl = thumbnails.last.url; musicModel.coverUrl = thumbnails.last.url;
} }
} }
if (ObjUtil.isEmpty(musicModel.value.musicType)) { if (ObjUtil.isEmpty(musicModel.musicType)) {
musicModel.value.musicType = playerModel.videoDetails?.musicVideoType; musicModel.musicType = playerModel.videoDetails?.musicVideoType;
} }
var formats = playerModel.streamingData?.formats; var formats = playerModel.streamingData?.formats;
if (formats != null && formats.isNotEmpty) { if (formats != null && formats.isNotEmpty) {
@ -157,9 +155,24 @@ class DownloadManager {
} }
} }
void cancelDownload(Rx<MusicModel>? musicModel) { void cancelDownload(String? videoId) {
if (musicModel == null || musicModel.value.videoId == null) return; if (ObjUtil.isEmpty(videoId)) return;
musicModel.value.cancelToken?.cancel(); final m = getMusicModel(videoId);
FileDownloader().cancelTaskWithId(musicModel.value.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;
} }
} }

View File

@ -79,7 +79,7 @@ class MyApp extends StatelessWidget {
MusicBar().hide(); MusicBar().hide();
} else { } else {
if (Get.isRegistered<MusicPlayerController>()) { if (Get.isRegistered<MusicPlayerController>()) {
if (MusicPlayerController.to.getMusicModel()?.value.videoId != null) { if (MusicPlayerController.to.getMusicModel()?.videoId != null) {
if (Get.isBottomSheetOpen != null && Get.isBottomSheetOpen!) { if (Get.isBottomSheetOpen != null && Get.isBottomSheetOpen!) {
MusicBar().hide(); MusicBar().hide();
} else { } else {

View File

@ -1,10 +1,11 @@
import 'dart:ffi';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart'; import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/view_state_widget.dart'; import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.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/browse_model.dart';
import 'package:tone_snap/data/models/music_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/data/storage/collect_playlists_box.dart';
import 'package:tone_snap/modules/sideb/collect_playlists/collect_playlists_controller.dart'; import 'package:tone_snap/modules/sideb/collect_playlists/collect_playlists_controller.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_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 musicPlayerController = MusicPlayerController.to;
var browseId = ''; var browseId = '';
var params = ''; var params = '';
var musicType = '';
/// url /// url
var coverUrl = ''.obs; var coverUrl = ''.obs;
@ -25,7 +27,7 @@ class AlbumSongListController extends GetxController {
var viewState = ViewState.loading.obs; var viewState = ViewState.loading.obs;
/// / /// /
var musicList = <Rx<MusicModel>>[].obs; var musicList = <MusicModel>[].obs;
/// ///
var isCollect = false.obs; var isCollect = false.obs;
@ -34,6 +36,7 @@ class AlbumSongListController extends GetxController {
void onInit() { void onInit() {
super.onInit(); super.onInit();
Map<String, dynamic> arguments = Get.arguments; Map<String, dynamic> arguments = Get.arguments;
musicType = arguments['musicType'] ?? '';
browseId = arguments['browseId'] ?? ''; browseId = arguments['browseId'] ?? '';
params = arguments['params'] ?? ''; params = arguments['params'] ?? '';
coverUrl.value = arguments['coverUrl'] ?? ''; coverUrl.value = arguments['coverUrl'] ?? '';
@ -129,7 +132,7 @@ class AlbumSongListController extends GetxController {
if (playlistItemData != null) { if (playlistItemData != null) {
musicModel.videoId = playlistItemData.videoId; 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) { if (musicList.isNotEmpty) {
int index = 0; int index = 0;
if (type == 1) { 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); 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); for (var o in musicList) {
if (ObjUtil.isEmpty(o.coverUrl)) {
o.coverUrl = coverUrl.value;
} }
} }
musicPlayerController.playMusic(musicList[index].videoId, playList: musicList);
///
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);
}
}
}
}
}
}
}
return playList;
} }
/// ///
@ -212,6 +173,7 @@ class AlbumSongListController extends GetxController {
params: params, params: params,
coverUrl: coverUrl.value, coverUrl: coverUrl.value,
subtitle: subtitle, subtitle: subtitle,
musicType: musicType,
); );
BaseEasyLoading.toast('Collected'); BaseEasyLoading.toast('Collected');
} }

View File

@ -6,6 +6,7 @@ import 'package:get/get.dart';
import 'package:tone_snap/components/base_scrollbar.dart'; import 'package:tone_snap/components/base_scrollbar.dart';
import 'package:tone_snap/components/network_image_widget.dart'; import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/components/view_state_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/generated/assets.dart';
import 'package:tone_snap/modules/sideb/album_song_list/album_song_list_controller.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'; import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
@ -109,9 +110,9 @@ class AlbumSongListView extends StatelessWidget {
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
_buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', 0), _buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', false),
SizedBox(width: 10.w), 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( return IntrinsicWidth(
child: GestureDetector( child: GestureDetector(
onTap: () => controller.onTapPlayAll(type), onTap: () => controller.onTapPlayAll(isShuffle),
child: Container( child: Container(
height: 32.h, height: 32.h,
padding: const EdgeInsets.symmetric(horizontal: 4).w, padding: const EdgeInsets.only(left: 4, right: 6).w,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16).r, borderRadius: BorderRadius.circular(16).r,
color: const Color(0x1A80F988), color: const Color(0x1A80F988),
@ -163,7 +164,7 @@ class AlbumSongListView extends StatelessWidget {
SizedBox(width: 4.w), SizedBox(width: 4.w),
Flexible( Flexible(
child: Visibility( child: Visibility(
visible: type == 0, visible: !isShuffle,
replacement: Text( replacement: Text(
label, label,
maxLines: 1, maxLines: 1,
@ -205,7 +206,9 @@ class AlbumSongListView extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
return MusicItem( return MusicItem(
musicModel: controller.musicList[index], 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]),
); );
}, },
); );

View File

@ -5,7 +5,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:tone_snap/components/base_easyloading.dart'; import 'package:tone_snap/components/base_easyloading.dart';
@ -63,7 +62,7 @@ class MusicPlayerController extends GetxController {
final List<String> _playHistory = []; final List<String> _playHistory = [];
/// ///
var playlist = <Rx<MusicModel>>[].obs; var playlist = <MusicModel>[].obs;
/// ///
var currentIndex = 0.obs; var currentIndex = 0.obs;
@ -83,18 +82,18 @@ class MusicPlayerController extends GetxController {
} }
void playMusic(String? videoId, {List<MusicModel>? playList, bool isCurrentPlaylist = false}) { void playMusic(String? videoId, {List<MusicModel>? playList, bool isCurrentPlaylist = false}) {
if (videoId == null) return; if (ObjUtil.isEmpty(videoId)) return;
if (!isCurrentPlaylist) { if (!isCurrentPlaylist) {
if (playList == null || playList.isEmpty) return; if (playList == null || playList.isEmpty) return;
_playHistory.clear(); _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; currentIndex.value = model != null ? playlist.indexOf(model) : 0;
_startPlay(); _startPlay();
} }
Rx<MusicModel>? getMusicModel() { MusicModel? getMusicModel() {
return playlist.isNotEmpty ? playlist[currentIndex.value] : null; return playlist.isNotEmpty ? playlist[currentIndex.value] : null;
} }
@ -104,7 +103,7 @@ class MusicPlayerController extends GetxController {
resetPlaybackStatus(); resetPlaybackStatus();
Get.currentRoute == AppRoutes.playPage ? MusicBar().hide() : MusicBar().show(); Get.currentRoute == AppRoutes.playPage ? MusicBar().hide() : MusicBar().show();
try { 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)) { if (model != null && ObjUtil.isNotEmpty(model.localPath)) {
// //
LogUtil.d('读取下载路径=${model.localPath}'); LogUtil.d('读取下载路径=${model.localPath}');
@ -115,7 +114,7 @@ class MusicPlayerController extends GetxController {
await _player.setFilePath(model.localPath!); await _player.setFilePath(model.localPath!);
} else { } else {
// //
FileInfo? fileInfo = await MusicCacheManager.checkCache(getMusicModel()!.value.videoId!); var fileInfo = await MusicCacheManager.checkCache(getMusicModel()?.videoId);
if (fileInfo != null) { if (fileInfo != null) {
// //
LogUtil.d('读取缓存路径=${fileInfo.file.path}'); LogUtil.d('读取缓存路径=${fileInfo.file.path}');
@ -129,7 +128,7 @@ class MusicPlayerController extends GetxController {
} }
await _player.setUrl(url!); 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}'); LogUtil.d('缓存下载路径=${fileInfo.file.path}');
}); });
} }
@ -146,7 +145,7 @@ class MusicPlayerController extends GetxController {
/// url /// url
Future<String?> _getMusicUrl() async { 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) { if (model != null && model.streamingData != null) {
var formats = model.streamingData?.formats; var formats = model.streamingData?.formats;
if (formats != null && playlist.isNotEmpty) { if (formats != null && playlist.isNotEmpty) {
@ -260,7 +259,7 @@ class MusicPlayerController extends GetxController {
bool historyExist = false; bool historyExist = false;
for (var i = _playHistory.length - 1; i >= 0; --i) { for (var i = _playHistory.length - 1; i >= 0; --i) {
var history = _playHistory[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) { if (model != null) {
currentIndex.value = playlist.indexOf(model); currentIndex.value = playlist.indexOf(model);
_playHistory.remove(history); _playHistory.remove(history);
@ -293,7 +292,7 @@ class MusicPlayerController extends GetxController {
break; break;
case PlayMode.random: case PlayMode.random:
// //
_playHistory.add(getMusicModel()!.value.videoId!); _playHistory.add(getMusicModel()!.videoId!);
currentIndex.value = _getRandomNumber(); currentIndex.value = _getRandomNumber();
_startPlay(); _startPlay();
break; break;

View File

@ -14,11 +14,12 @@ class CustomPlaylistController extends GetxController {
static CustomPlaylistController get to => Get.find<CustomPlaylistController>(); static CustomPlaylistController get to => Get.find<CustomPlaylistController>();
var musicPlayerController = MusicPlayerController.to; var musicPlayerController = MusicPlayerController.to;
late String playlistModelId; late String playlistModelId;
var playlistModel = Rx<PlaylistModel?>(null); late PlaylistModel playlistModel;
var viewState = ViewState.normal.obs; var title = ''.obs;
var musicList = <Rx<MusicModel>>[].obs;
var showSearch = false.obs; var showSearch = false.obs;
var textEditingController = TextEditingController(); var textEditingController = TextEditingController();
var viewState = ViewState.normal.obs;
var musicList = <MusicModel>[].obs;
@override @override
void onInit() { void onInit() {
@ -34,21 +35,21 @@ class CustomPlaylistController extends GetxController {
} }
void getPlaylistMode() { void getPlaylistMode() {
playlistModel.value = PlaylistsBox().getPlaylistModel(playlistModelId); if (PlaylistsBox().getPlaylistModel(playlistModelId) != null) {
playlistModel.update((fn) {}); playlistModel = PlaylistsBox().getPlaylistModel(playlistModelId)!;
title.value = playlistModel.title;
_getList(); _getList();
} }
}
void _getList() { void _getList() {
if (playlistModel.value != null && playlistModel.value!.musicList != null) { musicList.value = playlistModel.musicList?.toList() ?? <MusicModel>[];
musicList.value = playlistModel.value!.musicList!.map((e) => e.obs).toList();
}
viewState.value = musicList.isNotEmpty ? ViewState.normal : ViewState.empty; viewState.value = musicList.isNotEmpty ? ViewState.normal : ViewState.empty;
} }
String? getFirstCoverUrl() { String? getFirstCoverUrl() {
if (musicList.isNotEmpty) { if (musicList.isNotEmpty) {
return musicList.first.value.coverUrl; return musicList.first.coverUrl;
} }
return null; return null;
} }
@ -56,18 +57,23 @@ class CustomPlaylistController extends GetxController {
void onTapMore() { void onTapMore() {
Get.bottomSheet( Get.bottomSheet(
MorePlaylistBottomSheetView( MorePlaylistBottomSheetView(
playlistModel: playlistModel.value!, playlistModel: playlistModel,
), ),
); );
} }
Future<void> onTapPlayAll(int type) async { Future<void> onTapPlayAll(bool isShuffle) async {
if (musicList.isNotEmpty) { if (musicList.isNotEmpty) {
int index = 0; int index = 0;
if (type == 1) { 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); 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) { void onTapSearch(String keywords) {
if (ObjUtil.isNotEmpty(keywords)) { if (ObjUtil.isNotEmpty(keywords)) {
final result = <Rx<MusicModel>>[].obs; final result = <MusicModel>[];
playlistModel.value!.musicList!.map((e) { playlistModel.musicList!.map((e) {
if (e.title!.toLowerCase().contains(keywords.trim().toLowerCase())) { if (e.title!.toLowerCase().contains(keywords.trim().toLowerCase())) {
result.add(e.obs); result.add(e);
} }
}).toList(); }).toList();
musicList.value = result; musicList.value = result;
@ -99,7 +105,7 @@ class CustomPlaylistController extends GetxController {
void onTapItem(MusicModel musicModel) { void onTapItem(MusicModel musicModel) {
Get.toNamed(AppRoutes.playPage, arguments: { Get.toNamed(AppRoutes.playPage, arguments: {
'videoId': musicModel.videoId, 'videoId': musicModel.videoId,
'playList': musicList.map((e) => e.value).toList(), 'playList': musicList.toList(),
}); });
} }
} }

View File

@ -101,7 +101,7 @@ class CustomPlaylistView extends StatelessWidget {
padding: const EdgeInsets.only(top: 20.0).h, padding: const EdgeInsets.only(top: 20.0).h,
child: Obx(() { child: Obx(() {
return MyMarqueeText( return MyMarqueeText(
text: ObjUtil.getStr(controller.playlistModel.value!.title), text: ObjUtil.getStr(controller.title.value),
textStyle: TextStyle( textStyle: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18.sp, fontSize: 18.sp,
@ -115,7 +115,7 @@ class CustomPlaylistView extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 2).h, padding: const EdgeInsets.only(top: 2).h,
child: Text( 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, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@ -138,9 +138,9 @@ class CustomPlaylistView extends StatelessWidget {
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
_buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', 0), _buildPlayAll(Assets.sideBPlaylistPlayAll, 'Play all', false),
SizedBox(width: 10.w), 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( return IntrinsicWidth(
child: GestureDetector( child: GestureDetector(
onTap: () => controller.onTapPlayAll(type), onTap: () => controller.onTapPlayAll(isShuffle),
child: Container( child: Container(
height: 32.h, height: 32.h,
padding: const EdgeInsets.symmetric(horizontal: 4).w, padding: const EdgeInsets.only(left: 4, right: 6).w,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16).r, borderRadius: BorderRadius.circular(16).r,
color: const Color(0x1A80F988), color: const Color(0x1A80F988),
@ -236,7 +236,7 @@ class CustomPlaylistView extends StatelessWidget {
SizedBox(width: 4.w), SizedBox(width: 4.w),
Flexible( Flexible(
child: Visibility( child: Visibility(
visible: type == 0, visible: !isShuffle,
replacement: Text( replacement: Text(
label, label,
maxLines: 1, maxLines: 1,
@ -273,17 +273,15 @@ class CustomPlaylistView extends StatelessWidget {
viewState: controller.viewState.value, viewState: controller.viewState.value,
child: BaseScrollbar( child: BaseScrollbar(
child: Obx(() { child: Obx(() {
return Visibility( return ListView.builder(
child: ListView.builder(
itemCount: controller.musicList.length, itemCount: controller.musicList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return MusicItem( return MusicItem(
musicModel: controller.musicList[index], musicModel: controller.musicList[index],
playlistModelId: controller.playlistModel.value!.id, playlistModelId: controller.playlistModelId,
onTapItem: () => controller.onTapItem(controller.musicList[index].value), 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,
// ),
// ),
// ),
// ),
// ],
// );
// }
} }

View File

@ -1,3 +1,5 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tone_snap/components/view_state_widget.dart'; import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.dart'; import 'package:tone_snap/data/api/music_api.dart';
@ -12,6 +14,10 @@ class HomeController extends GetxController {
var viewState = ViewState.loading.obs; var viewState = ViewState.loading.obs;
var groupList = <BrowseGroupModel>[].obs; var groupList = <BrowseGroupModel>[].obs;
String? visitorData; String? visitorData;
var refreshController = EasyRefreshController(
controlFinishRefresh: true,
);
var isRefresh = false;
@override @override
void onReady() { void onReady() {
@ -19,6 +25,23 @@ class HomeController extends GetxController {
firstBrowse(); 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 { Future<void> firstBrowse() async {
Map<String, dynamic> queryParameters = { Map<String, dynamic> queryParameters = {
@ -27,8 +50,9 @@ class HomeController extends GetxController {
}; };
BrowseModel? browseModel = await MusicApi.browse<BrowseModel>(queryParameters: queryParameters, formJson: BrowseModel.fromMap); BrowseModel? browseModel = await MusicApi.browse<BrowseModel>(queryParameters: queryParameters, formJson: BrowseModel.fromMap);
if (browseModel != null) { if (browseModel != null) {
_extractAssemblyData(browseModel); refreshController.finishRefresh();
_extractAssemblyData(browseModel);
// visitorData // visitorData
visitorData = browseModel.responseContext?.visitorData; visitorData = browseModel.responseContext?.visitorData;
@ -41,6 +65,8 @@ class HomeController extends GetxController {
clickTrackingParams = continuations[0].nextContinuationData?.clickTrackingParams; clickTrackingParams = continuations[0].nextContinuationData?.clickTrackingParams;
} }
_reBrowse(continuation: continuation, clickTrackingParams: clickTrackingParams); _reBrowse(continuation: continuation, clickTrackingParams: clickTrackingParams);
} else {
refreshController.finishRefresh(IndicatorResult.fail);
} }
viewState.value = groupList.isNotEmpty ? ViewState.normal : ViewState.empty; viewState.value = groupList.isNotEmpty ? ViewState.normal : ViewState.empty;
} }
@ -119,7 +145,7 @@ class HomeController extends GetxController {
} else { } else {
// //
if (runs != null && runs.isNotEmpty) { 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 (MusicTypeExtension.isThereAny(browseGroupModel.musicType)) {
if (isRefresh) {
groupList.clear();
isRefresh = false;
}
groupList.add(browseGroupModel); 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: { Get.toNamed(AppRoutes.albumSongList, arguments: {
'musicType': browseGroupModel.musicType,
'browseId': musicModel.browseId, 'browseId': musicModel.browseId,
'params': musicModel.params, 'params': musicModel.params,
'coverUrl': musicModel.coverUrl, 'coverUrl': musicModel.coverUrl,

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tone_snap/components/base_scrollbar.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/components/view_state_widget.dart';
import 'package:tone_snap/data/enum/music_type.dart'; import 'package:tone_snap/data/enum/music_type.dart';
import 'package:tone_snap/data/models/browse_group_model.dart'; import 'package:tone_snap/data/models/browse_group_model.dart';
@ -97,9 +98,16 @@ class HomeView extends GetView<HomeController> {
child: Obx(() { child: Obx(() {
return ViewStateWidget( return ViewStateWidget(
viewState: controller.viewState.value, viewState: controller.viewState.value,
child: BaseScrollbar( showTryAgain: true,
onTapTryAgain: controller.onTapTryAgain,
child: BaseEasyRefresh(
controller: controller.refreshController,
onRefresh: controller.onRefresh,
childBuilder: (context, physics) {
return BaseScrollbar(
child: Obx(() { child: Obx(() {
return ListView.separated( return ListView.separated(
physics: physics,
padding: const EdgeInsets.only(bottom: 16).h, padding: const EdgeInsets.only(bottom: 16).h,
itemCount: controller.groupList.length, itemCount: controller.groupList.length,
separatorBuilder: (context, index) => SizedBox(height: 16.h), separatorBuilder: (context, index) => SizedBox(height: 16.h),
@ -121,6 +129,8 @@ class HomeView extends GetView<HomeController> {
}, },
); );
}), }),
);
},
), ),
); );
}), }),
@ -136,9 +146,9 @@ class HomeView extends GetView<HomeController> {
itemCount: browseGroupModel.browseList != null ? browseGroupModel.browseList!.length : 0, itemCount: browseGroupModel.browseList != null ? browseGroupModel.browseList!.length : 0,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisCount: 3,
mainAxisSpacing: 0, mainAxisSpacing: 12.w,
crossAxisSpacing: 12.w, crossAxisSpacing: 12.w,
childAspectRatio: 60.w / (1.sw - 48.w), childAspectRatio: 60.w / (1.sw - 50.w),
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final musicModel = browseGroupModel.browseList![index]; final musicModel = browseGroupModel.browseList![index];
@ -163,7 +173,7 @@ class HomeView extends GetView<HomeController> {
final musicModel = browseGroupModel.browseList![index]; final musicModel = browseGroupModel.browseList![index];
return BrowseItemAlbumSongList( return BrowseItemAlbumSongList(
musicModel: musicModel, musicModel: musicModel,
onTap: () => controller.openAlbumSong(musicModel), onTap: () => controller.openAlbumSong(browseGroupModel, musicModel),
); );
}, },
), ),

View File

@ -7,7 +7,7 @@ import 'package:tone_snap/routes/app_routes.dart';
class LoveSongsController extends GetxController { class LoveSongsController extends GetxController {
static LoveSongsController get to => Get.find<LoveSongsController>(); static LoveSongsController get to => Get.find<LoveSongsController>();
var viewState = ViewState.loading.obs; var viewState = ViewState.loading.obs;
var loveList = <Rx<MusicModel>>[].obs; var loveList = <MusicModel>[].obs;
@override @override
void onReady() { void onReady() {
@ -16,7 +16,7 @@ class LoveSongsController extends GetxController {
} }
void getLoveList() { void getLoveList() {
loveList.value = LoveSongsBox().getReversedList().map((e) => e.obs).toList(); loveList.value = LoveSongsBox().getReversedList();
viewState.value = loveList.isNotEmpty ? ViewState.normal : ViewState.empty; viewState.value = loveList.isNotEmpty ? ViewState.normal : ViewState.empty;
} }

View File

@ -75,7 +75,7 @@ class LoveSongsView extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
return MusicItem( return MusicItem(
musicModel: controller.loveList[index], musicModel: controller.loveList[index],
onTapItem: () => controller.onTapItem(controller.loveList[index].value), onTapItem: () => controller.onTapItem(controller.loveList[index]),
); );
}, },
); );

View File

@ -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/offline/offline_controller.dart';
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_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/modules/sideb/playlists/playlists_controller.dart';
import 'package:tone_snap/utils/obj_util.dart';
class MoreBottomSheetController extends GetxController { class MoreBottomSheetController extends GetxController {
late Rx<MusicModel> musicModel; late MusicModel musicModel;
String? playlistModelId; String? playlistModelId;
var isLove = false.obs; var isLove = false.obs;
void setMusicModel(Rx<MusicModel> musicModel, {String? playlistModelId}) { void setMusicModel(MusicModel musicModel, {String? playlistModelId}) {
this.musicModel = musicModel; this.musicModel = musicModel;
this.playlistModelId = playlistModelId; this.playlistModelId = playlistModelId;
_checkIsLove(); _checkIsLove();
} }
void _checkIsLove() { void _checkIsLove() {
isLove.value = LoveSongsBox().checkLove(musicModel.value.videoId); isLove.value = LoveSongsBox().checkLove(musicModel.videoId);
} }
void onTapLove() async { void onTapLove() async {
if (musicModel.value.videoId == null) return; if (ObjUtil.isEmpty(musicModel.videoId)) return;
if (isLove.value) { if (isLove.value) {
await LoveSongsBox().delete(musicModel.value.videoId!); await LoveSongsBox().delete(musicModel.videoId!);
BaseEasyLoading.toast('Removed'); BaseEasyLoading.toast('Removed');
} else { } else {
await LoveSongsBox().add(musicModel.value.copyWith()); await LoveSongsBox().add(musicModel.copyWith());
BaseEasyLoading.toast('Collected'); BaseEasyLoading.toast('Collected');
} }
_checkIsLove(); _checkIsLove();
@ -48,14 +49,14 @@ class MoreBottomSheetController extends GetxController {
} }
void onTapDownload() { void onTapDownload() {
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) { if (OfflineBox().checkDownloaded(musicModel.videoId)) {
Get.dialog( Get.dialog(
RemindDialog( RemindDialog(
content: 'Confirm to remove this song?', content: 'Confirm to remove this song?',
confirmOnTap: () async { confirmOnTap: () async {
Get.back(); Get.back();
await OfflineBox().delete(musicModel.value.videoId!); await OfflineBox().delete(musicModel.videoId!);
musicModel.update((fn) => fn?.taskStatus = null); DownloadManager.to.updateDownloadState();
BaseEasyLoading.toast('Removed'); BaseEasyLoading.toast('Removed');
if (Get.isRegistered<PersonalMusicLibraryController>()) { if (Get.isRegistered<PersonalMusicLibraryController>()) {
PersonalMusicLibraryController.to.refreshOffline(); PersonalMusicLibraryController.to.refreshOffline();
@ -67,11 +68,11 @@ class MoreBottomSheetController extends GetxController {
), ),
); );
} else { } else {
if (musicModel.value.taskStatus == TaskStatus.enqueued if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|| musicModel.value.taskStatus == TaskStatus.running) { || DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
DownloadManager().cancelDownload(musicModel); DownloadManager.to.cancelDownload(musicModel.videoId);
} else { } else {
DownloadManager().downloadMusic(musicModel); DownloadManager.to.downloadMusic(musicModel);
} }
} }
} }
@ -80,15 +81,14 @@ class MoreBottomSheetController extends GetxController {
Get.back(); Get.back();
Get.bottomSheet( Get.bottomSheet(
AddToPlaylistBottomSheetView( AddToPlaylistBottomSheetView(
musicModel: musicModel.value, musicModel: musicModel,
), ),
); );
} }
Future<void> onTapRemove() async { Future<void> onTapRemove() async {
if (playlistModelId != null && musicModel.value.videoId != null) { if (ObjUtil.isNotEmpty(playlistModelId) && ObjUtil.isNotEmpty(musicModel.videoId)) {
bool result = await PlaylistsBox().removeMusic(playlistModelId!, musicModel.value.videoId!); await PlaylistsBox().removeMusic(playlistModelId!, musicModel.videoId!);
if (result) {
BaseEasyLoading.toast('Removed'); BaseEasyLoading.toast('Removed');
Get.back(); Get.back();
if (Get.isRegistered<PlaylistsController>()) { if (Get.isRegistered<PlaylistsController>()) {
@ -99,7 +99,6 @@ class MoreBottomSheetController extends GetxController {
} }
} }
} }
}
void onTapReport() { void onTapReport() {
Get.back(); Get.back();

View File

@ -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/models/music_model.dart';
import 'package:tone_snap/data/storage/offline_box.dart'; import 'package:tone_snap/data/storage/offline_box.dart';
import 'package:tone_snap/generated/assets.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/res/themes/app_colors.dart';
import 'package:tone_snap/utils/obj_util.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}); MoreBottomSheetView({super.key, required this.musicModel, this.playlistModelId});
final controller = Get.put(MoreBottomSheetController()); final controller = Get.put(MoreBottomSheetController());
final Rx<MusicModel> musicModel; final MusicModel musicModel;
final String? playlistModelId; final String? playlistModelId;
@override @override
@ -42,7 +43,7 @@ class MoreBottomSheetView extends StatelessWidget {
_buildDownload(), _buildDownload(),
_buildItem('Add to playlist', Assets.sideBAddToPlaylist, controller.onTapAddToPlaylist), _buildItem('Add to playlist', Assets.sideBAddToPlaylist, controller.onTapAddToPlaylist),
// _buildItem('Add to queue', Assets.sideBAddToQueue, () {}), // _buildItem('Add to queue', Assets.sideBAddToQueue, () {}),
if (playlistModelId != null) ...[ if (ObjUtil.isNotEmpty(playlistModelId)) ...[
_buildItem('Remove from list', Assets.sideBMoreRemove, controller.onTapRemove), _buildItem('Remove from list', Assets.sideBMoreRemove, controller.onTapRemove),
], ],
_buildItem('Report', Assets.sideBReport, controller.onTapReport), _buildItem('Report', Assets.sideBReport, controller.onTapReport),
@ -73,21 +74,19 @@ class MoreBottomSheetView extends StatelessWidget {
SizedBox(height: 20.h), SizedBox(height: 20.h),
Row( Row(
children: [ children: [
Obx(() { NetworkImageWidget(
return NetworkImageWidget( url: musicModel.coverUrl,
url: musicModel.value.coverUrl,
width: 50.w, width: 50.w,
height: 50.w, height: 50.w,
radius: 10.r, radius: 10.r,
); ),
}),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
ObjUtil.getStr(musicModel.value.title), ObjUtil.getStr(musicModel.title),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@ -96,7 +95,7 @@ class MoreBottomSheetView extends StatelessWidget {
), ),
), ),
Text( Text(
ObjUtil.getStr(musicModel.value.subtitle), ObjUtil.getStr(musicModel.subtitle),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@ -148,18 +147,20 @@ class MoreBottomSheetView extends StatelessWidget {
SizedBox( SizedBox(
width: 24.w, width: 24.w,
height: 24.w, height: 24.w,
child: Obx(() { child: GetBuilder<DownloadManager>(
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) { id: DownloadManager.to.downloadStateId,
builder: (_) {
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
return Image.asset(Assets.sideBDownloaded); return Image.asset(Assets.sideBDownloaded);
} else { } else {
if (musicModel.value.taskStatus == TaskStatus.enqueued) { if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued) {
return CircularProgressIndicator( return CircularProgressIndicator(
color: seedColor, color: seedColor,
strokeWidth: 2.w, strokeWidth: 2.w,
); );
} else if (musicModel.value.taskStatus == TaskStatus.running) { } else if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
return CircularProgressIndicator( return CircularProgressIndicator(
value: musicModel.value.progress, value: DownloadManager.to.getMusicModel(musicModel.videoId)?.progress,
backgroundColor: seedColor.withOpacity(0.2), backgroundColor: seedColor.withOpacity(0.2),
valueColor: const AlwaysStoppedAnimation<Color>(seedColor), valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
strokeWidth: 2.w, strokeWidth: 2.w,
@ -168,17 +169,20 @@ class MoreBottomSheetView extends StatelessWidget {
return Image.asset(Assets.sideBNotDownload1); return Image.asset(Assets.sideBNotDownload1);
} }
} }
}), },
),
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: Obx(() { child: GetBuilder<DownloadManager>(
id: DownloadManager.to.downloadStateId,
builder: (_) {
var text = ''; var text = '';
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) { if (OfflineBox().checkDownloaded(musicModel.videoId)) {
text = 'Removed'; text = 'Removed';
} else { } else {
if (musicModel.value.taskStatus == TaskStatus.enqueued if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|| musicModel.value.taskStatus == TaskStatus.running) { || DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
text = 'Cancel download'; text = 'Cancel download';
} else { } else {
text = 'Download'; text = 'Download';
@ -193,7 +197,8 @@ class MoreBottomSheetView extends StatelessWidget {
fontSize: 14.sp, fontSize: 14.sp,
), ),
); );
}), },
),
), ),
], ],
), ),

View File

@ -44,15 +44,15 @@ class MusicBarView extends StatelessWidget {
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
SizedBox( SizedBox(
width: 52.w, width: 51.w,
height: 52.w, height: 51.w,
child: Obx(() { child: Obx(() {
int comparison = musicPlayerController.positionDuration.value.compareTo(musicPlayerController.totalDuration.value); int comparison = musicPlayerController.positionDuration.value.compareTo(musicPlayerController.totalDuration.value);
double value = comparison < 0 ? (musicPlayerController.positionDuration.value.inSeconds / musicPlayerController.totalDuration.value.inSeconds).toDouble() : 0; double value = comparison < 0 ? (musicPlayerController.positionDuration.value.inSeconds / musicPlayerController.totalDuration.value.inSeconds).toDouble() : 0;
return CircularProgressIndicator( return CircularProgressIndicator(
value: value, value: value,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
strokeWidth: 4.w, strokeWidth: 3.w,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white), valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
); );
}), }),
@ -62,7 +62,7 @@ class MusicBarView extends StatelessWidget {
child: FittedBox( child: FittedBox(
fit: BoxFit.none, fit: BoxFit.none,
child: NetworkImageWidget( child: NetworkImageWidget(
url: musicPlayerController.getMusicModel()?.value.coverUrl, url: musicPlayerController.getMusicModel()?.coverUrl,
width: 48.w, width: 48.w,
height: 48.w, height: 48.w,
), ),
@ -79,7 +79,7 @@ class MusicBarView extends StatelessWidget {
children: [ children: [
Obx(() { Obx(() {
return MyMarqueeText( return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.title), text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.title),
textStyle: TextStyle( textStyle: TextStyle(
color: Colors.black, color: Colors.black,
fontSize: 16.sp, fontSize: 16.sp,
@ -90,7 +90,7 @@ class MusicBarView extends StatelessWidget {
SizedBox(height: 4.h), SizedBox(height: 4.h),
Obx(() { Obx(() {
return MyMarqueeText( return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.subtitle), text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.subtitle),
textStyle: TextStyle( textStyle: TextStyle(
color: Colors.black, color: Colors.black,
fontSize: 12.sp, fontSize: 12.sp,
@ -100,7 +100,7 @@ class MusicBarView extends StatelessWidget {
], ],
), ),
), ),
const SizedBox(width: 13), SizedBox(width: 13.w),
Obx(() { Obx(() {
return GestureDetector( return GestureDetector(
onTap: musicPlayerController.playPause, onTap: musicPlayerController.playPause,
@ -111,7 +111,7 @@ class MusicBarView extends StatelessWidget {
), ),
); );
}), }),
const SizedBox(width: 26), SizedBox(width: 26.w),
GestureDetector( GestureDetector(
onTap: musicPlayerController.nextTrack, onTap: musicPlayerController.nextTrack,
child: Image.asset( child: Image.asset(

View File

@ -7,7 +7,7 @@ import 'package:tone_snap/routes/app_routes.dart';
class OfflineController extends GetxController { class OfflineController extends GetxController {
static OfflineController get to => Get.find<OfflineController>(); static OfflineController get to => Get.find<OfflineController>();
var viewState = ViewState.loading.obs; var viewState = ViewState.loading.obs;
var offlineList = <Rx<MusicModel>>[].obs; var offlineList = <MusicModel>[].obs;
@override @override
void onReady() { void onReady() {
@ -16,7 +16,7 @@ class OfflineController extends GetxController {
} }
void getOfflineList() { void getOfflineList() {
offlineList.value = OfflineBox().getReversedList().map((e) => e.obs).toList(); offlineList.value = OfflineBox().getReversedList();
viewState.value = offlineList.isNotEmpty ? ViewState.normal : ViewState.empty; viewState.value = offlineList.isNotEmpty ? ViewState.normal : ViewState.empty;
} }

View File

@ -76,7 +76,7 @@ class OfflineView extends StatelessWidget {
return MusicItem( return MusicItem(
musicModel: controller.offlineList[index], musicModel: controller.offlineList[index],
showDownload: false, showDownload: false,
onTapItem: () => controller.onTapItem(controller.offlineList[index].value), onTapItem: () => controller.onTapItem(controller.offlineList[index]),
); );
}, },
); );

View File

@ -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/global/download_manager.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.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/modules/sideb/personal_music_library/personal_music_library_controller.dart';
import 'package:tone_snap/utils/obj_util.dart';
class PlayPageController extends GetxController { class PlayPageController extends GetxController {
static PlayPageController get to => Get.find<PlayPageController>();
var musicPlayerController = MusicPlayerController.to; var musicPlayerController = MusicPlayerController.to;
final loveStateId = 'loveStateId';
@override @override
void onReady() async { void onReady() async {
@ -85,30 +88,29 @@ class PlayPageController extends GetxController {
/// / /// /
Future<void> onTapLove() async { Future<void> onTapLove() async {
if (musicPlayerController.getMusicModel()?.value.videoId == null) return; if (ObjUtil.isEmpty(musicPlayerController.getMusicModel()?.videoId)) return;
final isLove = LoveSongsBox().checkLove(musicPlayerController.getMusicModel()!.value.videoId); final isLove = LoveSongsBox().checkLove(musicPlayerController.getMusicModel()!.videoId);
if (isLove) { if (isLove) {
await LoveSongsBox().delete(musicPlayerController.getMusicModel()!.value.videoId!); await LoveSongsBox().delete(musicPlayerController.getMusicModel()!.videoId!);
BaseEasyLoading.toast('Removed'); BaseEasyLoading.toast('Removed');
} else { } else {
await LoveSongsBox().add(musicPlayerController.getMusicModel()!.value.copyWith()); await LoveSongsBox().add(musicPlayerController.getMusicModel()!.copyWith());
BaseEasyLoading.toast('Collected'); BaseEasyLoading.toast('Collected');
} }
if (Get.isRegistered<PersonalMusicLibraryController>()) { if (Get.isRegistered<PersonalMusicLibraryController>()) {
PersonalMusicLibraryController.to.refreshLoveSongs(); PersonalMusicLibraryController.to.refreshLoveSongs();
} }
musicPlayerController.getMusicModel()!.update((fn) => fn?.isLove = LoveSongsBox().checkLove(musicPlayerController.getMusicModel()!.value.videoId)); update([loveStateId]);
} }
void onTapDownload() { void onTapDownload() {
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.value.videoId) if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.videoId)) {
|| musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.complete) {
Get.dialog( Get.dialog(
RemindDialog( RemindDialog(
content: 'Confirm to remove this song?', content: 'Confirm to remove this song?',
confirmOnTap: () async { confirmOnTap: () async {
await OfflineBox().delete(musicPlayerController.getMusicModel()!.value.videoId!); await OfflineBox().delete(musicPlayerController.getMusicModel()!.videoId!);
musicPlayerController.getMusicModel()!.update((fn) => fn?.taskStatus = null); DownloadManager.to.updateDownloadState();
BaseEasyLoading.toast('Removed'); BaseEasyLoading.toast('Removed');
if (Get.isRegistered<PersonalMusicLibraryController>()) { if (Get.isRegistered<PersonalMusicLibraryController>()) {
PersonalMusicLibraryController.to.refreshOffline(); PersonalMusicLibraryController.to.refreshOffline();
@ -117,11 +119,11 @@ class PlayPageController extends GetxController {
), ),
); );
} else { } else {
if (musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.enqueued if (DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.enqueued
|| musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.running) { || DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.running) {
DownloadManager().cancelDownload(musicPlayerController.getMusicModel()); DownloadManager.to.cancelDownload(musicPlayerController.getMusicModel()?.videoId);
} else { } else {
DownloadManager().downloadMusic(musicPlayerController.getMusicModel()); DownloadManager.to.downloadMusic(musicPlayerController.getMusicModel());
} }
} }
} }

View File

@ -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/love_songs_box.dart';
import 'package:tone_snap/data/storage/offline_box.dart'; import 'package:tone_snap/data/storage/offline_box.dart';
import 'package:tone_snap/generated/assets.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/controllers/music_player_controller.dart';
import 'package:tone_snap/modules/sideb/play_page/play_page_controller.dart'; import 'package:tone_snap/modules/sideb/play_page/play_page_controller.dart';
import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart'; import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
@ -67,7 +68,7 @@ class PlayPageView extends StatelessWidget {
children: [ children: [
Obx(() { Obx(() {
return NetworkImageWidget( return NetworkImageWidget(
url: musicPlayerController.getMusicModel()?.value.coverUrl, url: musicPlayerController.getMusicModel()?.coverUrl,
width: 1.sw, width: 1.sw,
height: 1.sh, height: 1.sh,
noPlaceholder: true, noPlaceholder: true,
@ -130,7 +131,7 @@ class PlayPageView extends StatelessWidget {
Widget _buildCover() { Widget _buildCover() {
return Obx(() { return Obx(() {
return NetworkImageWidget( return NetworkImageWidget(
url: musicPlayerController.getMusicModel()?.value.coverUrl, url: musicPlayerController.getMusicModel()?.coverUrl,
width: 1.sw, width: 1.sw,
height: 330.h, height: 330.h,
radius: 16.r, radius: 16.r,
@ -150,7 +151,7 @@ class PlayPageView extends StatelessWidget {
children: [ children: [
Obx(() { Obx(() {
return MyMarqueeText( return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.title), text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.title),
textStyle: TextStyle( textStyle: TextStyle(
color: const Color(0xD9FFFFFF), color: const Color(0xD9FFFFFF),
fontSize: 22.sp, fontSize: 22.sp,
@ -160,7 +161,7 @@ class PlayPageView extends StatelessWidget {
SizedBox(height: 6.h), SizedBox(height: 6.h),
Obx(() { Obx(() {
return MyMarqueeText( return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.value.subtitle), text: ObjUtil.getStr(musicPlayerController.getMusicModel()?.subtitle),
textStyle: TextStyle( textStyle: TextStyle(
color: const Color(0x99EEEEEE), color: const Color(0x99EEEEEE),
fontSize: 12.sp, fontSize: 12.sp,
@ -181,13 +182,16 @@ class PlayPageView extends StatelessWidget {
onTap: controller.onTapLove, onTap: controller.onTapLove,
child: Padding( child: Padding(
padding: const EdgeInsets.all(6).w, padding: const EdgeInsets.all(6).w,
child: Obx(() { child: GetBuilder<PlayPageController>(
id: PlayPageController.to.loveStateId,
builder: (_) {
return Image.asset( return Image.asset(
LoveSongsBox().checkLove(musicPlayerController.getMusicModel()?.value.videoId) ? Assets.sideBLoveSolid : Assets.sideBLove, LoveSongsBox().checkLove(musicPlayerController.getMusicModel()?.videoId) ? Assets.sideBLoveSolid : Assets.sideBLove,
width: 24.w, width: 24.w,
height: 24.w, height: 24.w,
); );
}), },
),
), ),
), ),
), ),
@ -208,19 +212,20 @@ class PlayPageView extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: 24.w, width: 24.w,
height: 24.w, height: 24.w,
child: Obx(() { child: GetBuilder<DownloadManager>(
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.value.videoId) id: DownloadManager.to.downloadStateId,
|| musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.complete) { builder: (_) {
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.videoId)) {
return Image.asset(Assets.sideBDownloaded); return Image.asset(Assets.sideBDownloaded);
} else { } else {
if (musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.enqueued) { if (DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.enqueued) {
return CircularProgressIndicator( return CircularProgressIndicator(
color: seedColor, color: seedColor,
strokeWidth: 2.w, strokeWidth: 2.w,
); );
} else if (musicPlayerController.getMusicModel()?.value.taskStatus == TaskStatus.running) { } else if (DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.taskStatus == TaskStatus.running) {
return CircularProgressIndicator( return CircularProgressIndicator(
value: musicPlayerController.getMusicModel()?.value.progress, value: DownloadManager.to.getMusicModel(musicPlayerController.getMusicModel()?.videoId)?.progress,
backgroundColor: seedColor.withOpacity(0.2), backgroundColor: seedColor.withOpacity(0.2),
valueColor: valueColor:
const AlwaysStoppedAnimation<Color>(seedColor), const AlwaysStoppedAnimation<Color>(seedColor),
@ -230,7 +235,8 @@ class PlayPageView extends StatelessWidget {
return Image.asset(Assets.sideBNotDownload1); return Image.asset(Assets.sideBNotDownload1);
} }
} }
}), },
),
), ),
), ),
), ),
@ -250,15 +256,17 @@ class PlayPageView extends StatelessWidget {
data: SliderTheme.of(context).copyWith( data: SliderTheme.of(context).copyWith(
activeTrackColor: seedColor, activeTrackColor: seedColor,
inactiveTrackColor: const Color(0x4DFFFFFF), inactiveTrackColor: const Color(0x4DFFFFFF),
disabledInactiveTrackColor: const Color(0x4DFFFFFF),
secondaryActiveTrackColor: seedColor.withOpacity(0.3), secondaryActiveTrackColor: seedColor.withOpacity(0.3),
trackHeight: 4.h, trackHeight: 4.h,
thumbColor: Colors.white, thumbColor: Colors.white,
disabledThumbColor: Colors.white,
thumbShape: RoundSliderThumbShape( thumbShape: RoundSliderThumbShape(
disabledThumbRadius: 8.w, disabledThumbRadius: 8.w,
enabledThumbRadius: 8.w, enabledThumbRadius: 8.w,
), ),
trackShape: FullWidthTrackShape(), trackShape: FullWidthTrackShape(),
overlayShape: RoundSliderOverlayShape(overlayRadius: 10.w), // overlayShape: RoundSliderOverlayShape(overlayRadius: 10.w),
), ),
child: Obx(() { child: Obx(() {
return Slider( return Slider(
@ -272,6 +280,7 @@ class PlayPageView extends StatelessWidget {
); );
}), }),
), ),
SizedBox(height: 4.h),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -380,9 +389,9 @@ class PlayPageView extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: 20.w, width: 20.w,
height: 20.w, height: 20.w,
child: const CircularProgressIndicator( child: CircularProgressIndicator(
color: seedColor, color: seedColor,
strokeWidth: 3, strokeWidth: 2.w,
), ),
), ),
), ),
@ -480,7 +489,7 @@ class PlayPageView extends StatelessWidget {
} }
Widget _buildPlayListItem(int index) { Widget _buildPlayListItem(int index) {
final musicModel = musicPlayerController.playlist[index].value; final musicModel = musicPlayerController.playlist[index];
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -503,13 +512,13 @@ class PlayPageView extends StatelessWidget {
children: [ children: [
Obx(() { Obx(() {
return Visibility( return Visibility(
visible: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId, visible: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId,
replacement: Text( replacement: Text(
ObjUtil.getStr(musicModel.title), ObjUtil.getStr(musicModel.title),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
? seedColor ? seedColor
: const Color(0xD9FFFFFF), : const Color(0xD9FFFFFF),
fontSize: 14.sp, fontSize: 14.sp,
@ -518,7 +527,7 @@ class PlayPageView extends StatelessWidget {
child: MyMarqueeText( child: MyMarqueeText(
text: ObjUtil.getStr(musicModel.title), text: ObjUtil.getStr(musicModel.title),
textStyle: TextStyle( textStyle: TextStyle(
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
? seedColor ? seedColor
: const Color(0xD9FFFFFF), : const Color(0xD9FFFFFF),
fontSize: 14.sp, fontSize: 14.sp,
@ -529,13 +538,13 @@ class PlayPageView extends StatelessWidget {
SizedBox(height: 8.h), SizedBox(height: 8.h),
Obx(() { Obx(() {
return Visibility( return Visibility(
visible: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId, visible: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId,
replacement: Text( replacement: Text(
ObjUtil.getStr(musicModel.subtitle), ObjUtil.getStr(musicModel.subtitle),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
? seedColor ? seedColor
: const Color(0x99FFFFFF), : const Color(0x99FFFFFF),
fontSize: 12.sp, fontSize: 12.sp,
@ -544,7 +553,7 @@ class PlayPageView extends StatelessWidget {
child: MyMarqueeText( child: MyMarqueeText(
text: ObjUtil.getStr(musicModel.subtitle), text: ObjUtil.getStr(musicModel.subtitle),
textStyle: TextStyle( textStyle: TextStyle(
color: musicPlayerController.getMusicModel()?.value.videoId == musicModel.videoId color: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId
? seedColor ? seedColor
: const Color(0x99FFFFFF), : const Color(0x99FFFFFF),
fontSize: 12.sp, fontSize: 12.sp,
@ -557,7 +566,7 @@ class PlayPageView extends StatelessWidget {
), ),
Obx(() { Obx(() {
return Visibility( return Visibility(
visible: musicPlayerController.getMusicModel()?.value.videoId != musicModel.videoId, visible: musicPlayerController.getMusicModel()?.videoId != musicModel.videoId,
child: ClipOval( child: ClipOval(
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
@ -577,7 +586,7 @@ class PlayPageView extends StatelessWidget {
); );
}), }),
Obx(() { 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);
}), }),
], ],
), ),

View File

@ -124,7 +124,7 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
await _cleanTab(); await _cleanTab();
searchResultViewState.value = ViewState.loading; searchResultViewState.value = ViewState.loading;
await _searchPreviewResult(value); 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); tabController.value = TabController(length: tabs.length, vsync: this);
} }
@ -257,6 +257,7 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
} }
} }
} }
}
for (var i = 0; i < this.tabs.length; ++i) { for (var i = 0; i < this.tabs.length; ++i) {
if (i == 0) { if (i == 0) {
pages.add(const SearchResultChildOptimumView()); pages.add(const SearchResultChildOptimumView());
@ -268,4 +269,3 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
} }
} }
} }
}

View File

@ -14,7 +14,7 @@ import 'package:tone_snap/utils/obj_util.dart';
class SearchResultChildController extends GetxController { class SearchResultChildController extends GetxController {
var viewState = ViewState.loading.obs; var viewState = ViewState.loading.obs;
var scrollController = ScrollController(); var scrollController = ScrollController();
var musicList = <Rx<MusicModel>>[].obs; var musicList = <MusicModel>[].obs;
String? params; String? params;
String? continuation; String? continuation;
String? clickTrackingParams; 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; viewState.value = musicList.isNotEmpty ? ViewState.normal : ViewState.empty;
} }
@ -124,7 +124,6 @@ class SearchResultChildController extends GetxController {
}; };
refreshController.callLoad(); refreshController.callLoad();
SearchResultMoreModel? searchResultMoreModel = await MusicApi.search<SearchResultMoreModel>(queryParameters: queryParameters, formJson: SearchResultMoreModel.fromMap); SearchResultMoreModel? searchResultMoreModel = await MusicApi.search<SearchResultMoreModel>(queryParameters: queryParameters, formJson: SearchResultMoreModel.fromMap);
final list = <MusicModel>[];
if (searchResultMoreModel != null) { if (searchResultMoreModel != null) {
var continuations = searchResultMoreModel.continuationContents?.musicShelfContinuation?.continuations; var continuations = searchResultMoreModel.continuationContents?.musicShelfContinuation?.continuations;
if (continuations != null && continuations.isNotEmpty) { if (continuations != null && continuations.isNotEmpty) {
@ -166,11 +165,10 @@ class SearchResultChildController extends GetxController {
musicModel.browseId = o.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId; musicModel.browseId = o.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId;
musicModel.musicType = o.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType; 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 (searchResultMoreModel != null) {
if (ObjUtil.isNotEmpty(continuation)) { if (ObjUtil.isNotEmpty(continuation)) {
refreshController.finishLoad(IndicatorResult.success); refreshController.finishLoad(IndicatorResult.success);
@ -197,6 +195,7 @@ class SearchResultChildController extends GetxController {
'coverUrl': musicModel.coverUrl, 'coverUrl': musicModel.coverUrl,
'title': musicModel.title, 'title': musicModel.title,
'subtitle': musicModel.subtitle, 'subtitle': musicModel.subtitle,
'musicType': musicModel.musicType,
}); });
} }
} }

View File

@ -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/refresh/base_easyrefresh.dart';
import 'package:tone_snap/components/view_state_widget.dart'; import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/enum/music_type.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 'package:tone_snap/modules/sideb/widgets/music_item.dart';
import '../../../data/models/search_result_tabbar_model.dart';
import 'search_result_child_controller.dart'; import 'search_result_child_controller.dart';
class SearchResultChildView extends GetView<SearchResultChildController> { class SearchResultChildView extends GetView<SearchResultChildController> {
@ -28,23 +28,26 @@ class SearchResultChildView extends GetView<SearchResultChildController> {
child: BaseEasyRefresh( child: BaseEasyRefresh(
controller: controller.refreshController, controller: controller.refreshController,
onLoad: controller.onLoad, onLoad: controller.onLoad,
child: BaseScrollbar( childBuilder: (context, physics) {
return BaseScrollbar(
scrollController: controller.scrollController, scrollController: controller.scrollController,
child: Obx(() { child: Obx(() {
return ListView.builder( return ListView.builder(
physics: physics,
controller: controller.scrollController, controller: controller.scrollController,
itemCount: controller.musicList.length, itemCount: controller.musicList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return MusicItem( return MusicItem(
musicModel: controller.musicList[index], musicModel: controller.musicList[index],
showDownload: controller.musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name, showDownload: controller.musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
showMore: controller.musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name, showMore: controller.musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
onTapItem: () => controller.onTapItem(controller.musicList[index].value), onTapItem: () => controller.onTapItem(controller.musicList[index]),
); );
}, },
); );
}), }),
), );
}
), ),
); );
}); });

View File

@ -18,6 +18,7 @@ class SearchResultChildOptimumController extends GetxController {
'coverUrl': musicModel.coverUrl, 'coverUrl': musicModel.coverUrl,
'title': musicModel.title, 'title': musicModel.title,
'subtitle': musicModel.subtitle, 'subtitle': musicModel.subtitle,
'musicType': musicModel.musicType,
}); });
} }
} }

View File

@ -20,7 +20,7 @@ class SearchResultChildOptimumView extends GetView<SearchResultChildOptimumContr
itemCount: SearchResultController.to.tabs.length, itemCount: SearchResultController.to.tabs.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final searchResultTabBarModel = SearchResultController.to.tabs[index]; final searchResultTabBarModel = SearchResultController.to.tabs[index];
final musicList = searchResultTabBarModel.musicList?.map((e) => e.obs).toList(); final musicList = searchResultTabBarModel.musicList;
if (index == 0) { if (index == 0) {
return Container(); return Container();
} }
@ -51,9 +51,9 @@ class SearchResultChildOptimumView extends GetView<SearchResultChildOptimumContr
itemBuilder: (context, index) { itemBuilder: (context, index) {
return MusicItem( return MusicItem(
musicModel: musicList[index], musicModel: musicList[index],
showDownload: musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name, showDownload: musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
showMore: musicList[index].value.musicType == MusicType.musicVideoTypeAtv.name, showMore: musicList[index].musicType == MusicType.musicVideoTypeAtv.name,
onTapItem: () => controller.onTapItem(musicList[index].value), onTapItem: () => controller.onTapItem(musicList[index]),
); );
}, },
), ),

View File

@ -17,6 +17,7 @@ class BrowseItemAlbumSongList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap, onTap: onTap,
child: SizedBox( child: SizedBox(
width: 109.w, width: 109.w,

View File

@ -19,7 +19,9 @@ class BrowseItemAtv extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return ClipRRect(
borderRadius: BorderRadius.circular(8).r,
child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
@ -34,6 +36,7 @@ class BrowseItemAtv extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
@ -80,9 +83,7 @@ class BrowseItemAtv extends StatelessWidget {
} }
Widget _buildMore() { Widget _buildMore() {
return Padding( return ClipOval(
padding: const EdgeInsets.only(right: 12).w,
child: ClipOval(
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -97,14 +98,13 @@ class BrowseItemAtv extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
void onTapMore() { void onTapMore() {
Get.bottomSheet( Get.bottomSheet(
MoreBottomSheetView( MoreBottomSheetView(
musicModel: musicModel.obs, musicModel: musicModel,
), ),
); );
} }

View File

@ -4,7 +4,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart'; import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/dialog/remind_dialog.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/components/network_image_widget.dart';
import 'package:tone_snap/data/models/music_model.dart'; import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/offline_box.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/more_bottom_sheet/more_bottom_sheet_view.dart';
import 'package:tone_snap/modules/sideb/offline/offline_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/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/res/themes/app_colors.dart';
import 'package:tone_snap/utils/obj_util.dart'; import 'package:tone_snap/utils/obj_util.dart';
@ -24,15 +24,19 @@ class MusicItem extends StatelessWidget {
required this.onTapItem, required this.onTapItem,
this.showDownload = true, this.showDownload = true,
this.showMore = true, this.showMore = true,
this.showNumber = false,
this.number = 0,
this.playlistModelId, this.playlistModelId,
}); });
final musicPlayerController = MusicPlayerController.to; final musicPlayerController = MusicPlayerController.to;
final Rx<MusicModel> musicModel; final MusicModel musicModel;
final Function() onTapItem; final Function() onTapItem;
final bool showDownload; final bool showDownload;
final bool showMore; final bool showMore;
final bool showNumber;
final int number;
final String? playlistModelId; final String? playlistModelId;
@override @override
@ -46,7 +50,11 @@ class MusicItem extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
SizedBox(width: 18.w), SizedBox(width: 18.w),
if (showNumber) ...[
_buildNumber(),
] else ...[
_buildCover(), _buildCover(),
],
SizedBox(width: 12.w), SizedBox(width: 12.w),
_buildContent(), _buildContent(),
SizedBox(width: 8.w), 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() { Widget _buildCover() {
return Obx(() {
return NetworkImageWidget( return NetworkImageWidget(
url: musicModel.value.coverUrl, url: musicModel.coverUrl,
width: 60.w, width: 60.w,
height: 60.w, height: 60.w,
radius: 8.r, radius: 8.r,
); );
});
} }
Widget _buildContent() { Widget _buildContent() {
@ -83,48 +110,17 @@ class MusicItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Obx(() { Obx(() {
bool isCurrentPlayModel = ObjUtil.isNotEmpty(musicModel.value.videoId) && musicPlayerController.getMusicModel()?.value.videoId == musicModel.value.videoId; return MusicItemMarqueeText(
return Visibility( text: musicModel.title,
visible: isCurrentPlayModel, showMarquee: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
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,
),
),
); );
}), }),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Obx(() { Obx(() {
bool isCurrentPlayModel = ObjUtil.isNotEmpty(musicModel.value.videoId) && musicPlayerController.getMusicModel()?.value.videoId == musicModel.value.videoId; return MusicItemMarqueeText(
return Visibility( text: musicModel.subtitle,
visible: isCurrentPlayModel, isTitle: false,
replacement: Text( showMarquee: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
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,
),
),
); );
}), }),
], ],
@ -143,18 +139,20 @@ class MusicItem extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: 24.w, width: 24.w,
height: 24.w, height: 24.w,
child: Obx(() { child: GetBuilder<DownloadManager>(
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) { id: DownloadManager.to.downloadStateId,
builder: (_) {
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
return Image.asset(Assets.sideBDownloaded); return Image.asset(Assets.sideBDownloaded);
} else { } else {
if (musicModel.value.taskStatus == TaskStatus.enqueued) { if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued) {
return CircularProgressIndicator( return CircularProgressIndicator(
color: seedColor, color: seedColor,
strokeWidth: 2.w, strokeWidth: 2.w,
); );
} else if (musicModel.value.taskStatus == TaskStatus.running) { } else if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
return CircularProgressIndicator( return CircularProgressIndicator(
value: musicModel.value.progress, value: DownloadManager.to.getMusicModel(musicModel.videoId)?.progress,
backgroundColor: seedColor.withOpacity(0.2), backgroundColor: seedColor.withOpacity(0.2),
valueColor: const AlwaysStoppedAnimation<Color>(seedColor), valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
strokeWidth: 2.w, strokeWidth: 2.w,
@ -163,7 +161,8 @@ class MusicItem extends StatelessWidget {
return Image.asset(Assets.sideBNotDownload2); return Image.asset(Assets.sideBNotDownload2);
} }
} }
}), },
),
), ),
), ),
), ),
@ -191,13 +190,13 @@ class MusicItem extends StatelessWidget {
} }
void onTapDownload() { void onTapDownload() {
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) { if (OfflineBox().checkDownloaded(musicModel.videoId)) {
Get.dialog( Get.dialog(
RemindDialog( RemindDialog(
content: 'Confirm to remove this song?', content: 'Confirm to remove this song?',
confirmOnTap: () async { confirmOnTap: () async {
await OfflineBox().delete(musicModel.value.videoId!); await OfflineBox().delete(musicModel.videoId!);
musicModel.update((fn) => fn?.taskStatus = null); DownloadManager.to.updateDownloadState();
BaseEasyLoading.toast('Removed'); BaseEasyLoading.toast('Removed');
if (Get.isRegistered<PersonalMusicLibraryController>()) { if (Get.isRegistered<PersonalMusicLibraryController>()) {
PersonalMusicLibraryController.to.refreshOffline(); PersonalMusicLibraryController.to.refreshOffline();
@ -209,11 +208,11 @@ class MusicItem extends StatelessWidget {
), ),
); );
} else { } else {
if (musicModel.value.taskStatus == TaskStatus.enqueued if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|| musicModel.value.taskStatus == TaskStatus.running) { || DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
DownloadManager().cancelDownload(musicModel); DownloadManager.to.cancelDownload(musicModel.videoId);
} else { } else {
DownloadManager().downloadMusic(musicModel); DownloadManager.to.downloadMusic(musicModel);
} }
} }
} }

View 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,
),
),
),
);
}
}

View File

@ -78,6 +78,7 @@ class PlaylistItem extends StatelessWidget {
'coverUrl': playlistModel.coverUrl, 'coverUrl': playlistModel.coverUrl,
'title': playlistModel.title, 'title': playlistModel.title,
'subtitle': playlistModel.subtitle, 'subtitle': playlistModel.subtitle,
'musicType': playlistModel.musicType,
}); });
} }
} }