mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
Infinite Scorll added
CategoryCard no-title bug fix shuffle support added
This commit is contained in:
parent
799e13c376
commit
0ef44709fa
@ -7,7 +7,7 @@ import 'package:spotube/provider/SpotifyDI.dart';
|
|||||||
|
|
||||||
class CategoryCard extends StatefulWidget {
|
class CategoryCard extends StatefulWidget {
|
||||||
final Category category;
|
final Category category;
|
||||||
CategoryCard(this.category);
|
const CategoryCard(this.category, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CategoryCardState createState() => _CategoryCardState();
|
_CategoryCardState createState() => _CategoryCardState();
|
||||||
@ -16,8 +16,7 @@ class CategoryCard extends StatefulWidget {
|
|||||||
class _CategoryCardState extends State<CategoryCard> {
|
class _CategoryCardState extends State<CategoryCard> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Column(
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
@ -33,12 +32,15 @@ class _CategoryCardState extends State<CategoryCard> {
|
|||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return PlaylistGenreView(widget.category.id!);
|
return PlaylistGenreView(
|
||||||
|
widget.category.id!,
|
||||||
|
widget.category.name!,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text("See all"),
|
child: const Text("See all"),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -51,10 +53,10 @@ class _CategoryCardState extends State<CategoryCard> {
|
|||||||
.getPage(4, 0),
|
.getPage(4, 0),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Center(child: Text("Error occurred"));
|
return const Center(child: Text("Error occurred"));
|
||||||
}
|
}
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return Center(child: Text("Loading.."));
|
return const Center(child: Text("Loading.."));
|
||||||
}
|
}
|
||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
@ -65,7 +67,6 @@ class _CategoryCardState extends State<CategoryCard> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
@ -10,11 +11,16 @@ import 'package:spotube/provider/Auth.dart';
|
|||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
class Home extends StatefulWidget {
|
class Home extends StatefulWidget {
|
||||||
|
const Home({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_HomeState createState() => _HomeState();
|
_HomeState createState() => _HomeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeState extends State<Home> {
|
class _HomeState extends State<Home> {
|
||||||
|
final PagingController<int, Category> _pagingController =
|
||||||
|
PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -39,12 +45,35 @@ class _HomeState extends State<Home> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_pagingController.addPageRequestListener((pageKey) async {
|
||||||
|
try {
|
||||||
|
SpotifyDI data = context.read<SpotifyDI>();
|
||||||
|
Page<Category> categories = await data.spotifyApi.categories
|
||||||
|
.list(country: "US")
|
||||||
|
.getPage(15, pageKey);
|
||||||
|
|
||||||
|
if (categories.isLast && categories.items != null) {
|
||||||
|
_pagingController.appendLastPage(categories.items!.toList());
|
||||||
|
} else if (categories.items != null) {
|
||||||
|
_pagingController.appendPage(
|
||||||
|
categories.items!.toList(), categories.nextOffset);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_pagingController.error = e;
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("[login state error]: $e");
|
print("[login state error]: $e");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pagingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Auth authProvider = Provider.of<Auth>(context);
|
Auth authProvider = Provider.of<Auth>(context);
|
||||||
@ -53,8 +82,7 @@ class _HomeState extends State<Home> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Container(
|
body: Column(
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
// Side Tab Bar
|
// Side Tab Bar
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -74,9 +102,8 @@ class _HomeState extends State<Home> {
|
|||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text("Spotube",
|
title: Text("Spotube",
|
||||||
style: Theme.of(context)
|
style:
|
||||||
.textTheme
|
Theme.of(context).textTheme.headline4),
|
||||||
.headline4),
|
|
||||||
leading:
|
leading:
|
||||||
const Icon(Icons.miscellaneous_services),
|
const Icon(Icons.miscellaneous_services),
|
||||||
),
|
),
|
||||||
@ -115,31 +142,17 @@ class _HomeState extends State<Home> {
|
|||||||
),
|
),
|
||||||
// contents of the spotify
|
// contents of the spotify
|
||||||
Consumer<SpotifyDI>(builder: (_, data, __) {
|
Consumer<SpotifyDI>(builder: (_, data, __) {
|
||||||
return FutureBuilder<Page<Category>>(
|
|
||||||
future: data.spotifyApi.categories
|
|
||||||
.list(country: "US")
|
|
||||||
.getPage(10, 0),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return const Center(child: Text("Error occured"));
|
|
||||||
}
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const Center(child: Text("Loading"));
|
|
||||||
}
|
|
||||||
List<Category> categories =
|
|
||||||
snapshot.data!.items!.toList();
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
isAlwaysShown: true,
|
child: PagedListView(
|
||||||
child: ListView.builder(
|
pagingController: _pagingController,
|
||||||
itemCount: categories.length,
|
builderDelegate: PagedChildBuilderDelegate<Category>(
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, item, index) {
|
||||||
return CategoryCard(categories[index]);
|
return CategoryCard(item);
|
||||||
},
|
},
|
||||||
),
|
)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -148,7 +161,6 @@ class _HomeState extends State<Home> {
|
|||||||
const player.Player()
|
const player.Player()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:spotube/components/PlayerControls.dart';
|
import 'package:spotube/components/PlayerControls.dart';
|
||||||
import 'package:spotube/provider/Playback.dart';
|
import 'package:spotube/provider/Playback.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -18,8 +20,7 @@ class _PlayerState extends State<Player> {
|
|||||||
late MPVPlayer player;
|
late MPVPlayer player;
|
||||||
|
|
||||||
bool _isPlaying = false;
|
bool _isPlaying = false;
|
||||||
String? _mediaTitle;
|
bool _shuffled = false;
|
||||||
String? _mediaArtists;
|
|
||||||
double _duration = 0;
|
double _duration = 0;
|
||||||
|
|
||||||
String? _currentPlaylistId;
|
String? _currentPlaylistId;
|
||||||
@ -45,8 +46,10 @@ class _PlayerState extends State<Player> {
|
|||||||
_volume = volume / 100;
|
_volume = volume / 100;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
print("[PLAYER]: $e");
|
print("[PLAYER]: $e");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
player.on(MPVEvents.paused, null, (ev, context) {
|
player.on(MPVEvents.paused, null, (ev, context) {
|
||||||
@ -65,20 +68,27 @@ class _PlayerState extends State<Player> {
|
|||||||
player.on(MPVEvents.status, null, (ev, _) async {
|
player.on(MPVEvents.status, null, (ev, _) async {
|
||||||
Map data = ev.eventData as Map;
|
Map data = ev.eventData as Map;
|
||||||
Playback playback = context.read<Playback>();
|
Playback playback = context.read<Playback>();
|
||||||
print("[DATA]: $data");
|
|
||||||
if (data["property"] == "media-title" && data["value"] != null) {
|
if (data["property"] == "media-title" && data["value"] != null) {
|
||||||
|
var containsYtdl = (data["value"] as String).contains("ytsearch:");
|
||||||
|
if (containsYtdl) {
|
||||||
var props = (data["value"] as String).split("-");
|
var props = (data["value"] as String).split("-");
|
||||||
|
var mediaTitle = props.last.trim();
|
||||||
|
var mediaArtists = props.first.split("ytsearch:").last.trim();
|
||||||
setState(() {
|
setState(() {
|
||||||
_isPlaying = true;
|
_isPlaying = true;
|
||||||
_mediaTitle = props.last.replaceAll(
|
|
||||||
RegExp(
|
|
||||||
"(official|video|lyric|[(){}\\[\\]\\|])",
|
|
||||||
caseSensitive: false,
|
|
||||||
),
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
_mediaArtists = props.first;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var matchedTracks = playback.currentPlaylist?.tracks.where(
|
||||||
|
(track) {
|
||||||
|
return track.name == mediaTitle &&
|
||||||
|
artistsToString(track.artists ?? []) == mediaArtists;
|
||||||
|
},
|
||||||
|
) ??
|
||||||
|
[];
|
||||||
|
if (matchedTracks.isNotEmpty) {
|
||||||
|
playback.setCurrentTrack = matchedTracks.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (data["property"] == "duration" && data["value"] != null) {
|
if (data["property"] == "duration" && data["value"] != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -90,30 +100,6 @@ class _PlayerState extends State<Player> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
Playback playback = context.read<Playback>();
|
|
||||||
|
|
||||||
String? prevTrackName = playback.currentTrack?.name;
|
|
||||||
String prevTrackArtists =
|
|
||||||
artistsToString(playback.currentTrack?.artists ?? []);
|
|
||||||
|
|
||||||
if (playback.currentPlaylist != null &&
|
|
||||||
playback.currentPlaylist!.tracks.isNotEmpty &&
|
|
||||||
prevTrackName != _mediaTitle &&
|
|
||||||
prevTrackArtists != _mediaArtists) {
|
|
||||||
var tracks = playback.currentPlaylist?.tracks.where((track) {
|
|
||||||
return _mediaTitle == track.name! &&
|
|
||||||
artistsToString(track.artists ?? []) == _mediaTitle;
|
|
||||||
}) ??
|
|
||||||
[];
|
|
||||||
if (tracks.isNotEmpty) {
|
|
||||||
playback.setCurrentTrack = tracks.first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
player.removeAllByEvent(MPVEvents.paused);
|
player.removeAllByEvent(MPVEvents.paused);
|
||||||
@ -134,7 +120,6 @@ class _PlayerState extends State<Player> {
|
|||||||
File file = File(playlistPath);
|
File file = File(playlistPath);
|
||||||
var newPlaylist = playlistToStr(playlist);
|
var newPlaylist = playlistToStr(playlist);
|
||||||
|
|
||||||
print("😃PLAYING PLAYLIST😃");
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
await file.create();
|
await file.create();
|
||||||
}
|
}
|
||||||
@ -144,6 +129,7 @@ class _PlayerState extends State<Player> {
|
|||||||
await player.loadPlaylist(playlistPath);
|
await player.loadPlaylist(playlistPath);
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentPlaylistId = playlist.id;
|
_currentPlaylistId = playlist.id;
|
||||||
|
_shuffled = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,21 +148,30 @@ class _PlayerState extends State<Player> {
|
|||||||
playPlaylist(playback.currentPlaylist!);
|
playPlaylist(playback.currentPlaylist!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? albumArt = playback.currentTrack?.album?.images?.last.url;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
|
if (albumArt != null)
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: albumArt,
|
||||||
|
maxHeightDiskCache: 50,
|
||||||
|
maxWidthDiskCache: 50,
|
||||||
|
),
|
||||||
// title of the currently playing track
|
// title of the currently playing track
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_mediaTitle ?? "Not playing",
|
playback.currentTrack?.name ?? "Not playing",
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Text(_mediaArtists ?? "")
|
Text(
|
||||||
|
artistsToString(playback.currentTrack?.artists ?? []))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -187,13 +182,28 @@ class _PlayerState extends State<Player> {
|
|||||||
player: player,
|
player: player,
|
||||||
isPlaying: _isPlaying,
|
isPlaying: _isPlaying,
|
||||||
duration: _duration,
|
duration: _duration,
|
||||||
|
shuffled: _shuffled,
|
||||||
|
onShuffle: () {
|
||||||
|
if (!_shuffled) {
|
||||||
|
player.shuffle().then(
|
||||||
|
(value) => setState(() {
|
||||||
|
_shuffled = true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
player.unshuffle().then(
|
||||||
|
(value) => setState(() {
|
||||||
|
_shuffled = false;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
onStop: () {
|
onStop: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
_currentPlaylistId = null;
|
_currentPlaylistId = null;
|
||||||
_mediaArtists = null;
|
|
||||||
_mediaTitle = null;
|
|
||||||
_duration = 0;
|
_duration = 0;
|
||||||
|
_shuffled = false;
|
||||||
});
|
});
|
||||||
playback.reset();
|
playback.reset();
|
||||||
},
|
},
|
||||||
|
@ -5,11 +5,15 @@ class PlayerControls extends StatefulWidget {
|
|||||||
final MPVPlayer player;
|
final MPVPlayer player;
|
||||||
final bool isPlaying;
|
final bool isPlaying;
|
||||||
final double duration;
|
final double duration;
|
||||||
|
final bool shuffled;
|
||||||
final Function? onStop;
|
final Function? onStop;
|
||||||
|
final Function? onShuffle;
|
||||||
const PlayerControls({
|
const PlayerControls({
|
||||||
required this.player,
|
required this.player,
|
||||||
required this.isPlaying,
|
required this.isPlaying,
|
||||||
required this.duration,
|
required this.duration,
|
||||||
|
required this.shuffled,
|
||||||
|
this.onShuffle,
|
||||||
this.onStop,
|
this.onStop,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -86,8 +90,10 @@ class _PlayerControlsState extends State<PlayerControls> {
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.shuffle_rounded),
|
icon: const Icon(Icons.shuffle_rounded),
|
||||||
onPressed: () async {
|
color:
|
||||||
await widget.player.shuffle();
|
widget.shuffled ? Theme.of(context).primaryColor : null,
|
||||||
|
onPressed: () {
|
||||||
|
widget.onShuffle?.call();
|
||||||
}),
|
}),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.skip_previous_rounded),
|
icon: const Icon(Icons.skip_previous_rounded),
|
||||||
|
@ -5,8 +5,10 @@ import 'package:spotube/components/PlaylistCard.dart';
|
|||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
class PlaylistGenreView extends StatefulWidget {
|
class PlaylistGenreView extends StatefulWidget {
|
||||||
String genre_id;
|
final String genreId;
|
||||||
PlaylistGenreView(this.genre_id);
|
final String genreName;
|
||||||
|
const PlaylistGenreView(this.genreId, this.genreName, {Key? key})
|
||||||
|
: super(key: key);
|
||||||
@override
|
@override
|
||||||
_PlaylistGenreViewState createState() => _PlaylistGenreViewState();
|
_PlaylistGenreViewState createState() => _PlaylistGenreViewState();
|
||||||
}
|
}
|
||||||
@ -15,17 +17,16 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Container(
|
body: Column(
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
BackButton(),
|
const BackButton(),
|
||||||
// genre name
|
// genre name
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Genre Name",
|
widget.genreName,
|
||||||
style: Theme.of(context).textTheme.headline4,
|
style: Theme.of(context).textTheme.headline4,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@ -37,14 +38,14 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: FutureBuilder<Iterable<PlaylistSimple>>(
|
child: FutureBuilder<Iterable<PlaylistSimple>>(
|
||||||
future: data.spotifyApi.playlists
|
future: data.spotifyApi.playlists
|
||||||
.getByCategoryId(widget.genre_id)
|
.getByCategoryId(widget.genreId)
|
||||||
.all(),
|
.all(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Center(child: Text("Error occurred"));
|
return const Center(child: Text("Error occurred"));
|
||||||
}
|
}
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return Center(child: Text("Loading.."));
|
return const Center(child: Text("Loading.."));
|
||||||
}
|
}
|
||||||
return Wrap(
|
return Wrap(
|
||||||
children: snapshot.data!
|
children: snapshot.data!
|
||||||
@ -62,7 +63,6 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import 'package:spotube/components/TrackButton.dart';
|
|||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
class PlaylistView extends StatefulWidget {
|
class PlaylistView extends StatefulWidget {
|
||||||
PlaylistSimple playlist;
|
final PlaylistSimple playlist;
|
||||||
PlaylistView(this.playlist);
|
const PlaylistView(this.playlist, {Key? key}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
_PlaylistViewState createState() => _PlaylistViewState();
|
_PlaylistViewState createState() => _PlaylistViewState();
|
||||||
}
|
}
|
||||||
@ -18,19 +18,12 @@ class _PlaylistViewState extends State<PlaylistView> {
|
|||||||
Playback playback = context.read<Playback>();
|
Playback playback = context.read<Playback>();
|
||||||
return Consumer<SpotifyDI>(builder: (_, data, __) {
|
return Consumer<SpotifyDI>(builder: (_, data, __) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Container(
|
body: FutureBuilder<Iterable<Track>>(
|
||||||
child: FutureBuilder<Iterable<Track>>(
|
|
||||||
future: data.spotifyApi.playlists
|
future: data.spotifyApi.playlists
|
||||||
.getTracksByPlaylistId(widget.playlist.id)
|
.getTracksByPlaylistId(widget.playlist.id)
|
||||||
.all(),
|
.all(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
List<Track> tracks = snapshot.data?.toList() ?? [];
|
||||||
return const Center(child: const Text("Error occurred"));
|
|
||||||
}
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const Center(child: const Text("Loading.."));
|
|
||||||
}
|
|
||||||
List<Track> tracks = snapshot.data!.toList();
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@ -43,24 +36,42 @@ class _PlaylistViewState extends State<PlaylistView> {
|
|||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
// play playlist
|
// play playlist
|
||||||
IconButton(
|
Consumer<Playback>(builder: (context, playback, widget) {
|
||||||
icon: const Icon(Icons.play_arrow_rounded),
|
var isPlaylistPlaying = playback.currentPlaylist?.id ==
|
||||||
onPressed: () {
|
this.widget.playlist.id;
|
||||||
playback.setCurrentPlaylist = CurrentPlaylist(
|
return IconButton(
|
||||||
tracks: tracks,
|
icon: Icon(
|
||||||
id: widget.playlist.id!,
|
isPlaylistPlaying
|
||||||
name: widget.playlist.name!,
|
? Icons.stop_rounded
|
||||||
thumbnail: widget.playlist.images![0].url!,
|
: Icons.play_arrow_rounded,
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
onPressed: snapshot.hasData
|
||||||
|
? () {
|
||||||
|
if (!isPlaylistPlaying) {
|
||||||
|
playback.setCurrentPlaylist =
|
||||||
|
CurrentPlaylist(
|
||||||
|
tracks: tracks,
|
||||||
|
id: this.widget.playlist.id!,
|
||||||
|
name: this.widget.playlist.name!,
|
||||||
|
thumbnail:
|
||||||
|
this.widget.playlist.images![0].url!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Text(widget.playlist.name!,
|
child: Text(widget.playlist.name!,
|
||||||
style: Theme.of(context).textTheme.headline4),
|
style: Theme.of(context).textTheme.headline4),
|
||||||
),
|
),
|
||||||
Expanded(
|
snapshot.hasError
|
||||||
|
? const Center(child: Text("Error occurred"))
|
||||||
|
: !snapshot.hasData
|
||||||
|
? const Center(child: Text("Loading.."))
|
||||||
|
: Expanded(
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
isAlwaysShown: true,
|
isAlwaysShown: true,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
@ -82,14 +93,16 @@ class _PlaylistViewState extends State<PlaylistView> {
|
|||||||
Track track = tracks[index - 1];
|
Track track = tracks[index - 1];
|
||||||
return TrackButton(
|
return TrackButton(
|
||||||
index: (index - 1).toString(),
|
index: (index - 1).toString(),
|
||||||
thumbnail_url: track.album?.images?.last.url ??
|
thumbnail_url: track
|
||||||
|
.album?.images?.last.url ??
|
||||||
"https://i.scdn.co/image/ab67616d00001e02b993cba8ff7d0a8e9ee18d46",
|
"https://i.scdn.co/image/ab67616d00001e02b993cba8ff7d0a8e9ee18d46",
|
||||||
trackName: track.name!,
|
trackName: track.name!,
|
||||||
artists:
|
artists: track.artists!
|
||||||
track.artists!.map((e) => e.name!).toList(),
|
.map((e) => e.name!)
|
||||||
|
.toList(),
|
||||||
album: track.album!.name!,
|
album: track.album!.name!,
|
||||||
playback_time:
|
playback_time: track.duration!.inMinutes
|
||||||
track.duration!.inMinutes.toString(),
|
.toString(),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -98,7 +111,6 @@ class _PlaylistViewState extends State<PlaylistView> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ class _TrackButtonState extends State<TrackButton> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
child: Ink(
|
child: Ink(
|
||||||
padding: EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -38,16 +38,16 @@ class _TrackButtonState extends State<TrackButton> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.index,
|
widget.index,
|
||||||
style: TextStyle(fontSize: 20),
|
style: const TextStyle(fontSize: 20),
|
||||||
),
|
),
|
||||||
SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
if (widget.thumbnail_url != null)
|
if (widget.thumbnail_url != null)
|
||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: widget.thumbnail_url!,
|
imageUrl: widget.thumbnail_url!,
|
||||||
maxHeightDiskCache: 50,
|
maxHeightDiskCache: 50,
|
||||||
maxWidthDiskCache: 50,
|
maxWidthDiskCache: 50,
|
||||||
),
|
),
|
||||||
SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
Container(
|
Container(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -55,7 +55,7 @@ class _TrackButtonState extends State<TrackButton> {
|
|||||||
Text(
|
Text(
|
||||||
widget.trackName,
|
widget.trackName,
|
||||||
textAlign: TextAlign.justify,
|
textAlign: TextAlign.justify,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold, fontSize: 17),
|
fontWeight: FontWeight.bold, fontSize: 17),
|
||||||
),
|
),
|
||||||
Text(widget.artists.join(", "))
|
Text(widget.artists.join(", "))
|
||||||
@ -64,9 +64,9 @@ class _TrackButtonState extends State<TrackButton> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
Text(widget.album),
|
Text(widget.album),
|
||||||
SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
Text(widget.playback_time)
|
Text(widget.playback_time)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -29,8 +29,8 @@ class MyApp extends StatelessWidget {
|
|||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primaryColor: Colors.greenAccent[400],
|
primaryColor: Colors.greenAccent[400],
|
||||||
primarySwatch: Colors.green,
|
primarySwatch: Colors.green,
|
||||||
buttonTheme: ButtonThemeData(
|
buttonTheme: const ButtonThemeData(
|
||||||
buttonColor: Colors.greenAccent[400],
|
buttonColor: Colors.green,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
home: Home(),
|
home: Home(),
|
||||||
|
14
pubspec.lock
14
pubspec.lock
@ -177,6 +177,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
infinite_scroll_pagination:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: infinite_scroll_pagination
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -406,6 +413,13 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.99"
|
||||||
|
sliver_tools:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sliver_tools
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.5"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -44,6 +44,7 @@ dependencies:
|
|||||||
youtube_explode_dart: ^1.10.8
|
youtube_explode_dart: ^1.10.8
|
||||||
mpv_dart:
|
mpv_dart:
|
||||||
path: ../mpv_dart
|
path: ../mpv_dart
|
||||||
|
infinite_scroll_pagination: ^3.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user