mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
hook support added to most of the components
This commit is contained in:
parent
d05ec0099d
commit
9fc155c000
@ -14,24 +14,19 @@ import 'package:spotube/helpers/zero-pad-num-str.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';
|
||||||
|
|
||||||
class ArtistProfile extends ConsumerStatefulWidget {
|
class ArtistProfile extends ConsumerWidget {
|
||||||
final String artistId;
|
final String artistId;
|
||||||
const ArtistProfile(this.artistId, {Key? key}) : super(key: key);
|
const ArtistProfile(this.artistId, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ArtistProfileState createState() => _ArtistProfileState();
|
Widget build(BuildContext context, ref) {
|
||||||
}
|
|
||||||
|
|
||||||
class _ArtistProfileState extends ConsumerState<ArtistProfile> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
SpotifyApi spotify = ref.watch(spotifyProvider);
|
SpotifyApi spotify = ref.watch(spotifyProvider);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: const PageWindowTitleBar(
|
appBar: const PageWindowTitleBar(
|
||||||
leading: BackButton(),
|
leading: BackButton(),
|
||||||
),
|
),
|
||||||
body: FutureBuilder<Artist>(
|
body: FutureBuilder<Artist>(
|
||||||
future: spotify.artists.get(widget.artistId),
|
future: spotify.artists.get(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());
|
||||||
@ -222,7 +217,7 @@ class _ArtistProfileState extends ConsumerState<ArtistProfile> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) => ArtistAlbumView(
|
builder: (context) => ArtistAlbumView(
|
||||||
widget.artistId,
|
artistId,
|
||||||
snapshot.data?.name ?? "KRTX",
|
snapshot.data?.name ?? "KRTX",
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
@ -260,7 +255,7 @@ class _ArtistProfileState extends ConsumerState<ArtistProfile> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
FutureBuilder<Iterable<Artist>>(
|
FutureBuilder<Iterable<Artist>>(
|
||||||
future: spotify.artists.getRelatedArtists(widget.artistId),
|
future: spotify.artists.getRelatedArtists(artistId),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const Center(
|
return const Center(
|
||||||
|
@ -5,7 +5,7 @@ import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
|||||||
import 'package:spotube/components/Playlist/PlaylistGenreView.dart';
|
import 'package:spotube/components/Playlist/PlaylistGenreView.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
class CategoryCard extends StatefulWidget {
|
class CategoryCard extends StatelessWidget {
|
||||||
final Category category;
|
final Category category;
|
||||||
final Iterable<PlaylistSimple>? playlists;
|
final Iterable<PlaylistSimple>? playlists;
|
||||||
const CategoryCard(
|
const CategoryCard(
|
||||||
@ -14,11 +14,6 @@ class CategoryCard extends StatefulWidget {
|
|||||||
this.playlists,
|
this.playlists,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_CategoryCardState createState() => _CategoryCardState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CategoryCardState extends State<CategoryCard> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
@ -29,7 +24,7 @@ class _CategoryCardState extends State<CategoryCard> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.category.name ?? "Unknown",
|
category.name ?? "Unknown",
|
||||||
style: Theme.of(context).textTheme.headline5,
|
style: Theme.of(context).textTheme.headline5,
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -38,9 +33,9 @@ class _CategoryCardState extends State<CategoryCard> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return PlaylistGenreView(
|
return PlaylistGenreView(
|
||||||
widget.category.id!,
|
category.id!,
|
||||||
widget.category.name!,
|
category.name!,
|
||||||
playlists: widget.playlists,
|
playlists: playlists,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -55,14 +50,13 @@ class _CategoryCardState extends State<CategoryCard> {
|
|||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
||||||
return FutureBuilder<Iterable<PlaylistSimple>>(
|
return FutureBuilder<Iterable<PlaylistSimple>>(
|
||||||
future: widget.playlists == null
|
future: playlists == null
|
||||||
? (widget.category.id != "user-featured-playlists"
|
? (category.id != "user-featured-playlists"
|
||||||
? spotifyApi.playlists
|
? spotifyApi.playlists.getByCategoryId(category.id!)
|
||||||
.getByCategoryId(widget.category.id!)
|
|
||||||
: spotifyApi.playlists.featured)
|
: spotifyApi.playlists.featured)
|
||||||
.getPage(4, 0)
|
.getPage(4, 0)
|
||||||
.then((value) => value.items ?? [])
|
.then((value) => value.items ?? [])
|
||||||
: Future.value(widget.playlists),
|
: Future.value(playlists),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return const Center(child: Text("Error occurred"));
|
return const Center(child: Text("Error occurred"));
|
||||||
|
@ -2,14 +2,8 @@ import 'package:flutter/material.dart' hide Image;
|
|||||||
import 'package:spotube/components/Library/UserArtists.dart';
|
import 'package:spotube/components/Library/UserArtists.dart';
|
||||||
import 'package:spotube/components/Library/UserPlaylists.dart';
|
import 'package:spotube/components/Library/UserPlaylists.dart';
|
||||||
|
|
||||||
class UserLibrary extends StatefulWidget {
|
class UserLibrary extends StatelessWidget {
|
||||||
const UserLibrary({Key? key}) : super(key: key);
|
const UserLibrary({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_UserLibraryState createState() => _UserLibraryState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _UserLibraryState extends State<UserLibrary> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:spotube/components/Shared/Hyperlink.dart';
|
import 'package:spotube/components/Shared/Hyperlink.dart';
|
||||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||||
@ -8,35 +9,32 @@ import 'package:spotube/models/LocalStorageKeys.dart';
|
|||||||
import 'package:spotube/provider/Auth.dart';
|
import 'package:spotube/provider/Auth.dart';
|
||||||
import 'package:spotube/provider/UserPreferences.dart';
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
|
|
||||||
class Login extends ConsumerStatefulWidget {
|
class Login extends HookConsumerWidget {
|
||||||
const Login({Key? key}) : super(key: key);
|
const Login({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LoginState createState() => _LoginState();
|
Widget build(BuildContext context, ref) {
|
||||||
}
|
var clientIdController = useTextEditingController();
|
||||||
|
var clientSecretController = useTextEditingController();
|
||||||
|
var accessTokenController = useTextEditingController();
|
||||||
|
var fieldError = useState(false);
|
||||||
|
|
||||||
class _LoginState extends ConsumerState<Login> {
|
Future handleLogin(Auth authState) async {
|
||||||
String clientId = "";
|
try {
|
||||||
String clientSecret = "";
|
if (clientIdController.value.text == "" ||
|
||||||
String accessToken = "";
|
clientSecretController.value.text == "") {
|
||||||
bool _fieldError = false;
|
fieldError.value = true;
|
||||||
|
}
|
||||||
Future handleLogin(Auth authState) async {
|
await oauthLogin(
|
||||||
try {
|
ref.read(authProvider),
|
||||||
if (clientId == "" || clientSecret == "") {
|
clientId: clientIdController.value.text,
|
||||||
return setState(() {
|
clientSecret: clientSecretController.value.text,
|
||||||
_fieldError = true;
|
);
|
||||||
});
|
} catch (e) {
|
||||||
|
print("[Login.handleLogin] $e");
|
||||||
}
|
}
|
||||||
await oauthLogin(ref.read(authProvider),
|
|
||||||
clientId: clientId, clientSecret: clientSecret);
|
|
||||||
} catch (e) {
|
|
||||||
print("[Login.handleLogin] $e");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Auth authState = ref.watch(authProvider);
|
Auth authState = ref.watch(authProvider);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: const PageWindowTitleBar(),
|
appBar: const PageWindowTitleBar(),
|
||||||
@ -65,15 +63,11 @@ class _LoginState extends ConsumerState<Login> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
|
controller: clientIdController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
hintText: "Spotify Client ID",
|
hintText: "Spotify Client ID",
|
||||||
label: Text("ClientID"),
|
label: Text("ClientID"),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
clientId = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
TextField(
|
TextField(
|
||||||
@ -81,11 +75,7 @@ class _LoginState extends ConsumerState<Login> {
|
|||||||
hintText: "Spotify Client Secret",
|
hintText: "Spotify Client Secret",
|
||||||
label: Text("Client Secret"),
|
label: Text("Client Secret"),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
controller: clientSecretController,
|
||||||
setState(() {
|
|
||||||
clientSecret = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
const Divider(color: Colors.grey),
|
const Divider(color: Colors.grey),
|
||||||
@ -94,11 +84,7 @@ class _LoginState extends ConsumerState<Login> {
|
|||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
label: Text("Genius Access Token (optional)"),
|
label: Text("Genius Access Token (optional)"),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
controller: accessTokenController,
|
||||||
setState(() {
|
|
||||||
accessToken = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
@ -110,12 +96,12 @@ class _LoginState extends ConsumerState<Login> {
|
|||||||
ref.read(userPreferencesProvider);
|
ref.read(userPreferencesProvider);
|
||||||
SharedPreferences localStorage =
|
SharedPreferences localStorage =
|
||||||
await SharedPreferences.getInstance();
|
await SharedPreferences.getInstance();
|
||||||
preferences.setGeniusAccessToken(accessToken);
|
preferences.setGeniusAccessToken(
|
||||||
|
accessTokenController.value.text);
|
||||||
await localStorage.setString(
|
await localStorage.setString(
|
||||||
LocalStorageKeys.geniusAccessToken, accessToken);
|
LocalStorageKeys.geniusAccessToken,
|
||||||
setState(() {
|
accessTokenController.value.text);
|
||||||
accessToken = "";
|
accessTokenController.text = "";
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: const Text("Submit"),
|
child: const Text("Submit"),
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.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';
|
||||||
@ -7,48 +8,53 @@ import 'package:spotube/helpers/getLyrics.dart';
|
|||||||
import 'package:spotube/provider/Playback.dart';
|
import 'package:spotube/provider/Playback.dart';
|
||||||
import 'package:spotube/provider/UserPreferences.dart';
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
|
|
||||||
class Lyrics extends ConsumerStatefulWidget {
|
class Lyrics extends HookConsumerWidget {
|
||||||
const Lyrics({Key? key}) : super(key: key);
|
const Lyrics({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<Lyrics> createState() => _LyricsState();
|
Widget build(BuildContext context, ref) {
|
||||||
}
|
|
||||||
|
|
||||||
class _LyricsState extends ConsumerState<Lyrics> {
|
|
||||||
Map<String, String> _lyrics = {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Playback playback = ref.watch(playbackProvider);
|
Playback playback = ref.watch(playbackProvider);
|
||||||
UserPreferences userPreferences = ref.watch(userPreferencesProvider);
|
UserPreferences userPreferences = ref.watch(userPreferencesProvider);
|
||||||
|
var lyrics = useState({});
|
||||||
|
|
||||||
bool hasToken = (userPreferences.geniusAccessToken != null ||
|
bool hasToken = (userPreferences.geniusAccessToken != null ||
|
||||||
(userPreferences.geniusAccessToken?.isNotEmpty ?? false));
|
(userPreferences.geniusAccessToken?.isNotEmpty ?? false));
|
||||||
|
var lyricsFuture = useMemoized(() {
|
||||||
if (playback.currentTrack != null &&
|
if (playback.currentTrack == null ||
|
||||||
hasToken &&
|
!hasToken ||
|
||||||
playback.currentTrack!.id != _lyrics["id"]) {
|
(playback.currentTrack?.id != null &&
|
||||||
getLyrics(
|
playback.currentTrack?.id == lyrics.value["id"])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getLyrics(
|
||||||
playback.currentTrack!.name!,
|
playback.currentTrack!.name!,
|
||||||
artistsToString<Artist>(playback.currentTrack!.artists ?? []),
|
artistsToString<Artist>(playback.currentTrack!.artists ?? []),
|
||||||
apiKey: userPreferences.geniusAccessToken!,
|
apiKey: userPreferences.geniusAccessToken!,
|
||||||
optimizeQuery: true,
|
optimizeQuery: true,
|
||||||
).then((lyrics) {
|
);
|
||||||
if (lyrics != null) {
|
}, [playback.currentTrack]);
|
||||||
setState(() {
|
|
||||||
_lyrics = {"lyrics": lyrics, "id": playback.currentTrack!.id!};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_lyrics["lyrics"] != null && playback.currentTrack == null) {
|
var lyricsSnapshot = useFuture(lyricsFuture);
|
||||||
setState(() {
|
|
||||||
_lyrics = {};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_lyrics["lyrics"] == null && playback.currentTrack != null) {
|
useEffect(() {
|
||||||
|
if (lyricsSnapshot.hasData && lyricsSnapshot.data != null) {
|
||||||
|
lyrics.value = {
|
||||||
|
"lyrics": lyricsSnapshot.data,
|
||||||
|
"id": playback.currentTrack!.id!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lyrics.value["lyrics"] != null && playback.currentTrack == null) {
|
||||||
|
lyrics.value = {};
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
lyricsSnapshot.data,
|
||||||
|
lyricsSnapshot.hasData,
|
||||||
|
lyrics.value,
|
||||||
|
playback.currentTrack,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (lyrics.value["lyrics"] == null && playback.currentTrack != null) {
|
||||||
if (!hasToken) {
|
if (!hasToken) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -99,9 +105,10 @@ class _LyricsState extends ConsumerState<Lyrics> {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
_lyrics["lyrics"] == null && playback.currentTrack == null
|
lyrics.value["lyrics"] == null &&
|
||||||
|
playback.currentTrack == null
|
||||||
? "No Track being played currently"
|
? "No Track being played currently"
|
||||||
: _lyrics["lyrics"]!,
|
: lyrics.value["lyrics"]!,
|
||||||
style: Theme.of(context).textTheme.headline6,
|
style: Theme.of(context).textTheme.headline6,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -176,7 +176,6 @@ class _PlayerState extends ConsumerState<Player> with WidgetsBindingObserver {
|
|||||||
await player.pause();
|
await player.pause();
|
||||||
await player.seek(Duration.zero);
|
await player.seek(Duration.zero);
|
||||||
_movePlaylistPositionBy(1);
|
_movePlaylistPositionBy(1);
|
||||||
print("ON NEXT");
|
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
print("[PlayerControls.onNext()] $e");
|
print("[PlayerControls.onNext()] $e");
|
||||||
print(stack);
|
print(stack);
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
||||||
import 'package:spotube/models/GlobalKeyActions.dart';
|
import 'package:spotube/models/GlobalKeyActions.dart';
|
||||||
import 'package:spotube/provider/UserPreferences.dart';
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
|
|
||||||
class PlayerControls extends ConsumerStatefulWidget {
|
class PlayerControls extends HookConsumerWidget {
|
||||||
final Stream<Duration> positionStream;
|
final Stream<Duration> positionStream;
|
||||||
final bool isPlaying;
|
final bool isPlaying;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
@ -34,33 +35,21 @@ class PlayerControls extends ConsumerStatefulWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_PlayerControlsState createState() => _PlayerControlsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PlayerControlsState extends ConsumerState<PlayerControls> {
|
|
||||||
StreamSubscription? _timePositionListener;
|
|
||||||
late List<GlobalKeyActions> _hotKeys = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() async {
|
|
||||||
await _timePositionListener?.cancel();
|
|
||||||
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)));
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_playOrPause(key) async {
|
_playOrPause(key) async {
|
||||||
try {
|
try {
|
||||||
widget.isPlaying ? widget.onPause?.call() : await widget.onPlay?.call();
|
isPlaying ? await onPause?.call() : await onPlay?.call();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
print("[PlayPauseShortcut] $e");
|
print("[PlayPauseShortcut] $e");
|
||||||
print(stack);
|
print(stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureHotKeys(UserPreferences preferences) async {
|
@override
|
||||||
await Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)))
|
Widget build(BuildContext context, ref) {
|
||||||
.then((val) async {
|
UserPreferences preferences = ref.watch(userPreferencesProvider);
|
||||||
|
|
||||||
|
var _hotKeys = [];
|
||||||
|
useEffect(() {
|
||||||
_hotKeys = [
|
_hotKeys = [
|
||||||
GlobalKeyActions(
|
GlobalKeyActions(
|
||||||
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
||||||
@ -68,14 +57,14 @@ class _PlayerControlsState extends ConsumerState<PlayerControls> {
|
|||||||
),
|
),
|
||||||
if (preferences.nextTrackHotKey != null)
|
if (preferences.nextTrackHotKey != null)
|
||||||
GlobalKeyActions(
|
GlobalKeyActions(
|
||||||
preferences.nextTrackHotKey!, (key) => widget.onNext?.call()),
|
preferences.nextTrackHotKey!, (key) => onNext?.call()),
|
||||||
if (preferences.prevTrackHotKey != null)
|
if (preferences.prevTrackHotKey != null)
|
||||||
GlobalKeyActions(
|
GlobalKeyActions(
|
||||||
preferences.prevTrackHotKey!, (key) => widget.onPrevious?.call()),
|
preferences.prevTrackHotKey!, (key) => onPrevious?.call()),
|
||||||
if (preferences.playPauseHotKey != null)
|
if (preferences.playPauseHotKey != null)
|
||||||
GlobalKeyActions(preferences.playPauseHotKey!, _playOrPause)
|
GlobalKeyActions(preferences.playPauseHotKey!, _playOrPause)
|
||||||
];
|
];
|
||||||
await Future.wait(
|
Future.wait(
|
||||||
_hotKeys.map((e) {
|
_hotKeys.map((e) {
|
||||||
return hotKeyManager.register(
|
return hotKeyManager.register(
|
||||||
e.hotKey,
|
e.hotKey,
|
||||||
@ -83,25 +72,22 @@ class _PlayerControlsState extends ConsumerState<PlayerControls> {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
return () {
|
||||||
|
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)));
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
UserPreferences preferences = ref.watch(userPreferencesProvider);
|
|
||||||
_configureHotKeys(preferences);
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 700),
|
constraints: const BoxConstraints(maxWidth: 700),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
StreamBuilder<Duration>(
|
StreamBuilder<Duration>(
|
||||||
stream: widget.positionStream,
|
stream: positionStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
var totalMinutes =
|
var totalMinutes =
|
||||||
zeroPadNumStr(widget.duration.inMinutes.remainder(60));
|
zeroPadNumStr(duration.inMinutes.remainder(60));
|
||||||
var totalSeconds =
|
var totalSeconds =
|
||||||
zeroPadNumStr(widget.duration.inSeconds.remainder(60));
|
zeroPadNumStr(duration.inSeconds.remainder(60));
|
||||||
var currentMinutes = snapshot.hasData
|
var currentMinutes = snapshot.hasData
|
||||||
? zeroPadNumStr(snapshot.data!.inMinutes.remainder(60))
|
? zeroPadNumStr(snapshot.data!.inMinutes.remainder(60))
|
||||||
: "00";
|
: "00";
|
||||||
@ -109,7 +95,7 @@ class _PlayerControlsState extends ConsumerState<PlayerControls> {
|
|||||||
? zeroPadNumStr(snapshot.data!.inSeconds.remainder(60))
|
? zeroPadNumStr(snapshot.data!.inSeconds.remainder(60))
|
||||||
: "00";
|
: "00";
|
||||||
|
|
||||||
var sliderMax = widget.duration.inSeconds;
|
var sliderMax = duration.inSeconds;
|
||||||
var sliderValue = snapshot.data?.inSeconds ?? 0;
|
var sliderValue = snapshot.data?.inSeconds ?? 0;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
@ -123,7 +109,7 @@ class _PlayerControlsState extends ConsumerState<PlayerControls> {
|
|||||||
: sliderValue / sliderMax,
|
: sliderValue / sliderMax,
|
||||||
onChanged: (value) {},
|
onChanged: (value) {},
|
||||||
onChangeEnd: (value) {
|
onChangeEnd: (value) {
|
||||||
widget.onSeek?.call(value * sliderMax);
|
onSeek?.call(value * sliderMax);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -138,30 +124,27 @@ class _PlayerControlsState extends ConsumerState<PlayerControls> {
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.shuffle_rounded),
|
icon: const Icon(Icons.shuffle_rounded),
|
||||||
color:
|
color: shuffled ? Theme.of(context).primaryColor : null,
|
||||||
widget.shuffled ? Theme.of(context).primaryColor : null,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.onShuffle?.call();
|
onShuffle?.call();
|
||||||
}),
|
}),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.skip_previous_rounded),
|
icon: const Icon(Icons.skip_previous_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.onPrevious?.call();
|
onPrevious?.call();
|
||||||
}),
|
}),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
widget.isPlaying
|
isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded,
|
||||||
? Icons.pause_rounded
|
|
||||||
: Icons.play_arrow_rounded,
|
|
||||||
),
|
),
|
||||||
onPressed: () => _playOrPause(null),
|
onPressed: () => _playOrPause(null),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.skip_next_rounded),
|
icon: const Icon(Icons.skip_next_rounded),
|
||||||
onPressed: () => widget.onNext?.call()),
|
onPressed: () => onNext?.call()),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.stop_rounded),
|
icon: const Icon(Icons.stop_rounded),
|
||||||
onPressed: () => widget.onStop?.call(),
|
onPressed: () => onStop?.call(),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.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/components/Shared/PlaybuttonCard.dart';
|
||||||
@ -7,27 +7,22 @@ import 'package:spotube/helpers/image-to-url-string.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';
|
||||||
|
|
||||||
class PlaylistCard extends ConsumerStatefulWidget {
|
class PlaylistCard extends ConsumerWidget {
|
||||||
final PlaylistSimple playlist;
|
final PlaylistSimple playlist;
|
||||||
const PlaylistCard(this.playlist, {Key? key}) : super(key: key);
|
const PlaylistCard(this.playlist, {Key? key}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
_PlaylistCardState createState() => _PlaylistCardState();
|
Widget build(BuildContext context, ref) {
|
||||||
}
|
|
||||||
|
|
||||||
class _PlaylistCardState extends ConsumerState<PlaylistCard> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Playback playback = ref.watch(playbackProvider);
|
Playback playback = ref.watch(playbackProvider);
|
||||||
bool isPlaylistPlaying = playback.currentPlaylist != null &&
|
bool isPlaylistPlaying = playback.currentPlaylist != null &&
|
||||||
playback.currentPlaylist!.id == widget.playlist.id;
|
playback.currentPlaylist!.id == playlist.id;
|
||||||
return PlaybuttonCard(
|
return PlaybuttonCard(
|
||||||
title: widget.playlist.name!,
|
title: playlist.name!,
|
||||||
imageUrl: widget.playlist.images![0].url!,
|
imageUrl: playlist.images![0].url!,
|
||||||
isPlaying: isPlaylistPlaying,
|
isPlaying: isPlaylistPlaying,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return PlaylistView(widget.playlist);
|
return PlaylistView(playlist);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
@ -35,9 +30,9 @@ class _PlaylistCardState extends ConsumerState<PlaylistCard> {
|
|||||||
if (isPlaylistPlaying) return;
|
if (isPlaylistPlaying) return;
|
||||||
SpotifyApi spotifyApi = ref.read(spotifyProvider);
|
SpotifyApi spotifyApi = ref.read(spotifyProvider);
|
||||||
|
|
||||||
List<Track> tracks = (widget.playlist.id != "user-liked-tracks"
|
List<Track> tracks = (playlist.id != "user-liked-tracks"
|
||||||
? await spotifyApi.playlists
|
? await spotifyApi.playlists
|
||||||
.getTracksByPlaylistId(widget.playlist.id!)
|
.getTracksByPlaylistId(playlist.id!)
|
||||||
.all()
|
.all()
|
||||||
: await spotifyApi.tracks.me.saved
|
: await spotifyApi.tracks.me.saved
|
||||||
.all()
|
.all()
|
||||||
@ -48,9 +43,9 @@ class _PlaylistCardState extends ConsumerState<PlaylistCard> {
|
|||||||
|
|
||||||
playback.setCurrentPlaylist = CurrentPlaylist(
|
playback.setCurrentPlaylist = CurrentPlaylist(
|
||||||
tracks: tracks,
|
tracks: tracks,
|
||||||
id: widget.playlist.id!,
|
id: playlist.id!,
|
||||||
name: widget.playlist.name!,
|
name: playlist.name!,
|
||||||
thumbnail: imageToUrlString(widget.playlist.images),
|
thumbnail: imageToUrlString(playlist.images),
|
||||||
);
|
);
|
||||||
playback.setCurrentTrack = tracks.first;
|
playback.setCurrentTrack = tracks.first;
|
||||||
},
|
},
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
class PlaylistGenreView extends StatefulWidget {
|
class PlaylistGenreView extends ConsumerWidget {
|
||||||
final String genreId;
|
final String genreId;
|
||||||
final String genreName;
|
final String genreName;
|
||||||
final Iterable<PlaylistSimple>? playlists;
|
final Iterable<PlaylistSimple>? playlists;
|
||||||
@ -15,13 +15,9 @@ class PlaylistGenreView extends StatefulWidget {
|
|||||||
this.playlists,
|
this.playlists,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@override
|
|
||||||
_PlaylistGenreViewState createState() => _PlaylistGenreViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, ref) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: const PageWindowTitleBar(
|
appBar: const PageWindowTitleBar(
|
||||||
leading: BackButton(),
|
leading: BackButton(),
|
||||||
@ -29,7 +25,7 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.genreName,
|
genreName,
|
||||||
style: Theme.of(context).textTheme.headline4,
|
style: Theme.of(context).textTheme.headline4,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@ -39,13 +35,13 @@ class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: FutureBuilder<Iterable<PlaylistSimple>>(
|
child: FutureBuilder<Iterable<PlaylistSimple>>(
|
||||||
future: widget.playlists == null
|
future: playlists == null
|
||||||
? (widget.genreId != "user-featured-playlists"
|
? (genreId != "user-featured-playlists"
|
||||||
? spotifyApi.playlists
|
? spotifyApi.playlists
|
||||||
.getByCategoryId(widget.genreId)
|
.getByCategoryId(genreId)
|
||||||
.all()
|
.all()
|
||||||
: spotifyApi.playlists.featured.all())
|
: spotifyApi.playlists.featured.all())
|
||||||
: Future.value(widget.playlists),
|
: Future.value(playlists),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return const Center(child: Text("Error occurred"));
|
return const Center(child: Text("Error occurred"));
|
||||||
|
@ -7,24 +7,20 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
class PlaylistView extends ConsumerStatefulWidget {
|
class PlaylistView extends ConsumerWidget {
|
||||||
final PlaylistSimple playlist;
|
final PlaylistSimple playlist;
|
||||||
const PlaylistView(this.playlist, {Key? key}) : super(key: key);
|
const PlaylistView(this.playlist, {Key? key}) : super(key: key);
|
||||||
@override
|
|
||||||
_PlaylistViewState createState() => _PlaylistViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PlaylistViewState extends ConsumerState<PlaylistView> {
|
|
||||||
playPlaylist(Playback playback, List<Track> tracks, {Track? currentTrack}) {
|
playPlaylist(Playback playback, List<Track> tracks, {Track? currentTrack}) {
|
||||||
currentTrack ??= tracks.first;
|
currentTrack ??= tracks.first;
|
||||||
var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
|
var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
|
||||||
playback.currentPlaylist?.id == widget.playlist.id;
|
playback.currentPlaylist?.id == playlist.id;
|
||||||
if (!isPlaylistPlaying) {
|
if (!isPlaylistPlaying) {
|
||||||
playback.setCurrentPlaylist = CurrentPlaylist(
|
playback.setCurrentPlaylist = CurrentPlaylist(
|
||||||
tracks: tracks,
|
tracks: tracks,
|
||||||
id: widget.playlist.id!,
|
id: playlist.id!,
|
||||||
name: widget.playlist.name!,
|
name: playlist.name!,
|
||||||
thumbnail: imageToUrlString(widget.playlist.images),
|
thumbnail: imageToUrlString(playlist.images),
|
||||||
);
|
);
|
||||||
playback.setCurrentTrack = currentTrack;
|
playback.setCurrentTrack = currentTrack;
|
||||||
} else if (isPlaylistPlaying &&
|
} else if (isPlaylistPlaying &&
|
||||||
@ -35,17 +31,15 @@ class _PlaylistViewState extends ConsumerState<PlaylistView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, ref) {
|
||||||
Playback playback = ref.watch(playbackProvider);
|
Playback playback = ref.watch(playbackProvider);
|
||||||
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
||||||
var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
|
var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
|
||||||
playback.currentPlaylist?.id == widget.playlist.id;
|
playback.currentPlaylist?.id == playlist.id;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: FutureBuilder<Iterable<Track>>(
|
body: FutureBuilder<Iterable<Track>>(
|
||||||
future: widget.playlist.id != "user-liked-tracks"
|
future: playlist.id != "user-liked-tracks"
|
||||||
? spotifyApi.playlists
|
? spotifyApi.playlists.getTracksByPlaylistId(playlist.id).all()
|
||||||
.getTracksByPlaylistId(widget.playlist.id)
|
|
||||||
.all()
|
|
||||||
: spotifyApi.tracks.me.saved
|
: spotifyApi.tracks.me.saved
|
||||||
.all()
|
.all()
|
||||||
.then((tracks) => tracks.map((e) => e.track!)),
|
.then((tracks) => tracks.map((e) => e.track!)),
|
||||||
@ -78,7 +72,7 @@ class _PlaylistViewState extends ConsumerState<PlaylistView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Text(widget.playlist.name!,
|
child: Text(playlist.name!,
|
||||||
style: Theme.of(context).textTheme.headline4),
|
style: Theme.of(context).textTheme.headline4),
|
||||||
),
|
),
|
||||||
snapshot.hasError
|
snapshot.hasError
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Album/AlbumCard.dart';
|
import 'package:spotube/components/Album/AlbumCard.dart';
|
||||||
import 'package:spotube/components/Artist/ArtistCard.dart';
|
import 'package:spotube/components/Artist/ArtistCard.dart';
|
||||||
@ -11,27 +12,14 @@ import 'package:spotube/helpers/zero-pad-num-str.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';
|
||||||
|
|
||||||
class Search extends ConsumerStatefulWidget {
|
class Search extends HookConsumerWidget {
|
||||||
const Search({Key? key}) : super(key: key);
|
const Search({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<Search> createState() => _SearchState();
|
Widget build(BuildContext context, ref) {
|
||||||
}
|
|
||||||
|
|
||||||
class _SearchState extends ConsumerState<Search> {
|
|
||||||
late TextEditingController _controller;
|
|
||||||
|
|
||||||
String searchTerm = "";
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_controller = TextEditingController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
SpotifyApi spotify = ref.watch(spotifyProvider);
|
SpotifyApi spotify = ref.watch(spotifyProvider);
|
||||||
|
var controller = useTextEditingController();
|
||||||
|
var searchTerm = useState("");
|
||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -43,11 +31,9 @@ class _SearchState extends ConsumerState<Search> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
decoration: const InputDecoration(hintText: "Search..."),
|
decoration: const InputDecoration(hintText: "Search..."),
|
||||||
controller: _controller,
|
controller: controller,
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
setState(() {
|
searchTerm.value = controller.value.text;
|
||||||
searchTerm = _controller.value.text;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -60,24 +46,22 @@ class _SearchState extends ConsumerState<Search> {
|
|||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
child: const Icon(Icons.search_rounded),
|
child: const Icon(Icons.search_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
searchTerm.value = controller.value.text;
|
||||||
searchTerm = _controller.value.text;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FutureBuilder<List<Page>>(
|
FutureBuilder<List<Page>>(
|
||||||
future: searchTerm.isNotEmpty
|
future: searchTerm.value.isNotEmpty
|
||||||
? spotify.search.get(searchTerm).first(10)
|
? spotify.search.get(searchTerm.value).first(10)
|
||||||
: null,
|
: null,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData && searchTerm.isNotEmpty) {
|
if (!snapshot.hasData && searchTerm.value.isNotEmpty) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator.adaptive(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
);
|
);
|
||||||
} else if (!snapshot.hasData && searchTerm.isEmpty) {
|
} else if (!snapshot.hasData && searchTerm.value.isEmpty) {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
Playback playback = ref.watch(playbackProvider);
|
Playback playback = ref.watch(playbackProvider);
|
||||||
|
@ -1,45 +1,28 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:spotube/components/Settings/SettingsHotkeyTile.dart';
|
import 'package:spotube/components/Settings/SettingsHotkeyTile.dart';
|
||||||
import 'package:spotube/components/Shared/Hyperlink.dart';
|
import 'package:spotube/components/Shared/Hyperlink.dart';
|
||||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||||
import 'package:spotube/main.dart';
|
|
||||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||||
import 'package:spotube/provider/Auth.dart';
|
import 'package:spotube/provider/Auth.dart';
|
||||||
|
import 'package:spotube/provider/ThemeProvider.dart';
|
||||||
import 'package:spotube/provider/UserPreferences.dart';
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
|
|
||||||
class Settings extends ConsumerStatefulWidget {
|
class Settings extends HookConsumerWidget {
|
||||||
const Settings({Key? key}) : super(key: key);
|
const Settings({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SettingsState createState() => _SettingsState();
|
Widget build(BuildContext context, ref) {
|
||||||
}
|
|
||||||
|
|
||||||
class _SettingsState extends ConsumerState<Settings> {
|
|
||||||
TextEditingController? _textEditingController;
|
|
||||||
String? _geniusAccessToken;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_textEditingController = TextEditingController();
|
|
||||||
_textEditingController?.addListener(() {
|
|
||||||
setState(() {
|
|
||||||
_geniusAccessToken = _textEditingController?.value.text;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_textEditingController?.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
UserPreferences preferences = ref.watch(userPreferencesProvider);
|
UserPreferences preferences = ref.watch(userPreferencesProvider);
|
||||||
|
ThemeMode theme = ref.watch(themeProvider);
|
||||||
|
var geniusAccessToken = useState<String?>(null);
|
||||||
|
TextEditingController textEditingController = useTextEditingController();
|
||||||
|
|
||||||
|
textEditingController.addListener(() {
|
||||||
|
geniusAccessToken.value = textEditingController.value.text;
|
||||||
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PageWindowTitleBar(
|
appBar: PageWindowTitleBar(
|
||||||
@ -65,7 +48,7 @@ class _SettingsState extends ConsumerState<Settings> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _textEditingController,
|
controller: textEditingController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: preferences.geniusAccessToken,
|
hintText: preferences.geniusAccessToken,
|
||||||
),
|
),
|
||||||
@ -74,19 +57,19 @@ class _SettingsState extends ConsumerState<Settings> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _geniusAccessToken != null
|
onPressed: geniusAccessToken != null
|
||||||
? () async {
|
? () async {
|
||||||
SharedPreferences localStorage =
|
SharedPreferences localStorage =
|
||||||
await SharedPreferences.getInstance();
|
await SharedPreferences.getInstance();
|
||||||
preferences
|
preferences
|
||||||
.setGeniusAccessToken(_geniusAccessToken);
|
.setGeniusAccessToken(geniusAccessToken.value);
|
||||||
localStorage.setString(
|
localStorage.setString(
|
||||||
LocalStorageKeys.geniusAccessToken,
|
LocalStorageKeys.geniusAccessToken,
|
||||||
_geniusAccessToken!);
|
geniusAccessToken.value ?? "");
|
||||||
setState(() {
|
|
||||||
_geniusAccessToken = null;
|
geniusAccessToken.value = null;
|
||||||
});
|
|
||||||
_textEditingController?.text = "";
|
textEditingController.text = "";
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: const Text("Save"),
|
child: const Text("Save"),
|
||||||
@ -121,7 +104,7 @@ class _SettingsState extends ConsumerState<Settings> {
|
|||||||
children: [
|
children: [
|
||||||
const Text("Theme"),
|
const Text("Theme"),
|
||||||
DropdownButton<ThemeMode>(
|
DropdownButton<ThemeMode>(
|
||||||
value: MyApp.of(context)?.getThemeMode(),
|
value: theme,
|
||||||
items: const [
|
items: const [
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -142,7 +125,7 @@ class _SettingsState extends ConsumerState<Settings> {
|
|||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
MyApp.of(context)?.setThemeMode(value);
|
ref.read(themeProvider.notifier).state = value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
class AnchorButton<T> extends StatefulWidget {
|
class AnchorButton<T> extends HookWidget {
|
||||||
final String text;
|
final String text;
|
||||||
final TextStyle style;
|
final TextStyle style;
|
||||||
final TextAlign? textAlign;
|
final TextAlign? textAlign;
|
||||||
@ -16,33 +17,29 @@ class AnchorButton<T> extends StatefulWidget {
|
|||||||
this.style = const TextStyle(),
|
this.style = const TextStyle(),
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
State<AnchorButton<T>> createState() => _AnchorButtonState<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AnchorButtonState<T> extends State<AnchorButton<T>> {
|
|
||||||
bool _hover = false;
|
|
||||||
bool _tap = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var hover = useState(false);
|
||||||
|
var tap = useState(false);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: MaterialStateMouseCursor.clickable,
|
cursor: MaterialStateMouseCursor.clickable,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.text,
|
text,
|
||||||
style: widget.style.copyWith(
|
style: style.copyWith(
|
||||||
decoration: _hover || _tap ? TextDecoration.underline : null,
|
decoration:
|
||||||
|
hover.value || tap.value ? TextDecoration.underline : null,
|
||||||
),
|
),
|
||||||
textAlign: widget.textAlign,
|
textAlign: textAlign,
|
||||||
overflow: widget.overflow,
|
overflow: overflow,
|
||||||
),
|
),
|
||||||
onEnter: (event) => setState(() => _hover = true),
|
onEnter: (event) => hover.value = true,
|
||||||
onExit: (event) => setState(() => _hover = false),
|
onExit: (event) => hover.value = false,
|
||||||
),
|
),
|
||||||
onTapDown: (event) => setState(() => _tap = true),
|
onTapDown: (event) => tap.value = true,
|
||||||
onTapUp: (event) => setState(() => _tap = false),
|
onTapUp: (event) => tap.value = false,
|
||||||
onTap: widget.onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,106 +1,86 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/helpers/artist-to-string.dart';
|
import 'package:spotube/helpers/artist-to-string.dart';
|
||||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
class DownloadTrackButton extends StatefulWidget {
|
enum TrackStatus { downloading, idle, done }
|
||||||
|
|
||||||
|
class DownloadTrackButton extends HookWidget {
|
||||||
final Track? track;
|
final Track? track;
|
||||||
const DownloadTrackButton({Key? key, this.track}) : super(key: key);
|
const DownloadTrackButton({Key? key, this.track}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_DownloadTrackButtonState createState() => _DownloadTrackButtonState();
|
Widget build(BuildContext context) {
|
||||||
}
|
var status = useState<TrackStatus>(TrackStatus.idle);
|
||||||
|
YoutubeExplode yt = useMemoized(() => YoutubeExplode());
|
||||||
|
|
||||||
enum TrackStatus { downloading, idle, done }
|
var _downloadTrack = useCallback(() async {
|
||||||
|
if (track == null) return;
|
||||||
|
StreamManifest manifest =
|
||||||
|
await yt.videos.streamsClient.getManifest(track?.href);
|
||||||
|
|
||||||
class _DownloadTrackButtonState extends State<DownloadTrackButton> {
|
var audioStream = yt.videos.streamsClient.get(
|
||||||
late YoutubeExplode yt;
|
manifest.audioOnly
|
||||||
TrackStatus status = TrackStatus.idle;
|
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||||
|
.withHighestBitrate(),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
var statusCb = audioStream.listen(
|
||||||
void initState() {
|
(event) {
|
||||||
yt = YoutubeExplode();
|
if (status.value != TrackStatus.downloading) {
|
||||||
super.initState();
|
status.value = TrackStatus.downloading;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
@override
|
onDone: () async {
|
||||||
void dispose() {
|
status.value = TrackStatus.done;
|
||||||
yt.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_downloadTrack() async {
|
|
||||||
if (widget.track == null) return;
|
|
||||||
StreamManifest manifest =
|
|
||||||
await yt.videos.streamsClient.getManifest(widget.track?.href);
|
|
||||||
|
|
||||||
var audioStream = yt.videos.streamsClient
|
|
||||||
.get(manifest.audioOnly.withHighestBitrate())
|
|
||||||
.asBroadcastStream();
|
|
||||||
|
|
||||||
var statusCb = audioStream.listen(
|
|
||||||
(event) {
|
|
||||||
if (status != TrackStatus.downloading) {
|
|
||||||
setState(() {
|
|
||||||
status = TrackStatus.downloading;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDone: () async {
|
|
||||||
setState(() {
|
|
||||||
status = TrackStatus.done;
|
|
||||||
});
|
|
||||||
await Future.delayed(
|
|
||||||
const Duration(seconds: 3),
|
|
||||||
() {
|
|
||||||
if (status == TrackStatus.done) {
|
|
||||||
setState(() {
|
|
||||||
status = TrackStatus.idle;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
String downloadFolder = path.join(
|
|
||||||
(await path_provider.getDownloadsDirectory())!.path, "Spotube");
|
|
||||||
String fileName =
|
|
||||||
"${widget.track?.name} - ${artistsToString<Artist>(widget.track?.artists ?? [])}.mp3";
|
|
||||||
File outputFile = File(path.join(downloadFolder, fileName));
|
|
||||||
if (!outputFile.existsSync()) {
|
|
||||||
outputFile.createSync(recursive: true);
|
|
||||||
IOSink outputFileStream = outputFile.openWrite();
|
|
||||||
await audioStream.pipe(outputFileStream);
|
|
||||||
await outputFileStream.flush();
|
|
||||||
await outputFileStream.close().then((value) async {
|
|
||||||
if (status == TrackStatus.downloading) {
|
|
||||||
setState(() {
|
|
||||||
status = TrackStatus.done;
|
|
||||||
});
|
|
||||||
await Future.delayed(
|
await Future.delayed(
|
||||||
const Duration(seconds: 3),
|
const Duration(seconds: 3),
|
||||||
() {
|
() {
|
||||||
if (status == TrackStatus.done) {
|
if (status.value == TrackStatus.done) {
|
||||||
setState(() {
|
status.value = TrackStatus.idle;
|
||||||
status = TrackStatus.idle;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
return statusCb.cancel();
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
String downloadFolder = path.join(
|
||||||
Widget build(BuildContext context) {
|
(await path_provider.getDownloadsDirectory())!.path, "Spotube");
|
||||||
if (status == TrackStatus.downloading) {
|
String fileName =
|
||||||
|
"${track?.name} - ${artistsToString<Artist>(track?.artists ?? [])}.mp3";
|
||||||
|
File outputFile = File(path.join(downloadFolder, fileName));
|
||||||
|
if (!outputFile.existsSync()) {
|
||||||
|
outputFile.createSync(recursive: true);
|
||||||
|
IOSink outputFileStream = outputFile.openWrite();
|
||||||
|
await audioStream.pipe(outputFileStream);
|
||||||
|
await outputFileStream.flush();
|
||||||
|
await outputFileStream.close().then((value) async {
|
||||||
|
if (status.value == TrackStatus.downloading) {
|
||||||
|
status.value = TrackStatus.done;
|
||||||
|
await Future.delayed(
|
||||||
|
const Duration(seconds: 3),
|
||||||
|
() {
|
||||||
|
if (status.value == TrackStatus.done) {
|
||||||
|
status.value = TrackStatus.idle;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return statusCb.cancel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [track, status, yt]);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
return () => yt.close();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (status.value == TrackStatus.downloading) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
@ -108,13 +88,13 @@ class _DownloadTrackButtonState extends State<DownloadTrackButton> {
|
|||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
);
|
);
|
||||||
} else if (status == TrackStatus.done) {
|
} else if (status.value == TrackStatus.done) {
|
||||||
return const Icon(Icons.download_done_rounded);
|
return const Icon(Icons.download_done_rounded);
|
||||||
}
|
}
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: const Icon(Icons.download_rounded),
|
icon: const Icon(Icons.download_rounded),
|
||||||
onPressed: widget.track != null &&
|
onPressed: track != null &&
|
||||||
!(widget.track!.href ?? "").startsWith("https://api.spotify.com")
|
!(track!.href ?? "").startsWith("https://api.spotify.com")
|
||||||
? _downloadTrack
|
? _downloadTrack
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
|
|
||||||
class RecordHotKeyDialog extends StatefulWidget {
|
class RecordHotKeyDialog extends HookWidget {
|
||||||
final ValueChanged<HotKey> onHotKeyRecorded;
|
final ValueChanged<HotKey> onHotKeyRecorded;
|
||||||
|
|
||||||
const RecordHotKeyDialog({
|
const RecordHotKeyDialog({
|
||||||
@ -9,15 +10,9 @@ class RecordHotKeyDialog extends StatefulWidget {
|
|||||||
required this.onHotKeyRecorded,
|
required this.onHotKeyRecorded,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_RecordHotKeyDialogState createState() => _RecordHotKeyDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecordHotKeyDialogState extends State<RecordHotKeyDialog> {
|
|
||||||
HotKey _hotKey = HotKey(null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var _hotKey = useState(HotKey(null));
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: ListBody(
|
child: ListBody(
|
||||||
@ -58,9 +53,7 @@ class _RecordHotKeyDialogState extends State<RecordHotKeyDialog> {
|
|||||||
children: [
|
children: [
|
||||||
HotKeyRecorder(
|
HotKeyRecorder(
|
||||||
onHotKeyRecorded: (hotKey) {
|
onHotKeyRecorded: (hotKey) {
|
||||||
setState(() {
|
_hotKey.value = hotKey;
|
||||||
_hotKey = hotKey;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -78,10 +71,10 @@ class _RecordHotKeyDialogState extends State<RecordHotKeyDialog> {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
onPressed: !_hotKey.isSetted
|
onPressed: !_hotKey.value.isSetted
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
widget.onHotKeyRecorded(_hotKey);
|
onHotKeyRecorded(_hotKey.value);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:spotube/components/Home.dart';
|
import 'package:spotube/components/Home.dart';
|
||||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||||
|
import 'package:spotube/provider/ThemeProvider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await hotKeyManager.unregisterAll();
|
await hotKeyManager.unregisterAll();
|
||||||
runApp(const ProviderScope(child: MyApp()));
|
runApp(ProviderScope(child: MyApp()));
|
||||||
doWhenWindowReady(() {
|
doWhenWindowReady(() {
|
||||||
appWindow.minSize = const Size(900, 700);
|
appWindow.minSize = const Size(900, 700);
|
||||||
appWindow.size = const Size(900, 700);
|
appWindow.size = const Size(900, 700);
|
||||||
@ -19,56 +21,28 @@ void main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends HookConsumerWidget {
|
||||||
const MyApp({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
static _MyAppState? of(BuildContext context) =>
|
|
||||||
context.findAncestorStateOfType<_MyAppState>();
|
|
||||||
@override
|
@override
|
||||||
State<MyApp> createState() => _MyAppState();
|
Widget build(BuildContext context, ref) {
|
||||||
}
|
var themeMode = ref.watch(themeProvider);
|
||||||
|
useEffect(() {
|
||||||
|
SharedPreferences.getInstance().then((localStorage) {
|
||||||
|
String? themeMode = localStorage.getString(LocalStorageKeys.themeMode);
|
||||||
|
var themeNotifier = ref.read(themeProvider.notifier);
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
|
||||||
ThemeMode _themeMode = ThemeMode.system;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
|
||||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
|
||||||
String? themeMode = localStorage.getString(LocalStorageKeys.themeMode);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
switch (themeMode) {
|
switch (themeMode) {
|
||||||
case "light":
|
case "light":
|
||||||
_themeMode = ThemeMode.light;
|
themeNotifier.state = ThemeMode.light;
|
||||||
break;
|
break;
|
||||||
case "dark":
|
case "dark":
|
||||||
_themeMode = ThemeMode.dark;
|
themeNotifier.state = ThemeMode.dark;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_themeMode = ThemeMode.system;
|
themeNotifier.state = ThemeMode.system;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}, []);
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setThemeMode(ThemeMode themeMode) {
|
|
||||||
SharedPreferences.getInstance().then((localStorage) {
|
|
||||||
localStorage.setString(
|
|
||||||
LocalStorageKeys.themeMode, themeMode.toString().split(".").last);
|
|
||||||
setState(() {
|
|
||||||
_themeMode = themeMode;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeMode getThemeMode() {
|
|
||||||
return _themeMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Spotube',
|
title: 'Spotube',
|
||||||
@ -156,7 +130,7 @@ class _MyAppState extends State<MyApp> {
|
|||||||
),
|
),
|
||||||
canvasColor: Colors.blueGrey[900],
|
canvasColor: Colors.blueGrey[900],
|
||||||
),
|
),
|
||||||
themeMode: _themeMode,
|
themeMode: themeMode,
|
||||||
home: const Home(),
|
home: const Home(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
6
lib/provider/ThemeProvider.dart
Normal file
6
lib/provider/ThemeProvider.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
var themeProvider = StateProvider<ThemeMode>((ref) {
|
||||||
|
return ThemeMode.system;
|
||||||
|
});
|
14
pubspec.lock
14
pubspec.lock
@ -188,6 +188,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "3.3.0"
|
||||||
|
flutter_hooks:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_hooks
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.2+1"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -219,6 +226,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.14.3"
|
version: "0.14.3"
|
||||||
|
hooks_riverpod:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hooks_riverpod
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
hotkey_manager:
|
hotkey_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -50,6 +50,8 @@ dependencies:
|
|||||||
path_provider: ^2.0.8
|
path_provider: ^2.0.8
|
||||||
collection: ^1.15.0
|
collection: ^1.15.0
|
||||||
flutter_riverpod: ^1.0.3
|
flutter_riverpod: ^1.0.3
|
||||||
|
flutter_hooks: ^0.18.2+1
|
||||||
|
hooks_riverpod: ^1.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user