From e890aebb472b5195f9d7f1fa28e34340aadb9381 Mon Sep 17 00:00:00 2001 From: Hemant KArya <65885023+HemantKArya@users.noreply.github.com> Date: Fri, 12 Apr 2024 03:21:01 +0530 Subject: [PATCH] Youtube home added to explore page --- lib/blocs/explore/cubit/explore_cubits.dart | 30 ++ lib/blocs/explore/cubit/explore_states.dart | 22 ++ lib/routes_and_consts/routes.dart | 1 - .../screen/add_to_playlist_screen.dart | 5 +- lib/screens/screen/chart/chart_view.dart | 2 +- lib/screens/screen/explore_screen.dart | 77 ++-- .../screen/home_views/recents_view.dart | 2 +- .../home_views/youtube_views/playlist.dart | 357 ++++++++++++++++++ .../youtube_views/yt_song_tile.dart | 96 +++++ lib/screens/screen/library_screen.dart | 2 +- .../screen/library_views/playlist_screen.dart | 2 +- lib/screens/screen/offline_screen.dart | 2 +- lib/screens/screen/search_screen.dart | 2 +- lib/screens/screen/test_screen.dart | 14 +- lib/screens/widgets/carousal_widget.dart | 4 +- lib/screens/widgets/chart_list_tile.dart | 12 +- lib/screens/widgets/horizontal_card_view.dart | 137 +++++++ lib/screens/widgets/mini_player_widget.dart | 11 +- lib/screens/widgets/more_bottom_sheet.dart | 2 +- ...istCard_widget.dart => playlist_tile.dart} | 0 .../widgets/singleSongCard_widget.dart | 122 ------ lib/screens/widgets/snackbar.dart | 38 +- .../{song_card_widget.dart => song_tile.dart} | 0 lib/screens/widgets/square_card.dart | 158 ++++++++ lib/screens/widgets/tabList_widget.dart | 2 +- lib/services/db/bloomee_db_service.dart | 38 ++ lib/utils/external_list_importer.dart | 3 +- 27 files changed, 946 insertions(+), 195 deletions(-) create mode 100644 lib/screens/screen/home_views/youtube_views/playlist.dart create mode 100644 lib/screens/screen/home_views/youtube_views/yt_song_tile.dart create mode 100644 lib/screens/widgets/horizontal_card_view.dart rename lib/screens/widgets/{smallPlaylistCard_widget.dart => playlist_tile.dart} (100%) delete mode 100644 lib/screens/widgets/singleSongCard_widget.dart rename lib/screens/widgets/{song_card_widget.dart => song_tile.dart} (100%) create mode 100644 lib/screens/widgets/square_card.dart diff --git a/lib/blocs/explore/cubit/explore_cubits.dart b/lib/blocs/explore/cubit/explore_cubits.dart index 262a323..527259c 100644 --- a/lib/blocs/explore/cubit/explore_cubits.dart +++ b/lib/blocs/explore/cubit/explore_cubits.dart @@ -1,7 +1,9 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; +import 'dart:convert'; import 'dart:developer'; import 'dart:isolate'; +import 'package:Bloomee/repository/Youtube/yt_music_api.dart'; import 'package:Bloomee/services/db/GlobalDB.dart'; import 'package:bloc/bloc.dart'; import 'package:Bloomee/model/MediaPlaylistModel.dart'; @@ -10,6 +12,8 @@ import 'package:Bloomee/plugins/chart_defines.dart'; import 'package:Bloomee/repository/Youtube/yt_charts_home.dart'; import 'package:Bloomee/screens/screen/chart/show_charts.dart'; import 'package:Bloomee/services/db/bloomee_db_service.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:isar/isar.dart'; @@ -165,3 +169,29 @@ class FetchChartCubit extends Cubit { } } } + +class YTMusicCubit extends Cubit { + YTMusicCubit() : super(YTMusicCubitInitial()) { + fetchYTMusicDB(); + fetchYTMusic(); + } + + void fetchYTMusicDB() async { + final data = await BloomeeDBService.getAPICache("YTMusic"); + if (data != null) { + final ytmData = await compute(parseYTMusicData, data); + emit(state.copyWith(ytmData: ytmData)); + } + } + + Map> parseYTMusicData(String source) { + return jsonDecode(source); + } + + void fetchYTMusic() async { + final ytCharts = await YtMusicService().getMusicHome(); + emit(state.copyWith(ytmData: ytCharts)); + final ytChartsJson = jsonEncode(ytCharts); + BloomeeDBService.putAPICache("YTMusic", ytChartsJson); + } +} diff --git a/lib/blocs/explore/cubit/explore_states.dart b/lib/blocs/explore/cubit/explore_states.dart index 5cf8c95..4a217da 100644 --- a/lib/blocs/explore/cubit/explore_states.dart +++ b/lib/blocs/explore/cubit/explore_states.dart @@ -87,3 +87,25 @@ class FetchChartState { class FetchChartInitial extends FetchChartState { FetchChartInitial() : super(isFetched: false); } + +class YTMusicCubitState extends Equatable { + final Map> ytmData; + const YTMusicCubitState({ + required this.ytmData, + }); + + @override + List get props => [ytmData]; + + YTMusicCubitState copyWith({ + Map>? ytmData, + }) { + return YTMusicCubitState( + ytmData: ytmData ?? this.ytmData, + ); + } +} + +class YTMusicCubitInitial extends YTMusicCubitState { + YTMusicCubitInitial() : super(ytmData: {}); +} diff --git a/lib/routes_and_consts/routes.dart b/lib/routes_and_consts/routes.dart index 681145e..972a6a7 100644 --- a/lib/routes_and_consts/routes.dart +++ b/lib/routes_and_consts/routes.dart @@ -1,4 +1,3 @@ -import 'package:Bloomee/screens/screen/test_screen.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:Bloomee/routes_and_consts/global_str_consts.dart'; diff --git a/lib/screens/screen/add_to_playlist_screen.dart b/lib/screens/screen/add_to_playlist_screen.dart index 33eaed7..e4e0b51 100644 --- a/lib/screens/screen/add_to_playlist_screen.dart +++ b/lib/screens/screen/add_to_playlist_screen.dart @@ -7,7 +7,7 @@ import 'package:go_router/go_router.dart'; import 'package:Bloomee/blocs/add_to_playlist/cubit/add_to_playlist_cubit.dart'; import 'package:Bloomee/model/songModel.dart'; import 'package:Bloomee/screens/widgets/createPlaylist_bottomsheet.dart'; -import 'package:Bloomee/screens/widgets/smallPlaylistCard_widget.dart'; +import 'package:Bloomee/screens/widgets/playlist_tile.dart'; import 'package:Bloomee/services/db/GlobalDB.dart'; import 'package:Bloomee/theme_data/default.dart'; import 'package:Bloomee/routes_and_consts/global_conts.dart'; @@ -53,11 +53,12 @@ class _AddToPlaylistScreenState extends State { appBar: AppBar( backgroundColor: Default_Theme.themeColor, foregroundColor: Default_Theme.primaryColor1, + centerTitle: true, title: Text( 'Add to Playlist', style: const TextStyle( color: Default_Theme.primaryColor1, - fontSize: 25, + fontSize: 20, fontWeight: FontWeight.bold) .merge(Default_Theme.secondoryTextStyle), ), diff --git a/lib/screens/screen/chart/chart_view.dart b/lib/screens/screen/chart/chart_view.dart index 03dff6c..fb73ed3 100644 --- a/lib/screens/screen/chart/chart_view.dart +++ b/lib/screens/screen/chart/chart_view.dart @@ -91,7 +91,7 @@ class _ChartScreenState extends State { titlePadding: const EdgeInsets.only(left: 8, bottom: 0, right: 0, top: 0), title: Text(state.chartName, - textScaleFactor: 1, + textScaler: const TextScaler.linear(1.0), textAlign: TextAlign.start, style: Default_Theme.secondoryTextStyleMedium.merge(const TextStyle( fontSize: 24, color: Color.fromARGB(255, 255, 235, 251)))), diff --git a/lib/screens/screen/explore_screen.dart b/lib/screens/screen/explore_screen.dart index fd3f1ae..debae06 100644 --- a/lib/screens/screen/explore_screen.dart +++ b/lib/screens/screen/explore_screen.dart @@ -2,9 +2,8 @@ import 'package:Bloomee/blocs/explore/cubit/explore_cubits.dart'; import 'package:Bloomee/blocs/mediaPlayer/bloomee_player_cubit.dart'; import 'package:Bloomee/routes_and_consts/global_str_consts.dart'; import 'package:Bloomee/screens/screen/home_views/recents_view.dart'; -import 'package:Bloomee/screens/widgets/chart_list_tile.dart'; import 'package:Bloomee/screens/widgets/more_bottom_sheet.dart'; -import 'package:Bloomee/screens/widgets/song_card_widget.dart'; +import 'package:Bloomee/screens/widgets/song_tile.dart'; import 'package:Bloomee/services/db/cubit/bloomee_db_cubit.dart'; import 'package:Bloomee/utils/app_updater.dart'; import 'package:flutter/material.dart'; @@ -15,6 +14,7 @@ import 'package:Bloomee/theme_data/default.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:icons_plus/icons_plus.dart'; import '../widgets/carousal_widget.dart'; +import '../widgets/horizontal_card_view.dart'; import '../widgets/tabList_widget.dart'; class ExploreScreen extends StatefulWidget { @@ -41,14 +41,22 @@ class _ExploreScreenState extends State { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => TrendingCubit(), - lazy: false, - ), + // BlocProvider( + // create: (context) => TrendingCubit(), + // lazy: false, + // ), BlocProvider( create: (context) => RecentlyCubit(), lazy: false, ), + BlocProvider( + create: (context) => YTMusicCubit(), + lazy: false, + ), + BlocProvider( + create: (context) => FetchChartCubit(), + lazy: false, + ), ], child: Scaffold( body: CustomScrollView( @@ -119,38 +127,15 @@ class _ExploreScreenState extends State { ), ), ), - Padding( - padding: const EdgeInsets.only(top: 0.0), - child: SizedBox( - child: BlocBuilder( - builder: (context, state) { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 1000), - child: state is TrendingCubitInitial - ? const Center( - child: SizedBox( - height: 60, - width: 60, - child: CircularProgressIndicator( - color: Default_Theme.accentColor2, - )), - ) - : TabSongListWidget( - list: state.ytCharts![0].chartItems! - .map((e) => ChartListTile( - title: e.name ?? "", - subtitle: e.subtitle ?? "", - imgUrl: e.imageUrl ?? "", - rectangularImage: true, - )) - .toList(), - category: "Trending", - columnSize: 4, - ), - ); - }, - ), - ), + BlocBuilder( + builder: (context, state) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 400), + child: state is YTMusicCubitInitial + ? const SizedBox() + : ytSection(state.ytmData), + ); + }, ), ], ), @@ -162,6 +147,22 @@ class _ExploreScreenState extends State { ); } + Widget ytSection(Map> ytmData) { + List ytList = List.empty(growable: true); + // log(ytmData.toString()); + + for (var value in (ytmData["body"]!)) { + // log(value.toString()); + ytList.add(HorizontalCardView(data: value)); + } + return ListView( + shrinkWrap: true, + padding: const EdgeInsets.only(top: 0), + physics: const NeverScrollableScrollPhysics(), + children: ytList, + ); + } + SliverAppBar customDiscoverBar(BuildContext context) { return SliverAppBar( floating: true, diff --git a/lib/screens/screen/home_views/recents_view.dart b/lib/screens/screen/home_views/recents_view.dart index d95bfe1..d4b17e7 100644 --- a/lib/screens/screen/home_views/recents_view.dart +++ b/lib/screens/screen/home_views/recents_view.dart @@ -2,7 +2,7 @@ import 'package:Bloomee/blocs/history/cubit/history_cubit.dart'; import 'package:Bloomee/blocs/mediaPlayer/bloomee_player_cubit.dart'; import 'package:Bloomee/screens/widgets/more_bottom_sheet.dart'; -import 'package:Bloomee/screens/widgets/song_card_widget.dart'; +import 'package:Bloomee/screens/widgets/song_tile.dart'; import 'package:flutter/material.dart'; import 'package:Bloomee/theme_data/default.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/screens/screen/home_views/youtube_views/playlist.dart b/lib/screens/screen/home_views/youtube_views/playlist.dart new file mode 100644 index 0000000..26f994a --- /dev/null +++ b/lib/screens/screen/home_views/youtube_views/playlist.dart @@ -0,0 +1,357 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:async'; +import 'dart:core'; +import 'dart:developer'; +import 'package:Bloomee/blocs/mediaPlayer/bloomee_player_cubit.dart'; +import 'package:Bloomee/screens/widgets/import_playlist.dart'; +import 'package:Bloomee/screens/widgets/more_bottom_sheet.dart'; +import 'package:Bloomee/screens/widgets/snackbar.dart'; +import 'package:Bloomee/utils/external_list_importer.dart'; +import 'package:async/async.dart'; +import 'package:Bloomee/model/songModel.dart'; +import 'package:Bloomee/model/youtube_vid_model.dart'; +import 'package:Bloomee/repository/Youtube/youtube_api.dart'; +import 'package:Bloomee/repository/Youtube/yt_music_api.dart'; +import 'package:Bloomee/screens/screen/home_views/youtube_views/yt_song_tile.dart'; +import 'package:Bloomee/screens/widgets/sign_board_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:Bloomee/theme_data/default.dart'; +import 'package:Bloomee/utils/load_Image.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:icons_plus/icons_plus.dart'; +import 'package:share_plus/share_plus.dart'; + +class YoutubePlaylist extends StatefulWidget { + final String imgPath; + final String title; + final String subtitle; + final String type; + final String id; + const YoutubePlaylist({ + Key? key, + required this.imgPath, + required this.title, + required this.subtitle, + required this.type, + required this.id, + }) : super(key: key); + + @override + State createState() => _YoutubePlaylistState(); +} + +class _YoutubePlaylistState extends State { + late Future> data; + late List> items; + Map songList = {}; + CancelableOperation getMediaOps = + CancelableOperation.fromFuture(Future.value()); + + Future _loadData() async { + final res = await data; + items = res["songs"] as List>; + } + + Future fetchSong(String id, String imgUrl) async { + log("Fetching: $id", name: "YoutubePlaylist"); + final song = await YouTubeServices() + .formatVideoFromId(id: id.replaceAll("youtube", "")); + if (song != null) { + return fromYtVidSongMap2MediaItem(song)..artUri = Uri.parse(imgUrl); + } + return null; + } + + @override + void initState() { + data = YtMusicService() + .getPlaylistDetails(widget.id.replaceAll("youtube", "")); + super.initState(); + } + + @override + void dispose() { + getMediaOps.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _loadData(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: SizedBox( + height: 40, width: 40, child: CircularProgressIndicator()), + ); + } else if (snapshot.hasError) { + log(snapshot.error.toString()); + return const Center( + child: SignBoardWidget( + message: "Error loading data", + icon: MingCute.loading_line, + ), + ); + } else { + return CustomScrollView( + slivers: [ + SliverAppBar( + backgroundColor: Default_Theme.themeColor, + surfaceTintColor: Default_Theme.themeColor, + expandedHeight: MediaQuery.of(context).size.height * 0.29, + floating: false, + pinned: true, + flexibleSpace: FlexibleSpaceBar( + background: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.only(left: 16.0, right: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Stack( + children: [ + SizedBox( + height: 180, + width: 180, + child: loadImageCached(widget.imgPath), + ), + ], + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 8.0, right: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Default_Theme.primaryColor1, + ).merge(Default_Theme.secondoryTextStyle), + ), + Text( + widget.subtitle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: Default_Theme.primaryColor2 + .withOpacity(0.8), + ), + ), + Text( + "Youtube • ${(widget.type == 'playlist') ? 'Playlist' : 'Album'}", + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: Default_Theme.primaryColor2 + .withOpacity(0.8), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 8.0, right: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + padding: + const EdgeInsets.symmetric( + horizontal: 18, + vertical: 5), + backgroundColor: + Default_Theme.accentColor2, + ), + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + ImporterDialogWidget( + strm: ExternalMediaImporter + .ytPlaylistImporter( + "https://youtube.com/playlist?list=${widget.id.replaceAll("youtube", "")}", + )), + ); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + const Padding( + padding: + EdgeInsets.only(right: 6), + child: Icon( + size: 20, + MingCute.bookmark_fill, + color: Default_Theme + .primaryColor2, + ), + ), + Text( + "Save", + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Default_Theme + .primaryColor2, + ).merge(Default_Theme + .secondoryTextStyle), + ), + ], + ), + ), + IconButton( + onPressed: () { + Share.share( + "${widget.title} - ${widget.subtitle} \nhttps://youtube.com/playlist?list=${widget.id.replaceAll("youtube", "")}", + subject: "Youtube Playlist"); + }, + padding: const EdgeInsets.all(2), + constraints: const BoxConstraints(), + icon: const Icon( + MingCute.share_forward_line, + color: + Default_Theme.primaryColor1, + size: 30, + ), + ) + ], + ), + ), + ], + ), + ), + ), + ], + ) + ], + ), + ), + ), + SliverList.list( + children: [ + Padding( + padding: + const EdgeInsets.only(left: 16, top: 20, bottom: 5), + child: Text("Songs", + style: Default_Theme.secondoryTextStyle.merge( + const TextStyle( + color: Default_Theme.accentColor2, + fontSize: 20, + fontWeight: FontWeight.bold, + letterSpacing: 1.5, + height: 1.5))), + ), + ], + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return YtSongTile( + rectangularImage: true, + title: items[index]["title"]!, + subtitle: items[index]["subtitle"]!, + imgUrl: (items[index]["image"]! as String) + .replaceAll("w400-h400", "w100-h100"), + id: items[index]["id"]!, + permalink: items[index]["perma_url"]!, + onOpts: () { + SnackbarService.showMessage( + "Getting song information...", + loading: true); + fetchSong(items[index]["id"]!, items[index]["image"]!) + .then((value) { + SnackbarService.showMessage( + "Song information ready!", + ); + if (value != null) { + showMoreBottomSheet(context, value); + } + }).onError((error, stackTrace) { + log("Error: $error", name: "YoutubePlaylist"); + SnackbarService.showMessage( + "Error getting song information."); + }); + }, + onTap: () async { + SnackbarService.showMessage("Loading song...", + loading: true); + await getMediaOps.cancel(); + + if (songList[index] == null) { + getMediaOps = CancelableOperation.fromFuture( + fetchSong( + items[index]["id"]!, items[index]["image"]!), + onCancel: () { + log("skipping....", name: "YoutubePlaylist"); + return; + }, + ); + getMediaOps.value.then( + (value) { + SnackbarService.showMessage( + "Song is ready!", + ); + if (value != null) { + log("Added: ${value.title}", + name: "YoutubePlaylist"); + songList[index] = value; + context + .read() + .bloomeePlayer + .addQueueItem(value, doPlay: true); + } + }, + ).onError((error, stackTrace) { + log("Skipped:", + error: error.toString(), + name: "YoutubePlaylist"); + }); + } else { + SnackbarService.showMessage( + "Playing song.", + ); + context + .read() + .bloomeePlayer + .addQueueItem(songList[index]!, doPlay: true); + } + }, + ); + }, + childCount: items.length, + ), + ), + ], + ); + } + }); + } +} diff --git a/lib/screens/screen/home_views/youtube_views/yt_song_tile.dart b/lib/screens/screen/home_views/youtube_views/yt_song_tile.dart new file mode 100644 index 0000000..ea77f71 --- /dev/null +++ b/lib/screens/screen/home_views/youtube_views/yt_song_tile.dart @@ -0,0 +1,96 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:Bloomee/theme_data/default.dart'; +import 'package:flutter/material.dart'; +import 'package:Bloomee/utils/load_Image.dart'; + +class YtSongTile extends StatefulWidget { + final String title; + final String subtitle; + final String imgUrl; + final bool rectangularImage; + final VoidCallback? onTap; + final VoidCallback? onOpts; + final String permalink; + final String id; + + const YtSongTile({ + Key? key, + required this.title, + required this.subtitle, + required this.imgUrl, + this.onTap, + this.onOpts, + this.rectangularImage = false, + required this.permalink, + required this.id, + }) : super(key: key); + + @override + State createState() => _YtSongTileState(); +} + +class _YtSongTileState extends State { + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (widget.onTap != null) { + widget.onTap!(); + } else {} + }, + child: SizedBox( + // width: 320, + child: ListTile( + leading: Padding( + padding: const EdgeInsets.only(left: 16, right: 0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: widget.rectangularImage + ? SizedBox( + height: 60, + width: 80, + child: + loadImageCached(widget.imgUrl, fit: BoxFit.cover), + ) + : SizedBox( + height: 60, + width: 60, + child: loadImageCached(widget.imgUrl))), + ), + title: Text( + widget.title, + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: Default_Theme.tertiaryTextStyle.merge(const TextStyle( + fontWeight: FontWeight.w600, + color: Default_Theme.primaryColor1, + fontSize: 14)), + ), + subtitle: Text(widget.subtitle, + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: Default_Theme.tertiaryTextStyle.merge(TextStyle( + color: Default_Theme.primaryColor1.withOpacity(0.8), + fontSize: 13))), + // dense: true, + contentPadding: const EdgeInsets.all(0), + trailing: IconButton( + constraints: const BoxConstraints(), + padding: const EdgeInsets.all(8), + onPressed: () { + if (widget.onOpts != null) { + widget.onOpts!(); + } + }, + icon: const Icon( + Icons.more_vert, + color: Default_Theme.primaryColor1, + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/screen/library_screen.dart b/lib/screens/screen/library_screen.dart index 9dfb834..791e699 100644 --- a/lib/screens/screen/library_screen.dart +++ b/lib/screens/screen/library_screen.dart @@ -8,7 +8,7 @@ import 'package:go_router/go_router.dart'; import 'package:Bloomee/blocs/library/cubit/library_items_cubit.dart'; import 'package:Bloomee/routes_and_consts/global_str_consts.dart'; import 'package:Bloomee/screens/widgets/createPlaylist_bottomsheet.dart'; -import 'package:Bloomee/screens/widgets/smallPlaylistCard_widget.dart'; +import 'package:Bloomee/screens/widgets/playlist_tile.dart'; import 'package:Bloomee/theme_data/default.dart'; import 'package:icons_plus/icons_plus.dart'; diff --git a/lib/screens/screen/library_views/playlist_screen.dart b/lib/screens/screen/library_views/playlist_screen.dart index d19548f..f9aead1 100644 --- a/lib/screens/screen/library_views/playlist_screen.dart +++ b/lib/screens/screen/library_views/playlist_screen.dart @@ -1,7 +1,7 @@ import 'package:Bloomee/model/MediaPlaylistModel.dart'; import 'package:Bloomee/screens/widgets/more_bottom_sheet.dart'; import 'package:Bloomee/screens/widgets/sign_board_widget.dart'; -import 'package:Bloomee/screens/widgets/song_card_widget.dart'; +import 'package:Bloomee/screens/widgets/song_tile.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/screens/screen/offline_screen.dart b/lib/screens/screen/offline_screen.dart index ea11a29..b59b3b8 100644 --- a/lib/screens/screen/offline_screen.dart +++ b/lib/screens/screen/offline_screen.dart @@ -4,7 +4,7 @@ import 'package:Bloomee/blocs/offline/offline_cubit.dart'; import 'package:Bloomee/model/MediaPlaylistModel.dart'; import 'package:Bloomee/screens/widgets/more_bottom_sheet.dart'; import 'package:Bloomee/screens/widgets/sign_board_widget.dart'; -import 'package:Bloomee/screens/widgets/song_card_widget.dart'; +import 'package:Bloomee/screens/widgets/song_tile.dart'; import 'package:flutter/material.dart'; import 'package:Bloomee/theme_data/default.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/screens/screen/search_screen.dart b/lib/screens/screen/search_screen.dart index d03b963..87bf162 100644 --- a/lib/screens/screen/search_screen.dart +++ b/lib/screens/screen/search_screen.dart @@ -4,7 +4,7 @@ import 'package:Bloomee/blocs/mediaPlayer/bloomee_player_cubit.dart'; import 'package:Bloomee/model/MediaPlaylistModel.dart'; import 'package:Bloomee/screens/widgets/more_bottom_sheet.dart'; import 'package:Bloomee/screens/widgets/sign_board_widget.dart'; -import 'package:Bloomee/screens/widgets/song_card_widget.dart'; +import 'package:Bloomee/screens/widgets/song_tile.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/screens/screen/test_screen.dart b/lib/screens/screen/test_screen.dart index 68f9e54..bb81960 100644 --- a/lib/screens/screen/test_screen.dart +++ b/lib/screens/screen/test_screen.dart @@ -1,4 +1,7 @@ -import 'package:Bloomee/utils/external_list_importer.dart'; +import 'dart:convert'; +import 'dart:developer'; + +import 'package:Bloomee/repository/Youtube/yt_music_api.dart'; import 'package:flutter/material.dart'; import 'package:Bloomee/theme_data/default.dart'; @@ -30,12 +33,9 @@ class TestView extends StatelessWidget { ), ElevatedButton( onPressed: () async { - // final data = - // await context.read().getTrackDetails(); - // log(data['name'].toString()); - - ExternalMediaImporter.sfyPlaylistImporter( - url: "", playlistID: "6FOuXCQ9f8D5HDuDhRstEa"); + YtMusicService().getMusicHome().then((value) { + log("Test API: ${jsonEncode(value)}"); + }); }, child: const Text( "Test API", diff --git a/lib/screens/widgets/carousal_widget.dart b/lib/screens/widgets/carousal_widget.dart index cee7542..13a792b 100644 --- a/lib/screens/widgets/carousal_widget.dart +++ b/lib/screens/widgets/carousal_widget.dart @@ -23,7 +23,7 @@ class CaraouselWidget extends StatefulWidget { class _CaraouselWidgetState extends State { bool _visibility = true; - final FetchChartCubit fetchChartCubit = FetchChartCubit(); + // final FetchChartCubit fetchChartCubit; List chartCubitList = List.empty(growable: true); bool autoSlideCharts = true; @@ -37,7 +37,7 @@ class _CaraouselWidgetState extends State { @override void initState() { for (var i in chartInfoList) { - chartCubitList.add(ChartCubit(i, fetchChartCubit)); + chartCubitList.add(ChartCubit(i, context.read())); } initSettings(); super.initState(); diff --git a/lib/screens/widgets/chart_list_tile.dart b/lib/screens/widgets/chart_list_tile.dart index d50d087..93909f3 100644 --- a/lib/screens/widgets/chart_list_tile.dart +++ b/lib/screens/widgets/chart_list_tile.dart @@ -12,20 +12,28 @@ class ChartListTile extends StatelessWidget { final String subtitle; final String imgUrl; final bool rectangularImage; + final VoidCallback? onTap; const ChartListTile({ Key? key, required this.title, required this.subtitle, required this.imgUrl, + this.onTap, this.rectangularImage = false, }) : super(key: key); @override Widget build(BuildContext context) { return InkWell( - onTap: () => context.push( - "/${GlobalStrConsts.searchScreen}?query=${title} by ${subtitle}"), + onTap: () { + if (onTap != null) { + onTap!(); + } else { + context.push( + "/${GlobalStrConsts.searchScreen}?query=${title} by ${subtitle}"); + } + }, child: SizedBox( // width: 320, child: ListTile( diff --git a/lib/screens/widgets/horizontal_card_view.dart b/lib/screens/widgets/horizontal_card_view.dart new file mode 100644 index 0000000..9c23687 --- /dev/null +++ b/lib/screens/widgets/horizontal_card_view.dart @@ -0,0 +1,137 @@ +import 'package:Bloomee/blocs/mediaPlayer/bloomee_player_cubit.dart'; +import 'package:Bloomee/screens/screen/home_views/youtube_views/playlist.dart'; +import 'package:Bloomee/screens/widgets/paging_scroll.dart'; +import 'package:Bloomee/screens/widgets/square_card.dart'; +import 'package:Bloomee/theme_data/default.dart'; +import 'package:Bloomee/utils/external_list_importer.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class HorizontalCardView extends StatelessWidget { + final Map data; + const HorizontalCardView({ + super.key, + required this.data, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 280, + child: Padding( + padding: const EdgeInsets.only( + top: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 20, + top: 5, + ), + child: Text( + data["title"], + textAlign: TextAlign.start, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Default_Theme.accentColor2) + .merge(Default_Theme.secondoryTextStyle), + ), + ), + // Padding( + // padding: const EdgeInsets.only( + // left: 20, + // ), + // child: Text( + // "YTMusic", + // textAlign: TextAlign.start, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.bold, + // color: Default_Theme.primaryColor2.withOpacity(0.8)) + // .merge(Default_Theme.secondoryTextStyle), + // ), + // ), + Expanded( + child: ListView( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: PagingScrollPhysics( + itemCount: 20, + viewSize: MediaQuery.of(context).size.width * 0.88), + scrollDirection: Axis.horizontal, + children: [ + const SizedBox( + width: 12, + ), + for (int i = 0; i < data['items'].length; i++) + SquareImgCard( + imgPath: data["items"][i]["image"], + title: data["items"][i]["title"], + subtitle: data["items"][i]["subtitle"], + isWide: (data["items"][i]['type'] == "video" || + data["items"][i]['type'] == "chart") + ? true + : false, + tag: (data["items"][i]['type'] == "playlist" || + data["items"][i]['type'] == "chart") + ? '${data["items"][i]["count"]} Tracks' + : data["items"][i]["count"], + isList: (data["items"][i]['type'] == "playlist" || + data["items"][i]['type'] == "chart") + ? true + : false, + onTap: () { + switch (data["items"][i]['type']) { + case "playlist": + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => YoutubePlaylist( + imgPath: data["items"][i]["image"], + title: data["items"][i]["title"], + subtitle: data["items"][i]["subtitle"], + type: data["items"][i]["type"], + id: data["items"][i]["id"]))); + break; + case "chart": + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => YoutubePlaylist( + imgPath: data["items"][i]["image"], + title: data["items"][i]["title"], + subtitle: data["items"][i]["subtitle"], + type: data["items"][i]["type"], + id: data["items"][i]["id"]))); + break; + case "video": + ExternalMediaImporter.ytMediaImporter( + 'https://youtu.be/${(data["items"][i]["id"] as String).replaceAll("youtube", "")}') + .then((value) async { + if (value != null) { + await context + .read() + .bloomeePlayer + .addQueueItem(value, doPlay: true); + } + }); + break; + default: + // Navigator.pushNamed(context, '/album', arguments: data["items"][i]); + } + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/widgets/mini_player_widget.dart b/lib/screens/widgets/mini_player_widget.dart index 71145ff..242ba16 100644 --- a/lib/screens/widgets/mini_player_widget.dart +++ b/lib/screens/widgets/mini_player_widget.dart @@ -68,10 +68,13 @@ class MiniPlayerWidget extends StatelessWidget { Padding( padding: const EdgeInsets.only(right: 1.5), child: ClipRRect( - borderRadius: BorderRadius.circular(15), - child: loadImageCached( - snapshot.data?.artUri.toString(), - fit: BoxFit.cover), + borderRadius: BorderRadius.circular(12), + child: SizedBox.square( + dimension: 70, + child: loadImageCached( + snapshot.data?.artUri.toString(), + fit: BoxFit.cover), + ), ), ), Padding( diff --git a/lib/screens/widgets/more_bottom_sheet.dart b/lib/screens/widgets/more_bottom_sheet.dart index 4e2be06..a3131f8 100644 --- a/lib/screens/widgets/more_bottom_sheet.dart +++ b/lib/screens/widgets/more_bottom_sheet.dart @@ -4,7 +4,7 @@ import 'package:Bloomee/blocs/mediaPlayer/bloomee_player_cubit.dart'; import 'package:Bloomee/model/songModel.dart'; import 'package:Bloomee/routes_and_consts/global_str_consts.dart'; import 'package:Bloomee/screens/widgets/snackbar.dart'; -import 'package:Bloomee/screens/widgets/song_card_widget.dart'; +import 'package:Bloomee/screens/widgets/song_tile.dart'; import 'package:Bloomee/services/db/GlobalDB.dart'; import 'package:Bloomee/services/db/bloomee_db_service.dart'; import 'package:Bloomee/services/db/cubit/bloomee_db_cubit.dart'; diff --git a/lib/screens/widgets/smallPlaylistCard_widget.dart b/lib/screens/widgets/playlist_tile.dart similarity index 100% rename from lib/screens/widgets/smallPlaylistCard_widget.dart rename to lib/screens/widgets/playlist_tile.dart diff --git a/lib/screens/widgets/singleSongCard_widget.dart b/lib/screens/widgets/singleSongCard_widget.dart deleted file mode 100644 index 58b3e7d..0000000 --- a/lib/screens/widgets/singleSongCard_widget.dart +++ /dev/null @@ -1,122 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'package:flutter/material.dart'; - -import 'package:Bloomee/screens/widgets/like_widget.dart'; -import 'package:Bloomee/theme_data/default.dart'; -import 'package:Bloomee/utils/load_Image.dart'; -import 'package:icons_plus/icons_plus.dart'; - -class SingleSongCardWidget extends StatelessWidget { - final String titleText; - final String subText; - final String artUri; - final bool showLiked; - final VoidCallback? onLiked; - final VoidCallback? onDisliked; - final VoidCallback? onClicked; - final boxWidth; - bool isLiked; - final bool showOptions; - SingleSongCardWidget({ - Key? key, - required this.titleText, - required this.subText, - this.artUri = "", - this.onLiked, - this.onDisliked, - this.onClicked, - this.showLiked = false, - this.isLiked = false, - this.showOptions = false, - this.boxWidth = 0.86, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return SizedBox( - // color: Colors.white.withOpacity(0.3), - // width: MediaQuery.of(context).size.width * boxWidth, - child: InkWell( - borderRadius: BorderRadius.circular(12), - focusColor: null, - highlightColor: null, - onTap: () { - if (onClicked != null) { - onClicked!(); - } - }, - child: Container( - padding: const EdgeInsets.only(left: 7, right: 7, top: 3, bottom: 3), - child: Row( - // mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(right: 12), - child: SizedBox( - width: 55, - height: 55, - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: loadImageCached(artUri), - )), - ), - Expanded( - flex: 15, - // width: MediaQuery.of(context).size.width * boxWidth, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - titleText, - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: Default_Theme.tertiaryTextStyle.merge( - const TextStyle( - fontWeight: FontWeight.w500, - color: Default_Theme.primaryColor1, - fontSize: 15)), - ), - const SizedBox(height: 4), - Text(subText, - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: Default_Theme.tertiaryTextStyle.merge(TextStyle( - color: - Default_Theme.primaryColor1.withOpacity(0.75), - fontSize: 13))) - ], - ), - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Visibility( - visible: showLiked, - child: Padding( - padding: const EdgeInsets.only(left: 8.0, bottom: 3), - child: LikeBtnWidget( - isLiked: isLiked, - iconSize: 29, - onLiked: onLiked, - onDisliked: onDisliked, - ), - ), - ), - Visibility( - visible: showOptions, - child: const Icon(MingCute.more_2_fill)), - ], - ) - ], - ), - ), - ), - ); - } -} diff --git a/lib/screens/widgets/snackbar.dart b/lib/screens/widgets/snackbar.dart index b0acc12..3fbe7dc 100644 --- a/lib/screens/widgets/snackbar.dart +++ b/lib/screens/widgets/snackbar.dart @@ -5,16 +5,40 @@ class SnackbarService { static final GlobalKey messengerKey = GlobalKey(); - static void showMessage(String message, - {SnackBarAction? action, - Duration duration = const Duration(seconds: 2)}) { + static void showMessage( + String message, { + SnackBarAction? action, + Duration duration = const Duration(seconds: 2), + bool loading = false, + }) { messengerKey.currentState!.removeCurrentSnackBar(); messengerKey.currentState!.showSnackBar( SnackBar( - content: Text(message, - style: const TextStyle( - color: Default_Theme.primaryColor1, fontSize: 16)), - duration: duration, + content: loading + ? Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(message, + style: const TextStyle( + color: Default_Theme.primaryColor1, fontSize: 16)), + const Padding( + padding: EdgeInsets.only(left: 10), + child: SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Default_Theme.primaryColor1), + ), + ), + ), + ], + ) + : Text(message, + style: const TextStyle( + color: Default_Theme.primaryColor1, fontSize: 16)), + duration: loading ? const Duration(minutes: 1) : duration, showCloseIcon: false, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), diff --git a/lib/screens/widgets/song_card_widget.dart b/lib/screens/widgets/song_tile.dart similarity index 100% rename from lib/screens/widgets/song_card_widget.dart rename to lib/screens/widgets/song_tile.dart diff --git a/lib/screens/widgets/square_card.dart b/lib/screens/widgets/square_card.dart new file mode 100644 index 0000000..c518d87 --- /dev/null +++ b/lib/screens/widgets/square_card.dart @@ -0,0 +1,158 @@ +import 'package:Bloomee/theme_data/default.dart'; +import 'package:Bloomee/utils/load_Image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:icons_plus/icons_plus.dart'; + +class SquareImgCard extends StatelessWidget { + final String imgPath; + final String title; + final String subtitle; + final Function? onTap; + final String? tag; + final bool isWide; + final bool isList; + + const SquareImgCard({ + super.key, + required this.imgPath, + required this.title, + required this.subtitle, + this.onTap, + this.isWide = false, + this.tag, + this.isList = true, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + if (onTap != null) onTap!(); + }, + child: InkWell( + splashColor: Default_Theme.accentColor2.withOpacity(0.15), + highlightColor: Default_Theme.accentColor1.withOpacity(0.12), + borderRadius: BorderRadius.circular(15), + onTap: () { + if (onTap != null) onTap!(); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: isWide ? 250 : 150, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Stack(children: [ + SizedBox( + height: 150, + width: isWide ? 250 : 150, + child: loadImageCached(imgPath), + ), + Visibility( + visible: tag != null, + child: Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: Default_Theme.accentColor2.withOpacity(0.95), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + ), + // border: Border.all( + // color: Default_Theme.primaryColor1, + // width: 1.5, + // ), + ), + child: isList + ? Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.only(right: 5), + child: Icon( + MingCute.playlist_2_line, + size: 18, + color: Default_Theme.primaryColor2, + ), + ), + Text( + "$tag", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: + Default_Theme.primaryColor2) + .merge( + Default_Theme.secondoryTextStyle), + ), + ], + ) + : Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.only(right: 5), + child: Icon( + MingCute.eye_2_line, + size: 18, + color: Default_Theme.primaryColor2, + ), + ), + Text( + "$tag", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: + Default_Theme.primaryColor2) + .merge( + Default_Theme.secondoryTextStyle), + ), + ], + ), + ), + ), + ), + ]), + ), + Text( + title, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: Default_Theme.primaryColor1) + .merge(Default_Theme.secondoryTextStyle), + ), + Text( + subtitle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.bold, + color: Default_Theme.primaryColor2.withOpacity(0.8)) + .merge(Default_Theme.secondoryTextStyle), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/widgets/tabList_widget.dart b/lib/screens/widgets/tabList_widget.dart index d86c622..db23eca 100644 --- a/lib/screens/widgets/tabList_widget.dart +++ b/lib/screens/widgets/tabList_widget.dart @@ -33,7 +33,7 @@ class TabSongListWidget extends StatelessWidget { category, style: Default_Theme.secondoryTextStyle.merge( const TextStyle( - color: Default_Theme.primaryColor1, + color: Default_Theme.accentColor2, fontWeight: FontWeight.bold, fontSize: 20), ), diff --git a/lib/services/db/bloomee_db_service.dart b/lib/services/db/bloomee_db_service.dart index 44cd860..a5c8455 100644 --- a/lib/services/db/bloomee_db_service.dart +++ b/lib/services/db/bloomee_db_service.dart @@ -377,6 +377,44 @@ class BloomeeDBService { } } + static Future putAPICache(String key, String value) async { + Isar isarDB = await db; + if (key.isNotEmpty && value.isNotEmpty) { + isarDB.writeTxnSync( + () => isarDB.appSettingsStrDBs.putSync( + AppSettingsStrDB( + settingName: key, + settingValue: value, + settingValue2: "CACHE", + lastUpdated: DateTime.now(), + ), + ), + ); + } + } + + static Future getAPICache(String key) async { + Isar isarDB = await db; + final apiCache = isarDB.appSettingsStrDBs + .filter() + .settingNameEqualTo(key) + .findFirstSync(); + if (apiCache != null) { + return apiCache.settingValue; + } + return null; + } + + static clearAPICache() async { + Isar isarDB = await db; + isarDB.writeTxnSync( + () => isarDB.appSettingsStrDBs + .filter() + .settingValue2Contains("CACHE") + .deleteAllSync(), + ); + } + static Future getSettingStr(String key, {String? defaultValue}) async { Isar isarDB = await db; diff --git a/lib/utils/external_list_importer.dart b/lib/utils/external_list_importer.dart index b6a5165..6d34e21 100644 --- a/lib/utils/external_list_importer.dart +++ b/lib/utils/external_list_importer.dart @@ -125,8 +125,7 @@ class ExternalMediaImporter { static Future ytMediaImporter(String url) async { final videoId = extractVideoId(url); - SnackbarService.showMessage("Getting Youtube Audio...", - duration: const Duration(seconds: 1)); + SnackbarService.showMessage("Getting Youtube Audio...", loading: true); if (videoId != null) { try { final video = await YoutubeExplode().videos.get(videoId);