mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Responsive Navigation for tablet & small devices
Responsive design utilites created
This commit is contained in:
parent
5b389564c1
commit
584f431b04
@ -1,12 +1,14 @@
|
|||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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/PlaylistCard.dart';
|
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistGenreView.dart';
|
import 'package:spotube/components/Playlist/PlaylistGenreView.dart';
|
||||||
import 'package:spotube/components/Shared/SpotubePageRoute.dart';
|
import 'package:spotube/components/Shared/SpotubePageRoute.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
class CategoryCard extends StatelessWidget {
|
class CategoryCard extends HookWidget {
|
||||||
final Category category;
|
final Category category;
|
||||||
final Iterable<PlaylistSimple>? playlists;
|
final Iterable<PlaylistSimple>? playlists;
|
||||||
const CategoryCard(
|
const CategoryCard(
|
||||||
@ -45,9 +47,10 @@ class CategoryCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Consumer(
|
HookConsumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
||||||
|
final scrollController = useScrollController();
|
||||||
return FutureBuilder<Iterable<PlaylistSimple>>(
|
return FutureBuilder<Iterable<PlaylistSimple>>(
|
||||||
future: playlists == null
|
future: playlists == null
|
||||||
? (category.id != "user-featured-playlists"
|
? (category.id != "user-featured-playlists"
|
||||||
@ -65,12 +68,18 @@ class CategoryCard extends StatelessWidget {
|
|||||||
child: CircularProgressIndicator.adaptive(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Wrap(
|
return Scrollbar(
|
||||||
spacing: 20,
|
controller: scrollController,
|
||||||
runSpacing: 20,
|
child: SingleChildScrollView(
|
||||||
|
controller: scrollController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: snapshot.data!
|
children: snapshot.data!
|
||||||
.map((playlist) => PlaylistCard(playlist))
|
.map((playlist) => PlaylistCard(playlist))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -2,9 +2,9 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
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:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:oauth2/oauth2.dart' show AuthorizationException;
|
import 'package:oauth2/oauth2.dart' show AuthorizationException;
|
||||||
import 'package:spotify/spotify.dart' hide Image, Player, Search;
|
import 'package:spotify/spotify.dart' hide Image, Player, Search;
|
||||||
|
|
||||||
@ -18,6 +18,9 @@ import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
|||||||
import 'package:spotube/components/Player/Player.dart';
|
import 'package:spotube/components/Player/Player.dart';
|
||||||
import 'package:spotube/components/Library/UserLibrary.dart';
|
import 'package:spotube/components/Library/UserLibrary.dart';
|
||||||
import 'package:spotube/helpers/oauth-login.dart';
|
import 'package:spotube/helpers/oauth-login.dart';
|
||||||
|
import 'package:spotube/hooks/useBreakpointValue.dart';
|
||||||
|
import 'package:spotube/hooks/usePagingController.dart';
|
||||||
|
import 'package:spotube/hooks/useSharedPreferences.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/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
@ -32,40 +35,70 @@ List<String> spotifyScopes = [
|
|||||||
"playlist-read-collaborative"
|
"playlist-read-collaborative"
|
||||||
];
|
];
|
||||||
|
|
||||||
class Home extends ConsumerStatefulWidget {
|
class Home extends HookConsumerWidget {
|
||||||
const Home({Key? key}) : super(key: key);
|
const Home({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_HomeState createState() => _HomeState();
|
Widget build(BuildContext context, ref) {
|
||||||
|
Auth auth = ref.watch(authProvider);
|
||||||
|
|
||||||
|
final pagingController =
|
||||||
|
usePagingController<int, Category>(firstPageKey: 0);
|
||||||
|
final int titleBarDragMaxWidth = useBreakpointValue(
|
||||||
|
md: 72,
|
||||||
|
lg: 256,
|
||||||
|
sm: 0,
|
||||||
|
xl: 0,
|
||||||
|
xxl: 0,
|
||||||
|
);
|
||||||
|
final _selectedIndex = useState(0);
|
||||||
|
_onSelectedIndexChanged(int index) => _selectedIndex.value = index;
|
||||||
|
|
||||||
|
final localStorage = useSharedPreferences();
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (localStorage == null) return null;
|
||||||
|
final String? clientId =
|
||||||
|
localStorage.getString(LocalStorageKeys.clientId);
|
||||||
|
final String? clientSecret =
|
||||||
|
localStorage.getString(LocalStorageKeys.clientSecret);
|
||||||
|
final String? accessToken =
|
||||||
|
localStorage.getString(LocalStorageKeys.accessToken);
|
||||||
|
final String? refreshToken =
|
||||||
|
localStorage.getString(LocalStorageKeys.refreshToken);
|
||||||
|
final String? expirationStr =
|
||||||
|
localStorage.getString(LocalStorageKeys.expiration);
|
||||||
|
listener(pageKey) async {
|
||||||
|
final spotify = ref.read(spotifyProvider);
|
||||||
|
try {
|
||||||
|
Page<Category> categories =
|
||||||
|
await spotify.categories.list(country: "US").getPage(15, pageKey);
|
||||||
|
|
||||||
|
var items = categories.items!.toList();
|
||||||
|
if (pageKey == 0) {
|
||||||
|
Category category = Category();
|
||||||
|
category.id = "user-featured-playlists";
|
||||||
|
category.name = "Featured";
|
||||||
|
items.insert(0, category);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeState extends ConsumerState<Home> {
|
if (categories.isLast && categories.items != null) {
|
||||||
final PagingController<int, Category> _pagingController =
|
pagingController.appendLastPage(items);
|
||||||
PagingController(firstPageKey: 0);
|
} else if (categories.items != null) {
|
||||||
|
pagingController.appendPage(items, categories.nextOffset);
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
pagingController.error = e;
|
||||||
|
print("[Home.pagingController.addPageRequestListener] $e");
|
||||||
|
print(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int _selectedIndex = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
|
||||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
|
||||||
String? clientId = localStorage.getString(LocalStorageKeys.clientId);
|
|
||||||
String? clientSecret =
|
|
||||||
localStorage.getString(LocalStorageKeys.clientSecret);
|
|
||||||
String? accessToken =
|
|
||||||
localStorage.getString(LocalStorageKeys.accessToken);
|
|
||||||
String? refreshToken =
|
|
||||||
localStorage.getString(LocalStorageKeys.refreshToken);
|
|
||||||
String? expirationStr =
|
|
||||||
localStorage.getString(LocalStorageKeys.expiration);
|
|
||||||
DateTime? expiration =
|
|
||||||
expirationStr != null ? DateTime.parse(expirationStr) : null;
|
|
||||||
try {
|
try {
|
||||||
Auth auth = ref.read(authProvider);
|
final DateTime? expiration =
|
||||||
|
expirationStr != null ? DateTime.parse(expirationStr) : null;
|
||||||
if (clientId != null && clientSecret != null) {
|
if (clientId != null && clientSecret != null) {
|
||||||
SpotifyApi spotifyApi = SpotifyApi(
|
SpotifyApi spotify = SpotifyApi(
|
||||||
SpotifyApiCredentials(
|
SpotifyApiCredentials(
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
@ -75,7 +108,7 @@ class _HomeState extends ConsumerState<Home> {
|
|||||||
scopes: spotifyScopes,
|
scopes: spotifyScopes,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
SpotifyApiCredentials credentials = await spotifyApi.getCredentials();
|
spotify.getCredentials().then((credentials) {
|
||||||
if (credentials.accessToken?.isNotEmpty ?? false) {
|
if (credentials.accessToken?.isNotEmpty ?? false) {
|
||||||
auth.setAuthState(
|
auth.setAuthState(
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
@ -87,35 +120,15 @@ class _HomeState extends ConsumerState<Home> {
|
|||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
_pagingController.addPageRequestListener((pageKey) async {
|
}).then((_) {
|
||||||
try {
|
pagingController.addPageRequestListener(listener);
|
||||||
SpotifyApi spotifyApi = ref.read(spotifyProvider);
|
|
||||||
Page<Category> categories = await spotifyApi.categories
|
|
||||||
.list(country: "US")
|
|
||||||
.getPage(15, pageKey);
|
|
||||||
|
|
||||||
var items = categories.items!.toList();
|
|
||||||
if (pageKey == 0) {
|
|
||||||
Category category = Category();
|
|
||||||
category.id = "user-featured-playlists";
|
|
||||||
category.name = "Featured";
|
|
||||||
items.insert(0, category);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (categories.isLast && categories.items != null) {
|
|
||||||
_pagingController.appendLastPage(items);
|
|
||||||
} else if (categories.items != null) {
|
|
||||||
_pagingController.appendPage(items, categories.nextOffset);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_pagingController.error = e;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} on AuthorizationException catch (_) {
|
} on AuthorizationException catch (_) {
|
||||||
if (clientId != null && clientSecret != null) {
|
if (clientId != null && clientSecret != null) {
|
||||||
oauthLogin(
|
oauthLogin(
|
||||||
ref.read(authProvider),
|
auth,
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
clientSecret: clientSecret,
|
clientSecret: clientSecret,
|
||||||
);
|
);
|
||||||
@ -124,23 +137,11 @@ class _HomeState extends ConsumerState<Home> {
|
|||||||
print("[Home.initState]: $e");
|
print("[Home.initState]: $e");
|
||||||
print(stack);
|
print(stack);
|
||||||
}
|
}
|
||||||
});
|
return () {
|
||||||
}
|
pagingController.removePageRequestListener(listener);
|
||||||
|
};
|
||||||
|
}, [localStorage]);
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_pagingController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSelectedIndexChanged(int index) => setState(() {
|
|
||||||
_selectedIndex = index;
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Auth auth = ref.watch(authProvider);
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
|
||||||
if (!auth.isLoggedIn) {
|
if (!auth.isLoggedIn) {
|
||||||
return const Login();
|
return const Login();
|
||||||
}
|
}
|
||||||
@ -156,11 +157,8 @@ class _HomeState extends ConsumerState<Home> {
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: width > 400 && width <= 700
|
maxWidth: titleBarDragMaxWidth.toDouble(),
|
||||||
? 72
|
),
|
||||||
: width > 700
|
|
||||||
? 256
|
|
||||||
: 0),
|
|
||||||
color:
|
color:
|
||||||
Theme.of(context).navigationRailTheme.backgroundColor,
|
Theme.of(context).navigationRailTheme.backgroundColor,
|
||||||
child: MoveWindow(),
|
child: MoveWindow(),
|
||||||
@ -176,16 +174,16 @@ class _HomeState extends ConsumerState<Home> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Sidebar(
|
Sidebar(
|
||||||
selectedIndex: _selectedIndex,
|
selectedIndex: _selectedIndex.value,
|
||||||
onSelectedIndexChanged: _onSelectedIndexChanged,
|
onSelectedIndexChanged: _onSelectedIndexChanged,
|
||||||
),
|
),
|
||||||
// contents of the spotify
|
// contents of the spotify
|
||||||
if (_selectedIndex == 0)
|
if (_selectedIndex.value == 0)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: PagedListView(
|
child: PagedListView(
|
||||||
pagingController: _pagingController,
|
pagingController: pagingController,
|
||||||
builderDelegate: PagedChildBuilderDelegate<Category>(
|
builderDelegate: PagedChildBuilderDelegate<Category>(
|
||||||
itemBuilder: (context, item, index) {
|
itemBuilder: (context, item, index) {
|
||||||
return CategoryCard(item);
|
return CategoryCard(item);
|
||||||
@ -194,16 +192,16 @@ class _HomeState extends ConsumerState<Home> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_selectedIndex == 1) const Search(),
|
if (_selectedIndex.value == 1) const Search(),
|
||||||
if (_selectedIndex == 2) const UserLibrary(),
|
if (_selectedIndex.value == 2) const UserLibrary(),
|
||||||
if (_selectedIndex == 3) const Lyrics(),
|
if (_selectedIndex.value == 3) const Lyrics(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// player itself
|
// player itself
|
||||||
const Player(),
|
const Player(),
|
||||||
SpotubeNavigationBar(
|
SpotubeNavigationBar(
|
||||||
selectedIndex: _selectedIndex,
|
selectedIndex: _selectedIndex.value,
|
||||||
onSelectedIndexChanged: _onSelectedIndexChanged,
|
onSelectedIndexChanged: _onSelectedIndexChanged,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -6,6 +6,7 @@ import 'package:spotify/spotify.dart' hide Image;
|
|||||||
import 'package:spotube/components/Settings.dart';
|
import 'package:spotube/components/Settings.dart';
|
||||||
import 'package:spotube/components/Shared/SpotubePageRoute.dart';
|
import 'package:spotube/components/Shared/SpotubePageRoute.dart';
|
||||||
import 'package:spotube/helpers/image-to-url-string.dart';
|
import 'package:spotube/helpers/image-to-url-string.dart';
|
||||||
|
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
import '../../models/sideBarTiles.dart';
|
import '../../models/sideBarTiles.dart';
|
||||||
@ -28,7 +29,7 @@ class Sidebar extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToSettings(BuildContext context) {
|
static void goToSettings(BuildContext context) {
|
||||||
Navigator.of(context).push(SpotubePageRoute(
|
Navigator.of(context).push(SpotubePageRoute(
|
||||||
child: const Settings(),
|
child: const Settings(),
|
||||||
));
|
));
|
||||||
@ -36,21 +37,25 @@ class Sidebar extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final width = MediaQuery.of(context).size.width;
|
final breakpoints = useBreakpoints();
|
||||||
if (width <= 400) return Container();
|
if (breakpoints.isSm) return Container();
|
||||||
final extended = useState(false);
|
final extended = useState(false);
|
||||||
final SpotifyApi spotify = ref.watch(spotifyProvider);
|
final SpotifyApi spotify = ref.watch(spotifyProvider);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (width <= 700 && extended.value) {
|
if (breakpoints.isMd && extended.value) {
|
||||||
extended.value = false;
|
extended.value = false;
|
||||||
} else if (width > 700 && !extended.value) {
|
} else if (breakpoints.isMoreThanOrEqualTo(Breakpoints.lg) &&
|
||||||
|
!extended.value) {
|
||||||
extended.value = true;
|
extended.value = true;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return NavigationRail(
|
return NavigationRail(
|
||||||
destinations: sidebarTileList
|
destinations: sidebarTileList
|
||||||
.map((e) => NavigationRailDestination(
|
.map(
|
||||||
|
(e) => NavigationRailDestination(
|
||||||
icon: Icon(e.icon),
|
icon: Icon(e.icon),
|
||||||
label: Text(
|
label: Text(
|
||||||
e.title,
|
e.title,
|
||||||
@ -59,7 +64,8 @@ class Sidebar extends HookConsumerWidget {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
))
|
),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
selectedIndex: selectedIndex,
|
selectedIndex: selectedIndex,
|
||||||
onDestinationSelected: onSelectedIndexChanged,
|
onDestinationSelected: onSelectedIndexChanged,
|
||||||
@ -104,11 +110,11 @@ class Sidebar extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.settings_outlined),
|
icon: const Icon(Icons.settings_outlined),
|
||||||
onPressed: () => _goToSettings(context)),
|
onPressed: () => goToSettings(context)),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
: InkWell(
|
: InkWell(
|
||||||
onTap: () => _goToSettings(context),
|
onTap: () => goToSettings(context),
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
backgroundImage: CachedNetworkImageProvider(avatarImg),
|
backgroundImage: CachedNetworkImageProvider(avatarImg),
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:spotube/components/Home/Sidebar.dart';
|
||||||
|
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||||
import 'package:spotube/models/sideBarTiles.dart';
|
import 'package:spotube/models/sideBarTiles.dart';
|
||||||
|
|
||||||
class SpotubeNavigationBar extends HookWidget {
|
class SpotubeNavigationBar extends HookWidget {
|
||||||
@ -14,17 +16,21 @@ class SpotubeNavigationBar extends HookWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final width = MediaQuery.of(context).size.width;
|
final breakpoint = useBreakpoints();
|
||||||
|
|
||||||
if (width > 400) return Container();
|
if (breakpoint.isMoreThan(Breakpoints.sm)) return Container();
|
||||||
return NavigationBar(
|
return NavigationBar(
|
||||||
destinations: sidebarTileList
|
destinations: [
|
||||||
.map(
|
...sidebarTileList.map(
|
||||||
(e) => NavigationDestination(icon: Icon(e.icon), label: e.title),
|
(e) => NavigationDestination(icon: Icon(e.icon), label: e.title),
|
||||||
|
),
|
||||||
|
const NavigationDestination(
|
||||||
|
icon: Icon(Icons.settings_rounded),
|
||||||
|
label: "Settings",
|
||||||
)
|
)
|
||||||
.toList(),
|
],
|
||||||
selectedIndex: selectedIndex,
|
selectedIndex: selectedIndex,
|
||||||
onDestinationSelected: onSelectedIndexChanged,
|
onDestinationSelected: (i) => Sidebar.goToSettings(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ class PlaylistCard extends ConsumerWidget {
|
|||||||
bool isPlaylistPlaying = playback.currentPlaylist != null &&
|
bool isPlaylistPlaying = playback.currentPlaylist != null &&
|
||||||
playback.currentPlaylist!.id == playlist.id;
|
playback.currentPlaylist!.id == playlist.id;
|
||||||
return PlaybuttonCard(
|
return PlaybuttonCard(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
title: playlist.name!,
|
title: playlist.name!,
|
||||||
imageUrl: playlist.images![0].url!,
|
imageUrl: playlist.images![0].url!,
|
||||||
isPlaying: isPlaylistPlaying,
|
isPlaying: isPlaylistPlaying,
|
||||||
|
@ -5,6 +5,7 @@ class PlaybuttonCard extends StatelessWidget {
|
|||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
final void Function()? onPlaybuttonPressed;
|
final void Function()? onPlaybuttonPressed;
|
||||||
final String? description;
|
final String? description;
|
||||||
|
final EdgeInsetsGeometry? margin;
|
||||||
final String imageUrl;
|
final String imageUrl;
|
||||||
final bool isPlaying;
|
final bool isPlaying;
|
||||||
final String title;
|
final String title;
|
||||||
@ -12,6 +13,7 @@ class PlaybuttonCard extends StatelessWidget {
|
|||||||
required this.imageUrl,
|
required this.imageUrl,
|
||||||
required this.isPlaying,
|
required this.isPlaying,
|
||||||
required this.title,
|
required this.title,
|
||||||
|
this.margin,
|
||||||
this.description,
|
this.description,
|
||||||
this.onPlaybuttonPressed,
|
this.onPlaybuttonPressed,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
@ -20,7 +22,9 @@ class PlaybuttonCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
return Container(
|
||||||
|
margin: margin,
|
||||||
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 200),
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
@ -102,6 +106,7 @@ class PlaybuttonCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
lib/hooks/useAsyncEffect.dart
Normal file
18
lib/hooks/useAsyncEffect.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
|
void useAsyncEffect(
|
||||||
|
FutureOr<dynamic> Function() effect, [
|
||||||
|
FutureOr<dynamic> Function()? cleanup,
|
||||||
|
List<Object>? keys,
|
||||||
|
]) {
|
||||||
|
useEffect(() {
|
||||||
|
Future.microtask(effect);
|
||||||
|
return () {
|
||||||
|
if (cleanup != null) {
|
||||||
|
Future.microtask(cleanup);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, keys);
|
||||||
|
}
|
17
lib/hooks/useBreakpointValue.dart
Normal file
17
lib/hooks/useBreakpointValue.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||||
|
|
||||||
|
useBreakpointValue({sm, md, lg, xl, xxl}) {
|
||||||
|
final breakpoint = useBreakpoints();
|
||||||
|
|
||||||
|
if (breakpoint.isSm) {
|
||||||
|
return sm;
|
||||||
|
} else if (breakpoint.isMd) {
|
||||||
|
return md;
|
||||||
|
} else if (breakpoint.isXl) {
|
||||||
|
return xl;
|
||||||
|
} else if (breakpoint.isXxl) {
|
||||||
|
return xxl;
|
||||||
|
} else {
|
||||||
|
return lg;
|
||||||
|
}
|
||||||
|
}
|
99
lib/hooks/useBreakpoints.dart
Normal file
99
lib/hooks/useBreakpoints.dart
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
|
class BreakpointUtils {
|
||||||
|
Breakpoints breakpoint;
|
||||||
|
List<Breakpoints> breakpointList = [
|
||||||
|
Breakpoints.sm,
|
||||||
|
Breakpoints.md,
|
||||||
|
Breakpoints.lg,
|
||||||
|
Breakpoints.xl,
|
||||||
|
Breakpoints.xxl
|
||||||
|
];
|
||||||
|
BreakpointUtils(this.breakpoint);
|
||||||
|
|
||||||
|
get isSm => breakpoint == Breakpoints.sm;
|
||||||
|
get isMd => breakpoint == Breakpoints.md;
|
||||||
|
get isLg => breakpoint == Breakpoints.lg;
|
||||||
|
get isXl => breakpoint == Breakpoints.xl;
|
||||||
|
get isXxl => breakpoint == Breakpoints.xxl;
|
||||||
|
|
||||||
|
bool isMoreThanOrEqualTo(Breakpoints b) {
|
||||||
|
return breakpointList
|
||||||
|
.sublist(breakpointList.indexOf(b))
|
||||||
|
.contains(breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLessThanOrEqualTo(Breakpoints b) {
|
||||||
|
return breakpointList
|
||||||
|
.sublist(0, breakpointList.indexOf(b) + 1)
|
||||||
|
.contains(breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isMoreThan(Breakpoints b) {
|
||||||
|
return breakpointList
|
||||||
|
.sublist(breakpointList.indexOf(b) + 1)
|
||||||
|
.contains(breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLessThan(Breakpoints b) {
|
||||||
|
return breakpointList
|
||||||
|
.sublist(0, breakpointList.indexOf(b))
|
||||||
|
.contains(breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator >(other) {
|
||||||
|
return isMoreThan(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <(other) {
|
||||||
|
return isLessThan(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator >=(other) {
|
||||||
|
return isMoreThanOrEqualTo(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <=(other) {
|
||||||
|
return isLessThanOrEqualTo(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Breakpoints { sm, md, lg, xl, xxl }
|
||||||
|
|
||||||
|
BreakpointUtils useBreakpoints() {
|
||||||
|
final context = useContext();
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
final breakpoint = useState(Breakpoints.lg);
|
||||||
|
final utils = BreakpointUtils(breakpoint.value);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (width >= 1920 && breakpoint.value != Breakpoints.xxl) {
|
||||||
|
breakpoint.value = Breakpoints.xxl;
|
||||||
|
} else if (width >= 1366 &&
|
||||||
|
width < 1920 &&
|
||||||
|
breakpoint.value != Breakpoints.xl) {
|
||||||
|
breakpoint.value = Breakpoints.xl;
|
||||||
|
} else if (width >= 768 &&
|
||||||
|
width < 1366 &&
|
||||||
|
breakpoint.value != Breakpoints.lg) {
|
||||||
|
breakpoint.value = Breakpoints.lg;
|
||||||
|
} else if (width >= 360 &&
|
||||||
|
width < 768 &&
|
||||||
|
breakpoint.value != Breakpoints.md) {
|
||||||
|
breakpoint.value = Breakpoints.md;
|
||||||
|
} else if (width >= 250 &&
|
||||||
|
width < 360 &&
|
||||||
|
breakpoint.value != Breakpoints.sm) {
|
||||||
|
breakpoint.value = Breakpoints.sm;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [width]);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
utils.breakpoint = breakpoint.value;
|
||||||
|
return null;
|
||||||
|
}, [breakpoint.value]);
|
||||||
|
|
||||||
|
return utils;
|
||||||
|
}
|
53
lib/hooks/usePagingController.dart
Normal file
53
lib/hooks/usePagingController.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
|
||||||
|
PagingController<PageKeyType, ItemType>
|
||||||
|
usePagingController<PageKeyType, ItemType>({
|
||||||
|
required final PageKeyType firstPageKey,
|
||||||
|
final int? invisibleItemsThreshold,
|
||||||
|
List<Object?>? keys,
|
||||||
|
}) {
|
||||||
|
return use(
|
||||||
|
_PagingControllerHook<PageKeyType, ItemType>(
|
||||||
|
firstPageKey: firstPageKey,
|
||||||
|
invisibleItemsThreshold: invisibleItemsThreshold,
|
||||||
|
keys: keys,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PagingControllerHook<PageKeyType, ItemType>
|
||||||
|
extends Hook<PagingController<PageKeyType, ItemType>> {
|
||||||
|
const _PagingControllerHook({
|
||||||
|
required this.firstPageKey,
|
||||||
|
this.invisibleItemsThreshold,
|
||||||
|
List<Object?>? keys,
|
||||||
|
}) : super(keys: keys);
|
||||||
|
|
||||||
|
final PageKeyType firstPageKey;
|
||||||
|
final int? invisibleItemsThreshold;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HookState<PagingController<PageKeyType, ItemType>,
|
||||||
|
Hook<PagingController<PageKeyType, ItemType>>>
|
||||||
|
createState() => _PagingControllerHookState<PageKeyType, ItemType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PagingControllerHookState<PageKeyType, ItemType> extends HookState<
|
||||||
|
PagingController<PageKeyType, ItemType>,
|
||||||
|
_PagingControllerHook<PageKeyType, ItemType>> {
|
||||||
|
late final controller = PagingController<PageKeyType, ItemType>(
|
||||||
|
firstPageKey: hook.firstPageKey,
|
||||||
|
invisibleItemsThreshold: hook.invisibleItemsThreshold);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PagingController<PageKeyType, ItemType> build(BuildContext context) =>
|
||||||
|
controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() => controller.dispose();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugLabel => 'usePagingController';
|
||||||
|
}
|
9
lib/hooks/useSharedPreferences.dart
Normal file
9
lib/hooks/useSharedPreferences.dart
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
SharedPreferences? useSharedPreferences() {
|
||||||
|
final future = useMemoized(SharedPreferences.getInstance);
|
||||||
|
final snapshot = useFuture(future, initialData: null);
|
||||||
|
|
||||||
|
return snapshot.data;
|
||||||
|
}
|
@ -52,6 +52,11 @@ class Auth with ChangeNotifier {
|
|||||||
_isLoggedIn = false;
|
_isLoggedIn = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "Auth(clientId: $clientId, clientSecret: $clientSecret, accessToken: $accessToken, refreshToken: $refreshToken, expiration: $expiration, isLoggedIn: $isLoggedIn)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var authProvider = ChangeNotifierProvider<Auth>((ref) => Auth());
|
var authProvider = ChangeNotifierProvider<Auth>((ref) => Auth());
|
||||||
|
@ -7,6 +7,7 @@ import 'package:spotube/provider/Auth.dart';
|
|||||||
|
|
||||||
var spotifyProvider = Provider<SpotifyApi>((ref) {
|
var spotifyProvider = Provider<SpotifyApi>((ref) {
|
||||||
Auth authState = ref.watch(authProvider);
|
Auth authState = ref.watch(authProvider);
|
||||||
|
|
||||||
return SpotifyApi(
|
return SpotifyApi(
|
||||||
SpotifyApiCredentials(
|
SpotifyApiCredentials(
|
||||||
authState.clientId,
|
authState.clientId,
|
||||||
|
Loading…
Reference in New Issue
Block a user