个人曲库

This commit is contained in:
fengshengxiong 2024-07-14 16:13:46 +08:00
parent c4418a9e04
commit c7cbdb04be
115 changed files with 5608 additions and 1146 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

View File

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

@ -2,14 +2,12 @@
// Date: 2024/6/25
// Description: 广
import 'dart:io' show Platform;
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:tone_snap/routes/app_routes.dart';
import 'dart:io' show Platform;
import 'package:tone_snap/utils/log_util.dart';
class AppOpenAdManager {
@ -66,16 +64,16 @@ class AppOpenAdManager {
/// 广
/// 广广
void showAdIfAvailable() {
void showAdIfAvailable({Function()? onTap}) {
if (!isAdAvailable) {
LogUtil.d('尝试在可用之前显示广告');
loadAd();
_openInitial();
if(onTap != null) onTap();
return;
}
if (_isShowingAd) {
LogUtil.d('尝试在已显示广告的情况下显示广告');
if(onTap != null) onTap();
return;
}
if (DateTime.now().subtract(maxCacheDuration).isAfter(_appOpenLoadTime!)) {
@ -83,8 +81,7 @@ class AppOpenAdManager {
_appOpenAd!.dispose();
_appOpenAd = null;
loadAd();
_openInitial();
if(onTap != null) onTap();
return;
}
// fullScreenContentCallback 广
@ -107,7 +104,7 @@ class AppOpenAdManager {
ad.dispose();
_appOpenAd = null;
_openInitial();
if(onTap != null) onTap();
},
onAdDismissedFullScreenContent: (ad) {
LogUtil.d('$ad onAdDismissedFullScreenContent');
@ -119,7 +116,7 @@ class AppOpenAdManager {
_appOpenAd = null;
loadAd();
_openInitial();
if(onTap != null) onTap();
},
onAdClicked: (ad) {
LogUtil.d('$ad onAdClicked');
@ -127,9 +124,4 @@ class AppOpenAdManager {
);
_appOpenAd!.show();
}
///
void _openInitial() {
Get.offNamed(AppRoutes.initialA);
}
}

View File

@ -62,14 +62,16 @@ class InterstitialAdManager {
/// 广
/// 广广
void showAdIfAvailable() {
void showAdIfAvailable({Function()? onTap}) {
if (!isAdAvailable) {
LogUtil.d('尝试在可用之前显示广告');
loadAd();
if(onTap != null) onTap();
return;
}
if (isShowingAd) {
LogUtil.d('尝试在已显示广告的情况下显示广告');
if(onTap != null) onTap();
return;
}
if (DateTime.now().subtract(maxCacheDuration).isAfter(_appOpenLoadTime!)) {
@ -77,6 +79,7 @@ class InterstitialAdManager {
_interstitialAd!.dispose();
_interstitialAd = null;
loadAd();
if(onTap != null) onTap();
return;
}
// fullScreenContentCallback 广
@ -95,6 +98,8 @@ class InterstitialAdManager {
isShowingAd = false;
ad.dispose();
_interstitialAd = null;
if(onTap != null) onTap();
},
onAdDismissedFullScreenContent: (ad) {
LogUtil.d('$ad onAdDismissedFullScreenContent');
@ -102,6 +107,8 @@ class InterstitialAdManager {
ad.dispose();
_interstitialAd = null;
loadAd();
if(onTap != null) onTap();
},
onAdClicked: (ad) {
LogUtil.d('$ad onAdClicked');

View File

@ -2,11 +2,24 @@
// Date: 2024/5/7
// Description:
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:tone_snap/res/themes/app_colors.dart';
class BaseEasyLoading {
static void initGlobalConfig() {
static void configLoading() {
EasyLoading.instance
//
..loadingStyle = EasyLoadingStyle.custom
..indicatorType = EasyLoadingIndicatorType.ring
..indicatorSize = 30
..lineWidth = 3
..textColor = seedColor
..indicatorColor = seedColor
..progressColor = seedColor
..backgroundColor = scaffoldBgColor.withOpacity(0.8)
..maskColor = Colors.amber
..boxShadow = <BoxShadow>[]
// loading展示的时候.
..userInteractions = false
// .
@ -30,7 +43,7 @@ class BaseEasyLoading {
bool show = true,
}) {
EasyLoading.instance.userInteractions = false;
if (show) EasyLoading.show(status: value, dismissOnTap: dismissOnTap);
if (show) EasyLoading.show(status: value, dismissOnTap: true);
}
static void dismiss({bool dismiss = true}) {

View File

@ -3,21 +3,31 @@
// Description:
import 'package:flutter/material.dart';
import 'package:tone_snap/res/themes/app_colors.dart';
class BaseScrollbar extends StatelessWidget {
const BaseScrollbar({super.key, required this.child});
const BaseScrollbar({
super.key,
required this.child,
this.scrollController,
this.thumbColor,
});
final Widget child;
final Color? thumbColor;
final ScrollController? scrollController;
@override
Widget build(BuildContext context) {
return MediaQuery.removePadding(
context: context,
removeTop: true,
removeBottom: true,
child: RawScrollbar(
thumbColor: Colors.white,
controller: scrollController,
thumbColor: thumbColor ?? seedColor,
radius: const Radius.circular(8.0),
thickness: 4.0,
thickness: 2.0,
thumbVisibility: false,
child: child,
),

View File

@ -0,0 +1,34 @@
// Author: fengshengxiong
// Date: 2024/6/30
// Description:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/music_bar/music_bar_view.dart';
class MusicBar {
static final MusicBar _instance = MusicBar._();
factory MusicBar() => _instance;
MusicBar._();
OverlayEntry? overlayEntry;
var isShow = false.obs;
///
show() {
if (overlayEntry == null && Get.context != null) {
overlayEntry = OverlayEntry(builder: (BuildContext context) {
return MusicBarView();
});
Overlay.of(Get.context!).insert(overlayEntry!);
isShow.value = true;
}
}
///
void hide() {
overlayEntry?.remove();
overlayEntry = null;
isShow.value = false;
}
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'music_bar_controller.dart';
class MusicBarBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => MusicBarController());
}
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tone_snap/routes/app_routes.dart';
class MusicBarController extends GetxController with GetSingleTickerProviderStateMixin {
static MusicBarController get to => Get.find<MusicBarController>();
AnimationController? _controller;
var bottom = 0.0.obs;
double bottomPadding = 0.0;
@override
void onInit() {
super.onInit();
_controller = AnimationController(vsync: this)..duration = const Duration(milliseconds: 200);
WidgetsBinding.instance.addPostFrameCallback((_) {
bottomPadding = MediaQuery.of(Get.context!).padding.bottom;
bottom.value = kBottomNavigationBarHeight + bottomPadding;
});
}
@override
void onClose() {
_controller?.dispose();
super.onClose();
}
///
void toBottom() {
var animation = Tween<double>(begin: bottom.value, end: bottomPadding).animate(_controller!);
animation.addListener(() {
bottom.value = animation.value;
});
_controller!.forward();
}
///
void riseUp() {
var animation = Tween<double>(begin: bottom.value, end: kBottomNavigationBarHeight + bottomPadding).animate(_controller!);
animation.addListener(() {
bottom.value = animation.value;
});
_controller!.forward();
}
///
void openPlayPage() {
Get.toNamed(AppRoutes.playPage, arguments: {'isMusicBarOpen': true});
}
}

View File

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/my_marquee_text.dart';
import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/res/themes/app_sizes.dart';
import 'package:tone_snap/utils/obj_util.dart';
import 'music_bar_controller.dart';
class MusicBarView extends StatelessWidget {
MusicBarView({super.key});
final controller = Get.put(MusicBarController(), permanent: true);
final musicPlayerController = MusicPlayerController.to;
@override
Widget build(BuildContext context) {
return Obx(() {
return Stack(
fit: StackFit.expand,
children: [
Positioned(
bottom: controller.bottom.value,
left: 16.w,
right: 16.w,
child: GestureDetector(
onTap: controller.openPlayPage,
child: Container(
width: 1.sw - 32.w,
height: musicBarHeight,
padding: EdgeInsets.symmetric(horizontal: 26.w),
decoration: BoxDecoration(
color: const Color(0xFF80F988),
borderRadius: BorderRadius.circular(36).r,
boxShadow: const [
BoxShadow(
color: Color(0x40040604),
offset: Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
),
],
),
child: Row(
children: [
ClipOval(
child: Obx(() {
return NetworkImageWidget(
url: musicPlayerController.musicModel.value.thumbnail,
width: 48.w,
height: 48.w,
);
}),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.musicModel.value.title),
textStyle: TextStyle(
color: Colors.black,
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
);
}),
SizedBox(height: 4.h),
Obx(() {
return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.musicModel.value.subTitle),
textStyle: TextStyle(
color: Colors.black,
fontSize: 12.sp,
),
);
}),
],
),
),
],
),
),
),
),
],
);
});
}
}

View File

@ -6,19 +6,25 @@ import 'package:flutter/cupertino.dart';
import 'package:widget_marquee/widget_marquee.dart';
class MyMarqueeText extends StatelessWidget {
const MyMarqueeText({super.key, required this.text, required this.textStyle});
const MyMarqueeText({
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: const Duration(seconds: 2),
duration: const Duration(seconds: 6),
duration: Duration(seconds: enable ? 12 : 0),
pause: Duration.zero,
gap: 60,
gap: 80,
child: Text(text, style: textStyle),
);
}
}
}

View File

@ -12,10 +12,10 @@ class NetworkImageWidget extends StatelessWidget {
this.width,
this.height,
this.radius = 0.0,
required this.url,
this.url,
this.fit = BoxFit.cover,
this.placeholder,
this.errorWidget,
this.noPlaceholder = false,
});
final double? width;
@ -23,8 +23,8 @@ class NetworkImageWidget extends StatelessWidget {
final double radius;
final String? url;
final BoxFit fit;
final Widget? placeholder;
final Widget? errorWidget;
final String? placeholder;
final bool noPlaceholder;
@override
Widget build(BuildContext context) {
@ -35,23 +35,20 @@ class NetworkImageWidget extends StatelessWidget {
height: height,
imageUrl: '$url',
fit: fit,
placeholder: (context, url) {
return placeholder ?? _placeholderWidget(Assets.sideBImgPlaceholder);
placeholder: noPlaceholder ? null : (context, url) {
return _placeholderWidget(Assets.sideBImgPlaceholder);
},
errorWidget: (context, url, error) {
return errorWidget ?? _placeholderWidget(Assets.sideBImgError);
errorWidget: noPlaceholder ? null : (context, url, error) {
return _placeholderWidget(Assets.sideBImgError);
},
),
);
}
Widget _placeholderWidget(String imgName) {
return Container(
color: Colors.white10,
child: Image.asset(
imgName,
color: Colors.white12,
),
Widget _placeholderWidget(String img) {
return Image.asset(
placeholder ?? img,
color: Colors.white12,
);
}
}

View File

@ -0,0 +1,25 @@
// Author: fengshengxiong
// Date: 2024/6/30
// Description: ShaderMaskWidget
import 'package:flutter/material.dart';
class ShaderMaskWidget extends StatelessWidget {
const ShaderMaskWidget({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return ShaderMask(
shaderCallback: (Rect bounds) {
return const LinearGradient(
colors: [Colors.transparent, Colors.white, Colors.white, Colors.transparent],
stops: [0.0, 0.05, 0.95, 1],
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: child,
);
}
}

View File

@ -13,10 +13,12 @@ class ViewStateWidget extends StatelessWidget {
super.key,
required this.viewState,
required this.child,
this.cpiBgColor,
});
final ViewState viewState;
final Widget child;
final Color? cpiBgColor;
@override
Widget build(BuildContext context) {
@ -24,7 +26,9 @@ class ViewStateWidget extends StatelessWidget {
case ViewState.normal:
return child;
case ViewState.loading:
return loadingView();
return loadingView(
backgroundColor: cpiBgColor,
);
case ViewState.empty:
return emptyView();
case ViewState.error:
@ -42,9 +46,10 @@ Widget loadingView({
}) {
return Center(
child: CircularProgressIndicator(
strokeWidth: 3,
color: color,
valueColor: valueColor,
backgroundColor: backgroundColor ?? Colors.white,
backgroundColor: backgroundColor,
value: value,
),
);

View File

@ -3,18 +3,22 @@
// Description: Api
import 'package:devicelocale/devicelocale.dart';
import 'package:tone_snap/data/app_config.dart';
import 'package:tone_snap/data/sideb/models/browse_model.dart';
import 'package:tone_snap/data/models/player_model.dart';
import 'package:tone_snap/global/app_config.dart';
import 'package:tone_snap/data/models/browse_model.dart';
import 'package:tone_snap/data/network/dio_client.dart';
import 'package:tone_snap/data/sideb/models/next_model.dart';
import 'package:tone_snap/data/sideb/models/player_model.dart';
import 'package:tone_snap/data/models/next_model.dart';
import 'package:tone_snap/utils/date_util.dart';
class MusicApi {
static const String baseUrl = 'https://music.youtube.com/youtubei/v1/';
/// browse接口
static Future<BrowseModel?> browse({String? visitorData, Map<String, dynamic>? queryParameters}) async {
static Future<T?> browse<T>({
String? visitorData,
Map<String, dynamic>? queryParameters,
T Function(Map<String, dynamic>)? formJson,
}) async {
String date = DateUtil.getSevenDaysAgo();
String locale = await Devicelocale.currentLocale ?? AppConfig.defaultLocale;
final body = {
@ -26,20 +30,21 @@ class MusicApi {
"clientVersion": "1.20240607.01.00",
"platform": "DESKTOP",
"hl": locale,
"gl": AppConfig.isoCode
// "gl": AppConfig.isoCode
"gl": 'HK'
}
}
};
BrowseModel? browseModel;
await DioClient().request<BrowseModel>(
T? resultModel;
await DioClient().request<T>(
'browse',
requestMethod: RequestMethod.post,
data: body,
queryParameters: queryParameters,
formJson: BrowseModel.fromMap,
success: (model) => browseModel = model,
formJson: formJson,
success: (model) => resultModel = model,
);
return browseModel;
return resultModel;
}
/// next接口
@ -54,7 +59,8 @@ class MusicApi {
"clientVersion": "1.$date",
"platform": "DESKTOP",
"hl": locale,
"gl": AppConfig.isoCode
// "gl": AppConfig.isoCode
"gl": 'HK'
}
}
};
@ -66,6 +72,8 @@ class MusicApi {
NextModel? nextModel;
await DioClient().request<NextModel>(
'next',
showLoading: true,
showToast: true,
requestMethod: RequestMethod.post,
data: body,
queryParameters: queryParameters,
@ -96,6 +104,8 @@ class MusicApi {
PlayerModel? playerModel;
await DioClient().request<PlayerModel>(
'player',
showLoading: true,
showToast: true,
requestMethod: RequestMethod.post,
data: body,
queryParameters: queryParameters,

View File

@ -4,7 +4,7 @@
import 'package:tone_snap/data/models/base_model.dart';
import 'package:tone_snap/data/network/dio_client.dart';
import 'package:tone_snap/data/sideb/models/isocode_model.dart';
import 'package:tone_snap/data/models/isocode_model.dart';
class TikUsTokApi {
static const String baseUrl = 'https://api.tikustok.com/';

View File

@ -1,6 +1,6 @@
// Author: fengshengxiong
// Date: 2024/6/25
// Description:
// Description:
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
@ -13,4 +13,13 @@ class MusicCacheManager {
maxNrOfCacheObjects: 20,
),
);
static String getCacheKey(String videoId) {
return 'music_$videoId';
}
///
static Future<FileInfo?> checkCache(String videoId) async {
return await instance.getFileFromCache(getCacheKey(videoId));
}
}

View File

@ -1,23 +1,34 @@
// Author: fengshengxiong
// Date: 2024/6/13
// Description: BrowseType
// Description:
enum BrowseType {
musicVideoTypeAtv(name: 'MUSIC_VIDEO_TYPE_ATV', remark: '电台/单曲'),
musicVideoTypeOmv(name: 'MUSIC_VIDEO_TYPE_OMV', remark: '视频'),
musicPageTypeAlbum(name: 'MUSIC_PAGE_TYPE_ALBUM', remark: '专辑'),
// musicPageTypeArtist(name: 'MUSIC_PAGE_TYPE_ARTIST', remark: '艺术家'),
musicPageTypePlaylist(name: 'MUSIC_PAGE_TYPE_PLAYLIST', remark: '歌单/列表');
// musicPageTypeTrackLyrics(name: 'MUSIC_PAGE_TYPE_TRACK_LYRICS', remark: '歌词'),
// musicPageTypeTrackRelated(name: 'MUSIC_PAGE_TYPE_TRACK_RELATED', remark: '相关内容');
/// /
musicVideoTypeAtv(name: 'MUSIC_VIDEO_TYPE_ATV'),
///
musicVideoTypeOmv(name: 'MUSIC_VIDEO_TYPE_OMV'),
///
musicPageTypeAlbum(name: 'MUSIC_PAGE_TYPE_ALBUM'),
///
// musicPageTypeArtist(name: 'MUSIC_PAGE_TYPE_ARTIST'),
/// /
musicPageTypePlaylist(name: 'MUSIC_PAGE_TYPE_PLAYLIST');
///
// musicPageTypeTrackLyrics(name: 'MUSIC_PAGE_TYPE_TRACK_LYRICS'),
///
// musicPageTypeTrackRelated(name: 'MUSIC_PAGE_TYPE_TRACK_RELATED');
const BrowseType({
required this.name,
required this.remark,
});
final String name;
final String remark;
}
extension BrowseTypeExtension on BrowseType {

View File

@ -0,0 +1,19 @@
// Author: fengshengxiong
// Date: 2024/6/13
// Description:
import 'package:hive/hive.dart';
part 'play_mode.g.dart';
@HiveType(typeId: 1)
enum PlayMode {
@HiveField(0)
listLoop,
@HiveField(1)
random,
@HiveField(2)
singleCycle;
}

View File

@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'play_mode.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class PlayModeAdapter extends TypeAdapter<PlayMode> {
@override
final int typeId = 1;
@override
PlayMode read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return PlayMode.listLoop;
case 1:
return PlayMode.random;
case 2:
return PlayMode.singleCycle;
default:
return PlayMode.listLoop;
}
}
@override
void write(BinaryWriter writer, PlayMode obj) {
switch (obj) {
case PlayMode.listLoop:
writer.writeByte(0);
break;
case PlayMode.random:
writer.writeByte(1);
break;
case PlayMode.singleCycle:
writer.writeByte(2);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PlayModeAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
// Author: fengshengxiong
// Date: 2024/6/18
// Description: Browse接口模
// Description: Browse-
import 'dart:convert';
@ -550,10 +550,12 @@ class PurpleNavigationEndpoint {
class PurpleBrowseEndpoint {
String? browseId;
String? params;
BrowseEndpointContextSupportedConfigs? browseEndpointContextSupportedConfigs;
PurpleBrowseEndpoint({
this.browseId,
this.params,
this.browseEndpointContextSupportedConfigs,
});
@ -563,11 +565,13 @@ class PurpleBrowseEndpoint {
factory PurpleBrowseEndpoint.fromMap(Map<String, dynamic> json) => PurpleBrowseEndpoint(
browseId: json["browseId"],
params: json["params"],
browseEndpointContextSupportedConfigs: json["browseEndpointContextSupportedConfigs"] == null ? null : BrowseEndpointContextSupportedConfigs.fromMap(json["browseEndpointContextSupportedConfigs"]),
);
Map<String, dynamic> toMap() => {
"browseId": browseId,
"params": params,
"browseEndpointContextSupportedConfigs": browseEndpointContextSupportedConfigs?.toMap(),
};
}

View File

@ -39,6 +39,7 @@ class Content {
String? videoId;
String? playlistId;
String? browseId;
String? params;
Content({
this.title,
@ -47,6 +48,7 @@ class Content {
this.videoId,
this.playlistId,
this.browseId,
this.params,
});
factory Content.fromJson(String str) => Content.fromMap(json.decode(str));
@ -60,6 +62,7 @@ class Content {
videoId: json["videoId"],
playlistId: json["playlistId"],
browseId: json["browseId"],
params: json["params"],
);
Map<String, dynamic> toMap() => {
@ -69,5 +72,6 @@ class Content {
"videoId": videoId,
"playlistId": playlistId,
"browseId": browseId,
"params": params,
};
}

View File

@ -0,0 +1,74 @@
// Author: fengshengxiong
// Date: 2024/6/24
// Description:
import 'dart:convert';
import 'package:hive/hive.dart';
part 'music_model.g.dart';
@HiveType(typeId: 2)
class MusicModel extends HiveObject {
@HiveField(0)
String? title;
@HiveField(1)
String? subTitle;
@HiveField(2)
String? thumbnail;
@HiveField(3)
String? videoId;
@HiveField(4)
String? playlistId;
@HiveField(5)
String? url;
MusicModel({
this.title,
this.subTitle,
this.thumbnail,
this.videoId,
this.playlistId,
this.url,
});
MusicModel copyWith({
String? title,
String? subTitle,
String? thumbnail,
String? url,
String? videoId,
String? playlistId,
}) =>
MusicModel(
title: title ?? this.title,
subTitle: subTitle ?? this.subTitle,
thumbnail: thumbnail ?? this.thumbnail,
url: url ?? this.url,
videoId: videoId ?? this.videoId,
playlistId: playlistId ?? this.playlistId,
);
factory MusicModel.fromJson(String str) =>
MusicModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory MusicModel.fromMap(Map<String, dynamic> json) => MusicModel(
title: json["title"],
subTitle: json["subTitle"],
thumbnail: json["thumbnail"],
videoId: json["videoId"],
playlistId: json["playlistId"],
url: json["url"],
);
Map<String, dynamic> toMap() => {
"title": title,
"subTitle": subTitle,
"thumbnail": thumbnail,
"videoId": videoId,
"playlistId": playlistId,
"url": url,
};
}

View File

@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'music_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class MusicModelAdapter extends TypeAdapter<MusicModel> {
@override
final int typeId = 2;
@override
MusicModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return MusicModel(
title: fields[0] as String?,
subTitle: fields[1] as String?,
thumbnail: fields[2] as String?,
videoId: fields[3] as String?,
playlistId: fields[4] as String?,
url: fields[5] as String?,
);
}
@override
void write(BinaryWriter writer, MusicModel obj) {
writer
..writeByte(6)
..writeByte(0)
..write(obj.title)
..writeByte(1)
..write(obj.subTitle)
..writeByte(2)
..write(obj.thumbnail)
..writeByte(3)
..write(obj.videoId)
..writeByte(4)
..write(obj.playlistId)
..writeByte(5)
..write(obj.url);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MusicModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@ -7,7 +7,7 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/sideb/api/music_api.dart';
import 'package:tone_snap/data/api/music_api.dart';
import 'package:tone_snap/data/network/base_error.dart';
import 'package:tone_snap/data/network/dio_interceptor.dart';
import 'package:tone_snap/utils/log_util.dart';
@ -61,14 +61,14 @@ class DioClient {
///
Future request<T>(
String path, {
bool showLoading = false,
bool showToast = false,
required RequestMethod requestMethod,
dynamic data,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
Options? options,
bool isFormData = false,
bool showLoading = false,
bool showToast = false,
T Function(Map<String, dynamic>)? formJson,
required Function(T? result) success,
Function(BaseError baseError)? fail,
@ -94,7 +94,7 @@ class DioClient {
} catch (e) {
BaseError baseError = getError(e);
if (fail != null) fail(baseError);
BaseEasyLoading.toast(baseError.message);
BaseEasyLoading.toast(baseError.message, show: showToast);
LogUtil.e(e.toString());
}
}

View File

@ -1,45 +0,0 @@
// Author: fengshengxiong
// Date: 2024/6/24
// Description:
import 'dart:convert';
class PlayListModel {
String? title;
String? subTitle;
String? thumbnail;
String? videoId;
String? playlistId;
String? url;
PlayListModel({
this.title,
this.subTitle,
this.thumbnail,
this.videoId,
this.playlistId,
this.url,
});
factory PlayListModel.fromJson(String str) => PlayListModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory PlayListModel.fromMap(Map<String, dynamic> json) => PlayListModel(
title: json["title"],
subTitle: json["subTitle"],
thumbnail: json["thumbnail"],
videoId: json["videoId"],
playlistId: json["playlistId"],
url: json["url"],
);
Map<String, dynamic> toMap() => {
"title": title,
"subTitle": subTitle,
"thumbnail": thumbnail,
"videoId": videoId,
"playlistId": playlistId,
"url": url,
};
}

View File

@ -2,18 +2,18 @@
// Date: 2024/5/8
// Description:
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/hive_storage.dart';
class FavoriteData {
class FavoriteBox {
///
FavoriteData._();
FavoriteBox._();
///
static final FavoriteData _instance = FavoriteData._();
static final FavoriteBox _instance = FavoriteBox._();
///
factory FavoriteData() {
factory FavoriteBox() {
return _instance;
}

View File

@ -3,19 +3,27 @@
// Description:
import 'package:hive_flutter/hive_flutter.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/enum/play_mode.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/voice_model.dart';
const myVoiceBox = 'myVoiceBox';
const favoriteBox = 'favoriteBox';
const musicBox = 'musicBox';
const loveSongsBox = 'loveSongsBox';
Future initHive() async {
//
await Hive.initFlutter();
//
Hive.registerAdapter(VoiceModelAdapter());
Hive.registerAdapter(PlayModeAdapter());
Hive.registerAdapter(MusicModelAdapter());
//
await Hive.openBox<VoiceModel>(myVoiceBox);
await Hive.openBox<VoiceModel>(favoriteBox);
await Hive.openBox(musicBox);
await Hive.openBox<MusicModel>(loveSongsBox);
}
Box<VoiceModel> getMyVoiceBox() {
@ -24,4 +32,12 @@ Box<VoiceModel> getMyVoiceBox() {
Box<VoiceModel> getFavoriteBox() {
return Hive.box<VoiceModel>(favoriteBox);
}
Box getMusicBox() {
return Hive.box(musicBox);
}
Box<MusicModel> getLoveSongsBox() {
return Hive.box<MusicModel>(loveSongsBox);
}

View File

@ -0,0 +1,53 @@
// Author: fengshengxiong
// Date: 2024/5/8
// Description:
import 'package:get/get.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/hive_storage.dart';
class LoveSongsBox {
LoveSongsBox._();
static final LoveSongsBox _instance = LoveSongsBox._();
factory LoveSongsBox() {
return _instance;
}
///
/// , main函数中这个盒子已经打开,
final _box = getLoveSongsBox();
///
List<MusicModel> getList() {
return _box.values.toList();
}
///
Future<int> addData(MusicModel model) async {
return await _box.add(model);
}
///
bool isLove(String videoId) {
var model = getList().firstWhereOrNull((e) => e.videoId == videoId);
return model != null;
}
///
Future<void> delete(String videoId) async {
var list = getList();
var model = list.firstWhereOrNull((e) => e.videoId == videoId);
if (model != null) {
await _box.delete(list.indexOf(model));
await _box.flush();
}
}
///
Future<void> clear() async {
await _box.clear();
await _box.flush();
}
}

View File

@ -0,0 +1,30 @@
// Author: fengshengxiong
// Date: 2024/5/8
// Description:
import 'package:tone_snap/data/enum/play_mode.dart';
import 'package:tone_snap/data/storage/hive_storage.dart';
class MusicBox {
MusicBox._();
static final MusicBox _instance = MusicBox._();
factory MusicBox() {
return _instance;
}
///
/// , main函数中这个盒子已经打开,
final _box = getMusicBox();
///
Future<void> putPlayMode(PlayMode playMode) {
return _box.put('play_mode', playMode);
}
///
PlayMode getPlayMode() {
return _box.get('play_mode') ?? PlayMode.listLoop;
}
}

View File

@ -1,19 +1,19 @@
// Author: fengshengxiong
// Date: 2024/5/8
// Description:
// Description:
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/hive_storage.dart';
class MyVoiceData {
class MyVoiceBox {
///
MyVoiceData._();
MyVoiceBox._();
///
static final MyVoiceData _instance = MyVoiceData._();
static final MyVoiceBox _instance = MyVoiceBox._();
///
factory MyVoiceData() {
factory MyVoiceBox() {
return _instance;
}

View File

@ -63,9 +63,12 @@ class Assets {
static const String sideAUploadRecordSound = 'assets/images/side_a/upload_record_sound.png';
static const String sideAUserAgreement = 'assets/images/side_a/user_agreement.png';
static const String sideAVoiceDefault = 'assets/images/side_a/voice_default.png';
static const String sideBAlbumTitleBg = 'assets/images/side_b/album_title_bg.png';
static const String sideBAlbumTotal = 'assets/images/side_b/album_total.png';
static const String sideBArrowDownBack = 'assets/images/side_b/arrow_down_back.png';
static const String sideBArrowLeftBack = 'assets/images/side_b/arrow_left_back.png';
static const String sideBArrowRightItem = 'assets/images/side_b/arrow_right_item.png';
static const String sideBArtists = 'assets/images/side_b/artists.png';
static const String sideBBnb1Selected = 'assets/images/side_b/bnb1_selected.png';
static const String sideBBnb1Unselected = 'assets/images/side_b/bnb1_unselected.png';
static const String sideBBnb2Selected = 'assets/images/side_b/bnb2_selected.png';
@ -73,20 +76,28 @@ class Assets {
static const String sideBBnb3Selected = 'assets/images/side_b/bnb3_selected.png';
static const String sideBBnb3Unselected = 'assets/images/side_b/bnb3_unselected.png';
static const String sideBBnbBg = 'assets/images/side_b/bnb_bg.png';
static const String sideBBottomSheetIndicator = 'assets/images/side_b/bottom_sheet_indicator.png';
static const String sideBCollectionAlbum = 'assets/images/side_b/collection_album.png';
static const String sideBCrossCircle = 'assets/images/side_b/cross_circle.png';
static const String sideBDownload = 'assets/images/side_b/download.png';
static const String sideBHomeBg = 'assets/images/side_b/home_bg.png';
static const String sideBImgError = 'assets/images/side_b/img_error.png';
static const String sideBImgPlaceholder = 'assets/images/side_b/img_placeholder.png';
static const String sideBItemPlayer1 = 'assets/images/side_b/item_player1.png';
static const String sideBLaunchImage = 'assets/images/side_b/launch_image.png';
static const String sideBLineMenu = 'assets/images/side_b/line_menu.png';
static const String sideBListLoop = 'assets/images/side_b/list_loop.png';
static const String sideBLove = 'assets/images/side_b/love.png';
static const String sideBNextSong = 'assets/images/side_b/next_song.png';
static const String sideBLoveSolid = 'assets/images/side_b/love_solid.png';
static const String sideBNextTrack = 'assets/images/side_b/next_track.png';
static const String sideBNotCollectionAlbum = 'assets/images/side_b/not_collection_album.png';
static const String sideBOfflineDownload = 'assets/images/side_b/offline_download.png';
static const String sideBPausePlay = 'assets/images/side_b/pause_play.png';
static const String sideBPersonalMusicLibraryBg = 'assets/images/side_b/personal_music_library_bg.png';
static const String sideBPlaceholderLibrary = 'assets/images/side_b/placeholder_library.png';
static const String sideBPlayList = 'assets/images/side_b/play_list.png';
static const String sideBPreviousSong = 'assets/images/side_b/previous_song.png';
static const String sideBPlayListDelete = 'assets/images/side_b/play_list_delete.png';
static const String sideBPlaylistsAdd = 'assets/images/side_b/playlists_add.png';
static const String sideBPreviousTrack = 'assets/images/side_b/previous_track.png';
static const String sideBShufflePlayback = 'assets/images/side_b/shuffle_playback.png';
static const String sideBSingleCycle = 'assets/images/side_b/single_cycle.png';
static const String sideBStartPlay = 'assets/images/side_b/start_play.png';

View File

@ -5,8 +5,10 @@
import 'package:tone_snap/data/enum/app_side_enum.dart';
class AppConfig {
static const appName = 'ToneSnap';
/// App展示的一面
static const AppSideEnum appSideEnum = AppSideEnum.sideA;
static const AppSideEnum appSideEnum = AppSideEnum.sideB;
///
static String defaultLocale = 'zh-CN';

View File

@ -0,0 +1,51 @@
// Author: fengshengxiong
// Date: 2024/6/26
// Description:
import 'dart:async';
import 'dart:io';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:tone_snap/utils/log_util.dart';
class AppTrackingTransparencyManager {
AppTrackingTransparencyManager._();
static final AppTrackingTransparencyManager _instance = AppTrackingTransparencyManager._();
factory AppTrackingTransparencyManager() {
return _instance;
}
Timer? _timer;
///
Future<void> requestATT() async {
if (Platform.isIOS) {
final TrackingStatus status = await AppTrackingTransparency.trackingAuthorizationStatus;
if (status == TrackingStatus.notDetermined) {
if (_timer != null && _timer!.isActive) {
final TrackingStatus status = await AppTrackingTransparency.requestTrackingAuthorization();
LogUtil.d('跟踪授权状态: $status');
} else {
_startTimer();
}
} else {
_stopTimer();
}
}
}
///
void _startTimer() {
_timer = Timer.periodic(const Duration(seconds: 3), (Timer t) {
requestATT();
});
}
///
void _stopTimer() {
_timer?.cancel();
_timer = null;
}
}

View File

@ -10,13 +10,15 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/app_config.dart';
import 'package:tone_snap/components/music_bar.dart';
import 'package:tone_snap/components/music_bar/music_bar_controller.dart';
import 'package:tone_snap/data/enum/app_side_enum.dart';
import 'package:tone_snap/data/storage/hive_storage.dart';
import 'package:tone_snap/firebase/firebase_options.dart';
import 'package:tone_snap/global/app_config.dart';
import 'package:tone_snap/global/network_connectivity_service.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/res/themes/app_themes.dart';
import 'package:tone_snap/res/values/strings.dart';
import 'package:tone_snap/routes/app_pages.dart';
import 'package:tone_snap/routes/app_routes.dart';
import 'package:tone_snap/utils/file_util.dart';
@ -26,35 +28,37 @@ import 'package:tone_snap/utils/log_util.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Firebase
try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
} catch (e) {
LogUtil.e("Firebase initialization error: $e");
if (Platform.isIOS) {
// Firebase
try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
} catch (e) {
LogUtil.e("Firebase initialization error: $e");
}
//
FlutterError.onError = (errorDetails) {
FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
};
//
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: false);
return true;
};
// 广 SDK
MobileAds.instance.initialize();
//
await Get.putAsync(() async => NetworkConnectivityService());
}
//
FlutterError.onError = (errorDetails) {
FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
};
//
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: false);
return true;
};
// 广 SDK
MobileAds.instance.initialize();
// Hive
await initHive();
//
await Get.putAsync(() async => NetworkConnectivityService());
runApp(const MyApp());
//
@ -83,44 +87,54 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final easyLoading = EasyLoading.init();
ThemeData appTheme;
List<NavigatorObserver> navigatorObservers = const <NavigatorObserver>[];
if (AppConfig.appSideEnum == AppSideEnum.sideA) {
appTheme = sideATheme;
} else {
appTheme = sideBTheme;
navigatorObservers = [
GetObserver((_) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (Get.currentRoute == AppRoutes.playPage) {
MusicBar().hide();
} else {
if (Get.isRegistered<MusicPlayerController>() && MusicPlayerController.to.musicModel.value.videoId != null) {
MusicBar().show();
}
}
if (Get.isRegistered<MusicBarController>()) {
if (Get.currentRoute == AppRoutes.initialB) {
MusicBarController.to.riseUp();
} else {
MusicBarController.to.toBottom();
}
}
});
}),
];
}
return ScreenUtilInit(
// 稿
designSize: const Size(375, 812),
minTextAdapt: true,
builder: (context, child) {
if (AppConfig.appSideEnum == AppSideEnum.sideA) {
return GetMaterialApp(
title: appName,
debugShowCheckedModeBanner: false,
theme: sideATheme,
darkTheme: sideATheme,
themeMode: ThemeMode.dark,
initialRoute: AppRoutes.splashA,
getPages: AppPages.routes,
builder: (context, widget) {
BaseEasyLoading.initGlobalConfig();
widget = easyLoading(context, widget);
//
return MediaQuery.withNoTextScaling(child: widget);
},
);
} else {
return GetMaterialApp(
title: appName,
debugShowCheckedModeBanner: false,
theme: sideBTheme,
darkTheme: sideBTheme,
themeMode: ThemeMode.dark,
initialRoute: AppRoutes.splashB,
getPages: AppPages.routes,
builder: (context, widget) {
BaseEasyLoading.initGlobalConfig();
widget = easyLoading(context, widget);
//
return MediaQuery.withNoTextScaling(child: widget);
},
);
}
return GetMaterialApp(
title: AppConfig.appName,
debugShowCheckedModeBanner: false,
theme: appTheme,
darkTheme: appTheme,
themeMode: ThemeMode.dark,
initialRoute: AppRoutes.splash,
getPages: AppPages.routes,
navigatorObservers: navigatorObservers,
builder: (context, widget) {
BaseEasyLoading.configLoading();
widget = easyLoading(context, widget);
//
return MediaQuery.withNoTextScaling(child: widget);
},
);
},
);
}

View File

@ -3,8 +3,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/base_appbar.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/global/app_config.dart';
import 'package:tone_snap/modules/sidea/about/about_controller.dart';
import 'package:tone_snap/res/values/strings.dart';
class AboutView extends StatelessWidget {
AboutView({super.key});
@ -32,7 +32,7 @@ class AboutView extends StatelessWidget {
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
text: TextSpan(
text: appName,
text: AppConfig.appName,
style: TextStyle(
color: Colors.white,
fontSize: 22.sp,

View File

@ -1,11 +1,13 @@
import 'dart:io';
import 'package:ffmpeg_kit_flutter_audio/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_audio/ffmpeg_session.dart';
import 'package:ffmpeg_kit_flutter_audio/return_code.dart';
import 'package:get/get.dart';
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/sidea/storage/my_voice_data.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/my_voice_box.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
import 'package:tone_snap/modules/sidea/initial/initial_controller.dart';
@ -46,7 +48,9 @@ class ChangeVoiceController extends GetxController {
@override
void onInit() {
super.onInit();
InterstitialAdManager().loadAd();
if (Platform.isIOS) {
InterstitialAdManager().loadAd();
}
filePath = Get.arguments;
// playerController.setFilePath(filePath);
@ -104,62 +108,121 @@ class ChangeVoiceController extends GetxController {
///
Future<void> save() async {
// 广
InterstitialAdManager().showAdIfAvailable();
if (Platform.isIOS) {
// 广
InterstitialAdManager().showAdIfAvailable(
onTap: () async {
//
if (playerController.isPlaying.value) playerController.stopPlay();
BaseEasyLoading.loading();
//
if (playerController.isPlaying.value) playerController.stopPlay();
BaseEasyLoading.loading();
try {
// assets路径
if (filePath.contains('assets')) {
filePath = await FileUtil.getAssetsToFilePath(filePath);
}
try {
// assets路径
if (filePath.contains('assets')) {
filePath = await FileUtil.getAssetsToFilePath(filePath);
}
//
final outputDir = await LocalPathUtil.getVoiceChangeOutputDir();
String fileName = '${DateUtil.getNowTimeStr().replaceAll(' ', '_')}_output.mp3';
String outputPath = '${outputDir.path}/$fileName';
//
final outputDir = await LocalPathUtil.getVoiceChangeOutputDir();
String fileName = '${DateUtil.getNowTimeStr().replaceAll(' ', '_')}_output.mp3';
String outputPath = '${outputDir.path}/$fileName';
var filter = "";
// var timber = timberList.firstWhereOrNull((e) => e.check);
// if (timber != null) {
// int index = timberList.indexOf(timber);
// if (index == 5) filter = ",afftdn=nf=-30";
// }
var filter = "";
// var timber = timberList.firstWhereOrNull((e) => e.check);
// if (timber != null) {
// int index = timberList.indexOf(timber);
// if (index == 5) filter = ",afftdn=nf=-30";
// }
if (currentIndex.value == 5) filter = ",afftdn=nf=-30,aecho=0.8:0.88:60:0.4";
if (currentIndex.value == 5) filter = ",afftdn=nf=-30,aecho=0.8:0.88:60:0.4";
//
String sampleRate = await _getSampleRate() ?? '24000';
//
String sampleRate = await _getSampleRate() ?? '24000';
// FFmpeg
final String command = '-i $filePath -af "asetrate=$sampleRate*${toneValue.value},atempo=${soundSpeedValue.value}$filter" $outputPath';
// FFmpeg
final String command = '-i $filePath -af "asetrate=$sampleRate*${toneValue.value},atempo=${soundSpeedValue.value}$filter" $outputPath';
// FFmpeg
FFmpegSession session = await FFmpegKit.execute(command);
// FFmpeg
FFmpegSession session = await FFmpegKit.execute(command);
//
final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
LogUtil.d('Audio processing successful');
try {
await MyVoiceBox().addData(VoiceModel(name: fileName, path: outputPath));
BaseEasyLoading.toast('Save successful');
//
final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
LogUtil.d('Audio processing successful');
try {
await MyVoiceData().addData(VoiceModel(name: fileName, path: outputPath));
BaseEasyLoading.toast('Save successful');
// -
Get.until((route) => route.settings.name == AppRoutes.initialA);
InitialController.to.onBottomAppBarItemChanged(2);
} catch (e) {
BaseEasyLoading.toast('Save failed');
}
} else {
LogUtil.d('Audio processing failed');
BaseEasyLoading.toast('Audio processing failed');
}
} catch (e) {
BaseEasyLoading.toast('Audio processing failed');
}
},
);
} else {
//
if (playerController.isPlaying.value) playerController.stopPlay();
BaseEasyLoading.loading();
// -
Get.until((route) => route.settings.name == AppRoutes.initialA);
InitialController.to.onBottomAppBarItemChanged(2);
} catch (e) {
BaseEasyLoading.toast('Save failed');
try {
// assets路径
if (filePath.contains('assets')) {
filePath = await FileUtil.getAssetsToFilePath(filePath);
}
} else {
LogUtil.d('Audio processing failed');
//
final outputDir = await LocalPathUtil.getVoiceChangeOutputDir();
String fileName = '${DateUtil.getNowTimeStr().replaceAll(' ', '_')}_output.mp3';
String outputPath = '${outputDir.path}/$fileName';
var filter = "";
// var timber = timberList.firstWhereOrNull((e) => e.check);
// if (timber != null) {
// int index = timberList.indexOf(timber);
// if (index == 5) filter = ",afftdn=nf=-30";
// }
if (currentIndex.value == 5) filter = ",afftdn=nf=-30,aecho=0.8:0.88:60:0.4";
//
String sampleRate = await _getSampleRate() ?? '24000';
// FFmpeg
final String command = '-i $filePath -af "asetrate=$sampleRate*${toneValue.value},atempo=${soundSpeedValue.value}$filter" $outputPath';
// FFmpeg
FFmpegSession session = await FFmpegKit.execute(command);
//
final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
LogUtil.d('Audio processing successful');
try {
await MyVoiceBox().addData(VoiceModel(name: fileName, path: outputPath));
BaseEasyLoading.toast('Save successful');
// -
Get.until((route) => route.settings.name == AppRoutes.initialA);
InitialController.to.onBottomAppBarItemChanged(2);
} catch (e) {
BaseEasyLoading.toast('Save failed');
}
} else {
LogUtil.d('Audio processing failed');
BaseEasyLoading.toast('Audio processing failed');
}
} catch (e) {
BaseEasyLoading.toast('Audio processing failed');
}
} catch (e) {
BaseEasyLoading.toast('Audio processing failed');
}
}

View File

@ -4,8 +4,8 @@ import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/dialog/remind_dialog.dart';
import 'package:tone_snap/components/dialog/rename_dialog.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/sidea/storage/favorite_data.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/favorite_box.dart';
import 'package:tone_snap/modules/sidea/initial/initial_controller.dart';
import 'package:tone_snap/routes/app_routes.dart';
@ -22,7 +22,7 @@ class FavouriteController extends GetxController {
}
void getData() {
voiceList.value = FavoriteData().getList().reversed.toList();
voiceList.value = FavoriteBox().getList().reversed.toList();
_refreshList();
}

View File

@ -1,6 +1,6 @@
import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
import 'package:tone_snap/modules/sidea/initial/initial_controller.dart';

View File

@ -4,7 +4,7 @@ import 'package:get/get.dart';
import 'package:tone_snap/components/circular_notch_clipper.dart';
import 'package:tone_snap/components/my_marquee_text.dart';
import 'package:tone_snap/modules/sidea/widgets/head_label.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sidea/home/home_controller.dart';
import 'package:tone_snap/utils/obj_util.dart';

View File

@ -1,11 +1,12 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tone_snap/global/app_lifecycle_reactor.dart';
import 'package:tone_snap/ads/app_open_ad_manager.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/sidea/storage/favorite_data.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/favorite_box.dart';
import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/global/app_lifecycle_reactor.dart';
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
import 'package:tone_snap/modules/sidea/favourite/favourite_controller.dart';
import 'package:tone_snap/modules/sidea/home/home_view.dart';
@ -14,8 +15,6 @@ import 'package:tone_snap/modules/sidea/my_voice/my_voice_controller.dart';
import 'package:tone_snap/modules/sidea/settings/settings_view.dart';
import 'package:tone_snap/routes/app_routes.dart';
import '../../../utils/tracking_authorization_util.dart';
class InitialController extends GetxController {
static InitialController get to => Get.find<InitialController>();
var playerController = PlayerController.to;
@ -37,11 +36,8 @@ class InitialController extends GetxController {
@override
void onInit() {
super.onInit();
// ATT授权
TrackingAuthorizationUtil.requestTrackingAuthorization();
_appLifecycleReactor = AppLifecycleReactor();
_appLifecycleReactor.listenToAppStateChanges();
// _appLifecycleReactor = AppLifecycleReactor();
// _appLifecycleReactor.listenToAppStateChanges();
pageController = PageController(initialPage: currentIndex.value);
}
@ -49,8 +45,8 @@ class InitialController extends GetxController {
@override
void onReady() async {
super.onReady();
await FirebaseAnalyticsManager.setCrashlyticsCollectionEnabled();
_addEventLog();
// await FirebaseAnalyticsManager.setCrashlyticsCollectionEnabled();
// _addEventLog();
}
@override
@ -64,9 +60,9 @@ class InitialController extends GetxController {
await PlayerController.to.stopPlay();
Get.toNamed(AppRoutes.uploadMethod);
} else {
if (index == 0) {
_addEventLog();
}
// if (index == 0) {
// _addEventLog();
// }
if (index == 2) _refreshMe();
currentIndex.value = index;
pageController.jumpToPage(index);
@ -84,7 +80,7 @@ class InitialController extends GetxController {
}
VoiceModel? getIsFavouriteModel() {
final list = FavoriteData().getList();
final list = FavoriteBox().getList();
return list.firstWhereOrNull((e) => e.path == currentPlayVoiceModel.value?.path);
}
@ -94,7 +90,7 @@ class InitialController extends GetxController {
isFavourite.value = false;
} else {
if (currentPlayVoiceModel.value != null) {
await FavoriteData().addData(currentPlayVoiceModel.value!.copyWith());
await FavoriteBox().addData(currentPlayVoiceModel.value!.copyWith());
isFavourite.value = true;
}
}

View File

@ -3,7 +3,7 @@ import 'package:get/get.dart';
import 'package:tone_snap/modules/sidea/favourite/favourite_view.dart';
import 'package:tone_snap/modules/sidea/my_voice/my_voice_view.dart';
class MeController extends GetxController with GetTickerProviderStateMixin {
class MeController extends GetxController with GetSingleTickerProviderStateMixin {
late TabController tabController;
final labels = ['My Voice', 'Favourite'];
final pages = [const MyVoiceView(), const FavouriteView()];

View File

@ -4,8 +4,8 @@ import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/dialog/remind_dialog.dart';
import 'package:tone_snap/components/dialog/rename_dialog.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/sidea/storage/my_voice_data.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/my_voice_box.dart';
import 'package:tone_snap/modules/sidea/initial/initial_controller.dart';
import 'package:tone_snap/routes/app_routes.dart';
@ -22,7 +22,7 @@ class MyVoiceController extends GetxController {
}
void getData() {
voiceList.value = MyVoiceData().getList().reversed.toList();
voiceList.value = MyVoiceBox().getList().reversed.toList();
_refreshList();
}

View File

@ -1,7 +1,7 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
import 'package:tone_snap/modules/sidea/initial/initial_controller.dart';
import 'package:tone_snap/routes/app_routes.dart';

View File

@ -1,44 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tone_snap/ads/app_open_ad_manager.dart';
import 'package:tone_snap/utils/tracking_authorization_util.dart';
class SplashController extends GetxController with GetSingleTickerProviderStateMixin {
var processValue = 0.0.obs;
late AnimationController _controller;
late Animation<double> _animation;
@override
void onInit() {
super.onInit();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
);
// Tween AnimationController
_animation = Tween<double>(begin: 0, end: 1).animate(_controller)
..addListener(() {
processValue.value = _animation.value;
if (processValue.value >= 1) {
// 广
AppOpenAdManager().showAdIfAvailable();
}
});
//
_controller.forward();
// 3
Future.delayed(const Duration(seconds: 3), () {
// ATT授权
TrackingAuthorizationUtil.requestTrackingAuthorization();
});
}
@override
void onClose() {
_controller.dispose();
super.onClose();
}
}

View File

@ -5,7 +5,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:tone_snap/components/my_marquee_text.dart';
import 'package:tone_snap/data/sidea/models/voice_model.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/utils/obj_util.dart';

View File

@ -0,0 +1,9 @@
import 'package:get/get.dart';
import 'package:tone_snap/modules/sideb/album/album_controller.dart';
class AlbumBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => AlbumController());
}
}

View File

@ -0,0 +1,104 @@
import 'package:get/get.dart';
import 'package:tone_snap/data/api/music_api.dart';
import 'package:tone_snap/data/models/browse_album_model.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/routes/app_routes.dart';
import 'package:tone_snap/utils/obj_util.dart';
class AlbumController extends GetxController {
var musicPlayerController = MusicPlayerController.to;
String? browseId;
String? params;
///
var bgThumbnail = Rx<String?>(null);
var title = Rx<String?>(null);
var description = Rx<String?>(null);
///
var albumList = <MusicModel>[].obs;
@override
void onInit() {
super.onInit();
browseId = Get.arguments['browseId'];
params = Get.arguments['params'];
}
@override
void onReady() {
super.onReady();
_requestAlbum();
}
///
Future<void> _requestAlbum() async {
Map<String, dynamic> queryParameters = {
'browseId': browseId,
'params': params,
'prettyPrint': false
};
BrowseAlbumModel? browseAlbumModel = await MusicApi.browse<BrowseAlbumModel>(queryParameters: queryParameters, formJson: BrowseAlbumModel.fromMap);
if (browseAlbumModel != null) {
var thumbnails = browseAlbumModel.background?.musicThumbnailRenderer?.thumbnail?.thumbnails;
if (thumbnails != null && thumbnails.isNotEmpty) {
bgThumbnail.value = thumbnails.last.url;
}
var tabs = browseAlbumModel.contents?.twoColumnBrowseResultsRenderer?.tabs;
if (tabs != null && tabs.isNotEmpty) {
var contents = tabs[0].tabRenderer?.content?.sectionListRenderer?.contents;
if (contents != null && contents.isNotEmpty) {
var runs = contents[0].musicResponsiveHeaderRenderer?.title?.runs;
if (runs != null && runs.isNotEmpty) {
title.value = runs[0].text;
}
var runs1 = contents[0].musicResponsiveHeaderRenderer?.description?.musicDescriptionShelfRenderer?.description?.runs;
if (runs1 != null && runs1.isNotEmpty) {
description.value = runs1.map((e) => e.text).join();
}
}
}
var contents = browseAlbumModel.contents?.twoColumnBrowseResultsRenderer?.secondaryContents?.sectionListRenderer?.contents;
if (contents != null && contents.isNotEmpty) {
var contents1 = contents[0].musicShelfRenderer?.contents;
if (contents1 != null && contents1.isNotEmpty) {
for (var o in contents1) {
var playListModel = MusicModel();
var flexColumns = o.musicResponsiveListItemRenderer?.flexColumns;
if (flexColumns != null && flexColumns.isNotEmpty) {
var runs = flexColumns[0].musicResponsiveListItemFlexColumnRenderer?.text?.runs;
if (runs != null && runs.isNotEmpty) {
playListModel.title = runs[0].text;
}
for (var o in flexColumns) {
var runs = o.musicResponsiveListItemFlexColumnRenderer?.text?.runs;
if (runs != null && runs.isNotEmpty) {
playListModel.subTitle = runs.map((e) => e.text).join();
}
}
}
var fixedColumns = o.musicResponsiveListItemRenderer?.fixedColumns;
if (fixedColumns != null && fixedColumns.isNotEmpty) {
var runs = fixedColumns[0].musicResponsiveListItemFixedColumnRenderer?.text?.runs;
if (runs != null && runs.isNotEmpty) {
playListModel.subTitle = '${ObjUtil.getStr(playListModel.subTitle)}${runs.map((e) => e.text).join()}';
}
}
var playlistItemData = o.musicResponsiveListItemRenderer?.playlistItemData;
if (playlistItemData != null) {
playListModel.videoId = playlistItemData.videoId;
}
albumList.add(playListModel);
}
}
}
}
}
///
void onTapAlbumItem(int index, MusicModel model) {
Get.toNamed(AppRoutes.playPage, arguments: {'playList': albumList, 'videoId': model.videoId});
}
}

View File

@ -0,0 +1,229 @@
import 'dart:ui';
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/music_bar.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/generated/assets.dart';
import 'package:tone_snap/modules/sideb/album/album_controller.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
import 'package:tone_snap/res/themes/app_colors.dart';
import 'package:tone_snap/res/themes/app_sizes.dart';
import 'package:tone_snap/utils/obj_util.dart';
class AlbumView extends StatelessWidget {
AlbumView({super.key});
final controller = Get.find<AlbumController>();
final musicPlayerController = MusicPlayerController.to;
@override
Widget build(BuildContext context) {
return Stack(
children: [
Obx(() {
return NetworkImageWidget(
url: controller.bgThumbnail.value,
width: 1.sw,
height: 413.h,
noPlaceholder: true,
);
}),
Scaffold(
backgroundColor: Colors.transparent,
body: Column(
children: [
const MusicAppbar(),
SizedBox(height: 152.h),
_buildIntroduction(),
_buildList(),
Obx(() {
return Visibility(
visible: MusicBar().isShow.value,
child: SizedBox(height: paddingBottomMusicBarHeight(context)),
);
}),
],
),
),
],
);
}
Widget _buildIntroduction() {
return Stack(
children: [
ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(16.r), topRight: Radius.circular(16.r)),
child: Image.asset(
Assets.sideBAlbumTitleBg,
width: 1.sw,
height: 173.h,
fit: BoxFit.fitWidth,
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18).w,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 18.h),
Obx(() {
return Text(
ObjUtil.getStr(controller.title.value),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 22.sp,
fontWeight: FontWeight.w500,
),
);
}),
SizedBox(height: 12.h),
Obx(() {
return Text(
ObjUtil.getStr(controller.description.value),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0x99FFFFFF),
fontSize: 12.sp,
),
);
}),
SizedBox(height: 24.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: 138.w,
height: 32.h,
padding: const EdgeInsets.symmetric(horizontal: 4).w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16).r,
color: const Color(0x1A80F988),
),
child: Row(
children: [
Image.asset(
Assets.sideBAlbumTotal,
width: 24.w,
height: 24.w,
),
SizedBox(width: 4.w),
Expanded(
child: Obx(() {
return Text(
'Play all (${controller.albumList.length})',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
);
}),
),
],
),
),
Image.asset(
Assets.sideBNotCollectionAlbum,
width: 24.w,
height: 24.w,
),
],
),
],
),
),
],
);
}
Widget _buildList() {
return Expanded(
child: Container(
color: const Color(0xFF121212),
child: BaseScrollbar(
child: Obx(() {
return ListView.builder(
itemCount: controller.albumList.length,
itemBuilder: (context, index) {
return _buildListItem(index, controller.albumList[index]);
},
);
}),
),
),
);
}
Widget _buildListItem(int index, MusicModel model) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => controller.onTapAlbumItem(index, model),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8).h,
child: Row(
children: [
SizedBox(width: 18.w),
Text(
(index + 1).toString(),
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
return MyMarqueeText(
enable: musicPlayerController.musicModel.value.videoId == model.videoId,
text: ObjUtil.getStr(model.title),
textStyle: TextStyle(
color: musicPlayerController.musicModel.value.videoId == model.videoId
? seedColor
: Colors.white,
fontSize: 14.sp,
),
);
}),
SizedBox(height: 8.h),
Obx(() {
return MyMarqueeText(
enable: musicPlayerController.musicModel.value.videoId == model.videoId,
text: ObjUtil.getStr(model.subTitle),
textStyle: TextStyle(
color: musicPlayerController.musicModel.value.videoId == model.videoId
? seedColor
: const Color(0x99FFFFFF),
fontSize: 12.sp,
),
);
}),
],
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'collect_playlists_controller.dart';
class CollectPlaylistsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => CollectPlaylistsController());
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class CollectPlaylistsController extends GetxController {
ScrollController scrollController = ScrollController();
@override
void onClose() {
scrollController.dispose();
super.onClose();
}
}

View File

@ -0,0 +1,73 @@
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/network_image_widget.dart';
import 'collect_playlists_controller.dart';
class CollectPlaylistsView extends GetView<CollectPlaylistsController> {
const CollectPlaylistsView({super.key});
@override
Widget build(BuildContext context) {
Get.find<CollectPlaylistsController>();
return BaseScrollbar(
scrollController: controller.scrollController,
child: ListView.builder(
controller: controller.scrollController,
itemCount: 10,
itemBuilder: (context, index) {
return _buildItem();
},
),
);
}
Widget _buildItem() {
return InkWell(
onTap: (){},
child: Padding(
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 16.w),
child: Row(
children: [
NetworkImageWidget(
url: '',
width: 60.w,
height: 60.w,
radius: 8.r,
),
SizedBox(width: 14.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'There For You',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
SizedBox(height: 4.h),
Text(
'234 songs',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0xFF999999),
fontSize: 12.sp,
),
),
],
),
),
],
),
),
);
}
}

View File

@ -3,16 +3,22 @@
// Description:
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';
import 'package:tone_snap/components/music_bar.dart';
import 'package:tone_snap/data/api/music_api.dart';
import 'package:tone_snap/data/cache/music_cache_manager.dart';
import 'package:tone_snap/data/enum/play_mode.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/player_model.dart';
import 'package:tone_snap/data/storage/music_box.dart';
import 'package:tone_snap/routes/app_routes.dart';
import 'package:tone_snap/utils/audio_util.dart';
import 'package:tone_snap/utils/date_util.dart';
import 'package:tone_snap/utils/log_util.dart';
import 'package:tone_snap/utils/num_util.dart';
class MusicPlayerController extends GetxController {
static MusicPlayerController get to => Get.put(MusicPlayerController(), permanent: true);
@ -22,31 +28,46 @@ class MusicPlayerController extends GetxController {
StreamSubscription<Duration>? _positionSubscription;
StreamSubscription<PlayerState>? _playerStateSubscription;
///
// final cacheManager = DefaultCacheManager();
final cacheManager = MusicCacheManager.instance;
var cacheKey = '';
///
var _isPlayingFromCache = false;
/// url
var currentUrl = '';
///
var isPlaying = false.obs;
///
var totalDuration = Duration.zero.obs;
var positionDuration = Duration.zero.obs;
var bufferedDuration = Duration.zero.obs;
///
Duration? bufferedPosition;
///
// final _cacheManager = DefaultCacheManager();
final _cacheManager = MusicCacheManager.instance;
///
///
var isPlaying = false.obs;
///
var processingState = ProcessingState.idle.obs;
/// _player.seek(Duration.zero) completed
/// completed
/// completed
bool _isCompletedHandled = false;
///
var seekFrontPlaying = true;
///
var playMode = MusicBox().getPlayMode().obs;
var _playModeIndex = 0;
///
var playList = <MusicModel>[].obs;
/// videoId
/// videoId
final List<String> _playHistory = [];
///
var musicModel = MusicModel().obs;
///
int _currentIndex = 0;
@override
void onInit() {
super.onInit();
@ -55,118 +76,96 @@ class MusicPlayerController extends GetxController {
@override
void onClose() {
MusicBar().hide();
_cancelListening();
_player.dispose();
super.onClose();
}
/// url
Future<void> playNewUrl(String newUrl, String videoId) async {
await _initializePlayer(newUrl, videoId);
_player.play();
///
void playMusicSpecifyIndex(int index) {
_currentIndex = index;
playMusic();
}
// 使 music_$videoId key
cacheKey = 'music_$videoId';
///
void setPlayList(String videoId, List<MusicModel> list) {
_resetPlaybackStatus();
playList.value = list;
_playHistory.clear();
MusicModel? musicModel = playList.firstWhereOrNull((e) => e.videoId == videoId);
int index = musicModel != null ? playList.indexOf(musicModel) : 0;
_currentIndex = index;
}
///
Future<void> playMusic() async {
Get.currentRoute != AppRoutes.playPage ? MusicBar().show() : MusicBar().hide();
_resetPlaybackStatus();
if (playList.isNotEmpty) {
musicModel.update((e) {
e?.thumbnail = playList[_currentIndex].thumbnail;
e?.title = playList[_currentIndex].title;
e?.subTitle = playList[_currentIndex].subTitle;
});
if (playList[_currentIndex].url == null) {
await _getMusicUrl();
}
if (playList[_currentIndex].url == null) {
BaseEasyLoading.toast('Resource not obtained');
// nextTrack();
return;
}
musicModel.value = playList[_currentIndex].copyWith();
await _initializePlayer();
}
}
/// url
Future<void> _getMusicUrl() async {
PlayerModel? model = await MusicApi.player(videoId: playList[_currentIndex].videoId);
if (model != null && model.streamingData != null) {
var formats = model.streamingData?.formats;
if (formats != null && playList.isNotEmpty) {
playList[_currentIndex].url = formats[0].url;
}
}
if (model != null && model.videoDetails?.thumbnail?.thumbnails != null) {
}
}
/// url
Future<void> _initializePlayer(String url, String videoId) async {
Future<void> _initializePlayer() async {
try {
currentUrl = url;
//
FileInfo? fileInfo = await cacheManager.getFileFromMemory(cacheKey);
FileInfo? fileInfo = await MusicCacheManager.checkCache(musicModel.value.videoId!);
if (fileInfo != null) {
LogUtil.d('有缓存,文件路径=${fileInfo.file.path}');
LogUtil.d('读取缓存=${fileInfo.file.path}');
// 使
await _player.setFilePath(fileInfo.file.path);
_isPlayingFromCache = true;
} else {
// url
await _player.setUrl(currentUrl);
_isPlayingFromCache = false;
// url
await _player.setUrl(musicModel.value.url!);
//
cacheManager.downloadFile(currentUrl, key: cacheKey).then((fileInfo) {
//
if (!_isPlayingFromCache) {
LogUtil.d('新歌曲缓存路径=${fileInfo.file.path}');
_player.setFilePath(fileInfo.file.path);
_isPlayingFromCache = true;
}
//
_cacheManager.downloadFile(musicModel.value.url!, key: MusicCacheManager.getCacheKey(musicModel.value.videoId!)).then((fileInfo) {
LogUtil.d('缓存下载成功=${fileInfo.file.path}');
});
}
_player.setLoopMode(LoopMode.off);
_cancelListening();
_addListening();
_player.play();
} catch (e) {
LogUtil.e('Error initializing player: $e');
BaseEasyLoading.toast('Error initializing player');
}
}
/// /
Future<void> playPause() async {
if (_player.playing) {
_player.pause();
} else {
if (_player.processingState == ProcessingState.idle) {
BaseEasyLoading.loading();
await _player.load();
BaseEasyLoading.dismiss();
}
_player.play();
}
}
///
void stopPlay() {
_player.stop();
_player.seek(Duration.zero);
positionDuration.value = Duration.zero;
}
///
void seekToPosition(double value) {
if (_player.processingState == ProcessingState.ready) {
positionDuration.value = Duration(seconds: value.toInt());
_player.seek(positionDuration.value);
}
}
/// /
Future<void> seekStartEnd(int value) async {
bufferedPosition = _player.bufferedPosition;
if (_player.processingState == ProcessingState.ready) {
if (value == 0) {
seekFrontPlaying = _player.playing;
if (_player.playing) {
_player.pause();
}
} else {
//
if (seekFrontPlaying) {
_player.play();
}
}
}
}
///
void seekToPrevious() {
_player.seekToPrevious();
}
///
void seekToNext() {
_player.seekToNext();
}
///
void _addListening() {
_bufferedSubscription = _player.bufferedPositionStream.listen((bufferedPosition) {
bufferedDuration.value = bufferedPosition;
});
_durationSubscription = _player.durationStream.listen((duration) {
totalDuration.value = duration ?? Duration.zero;
});
@ -175,8 +174,18 @@ class MusicPlayerController extends GetxController {
positionDuration.value = position;
});
_bufferedSubscription = _player.bufferedPositionStream.listen((bufferedPosition) {
int comparison = bufferedPosition.compareTo(totalDuration.value);
if (comparison > 0) {
bufferedDuration.value = totalDuration.value;
} else {
bufferedDuration.value = bufferedPosition;
}
});
_playerStateSubscription = _player.playerStateStream.listen((playerState) {
isPlaying.value = _player.playing;
processingState.value = playerState.processingState;
switch (playerState.processingState) {
case ProcessingState.idle:
break;
@ -185,9 +194,14 @@ class MusicPlayerController extends GetxController {
case ProcessingState.buffering:
break;
case ProcessingState.ready:
_isCompletedHandled = false;
break;
case ProcessingState.completed:
stopPlay();
if (!_isCompletedHandled) {
_isCompletedHandled = true;
_player.seek(Duration.zero);
nextTrack();
}
break;
}
});
@ -201,23 +215,118 @@ class MusicPlayerController extends GetxController {
_playerStateSubscription?.cancel();
}
///
String getTotalDuration() {
return DateUtil.playDuration(totalDuration.value);
}
///
String getPositionDuration() {
return DateUtil.playDuration(positionDuration.value);
}
///
Future<bool> _fileExists(String path) async {
if (await File(path).exists()) {
return true;
} else {
BaseEasyLoading.toast('Local resource does not exist');
return false;
///
void seekToPosition(double value) {
if (processingState.value == ProcessingState.ready) {
positionDuration.value = Duration(seconds: value.toInt());
_player.seek(positionDuration.value);
}
}
/// /
Future<void> seekStartEnd(int value) async {
if (processingState.value == ProcessingState.ready) {
if (value == 0) {
seekFrontPlaying = _player.playing;
if (_player.playing) {
_player.pause();
}
} else {
//
if (seekFrontPlaying) {
_player.play();
}
}
}
}
/// /
Future<void> playPause() async {
if (_player.playing) {
_player.pause();
} else {
if (processingState.value == ProcessingState.ready) {
_player.play();
}
}
}
///
void previousTrack() {
if (playList.isNotEmpty) {
switch(playMode.value) {
case PlayMode.listLoop:
_currentIndex = (_currentIndex - 1 + playList.length) % playList.length;
playMusic();
break;
case PlayMode.random:
bool historyExist = false;
for (var i = _playHistory.length - 1; i >= 0; --i) {
var history = _playHistory[i];
MusicModel? model = playList.firstWhereOrNull((e) => e.videoId == history);
if (model != null) {
_currentIndex = playList.indexOf(model);
_playHistory.remove(history);
historyExist = true;
break;
} else {
_playHistory.remove(history);
}
}
if (!historyExist) {
_getRandomNumber();
}
playMusic();
break;
case PlayMode.singleCycle:
break;
}
}
}
///
void nextTrack() {
if (playList.isNotEmpty) {
switch(playMode.value) {
case PlayMode.listLoop:
_currentIndex = (_currentIndex + 1) % playList.length;
playMusic();
break;
case PlayMode.random:
//
_playHistory.add(musicModel.value.videoId!);
_getRandomNumber();
playMusic();
break;
case PlayMode.singleCycle:
break;
}
}
}
///
void _getRandomNumber() {
_currentIndex = NumUtil.getRandomNumberExcludingCurrent(0, playList.length, _currentIndex);
}
///
void switchPlayMode() {
if (_playModeIndex == PlayMode.values.length - 1) {
_playModeIndex = 0;
} else {
_playModeIndex++;
}
playMode.value = PlayMode.values[_playModeIndex];
MusicBox().putPlayMode(playMode.value);
}
///
void _resetPlaybackStatus() {
_player.stop();
_player.seek(Duration.zero);
totalDuration.value = Duration.zero;
positionDuration.value = Duration.zero;
bufferedDuration.value = Duration.zero;
musicModel.update((model) => model = MusicModel());
}
}

View File

@ -1,13 +1,15 @@
import 'package:get/get.dart';
import 'package:tone_snap/data/sideb/api/music_api.dart';
import 'package:tone_snap/data/sideb/enum/browse_type.dart';
import 'package:tone_snap/data/sideb/models/browse_model.dart';
import 'package:tone_snap/data/sideb/models/home_model.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.dart';
import 'package:tone_snap/data/enum/browse_type.dart';
import 'package:tone_snap/data/models/browse_model.dart';
import 'package:tone_snap/data/models/home_model.dart';
import 'package:tone_snap/routes/app_routes.dart';
import 'package:tone_snap/utils/obj_util.dart';
class HomeController extends GetxController {
var data = <HomeModel>[].obs;
var viewState = ViewState.loading.obs;
var homeList = <HomeModel>[].obs;
String? visitorData;
@override
@ -22,7 +24,7 @@ class HomeController extends GetxController {
'prettyPrint': false,
'browseId': 'FEmusic_home'
};
BrowseModel? browseModel = await MusicApi.browse(queryParameters: queryParameters);
BrowseModel? browseModel = await MusicApi.browse<BrowseModel>(queryParameters: queryParameters, formJson: BrowseModel.fromMap);
if (browseModel != null) {
_extractAssemblyData(browseModel);
@ -50,7 +52,7 @@ class HomeController extends GetxController {
'itct': clickTrackingParams,
'prettyPrint': false
};
BrowseModel? browseModel = await MusicApi.browse(queryParameters: queryParameters, visitorData: visitorData);
BrowseModel? browseModel = await MusicApi.browse<BrowseModel>(visitorData: visitorData, queryParameters: queryParameters, formJson: BrowseModel.fromMap);
if (browseModel != null) {
_extractAssemblyData(browseModel);
@ -109,6 +111,7 @@ class HomeController extends GetxController {
} else if (runs[0].navigationEndpoint?.browseEndpoint != null) {
model.browseType = runs[0].navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType;
content.browseId = runs[0].navigationEndpoint?.browseEndpoint?.browseId;
content.params = runs[0].navigationEndpoint?.browseEndpoint?.params;
}
}
} else {
@ -138,6 +141,7 @@ class HomeController extends GetxController {
if (e2.musicTwoRowItemRenderer?.navigationEndpoint != null) {
model.browseType = e2.musicTwoRowItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType;
content.browseId = e2.musicTwoRowItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId;
content.params = e2.musicTwoRowItemRenderer?.navigationEndpoint?.browseEndpoint?.params;
}
}
if (thumbnails != null && thumbnails.isNotEmpty) {
@ -148,14 +152,20 @@ class HomeController extends GetxController {
}
//
if (BrowseTypeExtension.isThereAny(model.browseType)) {
data.add(model);
homeList.add(model);
}
}
}
viewState.value = homeList.isNotEmpty ? ViewState.normal : ViewState.empty;
}
///
void goPlayMusic(Content content) {
Get.toNamed(AppRoutes.playMusic, arguments: {'playlistId': content.playlistId, 'videoId': content.videoId});
///
void openPlayPage(Content content) {
Get.toNamed(AppRoutes.playPage, arguments: {'playlistId': content.playlistId, 'videoId': content.videoId});
}
/// /
void openSongSheet(Content content) {
Get.toNamed(AppRoutes.album, arguments: {'browseId': content.browseId});
}
}

View File

@ -2,11 +2,12 @@ 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/data/sideb/enum/browse_type.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/enum/browse_type.dart';
import 'package:tone_snap/data/models/home_model.dart';
import 'package:tone_snap/modules/sideb/widgets/album_item.dart';
import 'package:tone_snap/modules/sideb/widgets/atv_item.dart';
import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
import 'package:tone_snap/data/sideb/models/home_model.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sideb/widgets/omv_item.dart';
import 'package:tone_snap/utils/obj_util.dart';
@ -21,13 +22,13 @@ class HomeView extends GetView<HomeController> {
Get.find<HomeController>();
return Column(
children: [
_buildTitleWidget(),
_buildTitle(),
_buildListView(context),
],
);
}
Widget _buildTitleWidget() {
Widget _buildTitle() {
return MusicAppbar(
title: 'Musicoo',
showBackWidget: false,
@ -42,24 +43,74 @@ class HomeView extends GetView<HomeController> {
Widget _buildListView(BuildContext context) {
return Expanded(
child: BaseScrollbar(
child: Obx(() {
return ListView.separated(
itemCount: controller.data.length,
padding: EdgeInsets.fromLTRB(16.w, 0.h, 16.w, 16.h),
itemBuilder: (context, index) {
return _buildColumnItem(controller.data[index]);
},
separatorBuilder: (context, index) {
return SizedBox(height: 16.h);
},
);
}),
child: Obx(() {
return ViewStateWidget(
viewState: controller.viewState.value,
child: BaseScrollbar(
child: ListView.separated(
itemCount: controller.homeList.length,
padding: EdgeInsets.fromLTRB(16.w, 0.h, 16.w, 16.h),
itemBuilder: (context, index) {
return _buildItem(controller.homeList[index]);
},
separatorBuilder: (context, index) {
return SizedBox(height: 16.h);
},
),
),
);
}),
);
}
Widget _buildItem(HomeModel model) {
if (!BrowseTypeExtension.isThereAny(model.browseType)) {
return Container();
}
double? itemHeight;
if (model.browseType == BrowseType.musicVideoTypeAtv.name) {
itemHeight = 174.h;
} else if (model.browseType == BrowseType.musicPageTypeAlbum.name ||
model.browseType == BrowseType.musicPageTypePlaylist.name) {
itemHeight = 195.h;
} else if (model.browseType == BrowseType.musicVideoTypeOmv.name) {
itemHeight = 277.h;
}
return SizedBox(
height: itemHeight,
child: Column(
children: [
_labelWidget(ObjUtil.getStr(model.headerTitle)),
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: model.contents != null ? model.contents!.length : 0,
itemBuilder: (context, index) {
final content = model.contents![index];
if (model.browseType == BrowseType.musicVideoTypeAtv.name) {
return AtvItem(content: content,
onTap: () => controller.openPlayPage(content));
} else
if (model.browseType == BrowseType.musicPageTypeAlbum.name ||
model.browseType == BrowseType.musicPageTypePlaylist.name) {
return AlbumItem(content: content,
onTap: () => controller.openSongSheet(content));
} else
if (model.browseType == BrowseType.musicVideoTypeOmv.name) {
return OmvItem(content: content);
}
return Container();
},
separatorBuilder: (context, index) {
return SizedBox(width: 7.w);
},
),
),
],
),
);
}
///
Widget _labelWidget(String label) {
return InkWell(
onTap: () {},
@ -89,47 +140,4 @@ class HomeView extends GetView<HomeController> {
),
);
}
/// Item
Widget _buildColumnItem(HomeModel model) {
if (!BrowseTypeExtension.isThereAny(model.browseType)) {
return Container();
}
double? itemHeight;
if (model.browseType == BrowseType.musicVideoTypeAtv.name) {
itemHeight = 174.h;
} else if (model.browseType == BrowseType.musicPageTypeAlbum.name || model.browseType == BrowseType.musicPageTypePlaylist.name) {
itemHeight = 195.h;
} else if (model.browseType == BrowseType.musicVideoTypeOmv.name) {
itemHeight = 277.h;
}
return SizedBox(
height: itemHeight,
child: Column(
children: [
_labelWidget(ObjUtil.getStr(model.headerTitle)),
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: model.contents != null ? model.contents!.length : 0,
itemBuilder: (context, index) {
final content = model.contents![index];
if (model.browseType == BrowseType.musicVideoTypeAtv.name) {
return AtvItem(content: content, onTap: () => controller.goPlayMusic(content));
} else if (model.browseType == BrowseType.musicPageTypeAlbum.name || model.browseType == BrowseType.musicPageTypePlaylist.name) {
return AlbumItem(content: content);
} else if (model.browseType == BrowseType.musicVideoTypeOmv.name) {
return OmvItem(content: content);
}
return Container();
},
separatorBuilder: (context, index) {
return SizedBox(width: 7.w);
},
),
),
],
),
);
}
}

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sideb/home/home_view.dart';
import 'package:tone_snap/modules/sideb/me/me_view.dart';
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_view.dart';
import 'package:tone_snap/modules/sideb/search_music/search_music_view.dart';
class InitialController extends GetxController {
@ -11,7 +11,7 @@ class InitialController extends GetxController {
final pages = [
PageItem([Assets.sideBBnb1Unselected, Assets.sideBBnb1Selected], const HomeView()),
PageItem([Assets.sideBBnb2Unselected, Assets.sideBBnb2Selected], const SearchMusicView()),
PageItem([Assets.sideBBnb3Unselected, Assets.sideBBnb3Selected], const MeView()),
PageItem([Assets.sideBBnb3Unselected, Assets.sideBBnb3Selected], const PersonalMusicLibraryView()),
];
var currentIndex = 0.obs;

View File

@ -15,28 +15,35 @@ class InitialView extends StatelessWidget {
double bottomPadding = MediaQuery.of(context).padding.bottom;
return Stack(
children: [
Image.asset(
Assets.sideBHomeBg,
width: 1.sw,
fit: BoxFit.fitWidth,
),
_buildPageBg(),
Scaffold(
backgroundColor: Colors.transparent,
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: controller.pageController,
children: controller.pages.map((e) => KeepAliveWrapper(child: e.widget)).toList(),
),
body: _buildBody(),
bottomNavigationBar: _buildBottomAppBar(bottomPadding),
),
// _buildPlayerBar(bottomPadding),
],
);
}
Widget _buildPageBg() {
return Image.asset(
Assets.sideBHomeBg,
width: 1.sw,
fit: BoxFit.fitWidth,
);
}
Widget _buildBody() {
return PageView(
physics: const NeverScrollableScrollPhysics(),
controller: controller.pageController,
children: controller.pages.map((e) => KeepAliveWrapper(child: e.widget)).toList(),
);
}
Widget _buildBottomAppBar(double bottomPadding) {
return Container(
height: 72.h + bottomPadding,
height: kBottomNavigationBarHeight + bottomPadding,
padding: EdgeInsets.only(bottom: bottomPadding),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r)),
@ -79,28 +86,4 @@ class InitialView extends StatelessWidget {
),
);
}
Widget _buildPlayerBar(double bottomPadding) {
return Positioned(
bottom: 65.5.h + bottomPadding,
left: 16.w,
right: 16.w,
child: Container(
width: 1.sw,
height: 75.5.h,
decoration: BoxDecoration(
color: const Color(0xFF80F988),
borderRadius: BorderRadius.circular(36).r,
boxShadow: const [
BoxShadow(
color: Color(0x40040604),
offset: Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
),
],
),
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'love_songs_controller.dart';
class LoveSongsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => LoveSongsController());
}
}

View File

@ -0,0 +1,13 @@
import 'package:get/get.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/love_songs_box.dart';
class LoveSongsController extends GetxController {
var loveList = <MusicModel>[];
@override
void onInit() {
super.onInit();
loveList = LoveSongsBox().getList();
}
}

View File

@ -0,0 +1,107 @@
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/network_image_widget.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
import 'love_songs_controller.dart';
class LoveSongsView extends StatelessWidget {
LoveSongsView({super.key});
final controller = Get.find<LoveSongsController>();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const MusicAppbar(
title: 'Love Songs',
),
_buildTotal(),
_buildListView(),
],
),
),
);
}
Widget _buildTotal() {
return Padding(
padding: EdgeInsets.only(top: 34.h, bottom: 10.h, left: 18.w, right: 18.w),
child: Text(
'${controller.loveList.length} Songs',
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
overflow: TextOverflow.ellipsis,
),
),
);
}
Widget _buildListView() {
return Expanded(
child: BaseScrollbar(
child: ListView.builder(
itemCount: controller.loveList.length,
itemBuilder: (context, index) {
return _buildItem(controller.loveList[index]);
},
),
),
);
}
Widget _buildItem(MusicModel model) {
return InkWell(
onTap: (){},
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 18.w),
child: Row(
children: [
NetworkImageWidget(
url: '',
width: 60.w,
height: 60.w,
radius: 8.r,
),
SizedBox(width: 14.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'There For You',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
),
SizedBox(height: 4.h),
Text(
'234 songs',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0xFF999999),
fontSize: 12.sp,
),
),
],
),
),
],
),
),
);
}
}

View File

@ -1,10 +0,0 @@
import 'package:get/get.dart';
import 'me_controller.dart';
class MeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => MeController());
}
}

View File

@ -1,5 +0,0 @@
import 'package:get/get.dart';
class MeController extends GetxController {
}

View File

@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'me_controller.dart';
class MeView extends GetView<MeController> {
const MeView({super.key});
@override
Widget build(BuildContext context) {
Get.find<MeController>();
return Container();
}
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'personal_music_library_controller.dart';
class PersonalMusicLibraryBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => PersonalMusicLibraryController());
}
}

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tone_snap/modules/sideb/collect_playlists/collect_playlists_view.dart';
import 'package:tone_snap/modules/sideb/playlists/playlists_view.dart';
import 'package:tone_snap/routes/app_routes.dart';
class PersonalMusicLibraryController extends GetxController with GetSingleTickerProviderStateMixin {
late TabController tabController;
final labels = ['Playlists', 'Collect Playlists'];
final pages = [const PlaylistsView(), const CollectPlaylistsView()];
@override
void onInit() {
super.onInit();
tabController = TabController(length: labels.length, vsync: this);
}
@override
void onClose() {
tabController.dispose();
super.onClose();
}
void onTapModule(int index) {
if (index == 0) {
Get.toNamed(AppRoutes.loveSongs);
}
if (index == 1) {
Get.toNamed(AppRoutes.loveSongs);
}
if (index == 2) {
Get.toNamed(AppRoutes.loveSongs);
}
}
}

View File

@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/keep_alive_wrapper.dart';
import 'package:tone_snap/components/my_custom_indicator.dart';
import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/res/themes/app_colors.dart';
import 'personal_music_library_controller.dart';
class PersonalMusicLibraryView extends GetView<PersonalMusicLibraryController> {
const PersonalMusicLibraryView({super.key});
@override
Widget build(BuildContext context) {
Get.find<PersonalMusicLibraryController>();
return Stack(
children: [
_buildPageBg(),
SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitle(),
_buildModule(),
_buildTabBar(),
_buildTabBarView(),
],
),
),
],
);
}
Widget _buildPageBg() {
return Image.asset(
Assets.sideBPersonalMusicLibraryBg,
width: 1.sw,
fit: BoxFit.fitWidth,
);
}
Widget _buildTitle() {
return Padding(
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 16.w),
child: Text(
'Library',
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
),
),
);
}
Widget _buildModule() {
return Padding(
padding: EdgeInsets.symmetric(vertical: 28.h, horizontal: 16.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildModuleItem('', Assets.sideBLoveSolid, 'Songs 122', () => controller.onTapModule(0)),
_buildModuleItem('', Assets.sideBArtists, 'Artists 122', () => controller.onTapModule(1)),
_buildModuleItem('', Assets.sideBOfflineDownload, 'Offline 122', () => controller.onTapModule(2)),
],
),
);
}
Widget _buildModuleItem(String url, String labelImg, String label, Function() onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 109.w,
height: 109.w,
decoration: BoxDecoration(
color: const Color(0xFF242529),
borderRadius: BorderRadius.circular(8).r,
),
child: Stack(
children: [
Center(
child: NetworkImageWidget(
url: url,
width: 55.w,
height: 55.w,
placeholder: Assets.sideBPlaceholderLibrary,
),
),
Positioned(
bottom: 8.h,
left: 8.w,
right: 8.w,
child: Row(
children: [
Image.asset(
labelImg,
width: 20,
height: 20,
),
SizedBox(width: 4.w),
Expanded(
child: Text(
label,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
height: 1,
),
),
),
],
),
),
],
),
),
);
}
Widget _buildTabBar() {
return Container(
height: 32.h,
margin: const EdgeInsets.symmetric(vertical: 12).h,
child: TabBar(
controller: controller.tabController,
tabAlignment: TabAlignment.center,
dividerHeight: 0,
padding: const EdgeInsets.symmetric(horizontal: 4).w,
labelPadding: const EdgeInsets.symmetric(horizontal: 12).w,
labelStyle: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
unselectedLabelStyle: TextStyle(
color: const Color(0xFF999999),
fontSize: 14.sp,
),
indicatorPadding: EdgeInsets.zero,
indicator: MyCustomIndicator(
indWidth: 16.w,
indHeight: 2.h,
color: seedColor,
radius: 1.r,
),
tabs: controller.labels.map((e) => Tab(text: e)).toList(),
),
);
}
Widget _buildTabBarView() {
return Expanded(
child: TabBarView(
controller: controller.tabController,
children: controller.pages.map((e) => KeepAliveWrapper(child: e)).toList(),
),
);
}
}

View File

@ -1,10 +0,0 @@
import 'package:get/get.dart';
import 'play_music_controller.dart';
class PlayMusicBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => PlayMusicController());
}
}

View File

@ -1,317 +0,0 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/full_width_track_shape.dart';
import 'package:tone_snap/components/my_marquee_text.dart';
import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sideb/widgets/music_appbar.dart';
import 'package:tone_snap/res/themes/app_colors.dart';
import 'package:tone_snap/utils/obj_util.dart';
import 'play_music_controller.dart';
class PlayMusicView extends StatelessWidget {
PlayMusicView({super.key});
final controller = Get.find<PlayMusicController>();
@override
Widget build(BuildContext context) {
return Stack(
children: [
Obx(() {
return Visibility(
visible: ObjUtil.isNotEmptyStr(controller.thumbnail.value),
child: NetworkImageWidget(
url: controller.thumbnail.value,
width: 1.sw,
height: 1.sh,
placeholder: Container(),
errorWidget: Container(),
),
);
}),
_buildPageBg(),
Scaffold(
backgroundColor: Colors.transparent,
body: Column(
children: [
_buildHeader(),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20).w,
child: Column(
children: [
SizedBox(height: 12.h),
_buildCover(),
SizedBox(height: 36.h),
_buildMusicName(),
SizedBox(height: 24.h),
_processBar(context),
SizedBox(height: 100.h),
_buildPlayController(),
],
),
),
),
],
),
)
],
);
}
///
Widget _buildPageBg() {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50.0, sigmaY: 50.0),
child: Container(
width: 1.sw,
height: 1.sh,
color: const Color(0x26000000),
),
);
}
///
Widget _buildHeader() {
return MusicAppbar(
isDownBack: true,
isBackBorder: true,
actionOnTap: (){},
titleWidget: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildMenuText('SONG', true),
Container(
width: 1.w,
height: 14.h,
color: const Color(0x73FFFFFF),
),
_buildMenuText('LYRICS', false),
],
),
action: Image.asset(
Assets.sideBCrossCircle,
width: 24.w,
height: 24.w,
),
);
}
/// /
Widget _buildMenuText(String label, bool selected) {
return InkWell(
onTap: (){},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15.5.w, vertical: 8.h),
child: Text(
label,
style: TextStyle(
color: Color(selected ? 0xD9FFFFFF : 0x73FFFFFF),
fontSize: 15.sp,
),
),
),
);
}
///
Widget _buildCover() {
return Obx(() {
return NetworkImageWidget(
url: controller.thumbnail.value,
width: 1.sw,
height: 330.h,
radius: 16.r,
);
});
}
/// /
Widget _buildMusicName() {
return Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
return MyMarqueeText(
text: ObjUtil.getStr(controller.title.value),
textStyle: TextStyle(
color: const Color(0xD9FFFFFF),
fontSize: 22.sp,
),
);
}),
SizedBox(height: 6.h),
Obx(() {
return MyMarqueeText(
text: ObjUtil.getStr(controller.subTitle.value),
textStyle: TextStyle(
color: const Color(0x99EEEEEE),
fontSize: 12.sp,
),
);
}),
],
),
),
SizedBox(width: 10.w),
Image.asset(
Assets.sideBLove,
width: 24.w,
height: 24.w,
),
SizedBox(width: 20.w),
Image.asset(
Assets.sideBDownload,
width: 24.w,
height: 24.w,
),
],
);
}
///
Widget _processBar(BuildContext context) {
return Column(
children: [
SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: sideBSeedColor,
inactiveTrackColor: const Color(0x4DFFFFFF),
secondaryActiveTrackColor: sideBSeedColor.withOpacity(0.3),
trackHeight: 3.h,
thumbColor: Colors.white,
thumbShape: RoundSliderThumbShape(
disabledThumbRadius: 7.w,
enabledThumbRadius: 7.w,
),
trackShape: FullWidthTrackShape(),
),
child: SizedBox(
width: 1.sw,
height: 7.w,
child: Obx(() {
return Slider(
value: controller.musicPlayerController.positionDuration.value.inSeconds.toDouble(),
secondaryTrackValue: controller.musicPlayerController.bufferedDuration.value.inSeconds.toDouble(),
min: 0,
max: controller.musicPlayerController.totalDuration.value.inSeconds.toDouble(),
onChanged: (value) => controller.musicPlayerController.seekToPosition(value),
onChangeStart: (value) => controller.musicPlayerController.seekStartEnd(0),
onChangeEnd: (value) => controller.musicPlayerController.seekStartEnd(1),
);
}),
),
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Obx(() {
return Text(
controller.musicPlayerController.getPositionDuration(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0xD9FFFFFF),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
);
}),
),
Flexible(
child: Obx(() {
return Text(
controller.musicPlayerController.getTotalDuration(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0x99FFFFFF),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
);
}),
),
],
),
],
);
}
///
Widget _buildPlayController() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: (){},
child: Image.asset(
Assets.sideBListLoop,
width: 24.w,
height: 24.w,
),
),
GestureDetector(
onTap: controller.nextTrack,
child: Padding(
padding: const EdgeInsets.only(left: 20).w,
child: Image.asset(
Assets.sideBPreviousSong,
width: 20.w,
height: 20.w,
),
),
),
GestureDetector(
onTap: controller.playPause,
child: ClipOval(
child: Container(
width: 66.w,
height: 66.w,
color: Colors.white,
child: FittedBox(
fit: BoxFit.none,
child: Obx(() {
return Image.asset(
controller.musicPlayerController.isPlaying.value ? Assets.sideBPausePlay : Assets.sideBStartPlay,
width: 26.w,
height: 26.w,
);
}),
),
),
),
),
Padding(
padding: const EdgeInsets.only(right: 20).w,
child: GestureDetector(
onTap: controller.nextTrack,
child: Image.asset(
Assets.sideBNextSong,
width: 20.w,
height: 20.w,
),
),
),
GestureDetector(
onTap: (){},
child: Image.asset(
Assets.sideBPlayList,
width: 24.w,
height: 24.w,
),
),
],
);
}
}

View File

@ -0,0 +1,9 @@
import 'package:get/get.dart';
import 'package:tone_snap/modules/sideb/play_page/play_page_controller.dart';
class PlayPageBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => PlayPageController());
}
}

View File

@ -1,45 +1,52 @@
import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/sideb/api/music_api.dart';
import 'package:tone_snap/data/sideb/models/next_model.dart';
import 'package:tone_snap/data/sideb/models/play_list_model.dart';
import 'package:tone_snap/data/sideb/models/player_model.dart';
import 'package:tone_snap/data/api/music_api.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/love_songs_box.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/utils/obj_util.dart';
class PlayMusicController extends GetxController {
class PlayPageController extends GetxController {
var musicPlayerController = MusicPlayerController.to;
/// MusicBar
bool isMusicBarOpen = false;
///
var isLove = false.obs;
///
late String videoId;
late String playlistId;
var thumbnail = Rx<String?>(null);
var title = Rx<String?>(null);
var subTitle = Rx<String?>(null);
///
List<PlayListModel> playList = [];
///
int currentIndex = 0;
String? playlistId;
List<MusicModel>? playList;
@override
void onInit() {
super.onInit();
videoId = Get.arguments['videoId'] ?? '';
playlistId = Get.arguments['playlistId'];
videoId = Get.arguments['videoId'];
playList = Get.arguments['playList'];
isMusicBarOpen = Get.arguments['isMusicBarOpen'] ?? false;
isLove.value = LoveSongsBox().isLove(videoId);
}
@override
void onReady() async {
super.onReady();
_next();
if (!isMusicBarOpen) {
if (musicPlayerController.musicModel.value.videoId != videoId) {
playList ??= await _next();
musicPlayerController.setPlayList(videoId, playList!);
musicPlayerController.playMusic();
}
}
}
Future<dynamic> _next() async {
BaseEasyLoading.loading();
///
Future<List<MusicModel>> _next() async {
List<MusicModel> playList = [];
NextModel? model = await MusicApi.next(playlistId: playlistId, videoId: videoId);
BaseEasyLoading.dismiss();
if (model != null) {
var tabs = model.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs;
if (tabs != null && tabs.isNotEmpty) {
@ -49,7 +56,7 @@ class PlayMusicController extends GetxController {
var contents = o.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents;
if (contents != null && contents.isNotEmpty) {
for (var j = 0; j < contents.length; ++j) {
var playListModel = PlayListModel();
var playListModel = MusicModel();
var content = contents[j];
//
var thumbnails = content.playlistPanelVideoRenderer?.thumbnail?.thumbnails;
@ -81,56 +88,33 @@ class PlayMusicController extends GetxController {
}
}
}
_getCurrentMusicInfo();
_player();
}
return playList;
}
/// /
Future<void> addCancelLove() async {
if (isLove.value) {
await LoveSongsBox().delete(musicPlayerController.musicModel.value.videoId!);
isLove.value = false;
BaseEasyLoading.toast('Cancelled');
} else {
await LoveSongsBox().addData(musicPlayerController.musicModel.value.copyWith());
isLove.value = true;
BaseEasyLoading.toast('Collected');
}
}
///
void _getCurrentMusicInfo() {
if (playList.isNotEmpty) {
thumbnail.value = playList[currentIndex].thumbnail;
title.value = playList[currentIndex].title;
subTitle.value = playList[currentIndex].subTitle;
///
void switchMusic(int index, MusicModel model) {
if (musicPlayerController.musicModel.value.videoId != model.videoId) {
musicPlayerController.playMusicSpecifyIndex(index);
}
Get.back();
}
Future<dynamic> _player() async {
if (playList.isEmpty) return;
BaseEasyLoading.loading();
PlayerModel? model = await MusicApi.player(videoId: playList[currentIndex].videoId);
BaseEasyLoading.dismiss();
if (model != null && model.streamingData != null) {
var formats = model.streamingData?.formats;
if (formats != null && playList.isNotEmpty) {
playList[currentIndex].url = formats[0].url ?? '';
//
musicPlayerController.playNewUrl(playList[currentIndex].url!, ObjUtil.getStr(playList[currentIndex].videoId));
}
}
}
/// /
void playPause() {
musicPlayerController.playPause();
}
///
Future<void> previousTrack() async {
if (currentIndex > 0) {
currentIndex--;
}
_getCurrentMusicInfo();
_player();
}
///
Future<void> nextTrack() async {
if (currentIndex < playList.length - 1) {
currentIndex++;
}
_getCurrentMusicInfo();
_player();
///
void deleteMusic(int index) {
musicPlayerController.playList.removeAt(index);
}
}

View File

@ -0,0 +1,535 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import 'package:tone_snap/components/base_scrollbar.dart';
import 'package:tone_snap/components/full_width_track_shape.dart';
import 'package:tone_snap/components/my_marquee_text.dart';
import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/data/enum/play_mode.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/generated/assets.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';
import 'package:tone_snap/res/themes/app_colors.dart';
import 'package:tone_snap/utils/date_util.dart';
import 'package:tone_snap/utils/obj_util.dart';
class PlayPageView extends StatelessWidget {
PlayPageView({super.key});
final controller = Get.find<PlayPageController>();
final musicPlayerController = MusicPlayerController.to;
@override
Widget build(BuildContext context) {
return Stack(
children: [
_buildPageBgImg(),
_buildPageBgColor(),
Scaffold(
backgroundColor: Colors.transparent,
body: Column(
children: [
_buildHeader(),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20).w,
child: Column(
children: [
SizedBox(height: 12.h),
_buildCover(),
SizedBox(height: 36.h),
_buildMusicName(),
SizedBox(height: 24.h),
_processBar(context),
SizedBox(height: 24.h),
],
),
),
),
_buildPlayControlButtons(),
SizedBox(height: 67.h),
],
),
)
],
);
}
///
Widget _buildPageBgImg() {
return Obx(() {
return Visibility(
visible: ObjUtil.isNotEmptyStr(musicPlayerController.musicModel.value.thumbnail),
child: NetworkImageWidget(
url: musicPlayerController.musicModel.value.thumbnail,
width: 1.sw,
height: 1.sh,
noPlaceholder: true,
),
);
});
}
///
Widget _buildPageBgColor() {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50.0, sigmaY: 50.0),
child: Container(
width: 1.sw,
height: 1.sh,
color: const Color(0x26000000),
),
);
}
///
Widget _buildHeader() {
return MusicAppbar(
isDownBack: true,
isBackBorder: true,
actionOnTap: (){},
titleWidget: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildMenuText('SONG', true),
Container(
width: 1.w,
height: 14.h,
color: const Color(0x73FFFFFF),
),
_buildMenuText('LYRICS', false),
],
),
action: Image.asset(
Assets.sideBCrossCircle,
width: 24.w,
height: 24.w,
),
);
}
/// /
Widget _buildMenuText(String label, bool selected) {
return InkWell(
onTap: (){},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15.5.w, vertical: 8.h),
child: Text(
label,
style: TextStyle(
color: Color(selected ? 0xD9FFFFFF : 0x73FFFFFF),
fontSize: 15.sp,
),
),
),
);
}
///
Widget _buildCover() {
return Obx(() {
return NetworkImageWidget(
url: musicPlayerController.musicModel.value.thumbnail,
width: 1.sw,
height: 330.h,
radius: 16.r,
);
});
}
/// /
Widget _buildMusicName() {
return Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.musicModel.value.title),
textStyle: TextStyle(
color: const Color(0xD9FFFFFF),
fontSize: 22.sp,
),
);
}),
SizedBox(height: 6.h),
Obx(() {
return MyMarqueeText(
text: ObjUtil.getStr(musicPlayerController.musicModel.value.subTitle),
textStyle: TextStyle(
color: const Color(0x99EEEEEE),
fontSize: 12.sp,
),
);
}),
],
),
),
SizedBox(width: 10.w),
ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: controller.addCancelLove,
child: Padding(
padding: EdgeInsets.all(6.w),
child: Obx(() {
return Image.asset(
controller.isLove.value ? Assets.sideBLoveSolid : Assets.sideBLove,
width: 24.w,
height: 24.w,
);
}),
),
),
),
),
SizedBox(width: 8.w),
ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: (){},
child: Padding(
padding: EdgeInsets.all(6.w),
child: Image.asset(
Assets.sideBDownload,
width: 24.w,
height: 24.w,
),
),
),
),
),
],
);
}
///
Widget _processBar(BuildContext context) {
return Column(
children: [
SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: seedColor,
inactiveTrackColor: const Color(0x4DFFFFFF),
secondaryActiveTrackColor: seedColor.withOpacity(0.3),
trackHeight: 3.h,
thumbColor: Colors.white,
thumbShape: RoundSliderThumbShape(
disabledThumbRadius: 7.w,
enabledThumbRadius: 7.w,
),
trackShape: FullWidthTrackShape(),
),
child: SizedBox(
width: 1.sw,
height: 7.w,
child: Obx(() {
return Slider(
value: musicPlayerController.positionDuration.value.inSeconds.toDouble(),
secondaryTrackValue: musicPlayerController.bufferedDuration.value.inSeconds.toDouble(),
min: 0,
max: musicPlayerController.totalDuration.value.inSeconds.toDouble(),
onChanged: (value) => musicPlayerController.seekToPosition(value),
onChangeStart: (value) => musicPlayerController.seekStartEnd(0),
onChangeEnd: (value) => musicPlayerController.seekStartEnd(1),
);
}),
),
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Obx(() {
return Text(
DateUtil.playDuration(musicPlayerController.positionDuration.value),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0xD9FFFFFF),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
);
}),
),
Flexible(
child: Obx(() {
return Text(
DateUtil.playDuration(musicPlayerController.totalDuration.value),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0x99FFFFFF),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
);
}),
),
],
),
],
);
}
///
Widget _buildPlayControlButtons() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10).w,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: musicPlayerController.switchPlayMode,
child: Obx(() {
var playModeAssets = Assets.sideBListLoop;
if (musicPlayerController.playMode.value == PlayMode.listLoop) {
playModeAssets = Assets.sideBListLoop;
} else if (musicPlayerController.playMode.value == PlayMode.random) {
playModeAssets = Assets.sideBShufflePlayback;
} else if (musicPlayerController.playMode.value == PlayMode.singleCycle) {
playModeAssets = Assets.sideBSingleCycle;
}
return Padding(
padding: const EdgeInsets.all(10).w,
child: Image.asset(
playModeAssets,
width: 24.w,
height: 24.w,
),
);
}),
),
),
),
ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: musicPlayerController.previousTrack,
child: Padding(
padding: const EdgeInsets.all(12).w,
child: Obx(() {
return Image.asset(
Assets.sideBPreviousTrack,
width: 20.w,
height: 20.w,
color: musicPlayerController.playList.isNotEmpty ? Colors.white : const Color(0xFFa8a8a8),
);
}),
),
),
),
),
ClipOval(
child: Material(
color: Colors.white,
child: InkWell(
onTap: musicPlayerController.playPause,
child: SizedBox(
width: 66.w,
height: 66.w,
child: FittedBox(
fit: BoxFit.none,
child: Obx(() {
return Visibility(
visible: musicPlayerController.processingState.value == ProcessingState.ready
|| musicPlayerController.processingState.value == ProcessingState.completed,
replacement: Center(
child: SizedBox(
width: 20.w,
height: 20.w,
child: const CircularProgressIndicator(
color: seedColor,
strokeWidth: 3,
),
),
),
child: Image.asset(
musicPlayerController.isPlaying.value ? Assets.sideBPausePlay : Assets.sideBStartPlay,
width: 26.w,
height: 26.w,
),
);
}),
),
),
),
),
),
ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: musicPlayerController.nextTrack,
child: Padding(
padding: const EdgeInsets.all(12).w,
child: Obx(() {
return Image.asset(
Assets.sideBNextTrack,
width: 20.w,
height: 20.w,
color: musicPlayerController.playList.isNotEmpty ? Colors.white : const Color(0xFFa8a8a8),
);
}),
),
),
),
),
ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _openPlayList,
child: Padding(
padding: const EdgeInsets.all(10).w,
child: Image.asset(
Assets.sideBPlayList,
width: 24.w,
height: 24.w,
),
),
),
),
),
],
),
);
}
void _openPlayList() {
Get.bottomSheet(
isScrollControlled:true,
Container(
width: 1.sw,
height: 0.6.sh,
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A),
borderRadius: BorderRadius.only(topLeft: Radius.circular(18.r), topRight: Radius.circular(18.r)),
),
child: Column(
children: [
SizedBox(
width: double.infinity,
height: 30.h,
child: FittedBox(
fit: BoxFit.none,
child: Image.asset(
Assets.sideBBottomSheetIndicator,
width: 29.w,
height: 4.w,
),
),
),
Expanded(
child: BaseScrollbar(
child: Obx(() {
return ListView.builder(
itemCount: musicPlayerController.playList.length,
itemBuilder: (context, index) {
return _buildPlayListItem(index, musicPlayerController.playList[index]);
},
);
}),
),
),
],
),
),
);
}
Widget _buildPlayListItem(int index, MusicModel model) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => controller.switchMusic(index, model),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8).h,
child: Row(
children: [
SizedBox(width: 18.w),
NetworkImageWidget(
url: model.thumbnail,
width: 60.w,
height: 60.w,
radius: 10.r,
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
return MyMarqueeText(
enable: musicPlayerController.musicModel.value.videoId == model.videoId,
text: ObjUtil.getStr(model.title),
textStyle: TextStyle(
color: musicPlayerController.musicModel.value.videoId == model.videoId
? seedColor
: const Color(0xD9FFFFFF),
fontSize: 14.sp,
),
);
}),
SizedBox(height: 8.h),
Obx(() {
return MyMarqueeText(
enable: musicPlayerController.musicModel.value.videoId == model.videoId,
text: ObjUtil.getStr(model.subTitle),
textStyle: TextStyle(
color: musicPlayerController.musicModel.value.videoId == model.videoId
? seedColor
: const Color(0x99FFFFFF),
fontSize: 12.sp,
),
);
}),
],
),
),
Obx(() {
return Visibility(
visible: musicPlayerController.musicModel.value.videoId != model.videoId,
child: ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => controller.deleteMusic(index),
child: Padding(
padding: const EdgeInsets.all(10).w,
child: Image.asset(
Assets.sideBPlayListDelete,
width: 24.w,
height: 24.w,
),
),
),
),
),
);
}),
Obx(() {
return SizedBox(width: musicPlayerController.musicModel.value.videoId != model.videoId ? 8.w : 16.w);
}),
],
),
),
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'playlists_controller.dart';
class PlaylistsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => PlaylistsController());
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PlaylistsController extends GetxController {
ScrollController scrollController = ScrollController();
@override
void onClose() {
scrollController.dispose();
super.onClose();
}
}

View File

@ -0,0 +1,120 @@
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/network_image_widget.dart';
import 'package:tone_snap/generated/assets.dart';
import 'playlists_controller.dart';
class PlaylistsView extends GetView<PlaylistsController> {
const PlaylistsView({super.key});
@override
Widget build(BuildContext context) {
Get.find<PlaylistsController>();
return Column(
children: [
_buildAddPlaylists(),
_buildListView(),
],
);
}
Widget _buildAddPlaylists() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 16.w),
Expanded(
child: Text(
'Playlists',
style: TextStyle(
color: const Color(0xFF999999),
fontSize: 14.sp,
),
),
),
ClipOval(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {},
child: Padding(
padding: const EdgeInsets.all(10).w,
child: Image.asset(
Assets.sideBPlaylistsAdd,
width: 20.w,
height: 20.w,
),
),
),
),
),
SizedBox(width: 6.w),
],
);
}
Widget _buildListView() {
return Expanded(
child: BaseScrollbar(
scrollController: controller.scrollController,
child: ListView.builder(
controller: controller.scrollController,
itemCount: 10,
itemBuilder: (context, index) {
return _buildItem();
},
),
),
);
}
Widget _buildItem() {
return InkWell(
onTap: (){},
child: Padding(
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 16.w),
child: Row(
children: [
NetworkImageWidget(
url: '',
width: 60.w,
height: 60.w,
radius: 8.r,
),
SizedBox(width: 14.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'There For You',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
SizedBox(height: 4.h),
Text(
'234 songs',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0xFF999999),
fontSize: 12.sp,
),
),
],
),
),
],
),
),
);
}
}

View File

@ -1,9 +0,0 @@
import 'package:get/get.dart';
import 'package:tone_snap/modules/sideb/splash/splash_controller.dart';
class SplashBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SplashController());
}
}

View File

@ -1,65 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sideb/splash/splash_controller.dart';
import 'package:tone_snap/res/themes/app_colors.dart';
class SplashView extends StatelessWidget {
SplashView({super.key});
final controller = Get.find<SplashController>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
_buildImageBg(),
_buildProgress(),
],
),
);
}
Widget _buildImageBg() {
return Image.asset(
Assets.sideBLaunchImage,
width: 1.sw,
height: 1.sh,
fit: BoxFit.cover,
);
}
Widget _buildProgress() {
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(bottom: 60).h,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
width: 0.5.sw,
child: Obx(() {
return LinearProgressIndicator(
value: controller.processValue.value,
backgroundColor: Colors.white,
valueColor: const AlwaysStoppedAnimation<Color>(sideBSeedColor),
borderRadius: BorderRadius.circular(8).r,
);
}),
),
SizedBox(height: 14.h),
Text(
'Resource Loading...',
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}

View File

@ -5,18 +5,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/data/sideb/models/home_model.dart';
import 'package:tone_snap/data/models/home_model.dart';
import 'package:tone_snap/utils/obj_util.dart';
class AlbumItem extends StatelessWidget {
const AlbumItem({super.key, required this.content});
const AlbumItem({super.key, required this.content, required this.onTap});
final Content content;
final Function() onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {},
onTap: onTap,
child: SizedBox(
width: 109.w,
height: double.infinity,

View File

@ -7,7 +7,7 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:tone_snap/components/network_image_widget.dart';
import 'package:tone_snap/data/sideb/models/home_model.dart';
import 'package:tone_snap/data/models/home_model.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/utils/obj_util.dart';

Some files were not shown because too many files have changed in this diff Show More