Infinite Scorll added

CategoryCard no-title bug fix
shuffle support added
This commit is contained in:
Kingkor Roy Tirtho 2022-01-03 13:33:58 +06:00
parent 799e13c376
commit 0ef44709fa
10 changed files with 382 additions and 326 deletions

View File

@ -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,56 +16,57 @@ 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), child: Row(
child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
children: [ Text(
Text( widget.category.name ?? "Unknown",
widget.category.name ?? "Unknown", style: Theme.of(context).textTheme.headline5,
style: Theme.of(context).textTheme.headline5, ),
), TextButton(
TextButton( onPressed: () {
onPressed: () { Navigator.of(context).push(
Navigator.of(context).push( MaterialPageRoute(
MaterialPageRoute( builder: (context) {
builder: (context) { return PlaylistGenreView(
return PlaylistGenreView(widget.category.id!); widget.category.id!,
}, widget.category.name!,
), );
); },
}, ),
child: Text("See all"), );
) },
], child: const Text("See all"),
), )
],
), ),
Consumer<SpotifyDI>( ),
builder: (context, data, child) => Consumer<SpotifyDI>(
FutureBuilder<Page<PlaylistSimple>>( builder: (context, data, child) =>
future: data.spotifyApi.playlists FutureBuilder<Page<PlaylistSimple>>(
.getByCategoryId(widget.category.id!) future: data.spotifyApi.playlists
.getPage(4, 0), .getByCategoryId(widget.category.id!)
builder: (context, snapshot) { .getPage(4, 0),
if (snapshot.hasError) { builder: (context, snapshot) {
return Center(child: Text("Error occurred")); if (snapshot.hasError) {
} return const Center(child: Text("Error occurred"));
if (!snapshot.hasData) { }
return Center(child: Text("Loading..")); if (!snapshot.hasData) {
} return const Center(child: Text("Loading.."));
return Wrap( }
spacing: 20, return Wrap(
children: snapshot.data!.items! spacing: 20,
.map((playlist) => PlaylistCard(playlist)) children: snapshot.data!.items!
.toList(), .map((playlist) => PlaylistCard(playlist))
); .toList(),
}), );
) }),
], )
), ],
); );
} }
} }

View File

@ -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,101 +82,84 @@ 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( child: Row(
child: Row( children: [
children: [ Container(
Container( color: Colors.grey.shade100,
color: Colors.grey.shade100, constraints: const BoxConstraints(maxWidth: 230),
constraints: const BoxConstraints(maxWidth: 230), child: Material(
child: Material( type: MaterialType.transparency,
type: MaterialType.transparency, child: Column(
child: Column( children: [
children: [ Flexible(
Flexible( flex: 1,
flex: 1, // TabButtons
// TabButtons child: Column(
child: Column( children: [
children: [ ListTile(
ListTile( title: Text("Spotube",
title: Text("Spotube", style:
style: Theme.of(context) Theme.of(context).textTheme.headline4),
.textTheme leading:
.headline4), const Icon(Icons.miscellaneous_services),
leading: ),
const Icon(Icons.miscellaneous_services), const SizedBox(height: 20),
), ...sidebarTileList
const SizedBox(height: 20), .map(
...sidebarTileList (sidebarTile) => ListTile(
.map( title: Text(sidebarTile.title),
(sidebarTile) => ListTile( leading: Icon(sidebarTile.icon),
title: Text(sidebarTile.title), onTap: () {},
leading: Icon(sidebarTile.icon), ),
onTap: () {}, )
), .toList(),
) ],
.toList(),
],
),
), ),
// user name & settings ),
Padding( // user name & settings
padding: const EdgeInsets.all(8.0), Padding(
child: Row( padding: const EdgeInsets.all(8.0),
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
const Text( children: [
"User's name", const Text(
style: TextStyle(fontWeight: FontWeight.bold), "User's name",
), style: TextStyle(fontWeight: FontWeight.bold),
IconButton( ),
icon: const Icon(Icons.settings_outlined), IconButton(
onPressed: () {}), icon: const Icon(Icons.settings_outlined),
], onPressed: () {}),
), ],
) ),
], )
), ],
), ),
), ),
// contents of the spotify ),
Consumer<SpotifyDI>(builder: (_, data, __) { // contents of the spotify
return FutureBuilder<Page<Category>>( Consumer<SpotifyDI>(builder: (_, data, __) {
future: data.spotifyApi.categories return Expanded(
.list(country: "US") child: Scrollbar(
.getPage(10, 0), child: PagedListView(
builder: (context, snapshot) { pagingController: _pagingController,
if (snapshot.hasError) { builderDelegate: PagedChildBuilderDelegate<Category>(
return const Center(child: Text("Error occured")); itemBuilder: (context, item, index) {
} return CategoryCard(item);
if (!snapshot.hasData) { },
return const Center(child: Text("Loading")); )),
} ),
List<Category> categories = );
snapshot.data!.items!.toList(); }),
return Expanded( ],
child: Scrollbar(
isAlwaysShown: true,
child: ListView.builder(
itemCount: categories.length,
itemBuilder: (context, index) {
return CategoryCard(categories[index]);
},
),
),
);
});
}),
],
),
), ),
// player itself ),
const player.Player() // player itself
], const player.Player()
), ],
), ),
); );
} }

View File

@ -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,7 +46,9 @@ class _PlayerState extends State<Player> {
_volume = volume / 100; _volume = volume / 100;
}); });
} catch (e) { } catch (e) {
print("[PLAYER]: $e"); if (kDebugMode) {
print("[PLAYER]: $e");
}
} }
})(); })();
@ -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 props = (data["value"] as String).split("-"); var containsYtdl = (data["value"] as String).contains("ytsearch:");
setState(() { if (containsYtdl) {
_isPlaying = true; var props = (data["value"] as String).split("-");
_mediaTitle = props.last.replaceAll( var mediaTitle = props.last.trim();
RegExp( var mediaArtists = props.first.split("ytsearch:").last.trim();
"(official|video|lyric|[(){}\\[\\]\\|])", setState(() {
caseSensitive: false, _isPlaying = true;
), });
"",
); var matchedTracks = playback.currentPlaylist?.tracks.where(
_mediaArtists = props.first; (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();
}, },

View File

@ -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),

View File

@ -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,53 +17,51 @@ 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: [ const BackButton(),
BackButton(), // genre name
// genre name Expanded(
Expanded( child: Text(
child: Text( widget.genreName,
"Genre Name", style: Theme.of(context).textTheme.headline4,
style: Theme.of(context).textTheme.headline4, textAlign: TextAlign.center,
textAlign: TextAlign.center,
),
),
],
),
Consumer<SpotifyDI>(
builder: (context, data, child) => Expanded(
child: SingleChildScrollView(
child: FutureBuilder<Iterable<PlaylistSimple>>(
future: data.spotifyApi.playlists
.getByCategoryId(widget.genre_id)
.all(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text("Error occurred"));
}
if (!snapshot.hasData) {
return Center(child: Text("Loading.."));
}
return Wrap(
children: snapshot.data!
.map(
(playlist) => Padding(
padding: const EdgeInsets.all(8.0),
child: PlaylistCard(playlist),
),
)
.toList(),
);
}),
), ),
), ),
) ],
], ),
), Consumer<SpotifyDI>(
builder: (context, data, child) => Expanded(
child: SingleChildScrollView(
child: FutureBuilder<Iterable<PlaylistSimple>>(
future: data.spotifyApi.playlists
.getByCategoryId(widget.genreId)
.all(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text("Error occurred"));
}
if (!snapshot.hasData) {
return const Center(child: Text("Loading.."));
}
return Wrap(
children: snapshot.data!
.map(
(playlist) => Padding(
padding: const EdgeInsets.all(8.0),
child: PlaylistCard(playlist),
),
)
.toList(),
);
}),
),
),
)
],
), ),
); );
} }

View File

@ -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,87 +18,99 @@ 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) { List<Track> tracks = snapshot.data?.toList() ?? [];
if (snapshot.hasError) { return Column(
return const Center(child: const Text("Error occurred")); children: [
} Row(
if (!snapshot.hasData) { children: [
return const Center(child: const Text("Loading..")); // nav back
} const BackButton(),
List<Track> tracks = snapshot.data!.toList(); // heart playlist
return Column( IconButton(
children: [ icon: const Icon(Icons.favorite_outline_rounded),
Row( onPressed: () {},
children: [
// nav back
const BackButton(),
// heart playlist
IconButton(
icon: const Icon(Icons.favorite_outline_rounded),
onPressed: () {},
),
// play playlist
IconButton(
icon: const Icon(Icons.play_arrow_rounded),
onPressed: () {
playback.setCurrentPlaylist = CurrentPlaylist(
tracks: tracks,
id: widget.playlist.id!,
name: widget.playlist.name!,
thumbnail: widget.playlist.images![0].url!,
);
},
),
],
),
Center(
child: Text(widget.playlist.name!,
style: Theme.of(context).textTheme.headline4),
),
Expanded(
child: Scrollbar(
isAlwaysShown: true,
child: ListView.builder(
itemCount: tracks.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Column(
children: [
TrackButton(
index: "#",
trackName: "Title",
artists: ["Artist"],
album: "Album",
playback_time: "Time"),
const Divider()
],
);
}
Track track = tracks[index - 1];
return TrackButton(
index: (index - 1).toString(),
thumbnail_url: track.album?.images?.last.url ??
"https://i.scdn.co/image/ab67616d00001e02b993cba8ff7d0a8e9ee18d46",
trackName: track.name!,
artists:
track.artists!.map((e) => e.name!).toList(),
album: track.album!.name!,
playback_time:
track.duration!.inMinutes.toString(),
onTap: () {},
);
}),
), ),
), // play playlist
], Consumer<Playback>(builder: (context, playback, widget) {
); var isPlaylistPlaying = playback.currentPlaylist?.id ==
}), this.widget.playlist.id;
), return IconButton(
icon: Icon(
isPlaylistPlaying
? Icons.stop_rounded
: 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(
child: Text(widget.playlist.name!,
style: Theme.of(context).textTheme.headline4),
),
snapshot.hasError
? const Center(child: Text("Error occurred"))
: !snapshot.hasData
? const Center(child: Text("Loading.."))
: Expanded(
child: Scrollbar(
isAlwaysShown: true,
child: ListView.builder(
itemCount: tracks.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Column(
children: [
TrackButton(
index: "#",
trackName: "Title",
artists: ["Artist"],
album: "Album",
playback_time: "Time"),
const Divider()
],
);
}
Track track = tracks[index - 1];
return TrackButton(
index: (index - 1).toString(),
thumbnail_url: track
.album?.images?.last.url ??
"https://i.scdn.co/image/ab67616d00001e02b993cba8ff7d0a8e9ee18d46",
trackName: track.name!,
artists: track.artists!
.map((e) => e.name!)
.toList(),
album: track.album!.name!,
playback_time: track.duration!.inMinutes
.toString(),
onTap: () {},
);
}),
),
),
],
);
}),
); );
}); });
} }

View File

@ -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)
], ],
), ),

View File

@ -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(),

View File

@ -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:

View File

@ -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: