mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
Working Sidebar added
Unauthorized error fix Settings Page (initial) Redesinged PlaylistView with Table
This commit is contained in:
parent
809731f441
commit
76d0538f96
BIN
assets/spotube-logo.png
Normal file
BIN
assets/spotube-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
@ -1,15 +1,26 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart' hide Page;
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotify/spotify.dart' hide Image;
|
||||
import 'package:spotube/components/CategoryCard.dart';
|
||||
import 'package:spotube/components/Login.dart';
|
||||
import 'package:spotube/components/Player.dart' as player;
|
||||
import 'package:spotube/components/Settings.dart';
|
||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||
import 'package:spotube/models/sideBarTiles.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/provider/SpotifyDI.dart';
|
||||
|
||||
List<String> spotifyScopes = [
|
||||
"user-library-read",
|
||||
"user-library-modify",
|
||||
"user-read-private",
|
||||
"user-read-email",
|
||||
"playlist-read-collaborative"
|
||||
];
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
const Home({Key? key}) : super(key: key);
|
||||
|
||||
@ -21,6 +32,8 @@ class _HomeState extends State<Home> {
|
||||
final PagingController<int, Category> _pagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -28,24 +41,36 @@ class _HomeState extends State<Home> {
|
||||
try {
|
||||
Auth authProvider = context.read<Auth>();
|
||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||
String? clientId = localStorage.getString('client_id');
|
||||
String? clientSecret = localStorage.getString('client_secret');
|
||||
var clientId = localStorage.getString(LocalStorageKeys.clientId);
|
||||
var clientSecret =
|
||||
localStorage.getString(LocalStorageKeys.clientSecret);
|
||||
var accessToken = localStorage.getString(LocalStorageKeys.accessToken);
|
||||
var refreshToken =
|
||||
localStorage.getString(LocalStorageKeys.refreshToken);
|
||||
var expirationStr = localStorage.getString(LocalStorageKeys.expiration);
|
||||
var expiration =
|
||||
expirationStr != null ? DateTime.parse(expirationStr) : null;
|
||||
|
||||
if (clientId != null && clientSecret != null) {
|
||||
SpotifyApi spotifyApi = SpotifyApi(
|
||||
SpotifyApiCredentials(clientId, clientSecret, scopes: [
|
||||
"user-library-read",
|
||||
"user-library-modify",
|
||||
"user-read-private",
|
||||
"user-read-email",
|
||||
"playlist-read-collaborative"
|
||||
]),
|
||||
SpotifyApiCredentials(
|
||||
clientId,
|
||||
clientSecret,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
expiration: expiration,
|
||||
scopes: spotifyScopes,
|
||||
),
|
||||
);
|
||||
SpotifyApiCredentials credentials = await spotifyApi.getCredentials();
|
||||
if (credentials.accessToken?.isNotEmpty ?? false) {
|
||||
authProvider.setAuthState(
|
||||
clientId: credentials.clientId,
|
||||
clientSecret: credentials.clientSecret,
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
accessToken:
|
||||
credentials.accessToken, // accessToken can be new/refreshed
|
||||
refreshToken: refreshToken,
|
||||
expiration: credentials.expiration,
|
||||
isLoggedIn: true,
|
||||
);
|
||||
}
|
||||
@ -89,87 +114,98 @@ class _HomeState extends State<Home> {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
// Side Tab Bar
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
color: Colors.blueGrey[50],
|
||||
constraints: const BoxConstraints(maxWidth: 230),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
// TabButtons
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text("Spotube",
|
||||
style:
|
||||
Theme.of(context).textTheme.headline4),
|
||||
leading:
|
||||
const Icon(Icons.miscellaneous_services),
|
||||
NavigationRail(
|
||||
backgroundColor: Colors.blueGrey[50],
|
||||
destinations: sidebarTileList
|
||||
.map((e) => NavigationRailDestination(
|
||||
icon: Icon(e.icon),
|
||||
label: Text(
|
||||
e.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
...sidebarTileList
|
||||
.map(
|
||||
(sidebarTile) => ListTile(
|
||||
title: Text(sidebarTile.title),
|
||||
leading: Icon(sidebarTile.icon),
|
||||
onTap: () {},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
selectedIndex: _selectedIndex,
|
||||
onDestinationSelected: (value) => setState(() {
|
||||
_selectedIndex = value;
|
||||
}),
|
||||
extended: true,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(left: 15),
|
||||
child: Row(children: [
|
||||
Image.asset(
|
||||
"assets/spotube-logo.png",
|
||||
height: 50,
|
||||
width: 50,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text("Spotube",
|
||||
style: Theme.of(context).textTheme.headline4),
|
||||
]),
|
||||
),
|
||||
trailing:
|
||||
Consumer<SpotifyDI>(builder: (context, data, widget) {
|
||||
return FutureBuilder<User>(
|
||||
future: data.spotifyApi.me.get(),
|
||||
builder: (context, snapshot) {
|
||||
var avatarImg = snapshot.data?.images?.last.url;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (avatarImg != null)
|
||||
CircleAvatar(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: avatarImg,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
snapshot.data?.displayName ?? "User's name",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return const Settings();
|
||||
},
|
||||
));
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
// user name & settings
|
||||
Consumer<SpotifyDI>(builder: (context, data, widget) {
|
||||
return FutureBuilder<User>(
|
||||
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
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: PagedListView(
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Category>(
|
||||
itemBuilder: (context, item, index) {
|
||||
return CategoryCard(item);
|
||||
},
|
||||
if (_selectedIndex == 0)
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: PagedListView(
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Category>(
|
||||
itemBuilder: (context, item, index) {
|
||||
return CategoryCard(item);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// player itself
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -2,8 +2,10 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotify/spotify.dart' hide Image;
|
||||
import 'package:spotube/components/Home.dart';
|
||||
import 'package:spotube/helpers/server_ipc.dart';
|
||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
|
||||
class Login extends StatefulWidget {
|
||||
@ -12,38 +14,65 @@ class Login extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _LoginState extends State<Login> {
|
||||
String client_id = "";
|
||||
String client_secret = "";
|
||||
String clientId = "";
|
||||
String clientSecret = "";
|
||||
bool _fieldError = false;
|
||||
String? accessToken;
|
||||
String? refreshToken;
|
||||
DateTime? expiration;
|
||||
|
||||
handleLogin(Auth authState) async {
|
||||
try {
|
||||
if (client_id == "" || client_secret == "") {
|
||||
if (clientId == "" || clientSecret == "") {
|
||||
return setState(() {
|
||||
_fieldError = true;
|
||||
});
|
||||
}
|
||||
final credentials = SpotifyApiCredentials(client_id, client_secret);
|
||||
final credentials = SpotifyApiCredentials(clientId, clientSecret);
|
||||
final grant = SpotifyApi.authorizationCodeGrant(credentials);
|
||||
final redirectUri = "http://localhost:4304/auth/spotify/callback";
|
||||
final scopes = ["user-library-read", "user-library-modify"];
|
||||
const redirectUri = "http://localhost:4304/auth/spotify/callback";
|
||||
|
||||
final authUri =
|
||||
grant.getAuthorizationUrl(Uri.parse(redirectUri), scopes: scopes);
|
||||
final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri),
|
||||
scopes: spotifyScopes);
|
||||
|
||||
final responseUri = await connectIpc(authUri.toString(), redirectUri);
|
||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||
if (responseUri != null) {
|
||||
final SpotifyApi spotify =
|
||||
SpotifyApi.fromAuthCodeGrant(grant, responseUri);
|
||||
var credentials = await spotify.getCredentials();
|
||||
if (credentials.accessToken != null) {
|
||||
accessToken = credentials.accessToken;
|
||||
await localStorage.setString(
|
||||
LocalStorageKeys.accessToken, credentials.accessToken!);
|
||||
}
|
||||
if (credentials.refreshToken != null) {
|
||||
refreshToken = credentials.refreshToken;
|
||||
await localStorage.setString(
|
||||
LocalStorageKeys.refreshToken, credentials.refreshToken!);
|
||||
}
|
||||
if (credentials.expiration != null) {
|
||||
expiration = credentials.expiration;
|
||||
await localStorage.setString(LocalStorageKeys.expiration,
|
||||
credentials.expiration?.toString() ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||
await localStorage.setString('client_id', client_id);
|
||||
await localStorage.setString('client_secret', client_secret);
|
||||
await localStorage.setString(LocalStorageKeys.clientId, clientId);
|
||||
await localStorage.setString(
|
||||
LocalStorageKeys.clientSecret,
|
||||
clientSecret,
|
||||
);
|
||||
authState.setAuthState(
|
||||
clientId: client_id, clientSecret: client_secret, isLoggedIn: true);
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
expiration: expiration,
|
||||
isLoggedIn: true,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
print("[Login.handleLogin] $e");
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +86,11 @@ class _LoginState extends State<Login> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
"assets/spotube-logo.png",
|
||||
width: 400,
|
||||
height: 400,
|
||||
),
|
||||
Text("Add your spotify credentials to get started",
|
||||
style: Theme.of(context).textTheme.headline4),
|
||||
const Text(
|
||||
@ -77,7 +111,7 @@ class _LoginState extends State<Login> {
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
client_id = value;
|
||||
clientId = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
@ -91,7 +125,7 @@ class _LoginState extends State<Login> {
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
client_secret = value;
|
||||
clientSecret = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mpv_dart/mpv_dart.dart';
|
||||
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
||||
|
||||
class PlayerControls extends StatefulWidget {
|
||||
final MPVPlayer player;
|
||||
@ -43,10 +44,6 @@ class _PlayerControlsState extends State<PlayerControls> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String zeroPadNumStr(int input) {
|
||||
return input < 10 ? "0$input" : input.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var totalDuration = Duration(seconds: widget.duration.toInt());
|
||||
|
@ -1,8 +1,11 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/TrackButton.dart';
|
||||
import 'package:spotube/provider/SpotifyDI.dart';
|
||||
|
||||
class PlaylistView extends StatefulWidget {
|
||||
@ -13,6 +16,70 @@ class PlaylistView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlaylistViewState extends State<PlaylistView> {
|
||||
List<TableRow> trackToTableRow(List<Track> tracks) {
|
||||
return tracks.asMap().entries.map((track) {
|
||||
var thumbnailUrl = track.value.album?.images?.last.url;
|
||||
var duration =
|
||||
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
||||
return (TableRow(
|
||||
children: [
|
||||
TableCell(
|
||||
child: Text(
|
||||
track.key.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
TableCell(
|
||||
child: Row(
|
||||
children: [
|
||||
if (thumbnailUrl != null)
|
||||
CachedNetworkImage(
|
||||
imageUrl: thumbnailUrl,
|
||||
maxHeightDiskCache: 40,
|
||||
maxWidthDiskCache: 40,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
track.value.name ?? "",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 17,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
(track.value.artists ?? [])
|
||||
.map((e) => e.name)
|
||||
.join(", "),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Text(
|
||||
track.value.album?.name ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Text(
|
||||
duration,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<SpotifyDI>(builder: (_, data, __) {
|
||||
@ -23,6 +90,8 @@ class _PlaylistViewState extends State<PlaylistView> {
|
||||
.all(),
|
||||
builder: (context, snapshot) {
|
||||
List<Track> tracks = snapshot.data?.toList() ?? [];
|
||||
TextStyle tableHeadStyle =
|
||||
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
@ -72,39 +141,49 @@ class _PlaylistViewState extends State<PlaylistView> {
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
: 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: () {},
|
||||
);
|
||||
}),
|
||||
child: ListView(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Table(
|
||||
columnWidths: const {
|
||||
0: FixedColumnWidth(40),
|
||||
1: FlexColumnWidth(),
|
||||
2: FlexColumnWidth(),
|
||||
3: FixedColumnWidth(40),
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
TableCell(
|
||||
child: Text(
|
||||
"#",
|
||||
textAlign: TextAlign.center,
|
||||
style: tableHeadStyle,
|
||||
)),
|
||||
TableCell(
|
||||
child: Text(
|
||||
"Title",
|
||||
style: tableHeadStyle,
|
||||
)),
|
||||
TableCell(
|
||||
child: Text(
|
||||
"Album",
|
||||
style: tableHeadStyle,
|
||||
)),
|
||||
TableCell(
|
||||
child: Text(
|
||||
"Time",
|
||||
textAlign: TextAlign.center,
|
||||
style: tableHeadStyle,
|
||||
)),
|
||||
],
|
||||
),
|
||||
...trackToTableRow(tracks),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
46
lib/components/Settings.dart
Normal file
46
lib/components/Settings.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
|
||||
class Settings extends StatefulWidget {
|
||||
const Settings({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SettingsState createState() => _SettingsState();
|
||||
}
|
||||
|
||||
class _SettingsState extends State<Settings> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
iconTheme: Theme.of(context).iconTheme,
|
||||
title: const Text(
|
||||
"Settings",
|
||||
),
|
||||
centerTitle: true,
|
||||
titleTextStyle: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Builder(builder: (context) {
|
||||
var auth = context.read<Auth>();
|
||||
return ElevatedButton(
|
||||
child: const Text("Logout"),
|
||||
onPressed: () async {
|
||||
SharedPreferences localStorage =
|
||||
await SharedPreferences.getInstance();
|
||||
await localStorage.clear();
|
||||
auth.logout();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TrackButton extends StatefulWidget {
|
||||
final String index;
|
||||
final String trackName;
|
||||
final List<String> artists;
|
||||
final String album;
|
||||
final String playback_time;
|
||||
final String? thumbnail_url;
|
||||
final void Function()? onTap;
|
||||
TrackButton({
|
||||
required this.index,
|
||||
required this.trackName,
|
||||
required this.artists,
|
||||
required this.album,
|
||||
required this.playback_time,
|
||||
this.thumbnail_url,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
_TrackButtonState createState() => _TrackButtonState();
|
||||
}
|
||||
|
||||
class _TrackButtonState extends State<TrackButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.index,
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
if (widget.thumbnail_url != null)
|
||||
CachedNetworkImage(
|
||||
imageUrl: widget.thumbnail_url!,
|
||||
maxHeightDiskCache: 50,
|
||||
maxWidthDiskCache: 50,
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.trackName,
|
||||
textAlign: TextAlign.justify,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 17),
|
||||
),
|
||||
Text(widget.artists.join(", "))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Text(widget.album),
|
||||
const SizedBox(width: 15),
|
||||
Text(widget.playback_time)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
3
lib/helpers/zero-pad-num-str.dart
Normal file
3
lib/helpers/zero-pad-num-str.dart
Normal file
@ -0,0 +1,3 @@
|
||||
String zeroPadNumStr(int input) {
|
||||
return input < 10 ? "0$input" : input.toString();
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/Home.dart';
|
||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/SpotifyDI.dart';
|
||||
@ -18,8 +20,36 @@ class MyApp extends StatelessWidget {
|
||||
ChangeNotifierProvider<Auth>(create: (context) => Auth()),
|
||||
ChangeNotifierProvider<SpotifyDI>(create: (context) {
|
||||
Auth authState = Provider.of<Auth>(context, listen: false);
|
||||
return SpotifyDI(SpotifyApi(SpotifyApiCredentials(
|
||||
authState.cliendId, authState.clientSecret)));
|
||||
return SpotifyDI(
|
||||
SpotifyApi(
|
||||
SpotifyApiCredentials(
|
||||
authState.clientId,
|
||||
authState.clientSecret,
|
||||
accessToken: authState.accessToken,
|
||||
refreshToken: authState.refreshToken,
|
||||
expiration: authState.expiration,
|
||||
scopes: spotifyScopes,
|
||||
),
|
||||
onCredentialsRefreshed: (credentials) async {
|
||||
SharedPreferences localStorage =
|
||||
await SharedPreferences.getInstance();
|
||||
localStorage.setString(
|
||||
LocalStorageKeys.refreshToken,
|
||||
credentials.refreshToken!,
|
||||
);
|
||||
localStorage.setString(
|
||||
LocalStorageKeys.accessToken,
|
||||
credentials.accessToken!,
|
||||
);
|
||||
localStorage.setString(
|
||||
LocalStorageKeys.clientId, credentials.clientId!);
|
||||
localStorage.setString(
|
||||
LocalStorageKeys.clientSecret,
|
||||
credentials.clientSecret!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
ChangeNotifierProvider<Playback>(create: (context) => Playback()),
|
||||
],
|
||||
|
7
lib/models/LocalStorageKeys.dart
Normal file
7
lib/models/LocalStorageKeys.dart
Normal file
@ -0,0 +1,7 @@
|
||||
abstract class LocalStorageKeys {
|
||||
static String clientId = 'client_id';
|
||||
static String clientSecret = 'client_secret';
|
||||
static String accessToken = 'access_token';
|
||||
static String refreshToken = 'refresh_token';
|
||||
static String expiration = " expiration";
|
||||
}
|
@ -3,10 +3,17 @@ import 'package:flutter/cupertino.dart';
|
||||
class Auth with ChangeNotifier {
|
||||
String? _clientId;
|
||||
String? _clientSecret;
|
||||
String? _accessToken;
|
||||
String? _refreshToken;
|
||||
DateTime? _expiration;
|
||||
|
||||
bool _isLoggedIn = false;
|
||||
|
||||
String? get cliendId => _clientId;
|
||||
String? get clientId => _clientId;
|
||||
String? get clientSecret => _clientSecret;
|
||||
String? get accessToken => _accessToken;
|
||||
String? get refreshToken => _refreshToken;
|
||||
DateTime? get expiration => _expiration;
|
||||
bool get isLoggedIn => _isLoggedIn;
|
||||
|
||||
void setAuthState({
|
||||
@ -14,17 +21,34 @@ class Auth with ChangeNotifier {
|
||||
bool safe = true,
|
||||
String? clientId,
|
||||
String? clientSecret,
|
||||
String? refresh_token,
|
||||
String? access_token,
|
||||
String? refreshToken,
|
||||
String? accessToken,
|
||||
DateTime? expiration,
|
||||
}) {
|
||||
if (safe) {
|
||||
if (clientId != null) _clientId = clientId;
|
||||
if (clientSecret != null) _clientSecret = clientSecret;
|
||||
if (isLoggedIn != null) _isLoggedIn = isLoggedIn;
|
||||
if (refreshToken != null) _refreshToken = refreshToken;
|
||||
if (accessToken != null) _accessToken = accessToken;
|
||||
if (expiration != null) _expiration = expiration;
|
||||
} else {
|
||||
_clientId = clientId;
|
||||
_clientSecret = clientSecret;
|
||||
_accessToken = accessToken;
|
||||
_refreshToken = refreshToken;
|
||||
_expiration = expiration;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
logout() {
|
||||
_clientId = null;
|
||||
_clientSecret = null;
|
||||
_accessToken = null;
|
||||
_refreshToken = null;
|
||||
_expiration = null;
|
||||
_isLoggedIn = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +68,8 @@ flutter:
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
assets:
|
||||
- assets/
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
|
Loading…
Reference in New Issue
Block a user