mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
PageWindowTitlebar now compitable with scaffold's appBar
AlbumCard, ArtistCard, ArtistAlbumCard & ArtistProfile added UserArtist finished macos build artifacts upload path corrected
This commit is contained in:
parent
46b652788f
commit
ef121c3613
10
.github/workflows/flutter-build.yml
vendored
10
.github/workflows/flutter-build.yml
vendored
@ -60,12 +60,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: true
|
cache: true
|
||||||
- run: flutter config --enable-macos-desktop
|
- run: flutter config --enable-macos-desktop
|
||||||
- run: flutter create .
|
- run: flutter build macos
|
||||||
- run: flutter pub get
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Macos Source Code
|
name: Spotube-Macos-Bundle
|
||||||
path: ./
|
path: build/macos/Build/Release/Products/spotube.app
|
||||||
- run: flutter build macos
|
|
||||||
- run: brew install tree
|
|
||||||
- run: tree ./
|
|
||||||
|
27
lib/components/Album/AlbumCard.dart
Normal file
27
lib/components/Album/AlbumCard.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/Shared/PlaybuttonCard.dart';
|
||||||
|
import 'package:spotube/helpers/artist-to-string.dart';
|
||||||
|
import 'package:spotube/provider/Playback.dart';
|
||||||
|
|
||||||
|
class AlbumCard extends StatelessWidget {
|
||||||
|
final Album album;
|
||||||
|
const AlbumCard(this.album, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Playback playback = context.watch<Playback>();
|
||||||
|
|
||||||
|
return PlaybuttonCard(
|
||||||
|
imageUrl: album.images!.first.url!,
|
||||||
|
isPlaying: playback.currentPlaylist?.id != null &&
|
||||||
|
playback.currentPlaylist?.id == album.id,
|
||||||
|
title: album.name!,
|
||||||
|
description:
|
||||||
|
"Alubm • ${artistsToString<ArtistSimple>(album.artists ?? [])}",
|
||||||
|
onTap: () {},
|
||||||
|
onPlaybuttonPressed: () => {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
92
lib/components/Artist/ArtistAlbumView.dart
Normal file
92
lib/components/Artist/ArtistAlbumView.dart
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import 'package:flutter/material.dart' hide Page;
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/Album/AlbumCard.dart';
|
||||||
|
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
|
class ArtistAlbumView extends StatefulWidget {
|
||||||
|
final String artistId;
|
||||||
|
final String artistName;
|
||||||
|
const ArtistAlbumView(
|
||||||
|
this.artistId,
|
||||||
|
this.artistName, {
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ArtistAlbumView> createState() => _ArtistAlbumViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArtistAlbumViewState extends State<ArtistAlbumView> {
|
||||||
|
final PagingController<int, Album> _pagingController =
|
||||||
|
PagingController<int, Album>(firstPageKey: 0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_pagingController.addPageRequestListener((pageKey) {
|
||||||
|
_fetchPage(pageKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pagingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_fetchPage(int pageKey) async {
|
||||||
|
try {
|
||||||
|
SpotifyDI data = context.read<SpotifyDI>();
|
||||||
|
Page<Album> albums = await data.spotifyApi.artists
|
||||||
|
.albums(widget.artistId)
|
||||||
|
.getPage(8, pageKey);
|
||||||
|
|
||||||
|
var items = albums.items!.toList();
|
||||||
|
|
||||||
|
if (albums.isLast && albums.items != null) {
|
||||||
|
_pagingController.appendLastPage(items);
|
||||||
|
} else if (albums.items != null) {
|
||||||
|
_pagingController.appendPage(items, albums.nextOffset);
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
print("[ArtistAlbumView._fetchPage] $e");
|
||||||
|
print(stack);
|
||||||
|
_pagingController.error = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: const PageWindowTitleBar(leading: BackButton()),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.artistName,
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PagedGridView(
|
||||||
|
pagingController: _pagingController,
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 260,
|
||||||
|
childAspectRatio: 9 / 13,
|
||||||
|
crossAxisSpacing: 20,
|
||||||
|
mainAxisSpacing: 20,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||||
|
itemBuilder: (context, item, index) {
|
||||||
|
return AlbumCard(item);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/Album/AlbumCard.dart';
|
||||||
|
import 'package:spotube/components/Artist/ArtistAlbumView.dart';
|
||||||
|
import 'package:spotube/components/Artist/ArtistCard.dart';
|
||||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||||
import 'package:spotube/helpers/readable-number.dart';
|
import 'package:spotube/helpers/readable-number.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
@ -20,30 +23,31 @@ class _ArtistProfileState extends State<ArtistProfile> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SpotifyApi spotify = context.watch<SpotifyDI>().spotifyApi;
|
SpotifyApi spotify = context.watch<SpotifyDI>().spotifyApi;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
appBar: const PageWindowTitleBar(
|
||||||
|
leading: BackButton(),
|
||||||
|
),
|
||||||
body: FutureBuilder<Artist>(
|
body: FutureBuilder<Artist>(
|
||||||
future: spotify.artists.get(widget.artistId),
|
future: spotify.artists.get(widget.artistId),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const Center(child: CircularProgressIndicator.adaptive());
|
return const Center(child: CircularProgressIndicator.adaptive());
|
||||||
}
|
}
|
||||||
return Column(
|
return SingleChildScrollView(
|
||||||
children: [
|
|
||||||
const PageWindowTitleBar(
|
|
||||||
leading: BackButton(),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: Row(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 50),
|
const SizedBox(width: 50),
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
maxRadius: 250,
|
radius: MediaQuery.of(context).size.width * 0.18,
|
||||||
minRadius: 100,
|
|
||||||
backgroundImage: CachedNetworkImageProvider(
|
backgroundImage: CachedNetworkImageProvider(
|
||||||
snapshot.data!.images!.first.url!,
|
snapshot.data!.images!.first.url!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Flexible(
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -94,7 +98,8 @@ class _ArtistProfileState extends State<ArtistProfile> {
|
|||||||
text: snapshot
|
text: snapshot
|
||||||
.data?.externalUrls?.spotify),
|
.data?.externalUrls?.spotify),
|
||||||
).then((val) {
|
).then((val) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
width: 300,
|
width: 300,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
@ -112,10 +117,81 @@ class _ArtistProfileState extends State<ArtistProfile> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 50),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Albums",
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text("See All"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) => ArtistAlbumView(
|
||||||
|
widget.artistId,
|
||||||
|
snapshot.data?.name ?? "KRTX",
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FutureBuilder<List<Album>>(
|
||||||
|
future: spotify.artists
|
||||||
|
.albums(snapshot.data!.id!)
|
||||||
|
.getPage(5, 0)
|
||||||
|
.then((al) => al.items?.toList() ?? []),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator.adaptive());
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 20,
|
||||||
|
runSpacing: 20,
|
||||||
|
children: snapshot.data
|
||||||
|
?.map((album) => AlbumCard(album))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"Fans also likes",
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FutureBuilder<Iterable<Artist>>(
|
||||||
|
future: spotify.artists.getRelatedArtists(widget.artistId),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator.adaptive());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 20,
|
||||||
|
runSpacing: 20,
|
||||||
|
children: snapshot.data
|
||||||
|
?.map((artist) => ArtistCard(artist))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -13,8 +13,8 @@ class UserArtists extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _UserArtistsState extends State<UserArtists> {
|
class _UserArtistsState extends State<UserArtists> {
|
||||||
final PagingController<int, Artist> _pagingController =
|
final PagingController<String, Artist> _pagingController =
|
||||||
PagingController(firstPageKey: 0);
|
PagingController(firstPageKey: "");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -23,22 +23,16 @@ class _UserArtistsState extends State<UserArtists> {
|
|||||||
_pagingController.addPageRequestListener((pageKey) async {
|
_pagingController.addPageRequestListener((pageKey) async {
|
||||||
try {
|
try {
|
||||||
SpotifyDI data = context.read<SpotifyDI>();
|
SpotifyDI data = context.read<SpotifyDI>();
|
||||||
var offset =
|
|
||||||
_pagingController.value.itemList?.elementAt(pageKey).id ?? "";
|
|
||||||
CursorPage<Artist> artists = await data.spotifyApi.me
|
CursorPage<Artist> artists = await data.spotifyApi.me
|
||||||
.following(FollowingType.artist)
|
.following(FollowingType.artist)
|
||||||
.getPage(15, offset);
|
.getPage(15, pageKey);
|
||||||
|
|
||||||
var items = artists.items!.toList();
|
var items = artists.items!.toList();
|
||||||
|
|
||||||
if (artists.items != null && items.length < 15) {
|
if (artists.items != null && items.length < 15) {
|
||||||
_pagingController.appendLastPage(items);
|
_pagingController.appendLastPage(items);
|
||||||
} else if (artists.items != null) {
|
} else if (artists.items != null) {
|
||||||
var yetToBe = [
|
_pagingController.appendPage(items, items.last.id);
|
||||||
...(_pagingController.value.itemList ?? []),
|
|
||||||
...items
|
|
||||||
];
|
|
||||||
_pagingController.appendPage(items, yetToBe.length - 1);
|
|
||||||
}
|
}
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
_pagingController.error = e;
|
_pagingController.error = e;
|
||||||
@ -49,6 +43,12 @@ class _UserArtistsState extends State<UserArtists> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pagingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SpotifyDI data = context.watch<SpotifyDI>();
|
SpotifyDI data = context.watch<SpotifyDI>();
|
||||||
|
@ -34,11 +34,8 @@ class _LoginState extends State<Login> {
|
|||||||
return Consumer<Auth>(
|
return Consumer<Auth>(
|
||||||
builder: (context, authState, child) {
|
builder: (context, authState, child) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Column(
|
appBar: const PageWindowTitleBar(),
|
||||||
children: [
|
body: Center(
|
||||||
const PageWindowTitleBar(),
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@ -101,9 +98,6 @@ class _LoginState extends State<Login> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Settings.dart';
|
import 'package:spotube/components/Settings.dart';
|
||||||
import 'package:spotube/helpers/artist-to-string.dart';
|
import 'package:spotube/helpers/artist-to-string.dart';
|
||||||
import 'package:spotube/helpers/getLyrics.dart';
|
import 'package:spotube/helpers/getLyrics.dart';
|
||||||
@ -29,7 +30,7 @@ class _LyricsState extends State<Lyrics> {
|
|||||||
playback.currentTrack!.id != _lyrics["id"]) {
|
playback.currentTrack!.id != _lyrics["id"]) {
|
||||||
getLyrics(
|
getLyrics(
|
||||||
playback.currentTrack!.name!,
|
playback.currentTrack!.name!,
|
||||||
artistsToString(playback.currentTrack!.artists ?? []),
|
artistsToString<Artist>(playback.currentTrack!.artists ?? []),
|
||||||
apiKey: userPreferences.geniusAccessToken!,
|
apiKey: userPreferences.geniusAccessToken!,
|
||||||
optimizeQuery: true,
|
optimizeQuery: true,
|
||||||
).then((lyrics) {
|
).then((lyrics) {
|
||||||
@ -90,7 +91,7 @@ class _LyricsState extends State<Lyrics> {
|
|||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
artistsToString(playback.currentTrack?.artists ?? []),
|
artistsToString<Artist>(playback.currentTrack?.artists ?? []),
|
||||||
style: Theme.of(context).textTheme.headline5,
|
style: Theme.of(context).textTheme.headline5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -240,8 +240,8 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
playback.currentTrack?.name ?? "Not playing",
|
playback.currentTrack?.name ?? "Not playing",
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Text(
|
Text(artistsToString<Artist>(
|
||||||
artistsToString(playback.currentTrack?.artists ?? []))
|
playback.currentTrack?.artists ?? []))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistView.dart';
|
import 'package:spotube/components/Playlist/PlaylistView.dart';
|
||||||
|
import 'package:spotube/components/Shared/PlaybuttonCard.dart';
|
||||||
import 'package:spotube/provider/Playback.dart';
|
import 'package:spotube/provider/Playback.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
@ -16,7 +16,13 @@ class PlaylistCard extends StatefulWidget {
|
|||||||
class _PlaylistCardState extends State<PlaylistCard> {
|
class _PlaylistCardState extends State<PlaylistCard> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
Playback playback = context.watch<Playback>();
|
||||||
|
bool isPlaylistPlaying = playback.currentPlaylist != null &&
|
||||||
|
playback.currentPlaylist!.id == widget.playlist.id;
|
||||||
|
return PlaybuttonCard(
|
||||||
|
title: widget.playlist.name!,
|
||||||
|
imageUrl: widget.playlist.images![0].url!,
|
||||||
|
isPlaying: isPlaylistPlaying,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@ -24,61 +30,17 @@ class _PlaylistCardState extends State<PlaylistCard> {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
child: ConstrainedBox(
|
onPlaybuttonPressed: () async {
|
||||||
constraints: const BoxConstraints(maxWidth: 200),
|
|
||||||
child: Ink(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).backgroundColor,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 3),
|
|
||||||
spreadRadius: 5,
|
|
||||||
color: Theme.of(context).shadowColor)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
// thumbnail of the playlist
|
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
imageUrl: widget.playlist.images![0].url!,
|
|
||||||
progressIndicatorBuilder: (context, url, progress) {
|
|
||||||
return CircularProgressIndicator.adaptive(
|
|
||||||
value: progress.progress,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned.directional(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
bottom: 10,
|
|
||||||
end: 5,
|
|
||||||
child: Builder(builder: (context) {
|
|
||||||
Playback playback = context.watch<Playback>();
|
|
||||||
SpotifyDI data = context.watch<SpotifyDI>();
|
|
||||||
bool isPlaylistPlaying = playback.currentPlaylist !=
|
|
||||||
null &&
|
|
||||||
playback.currentPlaylist!.id == widget.playlist.id;
|
|
||||||
return ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
if (isPlaylistPlaying) return;
|
if (isPlaylistPlaying) return;
|
||||||
|
SpotifyDI data = context.read<SpotifyDI>();
|
||||||
|
|
||||||
List<Track> tracks =
|
List<Track> tracks = (widget.playlist.id != "user-liked-tracks"
|
||||||
(widget.playlist.id != "user-liked-tracks"
|
|
||||||
? await data.spotifyApi.playlists
|
? await data.spotifyApi.playlists
|
||||||
.getTracksByPlaylistId(
|
.getTracksByPlaylistId(widget.playlist.id!)
|
||||||
widget.playlist.id!)
|
|
||||||
.all()
|
.all()
|
||||||
: await data.spotifyApi.tracks.me.saved
|
: await data.spotifyApi.tracks.me.saved
|
||||||
.all()
|
.all()
|
||||||
.then((tracks) =>
|
.then((tracks) => tracks.map((e) => e.track!)))
|
||||||
tracks.map((e) => e.track!)))
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (tracks.isEmpty) return;
|
if (tracks.isEmpty) return;
|
||||||
@ -91,41 +53,6 @@ class _PlaylistCardState extends State<PlaylistCard> {
|
|||||||
);
|
);
|
||||||
playback.setCurrentTrack = tracks.first;
|
playback.setCurrentTrack = tracks.first;
|
||||||
},
|
},
|
||||||
child: Icon(
|
|
||||||
isPlaylistPlaying
|
|
||||||
? Icons.pause_rounded
|
|
||||||
: Icons.play_arrow_rounded,
|
|
||||||
),
|
|
||||||
style: ButtonStyle(
|
|
||||||
shape: MaterialStateProperty.all(
|
|
||||||
const CircleBorder(),
|
|
||||||
),
|
|
||||||
padding: MaterialStateProperty.all(
|
|
||||||
const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.playlist.name!,
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,11 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Column(
|
appBar: const PageWindowTitleBar(
|
||||||
children: [
|
|
||||||
const PageWindowTitleBar(
|
|
||||||
leading: BackButton(),
|
leading: BackButton(),
|
||||||
),
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.genreName,
|
widget.genreName,
|
||||||
style: Theme.of(context).textTheme.headline4,
|
style: Theme.of(context).textTheme.headline4,
|
||||||
@ -51,7 +51,8 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const CircularProgressIndicator.adaptive();
|
return const CircularProgressIndicator.adaptive();
|
||||||
}
|
}
|
||||||
return Wrap(
|
return Center(
|
||||||
|
child: Wrap(
|
||||||
children: snapshot.data!
|
children: snapshot.data!
|
||||||
.map(
|
.map(
|
||||||
(playlist) => Padding(
|
(playlist) => Padding(
|
||||||
@ -60,6 +61,7 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -40,17 +40,14 @@ class _SettingsState extends State<Settings> {
|
|||||||
UserPreferences preferences = context.watch<UserPreferences>();
|
UserPreferences preferences = context.watch<UserPreferences>();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Column(
|
appBar: PageWindowTitleBar(
|
||||||
children: [
|
|
||||||
PageWindowTitleBar(
|
|
||||||
leading: const BackButton(),
|
leading: const BackButton(),
|
||||||
center: Text(
|
center: Text(
|
||||||
"Settings",
|
"Settings",
|
||||||
style: Theme.of(context).textTheme.headline5,
|
style: Theme.of(context).textTheme.headline5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
body: Padding(
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -145,8 +142,6 @@ class _SettingsState extends State<Settings> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ class _DownloadTrackButtonState extends State<DownloadTrackButton> {
|
|||||||
String downloadFolder = path.join(
|
String downloadFolder = path.join(
|
||||||
(await path_provider.getDownloadsDirectory())!.path, "Spotube");
|
(await path_provider.getDownloadsDirectory())!.path, "Spotube");
|
||||||
String fileName =
|
String fileName =
|
||||||
"${widget.track?.name} - ${artistsToString(widget.track?.artists ?? [])}.mp3";
|
"${widget.track?.name} - ${artistsToString<Artist>(widget.track?.artists ?? [])}.mp3";
|
||||||
File outputFile = File(path.join(downloadFolder, fileName));
|
File outputFile = File(path.join(downloadFolder, fileName));
|
||||||
if (!outputFile.existsSync()) {
|
if (!outputFile.existsSync()) {
|
||||||
outputFile.createSync(recursive: true);
|
outputFile.createSync(recursive: true);
|
||||||
|
@ -43,11 +43,15 @@ class TitleBarActionButtons extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PageWindowTitleBar extends StatelessWidget {
|
class PageWindowTitleBar extends StatelessWidget
|
||||||
|
implements PreferredSizeWidget {
|
||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
final Widget? center;
|
final Widget? center;
|
||||||
const PageWindowTitleBar({Key? key, this.leading, this.center})
|
const PageWindowTitleBar({Key? key, this.leading, this.center})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
@override
|
||||||
|
Size get preferredSize => Size.fromHeight(appWindow.titleBarHeight);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WindowTitleBarBox(
|
return WindowTitleBarBox(
|
||||||
|
110
lib/components/Shared/PlaybuttonCard.dart
Normal file
110
lib/components/Shared/PlaybuttonCard.dart
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PlaybuttonCard extends StatelessWidget {
|
||||||
|
final void Function()? onTap;
|
||||||
|
final void Function()? onPlaybuttonPressed;
|
||||||
|
final String? description;
|
||||||
|
final String imageUrl;
|
||||||
|
final bool isPlaying;
|
||||||
|
final String title;
|
||||||
|
const PlaybuttonCard({
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.isPlaying,
|
||||||
|
required this.title,
|
||||||
|
this.description,
|
||||||
|
this.onPlaybuttonPressed,
|
||||||
|
this.onTap,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
|
child: Ink(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
spreadRadius: 5,
|
||||||
|
color: Theme.of(context).shadowColor)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// thumbnail of the playlist
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
progressIndicatorBuilder: (context, url, progress) {
|
||||||
|
return CircularProgressIndicator.adaptive(
|
||||||
|
value: progress.progress,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned.directional(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
bottom: 10,
|
||||||
|
end: 5,
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: onPlaybuttonPressed,
|
||||||
|
child: Icon(
|
||||||
|
isPlaying
|
||||||
|
? Icons.pause_rounded
|
||||||
|
: Icons.play_arrow_rounded,
|
||||||
|
),
|
||||||
|
style: ButtonStyle(
|
||||||
|
shape: MaterialStateProperty.all(
|
||||||
|
const CircleBorder(),
|
||||||
|
),
|
||||||
|
padding: MaterialStateProperty.all(
|
||||||
|
const EdgeInsets.all(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
if (description != null) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
description!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Theme.of(context).textTheme.headline4?.color,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
|
||||||
String artistsToString(List<Artist> artists) {
|
String artistsToString<T extends ArtistSimple>(List<T> artists) {
|
||||||
return artists.map((e) => e.name?.replaceAll(",", " ")).join(", ");
|
return artists.map((e) => e.name?.replaceAll(",", " ")).join(", ");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user