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