diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 1f058de6..9449215d 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -362,6 +362,7 @@ enum YTHomePages { notifications, channels, playlists, + userplaylists, downloads, } diff --git a/lib/core/namida_converter_ext.dart b/lib/core/namida_converter_ext.dart index 620d065a..d54122cc 100644 --- a/lib/core/namida_converter_ext.dart +++ b/lib/core/namida_converter_ext.dart @@ -1270,6 +1270,7 @@ class _NamidaConverters { YTHomePages.notifications: lang.NOTIFICATIONS, YTHomePages.channels: lang.CHANNELS, YTHomePages.playlists: lang.PLAYLISTS, + YTHomePages.userplaylists: '${lang.PLAYLISTS} (${lang.YOUTUBE})', YTHomePages.downloads: lang.DOWNLOADS, }, TrackSearchFilter: { diff --git a/lib/ui/widgets/custom_widgets.dart b/lib/ui/widgets/custom_widgets.dart index 171634e1..937f3b4d 100644 --- a/lib/ui/widgets/custom_widgets.dart +++ b/lib/ui/widgets/custom_widgets.dart @@ -3355,7 +3355,7 @@ class _NamidaTabViewState extends State with SingleTickerProvider tabs: widget.tabs .map( (e) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 10.0), child: Text(e, maxLines: 1, overflow: TextOverflow.ellipsis), ), ) diff --git a/lib/youtube/controller/youtube_info_controller.dart b/lib/youtube/controller/youtube_info_controller.dart index 6250bc27..07a91861 100644 --- a/lib/youtube/controller/youtube_info_controller.dart +++ b/lib/youtube/controller/youtube_info_controller.dart @@ -41,6 +41,7 @@ class YoutubeInfoController { static const video = _VideoInfoController(); static const playlist = YoutiPie.playlist; + static const userplaylist = YoutiPie.userplaylist; static const comment = YoutiPie.comment; static const search = _SearchInfoController(); static const feed = YoutiPie.feed; diff --git a/lib/youtube/pages/youtube_feed_page.dart b/lib/youtube/pages/youtube_feed_page.dart index a5a69275..1b012056 100644 --- a/lib/youtube/pages/youtube_feed_page.dart +++ b/lib/youtube/pages/youtube_feed_page.dart @@ -52,10 +52,13 @@ class YoutubeHomeFeedPage extends StatelessWidget { const (PlaylistInfoItem) => YoutubePlaylistCard( key: Key((item as PlaylistInfoItem).id), playlist: item, + firstVideoID: item.initialVideos.firstOrNull?.id, thumbnailWidth: thumbnailWidth, thumbnailHeight: thumbnailHeight, subtitle: item.subtitle, playOnTap: true, + playingId: null, + isMixPlaylist: item.isMix, ), _ => const YoutubeVideoCardDummy( shimmerEnabled: true, diff --git a/lib/youtube/pages/youtube_home_view.dart b/lib/youtube/pages/youtube_home_view.dart index 74f5f7bc..c61b6d24 100644 --- a/lib/youtube/pages/youtube_home_view.dart +++ b/lib/youtube/pages/youtube_home_view.dart @@ -7,6 +7,7 @@ import 'package:namida/core/namida_converter_ext.dart'; import 'package:namida/ui/widgets/custom_widgets.dart'; import 'package:namida/youtube/pages/youtube_feed_page.dart'; import 'package:namida/youtube/pages/youtube_notifications_page.dart'; +import 'package:namida/youtube/pages/youtube_user_playlists_page.dart'; import 'package:namida/youtube/pages/yt_channels_page.dart'; import 'package:namida/youtube/pages/yt_downloads_page.dart'; import 'package:namida/youtube/youtube_playlists_view.dart'; @@ -32,6 +33,7 @@ class YouTubeHomeView extends StatelessWidget with NamidaRouteWidget { YoutubeNotificationsPage(), YoutubeChannelsPage(), YoutubePlaylistsView(), + YoutubePlaylistsPage(), YTDownloadsPage(), ], ), diff --git a/lib/youtube/pages/youtube_user_playlists_page.dart b/lib/youtube/pages/youtube_user_playlists_page.dart new file mode 100644 index 00000000..1980addf --- /dev/null +++ b/lib/youtube/pages/youtube_user_playlists_page.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:youtipie/class/result_wrapper/playlist_user_result.dart'; +import 'package:youtipie/class/youtipie_feed/playlist_info_item_user.dart'; +import 'package:youtipie/youtipie.dart'; + +import 'package:namida/core/dimensions.dart'; +import 'package:namida/core/translations/language.dart'; +import 'package:namida/youtube/controller/youtube_info_controller.dart'; +import 'package:namida/youtube/pages/youtube_main_page_fetcher_acc_base.dart'; +import 'package:namida/youtube/widgets/yt_playlist_card.dart'; +import 'package:namida/youtube/widgets/yt_video_card.dart'; + +class YoutubePlaylistsPage extends StatelessWidget { + const YoutubePlaylistsPage({super.key}); + + @override + Widget build(BuildContext context) { + const multiplier = 0.8; + const thumbnailHeight = multiplier * Dimensions.youtubeThumbnailHeight; + const thumbnailWidth = multiplier * Dimensions.youtubeThumbnailWidth; + const thumbnailItemExtent = thumbnailHeight + 8.0 * 2; + + return YoutubeMainPageFetcherAccBase( + transparentShimmer: true, + title: lang.PLAYLISTS, + cacheReader: YoutiPie.cacheBuilder.forUserPlaylists(), + networkFetcher: (details) => YoutubeInfoController.userplaylist.getUserPlaylists(details: details), + itemExtent: thumbnailItemExtent, + dummyCard: const YoutubeVideoCardDummy( + thumbnailWidth: thumbnailWidth, + thumbnailHeight: thumbnailHeight, + shimmerEnabled: true, + ), + itemBuilder: (playlist, index, list) { + return YoutubePlaylistCard( + key: Key(playlist.id), + playlist: playlist, + subtitle: playlist.infoTexts?.join(' - '), + thumbnailWidth: thumbnailWidth, + thumbnailHeight: thumbnailHeight, + firstVideoID: null, + isMixPlaylist: false, // TODO: is it possible? + playingId: null, + playOnTap: false, + ); + }); + } +} diff --git a/lib/youtube/pages/yt_search_results_page.dart b/lib/youtube/pages/yt_search_results_page.dart index 230d4a51..003519b1 100644 --- a/lib/youtube/pages/yt_search_results_page.dart +++ b/lib/youtube/pages/yt_search_results_page.dart @@ -318,7 +318,10 @@ class YoutubeSearchResultsPageState extends State with thumbnailWidth: thumbnailWidth, playOnTap: false, playlist: item as PlaylistInfoItem, + firstVideoID: item.initialVideos.firstOrNull?.id, subtitle: item.subtitle.isNotEmpty ? item.subtitle : item.initialVideos.firstOrNull?.title, + isMixPlaylist: item.isMix, + playingId: null, ), const (ChannelInfoItem) => YoutubeChannelCard( channel: item as ChannelInfoItem, diff --git a/lib/youtube/widgets/yt_playlist_card.dart b/lib/youtube/widgets/yt_playlist_card.dart index 7e03c125..56887f59 100644 --- a/lib/youtube/widgets/yt_playlist_card.dart +++ b/lib/youtube/widgets/yt_playlist_card.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:youtipie/class/execute_details.dart'; import 'package:youtipie/class/result_wrapper/playlist_result_base.dart'; import 'package:youtipie/class/youtipie_feed/playlist_basic_info.dart'; -import 'package:youtipie/class/youtipie_feed/playlist_info_item.dart'; import 'package:youtipie/core/enum.dart'; import 'package:youtipie/youtipie.dart'; @@ -24,12 +23,14 @@ import 'package:namida/youtube/widgets/yt_thumbnail.dart'; /// Playlist info is fetched automatically after 3 seconds of being displayed, or after attempting an action. class YoutubePlaylistCard extends StatefulWidget { - final PlaylistInfoItem playlist; + final PlaylistBasicInfo playlist; final String? subtitle; final double? thumbnailWidth; final double? thumbnailHeight; final bool playOnTap; - final String? playingId; + final String? firstVideoID; + final String Function()? playingId; + final bool isMixPlaylist; const YoutubePlaylistCard({ super.key, @@ -38,7 +39,9 @@ class YoutubePlaylistCard extends StatefulWidget { this.thumbnailWidth, this.thumbnailHeight, this.playOnTap = false, - this.playingId, + required this.firstVideoID, + required this.playingId, + required this.isMixPlaylist, }); @override @@ -46,18 +49,14 @@ class YoutubePlaylistCard extends StatefulWidget { } class _YoutubePlaylistCardState extends State { - String? get firstVideoID { - return widget.playlist.initialVideos.firstOrNull?.id; - } - YoutiPiePlaylistResultBase? playlistToFetch; Timer? _fetchTimer; final _isFetching = Rxn(); Future _fetchFunction({required bool forceRequest}) { final executeDetails = forceRequest ? ExecuteDetails.forceRequest() : ExecuteDetails.cache(CacheDecision.cacheOnly); - if (widget.playlist.isMix) { - final videoId = firstVideoID ?? widget.playingId; + if (widget.isMixPlaylist) { + final videoId = widget.firstVideoID ?? widget.playingId?.call(); if (videoId == null) return Future.value(null); return YoutubeInfoController.playlist.getMixPlaylist( videoId: videoId, @@ -120,14 +119,14 @@ class _YoutubePlaylistCardState extends State { Widget build(BuildContext context) { final playlist = widget.playlist; String countText; - if (playlist.isMix) { + if (widget.isMixPlaylist) { countText = '+25'; } else { countText = playlist.videosCount?.formatDecimalShort() ?? '?'; } final thumbnailUrl = playlist.thumbnails.pick()?.url; - final firstVideoID = this.firstVideoID; - final goodVideoID = firstVideoID != null && firstVideoID != ''; + final firstVideoID = widget.firstVideoID; + final goodVideoID = firstVideoID != null && firstVideoID.isNotEmpty; return NamidaPopupWrapper( openOnTap: false, openOnLongPress: true, diff --git a/lib/youtube/youtube_miniplayer.dart b/lib/youtube/youtube_miniplayer.dart index ab8200d6..e8fa3b80 100644 --- a/lib/youtube/youtube_miniplayer.dart +++ b/lib/youtube/youtube_miniplayer.dart @@ -868,7 +868,9 @@ class YoutubeMiniPlayerState extends State { playlist: item, subtitle: item.subtitle, playOnTap: true, - playingId: currentId, + firstVideoID: item.initialVideos.firstOrNull?.id, + playingId: () => currentId, + isMixPlaylist: item.isMix, ), _ => dummyVideoCard, }; diff --git a/pubspec.yaml b/pubspec.yaml index a5af7752..2284896a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: namida description: A Beautiful and Feature-rich Music Player, With YouTube & Video Support Built in Flutter publish_to: "none" -version: 3.0.8-beta+240708232 +version: 3.0.9-beta+240708233 environment: sdk: ">=3.4.0 <4.0.0"