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,
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),

View File

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

View File

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

View File

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

View File

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

View File

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

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(
id: id,
title: title,
params: params,
coverUrl: coverUrl,
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);
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;
}
///

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

View File

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

View File

@ -1,10 +1,11 @@
import 'dart:ffi';
import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.dart';
import 'package:tone_snap/data/models/browse_model.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/next_model.dart';
import 'package:tone_snap/data/storage/collect_playlists_box.dart';
import 'package:tone_snap/modules/sideb/collect_playlists/collect_playlists_controller.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
@ -16,6 +17,7 @@ class AlbumSongListController extends GetxController {
var musicPlayerController = MusicPlayerController.to;
var browseId = '';
var params = '';
var musicType = '';
/// url
var coverUrl = ''.obs;
@ -25,7 +27,7 @@ class AlbumSongListController extends GetxController {
var viewState = ViewState.loading.obs;
/// /
var musicList = <Rx<MusicModel>>[].obs;
var musicList = <MusicModel>[].obs;
///
var isCollect = false.obs;
@ -34,6 +36,7 @@ class AlbumSongListController extends GetxController {
void onInit() {
super.onInit();
Map<String, dynamic> arguments = Get.arguments;
musicType = arguments['musicType'] ?? '';
browseId = arguments['browseId'] ?? '';
params = arguments['params'] ?? '';
coverUrl.value = arguments['coverUrl'] ?? '';
@ -129,7 +132,7 @@ class AlbumSongListController extends GetxController {
if (playlistItemData != null) {
musicModel.videoId = playlistItemData.videoId;
}
musicList.add(musicModel.obs);
musicList.add(musicModel);
}
}
}
@ -137,66 +140,24 @@ class AlbumSongListController extends GetxController {
}
///
Future<void> onTapPlayAll(int type) async {
Future<void> onTapPlayAll(bool isShuffle) async {
if (musicList.isNotEmpty) {
int index = 0;
if (type == 1) {
index = NumUtil.getRandomNumber(0, musicList.length);
}
List<MusicModel> playList = await _next(musicList[index].value.videoId, playlistId: musicList[index].value.playlistId);
musicPlayerController.playMusic(playList[index].videoId, playList: playList);
}
}
///
Future<List<MusicModel>> _next(String? videoId, {String? playlistId}) async {
List<MusicModel> playList = [];
NextModel? model = await MusicApi.next(playlistId: playlistId, videoId: videoId, showLoading: true);
if (model != null) {
var tabs = model.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs;
if (tabs != null && tabs.isNotEmpty) {
for (var i = 0; i < tabs.length; ++i) {
var o = tabs[i];
if (i == 0) {
var contents = o.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents;
if (contents != null && contents.isNotEmpty) {
for (var j = 0; j < contents.length; ++j) {
var musicModel = MusicModel();
var content = contents[j];
if (content.playlistPanelVideoRenderer != null) {
//
var thumbnails = content.playlistPanelVideoRenderer?.thumbnail?.thumbnails;
if (thumbnails != null) {
musicModel.coverUrl = thumbnails.last.url;
}
//
var runs = content.playlistPanelVideoRenderer?.title?.runs;
if (runs != null && runs.isNotEmpty) {
musicModel.title = runs[0].text;
}
//
var subRuns = content.playlistPanelVideoRenderer?.longBylineText?.runs;
if (subRuns != null && subRuns.isNotEmpty) {
musicModel.subtitle = subRuns.map((e) => e.text).join();
}
// videoId, playlistId
var watchEndpoint = content.playlistPanelVideoRenderer?.navigationEndpoint?.watchEndpoint;
if (watchEndpoint != null) {
musicModel.videoId = watchEndpoint.videoId;
musicModel.playlistId = watchEndpoint.playlistId;
}
playList.add(musicModel);
}
}
}
}
if (isShuffle && musicList.length > 1) {
final n = musicList.indexWhere((e) => e.videoId == musicPlayerController.getMusicModel()?.videoId);
if (n != -1) {
index = NumUtil.getRandomNumberExcludingCurrent(0, musicList.length, n);
} else {
index = NumUtil.getRandomNumber(0, musicList.length);
}
}
for (var o in musicList) {
if (ObjUtil.isEmpty(o.coverUrl)) {
o.coverUrl = coverUrl.value;
}
}
musicPlayerController.playMusic(musicList[index].videoId, playList: musicList);
}
return playList;
}
///
@ -212,6 +173,7 @@ class AlbumSongListController extends GetxController {
params: params,
coverUrl: coverUrl.value,
subtitle: subtitle,
musicType: musicType,
);
BaseEasyLoading.toast('Collected');
}

View File

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

View File

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

View File

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

View File

@ -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,
// ),
// ),
// ),
// ),
// ],
// );
// }
}

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

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/base_scrollbar.dart';
import 'package:tone_snap/components/refresh/base_easyrefresh.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/enum/music_type.dart';
import 'package:tone_snap/data/models/browse_group_model.dart';
@ -97,30 +98,39 @@ class HomeView extends GetView<HomeController> {
child: Obx(() {
return ViewStateWidget(
viewState: controller.viewState.value,
child: BaseScrollbar(
child: Obx(() {
return ListView.separated(
padding: const EdgeInsets.only(bottom: 16).h,
itemCount: controller.groupList.length,
separatorBuilder: (context, index) => SizedBox(height: 16.h),
itemBuilder: (context, index) {
final browseGroupModel = controller.groupList[index];
if (!MusicTypeExtension.isThereAny(browseGroupModel.musicType)) {
return Container();
}
return Column(
children: [
_buildGroupTitle(ObjUtil.getStr(browseGroupModel.groupTitle)),
if (browseGroupModel.musicType == MusicType.musicVideoTypeAtv.name) ...[
_buildGroupAtv(browseGroupModel),
] else ...[
_buildGroupPlaylist(browseGroupModel),
],
],
showTryAgain: true,
onTapTryAgain: controller.onTapTryAgain,
child: BaseEasyRefresh(
controller: controller.refreshController,
onRefresh: controller.onRefresh,
childBuilder: (context, physics) {
return BaseScrollbar(
child: Obx(() {
return ListView.separated(
physics: physics,
padding: const EdgeInsets.only(bottom: 16).h,
itemCount: controller.groupList.length,
separatorBuilder: (context, index) => SizedBox(height: 16.h),
itemBuilder: (context, index) {
final browseGroupModel = controller.groupList[index];
if (!MusicTypeExtension.isThereAny(browseGroupModel.musicType)) {
return Container();
}
return Column(
children: [
_buildGroupTitle(ObjUtil.getStr(browseGroupModel.groupTitle)),
if (browseGroupModel.musicType == MusicType.musicVideoTypeAtv.name) ...[
_buildGroupAtv(browseGroupModel),
] else ...[
_buildGroupPlaylist(browseGroupModel),
],
],
);
},
);
},
}),
);
}),
},
),
);
}),
@ -136,9 +146,9 @@ class HomeView extends GetView<HomeController> {
itemCount: browseGroupModel.browseList != null ? browseGroupModel.browseList!.length : 0,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 0,
mainAxisSpacing: 12.w,
crossAxisSpacing: 12.w,
childAspectRatio: 60.w / (1.sw - 48.w),
childAspectRatio: 60.w / (1.sw - 50.w),
),
itemBuilder: (context, index) {
final musicModel = browseGroupModel.browseList![index];
@ -163,7 +173,7 @@ class HomeView extends GetView<HomeController> {
final musicModel = browseGroupModel.browseList![index];
return BrowseItemAlbumSongList(
musicModel: musicModel,
onTap: () => controller.openAlbumSong(musicModel),
onTap: () => controller.openAlbumSong(browseGroupModel, musicModel),
);
},
),

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

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

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

View File

@ -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]));
}
}
}

View File

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

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

@ -4,7 +4,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/dialog/remind_dialog.dart';
import 'package:tone_snap/components/my_marquee_text.dart';
import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
@ -14,6 +13,7 @@ import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart
import 'package:tone_snap/modules/sideb/more_bottom_sheet/more_bottom_sheet_view.dart';
import 'package:tone_snap/modules/sideb/offline/offline_controller.dart';
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
import 'package:tone_snap/modules/sideb/widgets/music_item_marquee_text.dart';
import 'package:tone_snap/res/themes/app_colors.dart';
import 'package:tone_snap/utils/obj_util.dart';
@ -24,15 +24,19 @@ class MusicItem extends StatelessWidget {
required this.onTapItem,
this.showDownload = true,
this.showMore = true,
this.showNumber = false,
this.number = 0,
this.playlistModelId,
});
final musicPlayerController = MusicPlayerController.to;
final Rx<MusicModel> musicModel;
final MusicModel musicModel;
final Function() onTapItem;
final bool showDownload;
final bool showMore;
final bool showNumber;
final int number;
final String? playlistModelId;
@override
@ -46,7 +50,11 @@ class MusicItem extends StatelessWidget {
child: Row(
children: [
SizedBox(width: 18.w),
_buildCover(),
if (showNumber) ...[
_buildNumber(),
] else ...[
_buildCover(),
],
SizedBox(width: 12.w),
_buildContent(),
SizedBox(width: 8.w),
@ -66,15 +74,34 @@ class MusicItem extends StatelessWidget {
);
}
Widget _buildNumber() {
return Container(
width: 60.w,
height: 60.w,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 10).w,
decoration: BoxDecoration(
color: const Color(0xFF242529),
borderRadius: BorderRadius.circular(8).r,
),
child: Text(
number.toString(),
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
),
);
}
Widget _buildCover() {
return Obx(() {
return NetworkImageWidget(
url: musicModel.value.coverUrl,
width: 60.w,
height: 60.w,
radius: 8.r,
);
});
return NetworkImageWidget(
url: musicModel.coverUrl,
width: 60.w,
height: 60.w,
radius: 8.r,
);
}
Widget _buildContent() {
@ -83,48 +110,17 @@ class MusicItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
bool isCurrentPlayModel = ObjUtil.isNotEmpty(musicModel.value.videoId) && musicPlayerController.getMusicModel()?.value.videoId == musicModel.value.videoId;
return Visibility(
visible: isCurrentPlayModel,
replacement: Text(
ObjUtil.getStr(musicModel.value.title),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
),
child: MyMarqueeText(
text: ObjUtil.getStr(musicModel.value.title),
textStyle: TextStyle(
color: seedColor,
fontSize: 14.sp,
),
),
return MusicItemMarqueeText(
text: musicModel.title,
showMarquee: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
);
}),
SizedBox(height: 4.h),
Obx(() {
bool isCurrentPlayModel = ObjUtil.isNotEmpty(musicModel.value.videoId) && musicPlayerController.getMusicModel()?.value.videoId == musicModel.value.videoId;
return Visibility(
visible: isCurrentPlayModel,
replacement: Text(
ObjUtil.getStr(musicModel.value.subtitle),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0x99FFFFFF),
fontSize: 12.sp,
),
),
child: MyMarqueeText(
text: ObjUtil.getStr(musicModel.value.subtitle),
textStyle: TextStyle(
color: seedColor,
fontSize: 12.sp,
),
),
return MusicItemMarqueeText(
text: musicModel.subtitle,
isTitle: false,
showMarquee: musicPlayerController.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
);
}),
],
@ -143,27 +139,30 @@ class MusicItem extends StatelessWidget {
child: SizedBox(
width: 24.w,
height: 24.w,
child: Obx(() {
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) {
return Image.asset(Assets.sideBDownloaded);
} else {
if (musicModel.value.taskStatus == TaskStatus.enqueued) {
return CircularProgressIndicator(
color: seedColor,
strokeWidth: 2.w,
);
} else if (musicModel.value.taskStatus == TaskStatus.running) {
return CircularProgressIndicator(
value: musicModel.value.progress,
backgroundColor: seedColor.withOpacity(0.2),
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
strokeWidth: 2.w,
);
child: GetBuilder<DownloadManager>(
id: DownloadManager.to.downloadStateId,
builder: (_) {
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
return Image.asset(Assets.sideBDownloaded);
} else {
return Image.asset(Assets.sideBNotDownload2);
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued) {
return CircularProgressIndicator(
color: seedColor,
strokeWidth: 2.w,
);
} else if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
return CircularProgressIndicator(
value: DownloadManager.to.getMusicModel(musicModel.videoId)?.progress,
backgroundColor: seedColor.withOpacity(0.2),
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
strokeWidth: 2.w,
);
} else {
return Image.asset(Assets.sideBNotDownload2);
}
}
}
}),
},
),
),
),
),
@ -191,13 +190,13 @@ class MusicItem extends StatelessWidget {
}
void onTapDownload() {
if (OfflineBox().checkDownloaded(musicModel.value.videoId) || musicModel.value.taskStatus == TaskStatus.complete) {
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
Get.dialog(
RemindDialog(
content: 'Confirm to remove this song?',
confirmOnTap: () async {
await OfflineBox().delete(musicModel.value.videoId!);
musicModel.update((fn) => fn?.taskStatus = null);
await OfflineBox().delete(musicModel.videoId!);
DownloadManager.to.updateDownloadState();
BaseEasyLoading.toast('Removed');
if (Get.isRegistered<PersonalMusicLibraryController>()) {
PersonalMusicLibraryController.to.refreshOffline();
@ -209,11 +208,11 @@ class MusicItem extends StatelessWidget {
),
);
} else {
if (musicModel.value.taskStatus == TaskStatus.enqueued
|| musicModel.value.taskStatus == TaskStatus.running) {
DownloadManager().cancelDownload(musicModel);
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|| DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
DownloadManager.to.cancelDownload(musicModel.videoId);
} else {
DownloadManager().downloadMusic(musicModel);
DownloadManager.to.downloadMusic(musicModel);
}
}
}

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,
'title': playlistModel.title,
'subtitle': playlistModel.subtitle,
'musicType': playlistModel.musicType,
});
}
}