diff --git a/lib/blocs/explore/cubit/explore_cubits.dart b/lib/blocs/explore/cubit/explore_cubits.dart index e20ace0..262a323 100644 --- a/lib/blocs/explore/cubit/explore_cubits.dart +++ b/lib/blocs/explore/cubit/explore_cubits.dart @@ -25,39 +25,51 @@ class TrendingCubit extends Cubit { } void getTrendingVideos() async { - final ytCharts = await fetchTrendingVideos(); - emit(state.copyWith(ytCharts: ytCharts)); + List ytCharts = await fetchTrendingVideos(); + ChartModel chart = ytCharts[0] + ..chartItems = getFirstElements(ytCharts[0].chartItems!, 16); + emit(state.copyWith(ytCharts: [chart])); isLatest = true; } + List getFirstElements(List list, int count) { + return list.length > count ? list.sublist(0, count) : list; + } + void getTrendingVideosFromDB() async { - final ytChart = await BloomeeDBService.getChart("Trending Videos"); + ChartModel? ytChart = await BloomeeDBService.getChart("Trending Videos"); if ((!isLatest) && ytChart != null && (ytChart.chartItems?.isNotEmpty ?? false)) { - emit(state.copyWith(ytCharts: [ytChart])); + ChartModel chart = ytChart + ..chartItems = getFirstElements(ytChart.chartItems!, 16); + emit(state.copyWith(ytCharts: [chart])); } } } class RecentlyCubit extends Cubit { - late Stream watcher; + StreamSubscription? watcher; RecentlyCubit() : super(RecentlyCubitInitial()) { - BloomeeDBService.refreshRecentlyPlayed(); getRecentlyPlayed(); watchRecentlyPlayed(); } Future watchRecentlyPlayed() async { - watcher = await BloomeeDBService.watchRecentlyPlayed(); - watcher.listen((event) { + (await BloomeeDBService.watchRecentlyPlayed()).listen((event) { getRecentlyPlayed(); log("Recently Played Updated"); }); } + @override + Future close() { + watcher?.cancel(); + return super.close(); + } + void getRecentlyPlayed() async { - final mediaPlaylist = await BloomeeDBService.getRecentlyPlayed(); + final mediaPlaylist = await BloomeeDBService.getRecentlyPlayed(limit: 15); emit(state.copyWith(mediaPlaylist: mediaPlaylist)); } } diff --git a/lib/blocs/history/cubit/history_cubit.dart b/lib/blocs/history/cubit/history_cubit.dart new file mode 100644 index 0000000..7dfd31c --- /dev/null +++ b/lib/blocs/history/cubit/history_cubit.dart @@ -0,0 +1,31 @@ +import 'dart:async'; +import 'dart:developer'; +import 'package:Bloomee/services/db/bloomee_db_service.dart'; +import 'package:bloc/bloc.dart'; +import 'package:Bloomee/model/MediaPlaylistModel.dart'; +part 'history_state.dart'; + +class HistoryCubit extends Cubit { + StreamSubscription? watcher; + HistoryCubit() : super(HistoryInitial()) { + getRecentlyPlayed(); + watchRecentlyPlayed(); + } + Future watchRecentlyPlayed() async { + watcher = (await BloomeeDBService.watchRecentlyPlayed()).listen((event) { + getRecentlyPlayed(); + log("History Updated"); + }); + } + + void getRecentlyPlayed() async { + final mediaPlaylist = await BloomeeDBService.getRecentlyPlayed(); + emit(state.copyWith(mediaPlaylist: mediaPlaylist)); + } + + @override + Future close() { + watcher?.cancel(); + return super.close(); + } +} diff --git a/lib/blocs/history/cubit/history_state.dart b/lib/blocs/history/cubit/history_state.dart new file mode 100644 index 0000000..ff27bf1 --- /dev/null +++ b/lib/blocs/history/cubit/history_state.dart @@ -0,0 +1,21 @@ +part of 'history_cubit.dart'; + +class HistoryState { + MediaPlaylist mediaPlaylist; + HistoryState({ + required this.mediaPlaylist, + }); + + HistoryState copyWith({ + MediaPlaylist? mediaPlaylist, + }) { + return HistoryState( + mediaPlaylist: mediaPlaylist ?? this.mediaPlaylist, + ); + } +} + +class HistoryInitial extends HistoryState { + HistoryInitial() + : super(mediaPlaylist: MediaPlaylist(albumName: "", mediaItems: [])); +} diff --git a/lib/screens/screen/explore_screen.dart b/lib/screens/screen/explore_screen.dart index 5b6042e..fd3f1ae 100644 --- a/lib/screens/screen/explore_screen.dart +++ b/lib/screens/screen/explore_screen.dart @@ -1,7 +1,7 @@ -import 'package:Bloomee/blocs/downloader/cubit/downloader_cubit.dart'; 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'; @@ -80,28 +80,38 @@ class _ExploreScreenState extends State { )), ) : ((state.mediaPlaylist.mediaItems.isNotEmpty) - ? TabSongListWidget( - list: state.mediaPlaylist.mediaItems - .map((e) { - return SongCardWidget( - song: e, - onTap: () { - context - .read() - .bloomeePlayer - .addQueueItem( - e, - ); - // context - // .read() - // .downloadSong(e); - }, - onOptionsTap: () => - showMoreBottomSheet(context, e), - ); - }).toList(), - category: "Recently", - columnSize: 3, + ? InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const HistoryView())); + }, + child: TabSongListWidget( + list: state.mediaPlaylist.mediaItems + .map((e) { + return SongCardWidget( + song: e, + onTap: () { + context + .read() + .bloomeePlayer + .addQueueItem( + e, + ); + // context + // .read() + // .downloadSong(e); + }, + onOptionsTap: () => + showMoreBottomSheet( + context, e), + ); + }).toList(), + category: "Recently", + columnSize: 3, + ), ) : const SizedBox()), ); @@ -126,15 +136,14 @@ class _ExploreScreenState extends State { )), ) : TabSongListWidget( - list: - state.ytCharts![0].chartItems!.map((e) { - return ChartListTile( - title: e.name ?? "", - subtitle: e.subtitle ?? "", - imgUrl: e.imageUrl ?? "", - rectangularImage: true, - ); - }).toList(), + list: state.ytCharts![0].chartItems! + .map((e) => ChartListTile( + title: e.name ?? "", + subtitle: e.subtitle ?? "", + imgUrl: e.imageUrl ?? "", + rectangularImage: true, + )) + .toList(), category: "Trending", columnSize: 4, ), diff --git a/lib/screens/screen/home_views/recents_view.dart b/lib/screens/screen/home_views/recents_view.dart new file mode 100644 index 0000000..d95bfe1 --- /dev/null +++ b/lib/screens/screen/home_views/recents_view.dart @@ -0,0 +1,95 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +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:flutter/material.dart'; +import 'package:Bloomee/theme_data/default.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class HistoryView extends StatelessWidget { + const HistoryView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Default_Theme.themeColor, + appBar: AppBar( + centerTitle: true, + backgroundColor: Default_Theme.themeColor, + surfaceTintColor: Default_Theme.themeColor, + foregroundColor: Default_Theme.primaryColor1, + title: Text( + 'History', + style: const TextStyle( + color: Default_Theme.primaryColor1, + fontSize: 20, + fontWeight: FontWeight.bold) + .merge(Default_Theme.secondoryTextStyle), + ), + ), + body: BlocProvider( + create: (context) => HistoryCubit(), + child: BlocBuilder( + builder: (context, state) { + return (state is HistoryInitial) + ? const Center( + child: CircularProgressIndicator(), + ) + : ListView.builder( + itemCount: state.mediaPlaylist.mediaItems.length, + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) { + return SongCardWidget( + song: state.mediaPlaylist.mediaItems[index], + onTap: () { + context + .read() + .bloomeePlayer + .addQueueItem( + state.mediaPlaylist.mediaItems[index], + ); + }, + onOptionsTap: () => showMoreBottomSheet( + context, state.mediaPlaylist.mediaItems[index]), + ); + }, + ); + }, + ), + ), + ); + } + + ListTile settingListTile( + {required String title, + required String subtitle, + required IconData icon, + VoidCallback? onTap}) { + return ListTile( + leading: Icon( + icon, + size: 30, + color: Default_Theme.primaryColor1, + ), + title: Text( + title, + style: const TextStyle(color: Default_Theme.primaryColor1, fontSize: 17) + .merge(Default_Theme.secondoryTextStyleMedium), + ), + subtitle: Text( + subtitle, + style: TextStyle( + color: Default_Theme.primaryColor1.withOpacity(0.5), + fontSize: 12.5) + .merge(Default_Theme.secondoryTextStyleMedium), + ), + onTap: () { + if (onTap != null) { + onTap(); + } + }, + ); + } +} diff --git a/lib/screens/screen/home_views/setting_view.dart b/lib/screens/screen/home_views/setting_view.dart index 59db700..23a4569 100644 --- a/lib/screens/screen/home_views/setting_view.dart +++ b/lib/screens/screen/home_views/setting_view.dart @@ -9,19 +9,9 @@ import 'package:flutter/material.dart'; import 'package:Bloomee/theme_data/default.dart'; import 'package:icons_plus/icons_plus.dart'; -class SettingsView extends StatefulWidget { +class SettingsView extends StatelessWidget { const SettingsView({super.key}); - @override - State createState() => _SettingsViewState(); -} - -class _SettingsViewState extends State { - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/screens/widgets/paging_scroll.dart b/lib/screens/widgets/paging_scroll.dart new file mode 100644 index 0000000..e6df623 --- /dev/null +++ b/lib/screens/widgets/paging_scroll.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +//credit goes to: https://stackoverflow.com/a/72817531/21571707 + +class PagingScrollPhysics extends ScrollPhysics { + const PagingScrollPhysics( + {required this.itemCount, required this.viewSize, super.parent}); + + final double viewSize; + + final int itemCount; + + @override + PagingScrollPhysics applyTo(ScrollPhysics? ancestor) => PagingScrollPhysics( + itemCount: itemCount, viewSize: viewSize, parent: buildParent(ancestor)); + + double _getPage(double current, double itemDimension) => + current / itemDimension; + + double _getTargetPixels( + ScrollMetrics position, Tolerance tolerance, double velocity) { + // plus view size because the max scroll extent is about where the screen + // starts not where the screen ends. + final pixels = position.maxScrollExtent + viewSize; + final itemDimension = pixels / itemCount; + var page = _getPage(position.pixels, itemDimension); + if (velocity < -tolerance.velocity) { + page -= 0.5; + } else if (velocity > tolerance.velocity) { + page += 0.5; + } + final pageRound = page.round(); + final itemsPerPage = viewSize ~/ itemDimension; + final showingLastItem = pageRound == itemCount - itemsPerPage; + if (showingLastItem) return pixels - viewSize; + + return pageRound * itemDimension; + } + + @override + Simulation? createBallisticSimulation( + ScrollMetrics position, double velocity) { + if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) || + (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) { + return super.createBallisticSimulation(position, velocity); + } + final Tolerance tolerance = this.tolerance; + final double target = _getTargetPixels(position, tolerance, velocity); + if (target != position.pixels) { + return ScrollSpringSimulation(spring, position.pixels, target, velocity, + tolerance: tolerance); + } + return null; + } + + @override + bool get allowImplicitScrolling => false; +} diff --git a/lib/screens/widgets/tabList_widget.dart b/lib/screens/widgets/tabList_widget.dart index 41eb7d6..d86c622 100644 --- a/lib/screens/widgets/tabList_widget.dart +++ b/lib/screens/widgets/tabList_widget.dart @@ -1,4 +1,5 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:Bloomee/screens/widgets/paging_scroll.dart'; import 'package:flutter/material.dart'; import '../../theme_data/default.dart'; @@ -47,7 +48,6 @@ class TabSongListWidget extends StatelessWidget { ); } - @override bool get wantKeepAlive => true; } @@ -55,6 +55,7 @@ Widget buildColumnsCards(List items, context, {int columnLength = 4}) { final cards = []; Widget feautredCards; int endIndex = columnLength; + double itemWidth = (MediaQuery.of(context).size.width - 40) * 0.88; if (endIndex > items.length) endIndex = items.length; if (items.isNotEmpty) { for (int i = 0; i < items.length; i += columnLength) { @@ -63,7 +64,7 @@ Widget buildColumnsCards(List items, context, {int columnLength = 4}) { // currentRow.add(const Spacer()); endIndex = endIndex + columnLength; cards.add(SizedBox( - width: (MediaQuery.of(context).size.width - 40) * 0.88, + width: itemWidth, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -72,21 +73,15 @@ Widget buildColumnsCards(List items, context, {int columnLength = 4}) { ), )); } - feautredCards = Container( - padding: const EdgeInsets.only(top: 16, bottom: 8), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - scrollDirection: Axis.horizontal, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: cards), - ), - ], + feautredCards = SizedBox( + height: 70 * columnLength.toDouble() + 10, + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + physics: PagingScrollPhysics( + itemCount: cards.length, + viewSize: (MediaQuery.of(context).size.width - 40)), + children: cards, ), ); } else { diff --git a/lib/services/db/bloomee_db_service.dart b/lib/services/db/bloomee_db_service.dart index d157e99..44cd860 100644 --- a/lib/services/db/bloomee_db_service.dart +++ b/lib/services/db/bloomee_db_service.dart @@ -498,17 +498,30 @@ class BloomeeDBService { } } - static Future getRecentlyPlayed() async { - Isar isarDB = await db; - List _recentlyPlayed = - isarDB.recentlyPlayedDBs.where().sortByLastPlayedDesc().findAllSync(); - List _mediaItems = []; - for (var element in _recentlyPlayed) { - if (element.mediaItem.value != null) { - _mediaItems.add(MediaItemDB2MediaItem(element.mediaItem.value!)); + static Future getRecentlyPlayed({int limit = 0}) async { + List mediaItems = []; + Isar isarDB = await db; + if (limit == 0) { + List recentlyPlayed = + isarDB.recentlyPlayedDBs.where().sortByLastPlayedDesc().findAllSync(); + for (var element in recentlyPlayed) { + if (element.mediaItem.value != null) { + mediaItems.add(MediaItemDB2MediaItem(element.mediaItem.value!)); + } + } + } else { + List recentlyPlayed = isarDB.recentlyPlayedDBs + .where() + .sortByLastPlayedDesc() + .limit(limit) + .findAllSync(); + for (var element in recentlyPlayed) { + if (element.mediaItem.value != null) { + mediaItems.add(MediaItemDB2MediaItem(element.mediaItem.value!)); + } } } - return MediaPlaylist(mediaItems: _mediaItems, albumName: "Recently Played"); + return MediaPlaylist(mediaItems: mediaItems, albumName: "Recently Played"); } static Future> watchRecentlyPlayed() async {