mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat(playlist,album page): play and shuffle take full width on smaller screens, add new xs breakpoint
This commit is contained in:
parent
7a8bd92104
commit
dce1b88694
@ -53,7 +53,7 @@ class AlbumCard extends HookConsumerWidget {
|
|||||||
[playlistNotifier, query?.data, album.tracks],
|
[playlistNotifier, query?.data, album.tracks],
|
||||||
);
|
);
|
||||||
final int marginH =
|
final int marginH =
|
||||||
useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
|
useBreakpointValue(xs: 10, sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
|
||||||
|
|
||||||
final updating = useState(false);
|
final updating = useState(false);
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
@ -35,6 +35,7 @@ class ArtistCard extends HookConsumerWidget {
|
|||||||
final radius = BorderRadius.circular(15);
|
final radius = BorderRadius.circular(15);
|
||||||
|
|
||||||
final double size = useBreakpointValue<double>(
|
final double size = useBreakpointValue<double>(
|
||||||
|
xs: 130,
|
||||||
sm: 130,
|
sm: 130,
|
||||||
md: 150,
|
md: 150,
|
||||||
others: 170,
|
others: 170,
|
||||||
|
@ -188,7 +188,7 @@ class _MultiSelectDialog<T> extends HookWidget {
|
|||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
title: dialogTitle ?? const Text('Select'),
|
title: dialogTitle ?? const Text('Select'),
|
||||||
contentPadding: mediaQuery.isSm ? const EdgeInsets.all(16) : null,
|
contentPadding: mediaQuery.mdAndUp ? null : const EdgeInsets.all(16),
|
||||||
insetPadding: const EdgeInsets.all(16),
|
insetPadding: const EdgeInsets.all(16),
|
||||||
actions: [
|
actions: [
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
|
@ -24,6 +24,7 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
final albumsQuery = useQueries.album.ofMine(ref);
|
final albumsQuery = useQueries.album.ofMine(ref);
|
||||||
|
|
||||||
final spacing = useBreakpointValue<double>(
|
final spacing = useBreakpointValue<double>(
|
||||||
|
xs: 0,
|
||||||
sm: 0,
|
sm: 0,
|
||||||
others: 20,
|
others: 20,
|
||||||
);
|
);
|
||||||
|
@ -39,7 +39,7 @@ class PlayerTrackDetails extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (mediaQuery.isSm || mediaQuery.isMd)
|
if (mediaQuery.mdAndDown)
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -121,7 +121,7 @@ class PlaylistCreateDialogButton extends HookConsumerWidget {
|
|||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
|
||||||
if (mediaQuery.isSm) {
|
if (mediaQuery.smAndDown) {
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
@ -58,8 +58,7 @@ class BottomPlayer extends HookConsumerWidget {
|
|||||||
// returning an empty non spacious Container as the overlay will take
|
// returning an empty non spacious Container as the overlay will take
|
||||||
// place in the global overlay stack aka [_entries]
|
// place in the global overlay stack aka [_entries]
|
||||||
if (layoutMode == LayoutMode.compact ||
|
if (layoutMode == LayoutMode.compact ||
|
||||||
((mediaQuery.isSm || mediaQuery.isMd) &&
|
((mediaQuery.mdAndDown) && layoutMode == LayoutMode.adaptive)) {
|
||||||
layoutMode == LayoutMode.adaptive)) {
|
|
||||||
return PlayerOverlay(albumArt: albumArt);
|
return PlayerOverlay(albumArt: albumArt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,16 +82,17 @@ class Sidebar extends HookConsumerWidget {
|
|||||||
}, [controller]);
|
}, [controller]);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
if (!context.mounted) return;
|
||||||
if (mediaQuery.lgAndUp && !controller.extended) {
|
if (mediaQuery.lgAndUp && !controller.extended) {
|
||||||
controller.setExtended(true);
|
controller.setExtended(true);
|
||||||
} else if ((mediaQuery.isSm || mediaQuery.isMd) && controller.extended) {
|
} else if (mediaQuery.mdAndDown && controller.extended) {
|
||||||
controller.setExtended(false);
|
controller.setExtended(false);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [mediaQuery, controller]);
|
}, [mediaQuery, controller]);
|
||||||
|
|
||||||
if (layoutMode == LayoutMode.compact ||
|
if (layoutMode == LayoutMode.compact ||
|
||||||
(mediaQuery.isSm && layoutMode == LayoutMode.adaptive)) {
|
(mediaQuery.smAndDown && layoutMode == LayoutMode.adaptive)) {
|
||||||
return Scaffold(body: child);
|
return Scaffold(body: child);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +187,7 @@ class SidebarHeader extends HookWidget {
|
|||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
if (mediaQuery.isSm || mediaQuery.isMd) {
|
if (mediaQuery.mdAndDown) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
@ -236,7 +237,7 @@ class SidebarFooter extends HookConsumerWidget {
|
|||||||
|
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
if (mediaQuery.isSm || mediaQuery.isMd) {
|
if (mediaQuery.mdAndDown) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: const Icon(SpotubeIcons.settings),
|
icon: const Icon(SpotubeIcons.settings),
|
||||||
onPressed: () => Sidebar.goToSettings(context),
|
onPressed: () => Sidebar.goToSettings(context),
|
||||||
|
@ -27,10 +27,11 @@ class AdaptiveListTile extends HookWidget {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: title,
|
title: title,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
trailing:
|
trailing: breakOn ?? mediaQuery.smAndDown
|
||||||
breakOn ?? mediaQuery.isSm ? null : trailing?.call(context, null),
|
? null
|
||||||
|
: trailing?.call(context, null),
|
||||||
leading: leading,
|
leading: leading,
|
||||||
onTap: breakOn ?? mediaQuery.isSm
|
onTap: breakOn ?? mediaQuery.smAndDown
|
||||||
? () {
|
? () {
|
||||||
onTap?.call();
|
onTap?.call();
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -40,6 +40,7 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
final radius = BorderRadius.circular(15);
|
final radius = BorderRadius.circular(15);
|
||||||
|
|
||||||
final double size = useBreakpointValue<double>(
|
final double size = useBreakpointValue<double>(
|
||||||
|
xs: 130,
|
||||||
sm: 130,
|
sm: 130,
|
||||||
md: 150,
|
md: 150,
|
||||||
others: 170,
|
others: 170,
|
||||||
@ -47,6 +48,7 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
170;
|
170;
|
||||||
|
|
||||||
final end = useBreakpointValue<double>(
|
final end = useBreakpointValue<double>(
|
||||||
|
xs: 15,
|
||||||
sm: 15,
|
sm: 15,
|
||||||
others: 20,
|
others: 20,
|
||||||
) ??
|
) ??
|
||||||
|
@ -21,6 +21,7 @@ class ShimmerArtistProfile extends HookWidget {
|
|||||||
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
||||||
|
|
||||||
final avatarWidth = useBreakpointValue(
|
final avatarWidth = useBreakpointValue(
|
||||||
|
xs: MediaQuery.of(context).size.width * 0.80,
|
||||||
sm: MediaQuery.of(context).size.width * 0.80,
|
sm: MediaQuery.of(context).size.width * 0.80,
|
||||||
md: MediaQuery.of(context).size.width * 0.50,
|
md: MediaQuery.of(context).size.width * 0.50,
|
||||||
lg: MediaQuery.of(context).size.width * 0.30,
|
lg: MediaQuery.of(context).size.width * 0.30,
|
||||||
|
@ -18,6 +18,7 @@ class ShimmerCategories extends HookWidget {
|
|||||||
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
||||||
|
|
||||||
final shimmerCount = useBreakpointValue(
|
final shimmerCount = useBreakpointValue(
|
||||||
|
xs: 2,
|
||||||
sm: 2,
|
sm: 2,
|
||||||
md: 3,
|
md: 3,
|
||||||
lg: 3,
|
lg: 3,
|
||||||
|
@ -32,7 +32,7 @@ class ShimmerLyrics extends HookWidget {
|
|||||||
if (mediaQuery.isMd) {
|
if (mediaQuery.isMd) {
|
||||||
widthsCp.removeLast();
|
widthsCp.removeLast();
|
||||||
}
|
}
|
||||||
if (mediaQuery.isSm) {
|
if (mediaQuery.smAndDown) {
|
||||||
widthsCp.removeLast();
|
widthsCp.removeLast();
|
||||||
widthsCp.removeLast();
|
widthsCp.removeLast();
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ class ShimmerPlaybuttonCard extends HookWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final Size size = useBreakpointValue<Size>(
|
final Size size = useBreakpointValue<Size>(
|
||||||
|
xs: const Size(130, 200),
|
||||||
sm: const Size(130, 200),
|
sm: const Size(130, 200),
|
||||||
md: const Size(150, 220),
|
md: const Size(150, 220),
|
||||||
others: const Size(170, 240),
|
others: const Size(170, 240),
|
||||||
|
@ -18,6 +18,7 @@ class ThemedButtonsTabBar extends HookWidget implements PreferredSizeWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final breakpoint = useBreakpointValue(
|
final breakpoint = useBreakpointValue(
|
||||||
|
xs: 85.0,
|
||||||
sm: 85.0,
|
sm: 85.0,
|
||||||
md: 35.0,
|
md: 35.0,
|
||||||
others: 0.0,
|
others: 0.0,
|
||||||
|
@ -0,0 +1,195 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/album/album_card.dart';
|
||||||
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
||||||
|
class TrackCollectionHeading<T> extends HookConsumerWidget {
|
||||||
|
final String title;
|
||||||
|
final String? description;
|
||||||
|
final String titleImage;
|
||||||
|
final List<Widget> buttons;
|
||||||
|
final AlbumSimple? album;
|
||||||
|
final Query<List<TrackSimple>, T> tracksSnapshot;
|
||||||
|
final bool isPlaying;
|
||||||
|
final void Function([Track? currentTrack]) onPlay;
|
||||||
|
final void Function([Track? currentTrack]) onShuffledPlay;
|
||||||
|
final PaletteColor? color;
|
||||||
|
|
||||||
|
const TrackCollectionHeading({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.titleImage,
|
||||||
|
required this.buttons,
|
||||||
|
required this.tracksSnapshot,
|
||||||
|
required this.isPlaying,
|
||||||
|
required this.onPlay,
|
||||||
|
required this.onShuffledPlay,
|
||||||
|
required this.color,
|
||||||
|
this.description,
|
||||||
|
this.album,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constrains) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: UniversalImage.imageProvider(titleImage),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Colors.black45,
|
||||||
|
theme.colorScheme.surface,
|
||||||
|
],
|
||||||
|
begin: const FractionalOffset(0, 0),
|
||||||
|
end: const FractionalOffset(0, 1),
|
||||||
|
tileMode: TileMode.clamp,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
|
child: Flex(
|
||||||
|
direction:
|
||||||
|
constrains.mdAndDown ? Axis.vertical : Axis.horizontal,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 200),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: UniversalImage(
|
||||||
|
path: titleImage,
|
||||||
|
placeholder: Assets.albumPlaceholder.path,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10, height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: theme.textTheme.titleLarge!.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (album != null)
|
||||||
|
Text(
|
||||||
|
"${AlbumType.from(album?.albumType).formatted} • ${context.l10n.released} • ${DateTime.tryParse(
|
||||||
|
album?.releaseDate ?? "",
|
||||||
|
)?.year}",
|
||||||
|
style: theme.textTheme.titleMedium!.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (description != null)
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constrains.mdAndDown ? 400 : 300,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
description!,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
IconTheme(
|
||||||
|
data: theme.iconTheme.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: buttons,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constrains.mdAndDown ? 400 : 300,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: constrains.smAndUp
|
||||||
|
? MainAxisSize.min
|
||||||
|
: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: color?.color,
|
||||||
|
),
|
||||||
|
label: Text(context.l10n.shuffle),
|
||||||
|
icon: const Icon(SpotubeIcons.shuffle),
|
||||||
|
onPressed:
|
||||||
|
tracksSnapshot.data == null || isPlaying
|
||||||
|
? null
|
||||||
|
: onShuffledPlay,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: color?.color,
|
||||||
|
foregroundColor: color?.bodyTextColor,
|
||||||
|
),
|
||||||
|
onPressed: tracksSnapshot.data != null
|
||||||
|
? onPlay
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
|
isPlaying
|
||||||
|
? SpotubeIcons.stop
|
||||||
|
: SpotubeIcons.play,
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
isPlaying
|
||||||
|
? context.l10n.stop
|
||||||
|
: context.l10n.play,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,12 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/album/album_card.dart';
|
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
|
||||||
import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
|
import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/hooks/use_custom_status_bar_color.dart';
|
import 'package:spotube/hooks/use_custom_status_bar_color.dart';
|
||||||
@ -31,8 +27,8 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
final String titleImage;
|
final String titleImage;
|
||||||
final bool isPlaying;
|
final bool isPlaying;
|
||||||
final void Function([Track? currentTrack]) onPlay;
|
final void Function([Track? currentTrack]) onPlay;
|
||||||
final void Function() onAddToQueue;
|
|
||||||
final void Function([Track? currentTrack]) onShuffledPlay;
|
final void Function([Track? currentTrack]) onShuffledPlay;
|
||||||
|
final void Function() onAddToQueue;
|
||||||
final void Function() onShare;
|
final void Function() onShare;
|
||||||
final Widget? heartBtn;
|
final Widget? heartBtn;
|
||||||
final AlbumSimple? album;
|
final AlbumSimple? album;
|
||||||
@ -187,145 +183,17 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: DecoratedBox(
|
background: TrackCollectionHeading<T>(
|
||||||
decoration: BoxDecoration(
|
color: color,
|
||||||
image: DecorationImage(
|
title: title,
|
||||||
image: UniversalImage.imageProvider(titleImage),
|
description: description,
|
||||||
fit: BoxFit.cover,
|
titleImage: titleImage,
|
||||||
),
|
isPlaying: isPlaying,
|
||||||
),
|
onPlay: onPlay,
|
||||||
child: BackdropFilter(
|
onShuffledPlay: onShuffledPlay,
|
||||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
tracksSnapshot: tracksSnapshot,
|
||||||
child: DecoratedBox(
|
buttons: buttons,
|
||||||
decoration: BoxDecoration(
|
album: album,
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
Colors.black45,
|
|
||||||
theme.colorScheme.surface,
|
|
||||||
],
|
|
||||||
begin: const FractionalOffset(0, 0),
|
|
||||||
end: const FractionalOffset(0, 1),
|
|
||||||
tileMode: TileMode.clamp,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
type: MaterialType.transparency,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 20,
|
|
||||||
runSpacing: 20,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
runAlignment: WrapAlignment.center,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
constraints:
|
|
||||||
const BoxConstraints(maxHeight: 200),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
child: UniversalImage(
|
|
||||||
path: titleImage,
|
|
||||||
placeholder:
|
|
||||||
Assets.albumPlaceholder.path,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: theme.textTheme.titleLarge!
|
|
||||||
.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (album != null)
|
|
||||||
Text(
|
|
||||||
"${AlbumType.from(album?.albumType).formatted} • ${context.l10n.released} • ${DateTime.tryParse(
|
|
||||||
album?.releaseDate ?? "",
|
|
||||||
)?.year}",
|
|
||||||
style: theme.textTheme.titleMedium!
|
|
||||||
.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (description != null)
|
|
||||||
Text(
|
|
||||||
description!,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
IconTheme(
|
|
||||||
data: theme.iconTheme.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: buttons,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
FilledButton.icon(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: color?.color,
|
|
||||||
),
|
|
||||||
label: Text(context.l10n.shuffle),
|
|
||||||
icon: const Icon(
|
|
||||||
SpotubeIcons.shuffle),
|
|
||||||
onPressed:
|
|
||||||
tracksSnapshot.data == null ||
|
|
||||||
isPlaying
|
|
||||||
? null
|
|
||||||
: onShuffledPlay,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
FilledButton.icon(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: color?.color,
|
|
||||||
foregroundColor:
|
|
||||||
color?.bodyTextColor,
|
|
||||||
),
|
|
||||||
onPressed:
|
|
||||||
tracksSnapshot.data != null
|
|
||||||
? onPlay
|
|
||||||
: null,
|
|
||||||
icon: Icon(
|
|
||||||
isPlaying
|
|
||||||
? SpotubeIcons.stop
|
|
||||||
: SpotubeIcons.play,
|
|
||||||
),
|
|
||||||
label: Text(
|
|
||||||
isPlaying
|
|
||||||
? context.l10n.stop
|
|
||||||
: context.l10n.play,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -361,7 +229,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
// scroll the flexible space
|
// scroll the flexible space
|
||||||
// to allow more space for search results
|
// to allow more space for search results
|
||||||
controller.animateTo(
|
controller.animateTo(
|
||||||
390,
|
330,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
@ -61,7 +61,7 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
|
|
||||||
return LayoutBuilder(builder: (context, constrains) {
|
return LayoutBuilder(builder: (context, constrains) {
|
||||||
return HoverBuilder(
|
return HoverBuilder(
|
||||||
permanentState: isPlaying || constrains.isSm ? true : null,
|
permanentState: isPlaying || constrains.smAndDown ? true : null,
|
||||||
builder: (context, isHovering) {
|
builder: (context, isHovering) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
selected: isPlaying,
|
selected: isPlaying,
|
||||||
@ -89,7 +89,7 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (constrains.isSm)
|
else if (constrains.smAndDown)
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
if (onChanged != null)
|
if (onChanged != null)
|
||||||
Checkbox.adaptive(
|
Checkbox.adaptive(
|
||||||
|
@ -390,6 +390,7 @@ class TracksTableView extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (isSliver) {
|
if (isSliver) {
|
||||||
return SliverSafeArea(
|
return SliverSafeArea(
|
||||||
|
top: false,
|
||||||
sliver: SliverList(delegate: SliverChildListDelegate(children)),
|
sliver: SliverList(delegate: SliverChildListDelegate(children)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,39 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
extension ContainerBreakpoints on BoxConstraints {
|
extension ContainerBreakpoints on BoxConstraints {
|
||||||
bool get isSm => biggest.width <= 640;
|
bool get isXs => biggest.width <= 480;
|
||||||
|
bool get isSm => biggest.width > 480 && biggest.width <= 640;
|
||||||
bool get isMd => biggest.width > 640 && biggest.width <= 768;
|
bool get isMd => biggest.width > 640 && biggest.width <= 768;
|
||||||
bool get isLg => biggest.width > 768 && biggest.width <= 1024;
|
bool get isLg => biggest.width > 768 && biggest.width <= 1024;
|
||||||
bool get isXl => biggest.width > 1024 && biggest.width <= 1280;
|
bool get isXl => biggest.width > 1024 && biggest.width <= 1280;
|
||||||
bool get is2Xl => biggest.width > 1280;
|
bool get is2Xl => biggest.width > 1280;
|
||||||
|
|
||||||
|
bool get smAndUp => isSm || isMd || isLg || isXl || is2Xl;
|
||||||
bool get mdAndUp => isMd || isLg || isXl || is2Xl;
|
bool get mdAndUp => isMd || isLg || isXl || is2Xl;
|
||||||
bool get lgAndUp => isLg || isXl || is2Xl;
|
bool get lgAndUp => isLg || isXl || is2Xl;
|
||||||
bool get xlAndUp => isXl || is2Xl;
|
bool get xlAndUp => isXl || is2Xl;
|
||||||
|
|
||||||
|
bool get smAndDown => isXs || isSm;
|
||||||
|
bool get mdAndDown => isXs || isSm || isMd;
|
||||||
|
bool get lgAndDown => isXs || isSm || isMd || isLg;
|
||||||
|
bool get xlAndDown => isXs || isSm || isMd || isLg || isXl;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ScreenBreakpoints on MediaQueryData {
|
extension ScreenBreakpoints on MediaQueryData {
|
||||||
bool get isSm => size.width <= 640;
|
bool get isXs => size.width <= 480;
|
||||||
|
bool get isSm => size.width > 480 && size.width <= 640;
|
||||||
bool get isMd => size.width > 640 && size.width <= 768;
|
bool get isMd => size.width > 640 && size.width <= 768;
|
||||||
bool get isLg => size.width > 768 && size.width <= 1024;
|
bool get isLg => size.width > 768 && size.width <= 1024;
|
||||||
bool get isXl => size.width > 1024 && size.width <= 1280;
|
bool get isXl => size.width > 1024 && size.width <= 1280;
|
||||||
bool get is2Xl => size.width > 1280;
|
bool get is2Xl => size.width > 1280;
|
||||||
|
|
||||||
|
bool get smAndUp => isSm || isMd || isLg || isXl || is2Xl;
|
||||||
bool get mdAndUp => isMd || isLg || isXl || is2Xl;
|
bool get mdAndUp => isMd || isLg || isXl || is2Xl;
|
||||||
bool get lgAndUp => isLg || isXl || is2Xl;
|
bool get lgAndUp => isLg || isXl || is2Xl;
|
||||||
bool get xlAndUp => isXl || is2Xl;
|
bool get xlAndUp => isXl || is2Xl;
|
||||||
|
|
||||||
|
bool get smAndDown => isXs || isSm;
|
||||||
|
bool get mdAndDown => isXs || isSm || isMd;
|
||||||
|
bool get lgAndDown => isXs || isSm || isMd || isLg;
|
||||||
|
bool get xlAndDown => isXs || isSm || isMd || isLg || isXl;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
|
|
||||||
T useBreakpointValue<T>({
|
T useBreakpointValue<T>({
|
||||||
|
T? xs,
|
||||||
T? sm,
|
T? sm,
|
||||||
T? md,
|
T? md,
|
||||||
T? lg,
|
T? lg,
|
||||||
@ -10,8 +11,12 @@ T useBreakpointValue<T>({
|
|||||||
T? xxl,
|
T? xxl,
|
||||||
T? others,
|
T? others,
|
||||||
}) {
|
}) {
|
||||||
final isSomeNull =
|
final isSomeNull = xs == null ||
|
||||||
sm == null || md == null || lg == null || xl == null || xxl == null;
|
sm == null ||
|
||||||
|
md == null ||
|
||||||
|
lg == null ||
|
||||||
|
xl == null ||
|
||||||
|
xxl == null;
|
||||||
assert(
|
assert(
|
||||||
(isSomeNull && others != null) || (!isSomeNull && others == null),
|
(isSomeNull && others != null) || (!isSomeNull && others == null),
|
||||||
'You must provide a value for all breakpoints or a default value for others',
|
'You must provide a value for all breakpoints or a default value for others',
|
||||||
@ -20,7 +25,9 @@ T useBreakpointValue<T>({
|
|||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
if (isSomeNull) {
|
if (isSomeNull) {
|
||||||
if (mediaQuery.isSm) {
|
if (mediaQuery.isXs) {
|
||||||
|
return xs ?? others!;
|
||||||
|
} else if (mediaQuery.isSm) {
|
||||||
return sm ?? others!;
|
return sm ?? others!;
|
||||||
} else if (mediaQuery.isMd) {
|
} else if (mediaQuery.isMd) {
|
||||||
return md ?? others!;
|
return md ?? others!;
|
||||||
@ -32,7 +39,9 @@ T useBreakpointValue<T>({
|
|||||||
return lg ?? others!;
|
return lg ?? others!;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mediaQuery.isSm) {
|
if (mediaQuery.isXs) {
|
||||||
|
return xs;
|
||||||
|
} else if (mediaQuery.isSm) {
|
||||||
return sm;
|
return sm;
|
||||||
} else if (mediaQuery.isMd) {
|
} else if (mediaQuery.isMd) {
|
||||||
return md;
|
return md;
|
||||||
|
@ -4,7 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/shared/heart_button.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_collection_view.dart';
|
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart';
|
||||||
import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
|
import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
@ -67,7 +67,7 @@ class AlbumPage extends HookConsumerWidget {
|
|||||||
tracksSnapshot: tracksSnapshot,
|
tracksSnapshot: tracksSnapshot,
|
||||||
album: album,
|
album: album,
|
||||||
routePath: "/album/${album.id}",
|
routePath: "/album/${album.id}",
|
||||||
bottomSpace: mediaQuery.isSm || mediaQuery.isMd,
|
bottomSpace: mediaQuery.mdAndDown,
|
||||||
onPlay: ([track]) {
|
onPlay: ([track]) {
|
||||||
if (tracksSnapshot.hasData) {
|
if (tracksSnapshot.hasData) {
|
||||||
if (!isAlbumPlaying) {
|
if (!isAlbumPlaying) {
|
||||||
|
@ -39,6 +39,7 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
final chipTextVariant = useBreakpointValue(
|
final chipTextVariant = useBreakpointValue(
|
||||||
|
xs: textTheme.bodySmall,
|
||||||
sm: textTheme.bodySmall,
|
sm: textTheme.bodySmall,
|
||||||
md: textTheme.bodyMedium,
|
md: textTheme.bodyMedium,
|
||||||
lg: textTheme.bodyLarge,
|
lg: textTheme.bodyLarge,
|
||||||
@ -49,6 +50,7 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
final avatarWidth = useBreakpointValue(
|
final avatarWidth = useBreakpointValue(
|
||||||
|
xs: mediaQuery.size.width * 0.50,
|
||||||
sm: mediaQuery.size.width * 0.50,
|
sm: mediaQuery.size.width * 0.50,
|
||||||
md: mediaQuery.size.width * 0.40,
|
md: mediaQuery.size.width * 0.40,
|
||||||
lg: mediaQuery.size.width * 0.18,
|
lg: mediaQuery.size.width * 0.18,
|
||||||
@ -155,7 +157,7 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
data.name!,
|
data.name!,
|
||||||
style: mediaQuery.isSm
|
style: mediaQuery.smAndDown
|
||||||
? textTheme.headlineSmall
|
? textTheme.headlineSmall
|
||||||
: textTheme.headlineMedium,
|
: textTheme.headlineMedium,
|
||||||
),
|
),
|
||||||
@ -166,8 +168,9 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight:
|
fontWeight: mediaQuery.mdAndUp
|
||||||
mediaQuery.isSm ? null : FontWeight.bold,
|
? FontWeight.bold
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
@ -35,7 +35,7 @@ class DesktopLoginPage extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Assets.spotubeLogoPng.image(
|
Assets.spotubeLogoPng.image(
|
||||||
width: MediaQuery.of(context).size.width *
|
width: MediaQuery.of(context).size.width *
|
||||||
(mediaQuery.isSm || mediaQuery.isMd ? .5 : .3),
|
(mediaQuery.mdAndDown ? .5 : .3),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
context.l10n.add_spotify_credentials,
|
context.l10n.add_spotify_credentials,
|
||||||
|
@ -2,7 +2,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/shared/heart_button.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_collection_view.dart';
|
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart';
|
||||||
import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
|
import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
@ -99,7 +99,7 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
playlistNotifier.addTracks(tracksSnapshot.data!);
|
playlistNotifier.addTracks(tracksSnapshot.data!);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bottomSpace: mediaQuery.isSm || mediaQuery.isMd,
|
bottomSpace: mediaQuery.mdAndDown,
|
||||||
showShare: playlist.id != "user-liked-tracks",
|
showShare: playlist.id != "user-liked-tracks",
|
||||||
routePath: "/playlist/${playlist.id}",
|
routePath: "/playlist/${playlist.id}",
|
||||||
onShare: () {
|
onShare: () {
|
||||||
|
Loading…
Reference in New Issue
Block a user