diff --git a/lib/components/player/player.dart b/lib/components/player/player.dart index 889b7c5c..3957cc65 100644 --- a/lib/components/player/player.dart +++ b/lib/components/player/player.dart @@ -28,9 +28,11 @@ import 'package:spotube/utils/type_conversion_utils.dart'; class PlayerView extends HookConsumerWidget { final PanelController panelController; + final ScrollController scrollController; const PlayerView({ Key? key, required this.panelController, + required this.scrollController, }) : super(key: key); @override @@ -119,40 +121,43 @@ class PlayerView extends HookConsumerWidget { preferredSize: Size.fromHeight( kToolbarHeight + topPadding, ), - child: Padding( - padding: EdgeInsets.only(top: topPadding), - child: PageWindowTitleBar( - backgroundColor: Colors.transparent, - foregroundColor: titleTextColor, - toolbarOpacity: 1, - leading: IconButton( - icon: const Icon(SpotubeIcons.angleDown, size: 18), - onPressed: panelController.close, + child: ForceDraggableWidget( + child: Padding( + padding: EdgeInsets.only(top: topPadding), + child: PageWindowTitleBar( + backgroundColor: Colors.transparent, + foregroundColor: titleTextColor, + toolbarOpacity: 1, + leading: IconButton( + icon: const Icon(SpotubeIcons.angleDown, size: 18), + onPressed: panelController.close, + ), + actions: [ + IconButton( + icon: const Icon(SpotubeIcons.info, size: 18), + tooltip: context.l10n.details, + style: IconButton.styleFrom( + foregroundColor: bodyTextColor), + onPressed: currentTrack == null + ? null + : () { + showDialog( + context: context, + builder: (context) { + return TrackDetailsDialog( + track: currentTrack, + ); + }); + }, + ) + ], ), - actions: [ - IconButton( - icon: const Icon(SpotubeIcons.info, size: 18), - tooltip: context.l10n.details, - style: - IconButton.styleFrom(foregroundColor: bodyTextColor), - onPressed: currentTrack == null - ? null - : () { - showDialog( - context: context, - builder: (context) { - return TrackDetailsDialog( - track: currentTrack, - ); - }); - }, - ) - ], ), ), ), extendBodyBehindAppBar: true, body: SingleChildScrollView( + controller: scrollController, child: Container( alignment: Alignment.center, width: double.infinity, @@ -163,27 +168,29 @@ class PlayerView extends HookConsumerWidget { padding: const EdgeInsets.all(8.0), child: Column( children: [ - Container( - margin: const EdgeInsets.all(8), - constraints: const BoxConstraints( - maxHeight: 300, maxWidth: 300), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 2, - blurRadius: 10, - offset: Offset(0, 0), + ForceDraggableWidget( + child: Container( + margin: const EdgeInsets.all(8), + constraints: const BoxConstraints( + maxHeight: 300, maxWidth: 300), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 2, + blurRadius: 10, + offset: Offset(0, 0), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: UniversalImage( + path: albumArt, + placeholder: Assets.albumPlaceholder.path, + fit: BoxFit.cover, ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: UniversalImage( - path: albumArt, - placeholder: Assets.albumPlaceholder.path, - fit: BoxFit.cover, ), ), ), diff --git a/lib/components/player/player_overlay.dart b/lib/components/player/player_overlay.dart index 4869a0fa..2d63811e 100644 --- a/lib/components/player/player_overlay.dart +++ b/lib/components/player/player_overlay.dart @@ -43,6 +43,7 @@ class PlayerOverlay extends HookConsumerWidget { final mediaQuery = MediaQuery.of(context); final panelController = useMemoized(() => PanelController(), []); + final scrollController = useScrollController(); useEffect(() { return () { @@ -174,6 +175,7 @@ class PlayerOverlay extends HookConsumerWidget { ), ), ), + scrollController: scrollController, panelBuilder: (position) { // this is the reason we're getting an update final navigationHeight = ref.watch(navigationPanelHeight); @@ -188,8 +190,11 @@ class PlayerOverlay extends HookConsumerWidget { decoration: navigationHeight == 0 ? const BoxDecoration(borderRadius: BorderRadius.zero) : const BoxDecoration(borderRadius: radius), - child: HorizontalScrollableWidget( - child: PlayerView(panelController: panelController), + child: IgnoreDraggableWidget( + child: PlayerView( + panelController: panelController, + scrollController: scrollController, + ), ), ), ); diff --git a/lib/components/shared/playbutton_card.dart b/lib/components/shared/playbutton_card.dart index 60db648b..d6226716 100644 --- a/lib/components/shared/playbutton_card.dart +++ b/lib/components/shared/playbutton_card.dart @@ -90,17 +90,19 @@ class PlaybuttonCard extends HookWidget { Stack( clipBehavior: Clip.none, children: [ - Padding( + Container( padding: const EdgeInsets.only( left: 8, right: 8, top: 8, ), + constraints: BoxConstraints(maxHeight: size), child: ClipRRect( borderRadius: radius, child: UniversalImage( path: imageUrl, placeholder: Assets.albumPlaceholder.path, + fit: BoxFit.cover, ), ), ), diff --git a/lib/components/shared/track_tile/track_tile.dart b/lib/components/shared/track_tile/track_tile.dart index 6d4e236a..961f29c9 100644 --- a/lib/components/shared/track_tile/track_tile.dart +++ b/lib/components/shared/track_tile/track_tile.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/hover_builder.dart'; @@ -158,26 +159,28 @@ class TrackTile extends HookConsumerWidget { child: IconTheme( data: theme.iconTheme .copyWith(size: 26, color: Colors.white), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: (isPlaying && playlist.isFetching) || - isLoading.value - ? const SizedBox( - width: 26, - height: 26, - child: CircularProgressIndicator( - strokeWidth: 1.5, - color: Colors.white, - ), - ) - : isPlaying - ? Icon( - SpotubeIcons.pause, - color: theme.colorScheme.primary, - ) - : !isHovering - ? const SizedBox.shrink() - : const Icon(SpotubeIcons.play), + child: Skeleton.ignore( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: (isPlaying && playlist.isFetching) || + isLoading.value + ? const SizedBox( + width: 26, + height: 26, + child: CircularProgressIndicator( + strokeWidth: 1.5, + color: Colors.white, + ), + ) + : isPlaying + ? Icon( + SpotubeIcons.pause, + color: theme.colorScheme.primary, + ) + : !isHovering + ? const SizedBox.shrink() + : const Icon(SpotubeIcons.play), + ), ), ), ), diff --git a/lib/components/shared/tracks_view/sections/body/track_view_body.dart b/lib/components/shared/tracks_view/sections/body/track_view_body.dart index 2ad1dc9b..20caf4f1 100644 --- a/lib/components/shared/tracks_view/sections/body/track_view_body.dart +++ b/lib/components/shared/tracks_view/sections/body/track_view_body.dart @@ -93,9 +93,14 @@ class TrackViewBodySection extends HookConsumerWidget { index: 0, ), ), - emptyBuilder: (context) => const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [NotFound()], + emptyBuilder: (context) => Skeletonizer( + enabled: true, + child: Column( + children: List.generate( + 10, + (index) => TrackTile(track: FakeData.track, index: index), + ), + ), ), itemBuilder: (context, index) { final track = tracks[index]; diff --git a/lib/components/shared/tracks_view/sections/header/flexible_header.dart b/lib/components/shared/tracks_view/sections/header/flexible_header.dart index 7c469654..e16ccbff 100644 --- a/lib/components/shared/tracks_view/sections/header/flexible_header.dart +++ b/lib/components/shared/tracks_view/sections/header/flexible_header.dart @@ -88,50 +88,68 @@ class TrackViewFlexHeader extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Flex( - direction: mediaQuery.mdAndDown - ? Axis.vertical - : Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: UniversalImage( - path: props.image, - width: 200, - height: 200, - placeholder: Assets.albumPlaceholder.path, + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: mediaQuery.mdAndDown + ? mediaQuery.size.width + : 800, + ), + child: Flex( + direction: mediaQuery.mdAndDown + ? Axis.vertical + : Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: UniversalImage( + path: props.image, + width: 200, + height: 200, + placeholder: Assets.albumPlaceholder.path, + ), ), - ), - const Gap(20), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: mediaQuery.mdAndDown - ? CrossAxisAlignment.center - : CrossAxisAlignment.start, - children: [ - Text(props.title, style: headingStyle), - const SizedBox(height: 10), - if (description != null && - description.isNotEmpty) - Text( - description, - style: defaultTextStyle.style.copyWith( - color: palette.bodyTextColor, + const Gap(20), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: mediaQuery.mdAndDown + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, + children: [ + Text( + props.title, + style: headingStyle, + textAlign: mediaQuery.mdAndDown + ? TextAlign.center + : TextAlign.start, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - textAlign: mediaQuery.mdAndDown - ? TextAlign.center - : TextAlign.start, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const Gap(10), - const TrackViewHeaderActions(), - const Gap(10), - TrackViewHeaderButtons(color: palette), - ], - ), - ], + const SizedBox(height: 10), + if (description != null && + description.isNotEmpty) + Text( + description, + style: + defaultTextStyle.style.copyWith( + color: palette.bodyTextColor, + ), + textAlign: mediaQuery.mdAndDown + ? TextAlign.center + : TextAlign.start, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const Gap(10), + const TrackViewHeaderActions(), + const Gap(10), + TrackViewHeaderButtons(color: palette), + ], + ), + ), + ], + ), ), ], ),