diff --git a/assets/spotube-logo.svg b/assets/spotube-logo.svg new file mode 100644 index 00000000..54cbb448 --- /dev/null +++ b/assets/spotube-logo.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/spotube_banner.svg b/assets/spotube_banner.svg index 76f4a7f8..040487e7 100644 --- a/assets/spotube_banner.svg +++ b/assets/spotube_banner.svg @@ -1,9 +1,70 @@ - - -Spotube \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Spotube + \ No newline at end of file diff --git a/lib/components/Home.dart b/lib/components/Home.dart index bd2b2ea2..463c76ba 100644 --- a/lib/components/Home.dart +++ b/lib/components/Home.dart @@ -33,8 +33,13 @@ class _HomeState extends State { if (clientId != null && clientSecret != null) { SpotifyApi spotifyApi = SpotifyApi( - SpotifyApiCredentials(clientId, clientSecret, - scopes: ["user-library-read", "user-library-modify"]), + SpotifyApiCredentials(clientId, clientSecret, scopes: [ + "user-library-read", + "user-library-modify", + "user-read-private", + "user-read-email", + "playlist-read-collaborative" + ]), ); SpotifyApiCredentials credentials = await spotifyApi.getCredentials(); if (credentials.accessToken?.isNotEmpty ?? false) { @@ -89,7 +94,7 @@ class _HomeState extends State { child: Row( children: [ Container( - color: Colors.grey.shade100, + color: Colors.blueGrey[50], constraints: const BoxConstraints(maxWidth: 230), child: Material( type: MaterialType.transparency, @@ -121,39 +126,50 @@ class _HomeState extends State { ), ), // user name & settings - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "User's name", - style: TextStyle(fontWeight: FontWeight.bold), - ), - IconButton( - icon: const Icon(Icons.settings_outlined), - onPressed: () {}), - ], - ), - ) + Consumer(builder: (context, data, widget) { + return FutureBuilder( + future: data.spotifyApi.me.get(), + builder: (context, snapshot) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + snapshot.data?.displayName ?? + "User's name", + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + IconButton( + icon: + const Icon(Icons.settings_outlined), + onPressed: () {}), + ], + ), + ); + }, + ); + }) ], ), ), ), // contents of the spotify - Consumer(builder: (_, data, __) { - return Expanded( - child: Scrollbar( - child: PagedListView( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return CategoryCard(item); - }, - )), + Expanded( + child: Scrollbar( + child: PagedListView( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return CategoryCard(item); + }, + ), ), - ); - }), + ), + ), ], ), ), diff --git a/lib/components/Login.dart b/lib/components/Login.dart index 19ba4255..88eb2a1b 100644 --- a/lib/components/Login.dart +++ b/lib/components/Login.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -51,43 +52,61 @@ class _LoginState extends State { return Consumer( builder: (context, authState, child) { return Scaffold( - body: Container( - padding: EdgeInsets.all(8.0), + body: Center( child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Text("Add your spotify credentials to get started", - style: Theme.of(context).textTheme.headline3), - Text( + style: Theme.of(context).textTheme.headline4), + const Text( "Don't worry, any of your credentials won't be collected or shared with anyone"), - TextField( - decoration: InputDecoration( - hintText: "Spotify Client ID", labelText: "ClientId"), - onChanged: (value) { - setState(() { - client_id = value; - }); - }, - ), - TextField( - decoration: InputDecoration( - hintText: "Spotify Client Secret", - labelText: "ClientSecret"), - onChanged: (value) { - setState(() { - client_secret = value; - }); - }, - ), - SizedBox( + const SizedBox( height: 10, ), - MaterialButton( - color: Theme.of(context).buttonColor, - onPressed: () { - handleLogin(authState); - }, - child: Text("Submit"), - ) + Container( + constraints: const BoxConstraints( + maxWidth: 400, + ), + child: Column( + children: [ + TextField( + decoration: const InputDecoration( + hintText: "Spotify Client ID", + label: Text("ClientID"), + ), + onChanged: (value) { + setState(() { + client_id = value; + }); + }, + ), + const SizedBox( + height: 10, + ), + TextField( + decoration: const InputDecoration( + hintText: "Spotify Client Secret", + label: Text("Client Secret"), + ), + onChanged: (value) { + setState(() { + client_secret = value; + }); + }, + ), + const SizedBox( + height: 10, + ), + ElevatedButton( + onPressed: () { + handleLogin(authState); + }, + child: const Text("Submit"), + ) + ], + ), + ), ], ), ), diff --git a/lib/components/Player.dart b/lib/components/Player.dart index b4161e1e..44585420 100644 --- a/lib/components/Player.dart +++ b/lib/components/Player.dart @@ -115,22 +115,27 @@ class _PlayerState extends State { } Future playPlaylist(CurrentPlaylist playlist) async { - if (player.isRunning() && playlist.id != _currentPlaylistId) { - var playlistPath = "/tmp/playlist-${playlist.id}.txt"; - File file = File(playlistPath); - var newPlaylist = playlistToStr(playlist); + try { + if (player.isRunning() && playlist.id != _currentPlaylistId) { + var playlistPath = "/tmp/playlist-${playlist.id}.txt"; + File file = File(playlistPath); + var newPlaylist = playlistToStr(playlist); - if (!await file.exists()) { - await file.create(); + if (!await file.exists()) { + await file.create(); + } + + await file.writeAsString(newPlaylist); + + await player.loadPlaylist(playlistPath); + setState(() { + _currentPlaylistId = playlist.id; + _shuffled = false; + }); } - - await file.writeAsString(newPlaylist); - - await player.loadPlaylist(playlistPath); - setState(() { - _currentPlaylistId = playlist.id; - _shuffled = false; - }); + } catch (e, stackTrace) { + print("[Player]: $e"); + print(stackTrace); } } diff --git a/lib/components/PlaylistCard.dart b/lib/components/PlaylistCard.dart index fc0ef33c..fbd1ea20 100644 --- a/lib/components/PlaylistCard.dart +++ b/lib/components/PlaylistCard.dart @@ -1,11 +1,14 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/PlaylistView.dart'; +import 'package:spotube/provider/Playback.dart'; +import 'package:spotube/provider/SpotifyDI.dart'; class PlaylistCard extends StatefulWidget { - PlaylistSimple playlist; - PlaylistCard(this.playlist); + final PlaylistSimple playlist; + const PlaylistCard(this.playlist, {Key? key}) : super(key: key); @override _PlaylistCardState createState() => _PlaylistCardState(); } @@ -22,7 +25,7 @@ class _PlaylistCardState extends State { )); }, child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 200), + constraints: const BoxConstraints(maxWidth: 200), child: Ink( decoration: BoxDecoration( color: Colors.white, @@ -30,7 +33,7 @@ class _PlaylistCardState extends State { boxShadow: [ BoxShadow( blurRadius: 10, - offset: Offset(0, 3), + offset: const Offset(0, 3), spreadRadius: 5, color: Colors.grey.shade300, ) @@ -40,21 +43,66 @@ class _PlaylistCardState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ // thumbnail of the playlist - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: CachedNetworkImage( - imageUrl: widget.playlist.images![0].url!), + Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: widget.playlist.images![0].url!), + ), + Positioned.directional( + textDirection: TextDirection.ltr, + bottom: 10, + end: 5, + child: Builder(builder: (context) { + Playback playback = context.watch(); + SpotifyDI data = context.watch(); + bool isPlaylistPlaying = playback.currentPlaylist != + null && + playback.currentPlaylist!.id == widget.playlist.id; + return ElevatedButton( + onPressed: () async { + if (isPlaylistPlaying) return; + + List tracks = (await data.spotifyApi.playlists + .getTracksByPlaylistId(widget.playlist.id!) + .all()) + .toList(); + + playback.setCurrentPlaylist = CurrentPlaylist( + tracks: tracks, + id: widget.playlist.id!, + name: widget.playlist.name!, + thumbnail: widget.playlist.images!.first.url!, + ); + }, + child: Icon( + isPlaylistPlaying + ? Icons.pause_rounded + : Icons.play_arrow_rounded, + ), + style: ButtonStyle( + shape: MaterialStateProperty.all( + const CircleBorder(), + ), + padding: MaterialStateProperty.all( + const EdgeInsets.all(16), + ), + ), + ); + }), + ) + ], ), - SizedBox(height: 5), + const SizedBox(height: 5), Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.playlist.name!, - style: TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), diff --git a/lib/components/PlaylistGenreView.dart b/lib/components/PlaylistGenreView.dart index 4714469c..9cffa10e 100644 --- a/lib/components/PlaylistGenreView.dart +++ b/lib/components/PlaylistGenreView.dart @@ -45,7 +45,7 @@ class _PlaylistGenreViewState extends State { return const Center(child: Text("Error occurred")); } if (!snapshot.hasData) { - return const Center(child: Text("Loading..")); + return const CircularProgressIndicator.adaptive(); } return Wrap( children: snapshot.data! diff --git a/lib/components/PlaylistView.dart b/lib/components/PlaylistView.dart index dd91906c..1872f161 100644 --- a/lib/components/PlaylistView.dart +++ b/lib/components/PlaylistView.dart @@ -15,7 +15,6 @@ class PlaylistView extends StatefulWidget { class _PlaylistViewState extends State { @override Widget build(BuildContext context) { - Playback playback = context.read(); return Consumer(builder: (_, data, __) { return Scaffold( body: FutureBuilder>( @@ -70,7 +69,7 @@ class _PlaylistViewState extends State { snapshot.hasError ? const Center(child: Text("Error occurred")) : !snapshot.hasData - ? const Center(child: Text("Loading..")) + ? const CircularProgressIndicator.adaptive() : Expanded( child: Scrollbar( isAlwaysShown: true, diff --git a/lib/main.dart b/lib/main.dart index 192aae66..80616a71 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,13 +27,38 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( - primaryColor: Colors.greenAccent[400], - primarySwatch: Colors.green, - buttonTheme: const ButtonThemeData( - buttonColor: Colors.green, - ), - ), - home: Home(), + primaryColor: Colors.greenAccent[400], + primarySwatch: Colors.green, + buttonTheme: const ButtonThemeData( + buttonColor: Colors.green, + ), + textTheme: TextTheme( + bodyText1: TextStyle(color: Colors.grey[850]), + headline1: TextStyle(color: Colors.grey[850]), + headline2: TextStyle(color: Colors.grey[850]), + headline3: TextStyle(color: Colors.grey[850]), + headline4: TextStyle(color: Colors.grey[850]), + headline5: TextStyle(color: Colors.grey[850]), + headline6: TextStyle(color: Colors.grey[850]), + ), + listTileTheme: ListTileThemeData( + iconColor: Colors.grey[850], + horizontalTitleGap: 0, + ), + inputDecorationTheme: InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.green[400]!, + width: 2.0, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey[800]!, + ), + ), + )), + home: const Home(), ), ); } diff --git a/lib/models/sideBarTiles.dart b/lib/models/sideBarTiles.dart index 2d553371..c4275788 100644 --- a/lib/models/sideBarTiles.dart +++ b/lib/models/sideBarTiles.dart @@ -7,7 +7,7 @@ class SideBarTiles { } List sidebarTileList = [ - SideBarTiles(icon: Icons.home_filled, title: "Browse"), - SideBarTiles(icon: Icons.search, title: "Search"), + SideBarTiles(icon: Icons.home_rounded, title: "Browse"), + SideBarTiles(icon: Icons.search_rounded, title: "Search"), SideBarTiles(icon: Icons.library_books_rounded, title: "Library"), ];