From 75bdbeccc0e57888d80f2b2ffa12fae3be9ab10e Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 25 Jul 2025 14:51:30 +0600 Subject: [PATCH] chore: fix track options causing track page exception --- .../track_tile/track_options_button.dart | 146 ++++++++++++++++++ lib/components/track_tile/track_tile.dart | 115 ++------------ lib/pages/track/track.dart | 4 +- 3 files changed, 157 insertions(+), 108 deletions(-) create mode 100644 lib/components/track_tile/track_options_button.dart diff --git a/lib/components/track_tile/track_options_button.dart b/lib/components/track_tile/track_options_button.dart new file mode 100644 index 00000000..b4217957 --- /dev/null +++ b/lib/components/track_tile/track_options_button.dart @@ -0,0 +1,146 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/routes.gr.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/image/universal_image.dart'; +import 'package:spotube/components/links/artist_link.dart'; +import 'package:spotube/components/track_tile/track_options.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class TrackOptionsButton extends HookConsumerWidget { + final SpotubeTrackObject track; + final bool userPlaylist; + final String? playlistId; + const TrackOptionsButton({ + super.key, + required this.track, + required this.userPlaylist, + this.playlistId, + }); + + static OverlayCompleter showOptions( + BuildContext context, + Offset offset, + SpotubeTrackObject track, { + bool userPlaylist = false, + String? playlistId, + }) { + return showPopover( + context: context, + position: offset, + alignment: Alignment.bottomRight, + builder: (context) { + return SizedBox( + width: 220 * context.theme.scaling, + child: Card( + padding: const EdgeInsets.all(8), + child: TrackOptions( + track: track, + playlistId: playlistId, + userPlaylist: userPlaylist, + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context, ref) { + final imageProvider = useMemoized( + () => UniversalImage.imageProvider( + (track.album.images).smallest(ImagePlaceholder.albumArt), + ), + [track.album.images], + ); + + return IconButton.ghost( + icon: const Icon(SpotubeIcons.moreHorizontal), + onPressed: () { + final mediaQuery = MediaQuery.sizeOf(context); + + if (mediaQuery.lgAndUp) { + final renderBox = context.findRenderObject() as RenderBox; + final position = RelativeRect.fromRect( + Rect.fromPoints( + renderBox.localToGlobal(Offset.zero, + ancestor: context.findRenderObject()), + renderBox.localToGlobal(renderBox.size.bottomRight(Offset.zero), + ancestor: context.findRenderObject()), + ), + Offset.zero & mediaQuery, + ); + final offset = Offset(position.left, position.top); + showOptions( + context, + offset, + track, + userPlaylist: userPlaylist, + playlistId: playlistId, + ); + } else { + openDrawer( + context: context, + position: OverlayPosition.bottom, + draggable: true, + showDragHandle: true, + borderRadius: context.theme.borderRadiusMd, + transformBackdrop: false, + builder: (context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + Basic( + leading: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: context.theme.borderRadiusMd, + image: DecorationImage( + fit: BoxFit.cover, + image: imageProvider, + ), + ), + ), + title: Text( + track.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ).semiBold(), + subtitle: Align( + alignment: Alignment.centerLeft, + child: ArtistLink( + artists: track.artists, + onOverflowArtistClick: () => context.navigateTo( + TrackRoute(trackId: track.id), + ), + ), + ), + ), + const Divider(), + TrackOptions( + track: track, + userPlaylist: userPlaylist, + playlistId: playlistId, + ), + ], + ), + ); + }, + ); + } + }, + ); + } +} diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index b3c387e5..972aab05 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -15,6 +15,7 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/components/track_tile/track_options.dart'; +import 'package:spotube/components/track_tile/track_options_button.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/duration.dart'; @@ -63,30 +64,6 @@ class TrackTile extends HookConsumerWidget { this.leadingActions, }); - OverlayCompleter showOptions( - BuildContext context, - Offset offset, - ) { - return showPopover( - context: context, - position: offset, - alignment: Alignment.bottomRight, - builder: (context) { - return SizedBox( - width: 220 * context.theme.scaling, - child: Card( - padding: const EdgeInsets.all(8), - child: TrackOptions( - track: track, - playlistId: playlistId, - userPlaylist: userPlaylist, - ), - ), - ); - }, - ); - } - @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); @@ -114,9 +91,12 @@ class TrackTile extends HookConsumerWidget { _overlay.value?.remove(); _overlay.value = null; } - _overlay.value = showOptions( + _overlay.value = TrackOptionsButton.showOptions( context, Offset.zero, + track, + userPlaylist: userPlaylist, + playlistId: playlistId, ); }, child: HoverBuilder( @@ -333,87 +313,10 @@ class TrackTile extends HookConsumerWidget { ), Builder( builder: (context) { - return IconButton.ghost( - icon: const Icon(SpotubeIcons.moreHorizontal), - onPressed: () { - final mediaQuery = MediaQuery.sizeOf(context); - - if (mediaQuery.lgAndUp) { - final renderBox = - context.findRenderObject() as RenderBox; - final position = RelativeRect.fromRect( - Rect.fromPoints( - renderBox.localToGlobal(Offset.zero, - ancestor: context.findRenderObject()), - renderBox.localToGlobal( - renderBox.size.bottomRight(Offset.zero), - ancestor: context.findRenderObject()), - ), - Offset.zero & mediaQuery, - ); - final offset = Offset(position.left, position.top); - showOptions(context, offset); - } else { - openDrawer( - context: context, - position: OverlayPosition.bottom, - draggable: true, - showDragHandle: true, - borderRadius: context.theme.borderRadiusMd, - transformBackdrop: false, - builder: (context) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 8, - children: [ - Basic( - leading: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - borderRadius: - context.theme.borderRadiusMd, - image: DecorationImage( - fit: BoxFit.cover, - image: imageProvider, - ), - ), - ), - title: Text( - track.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ).semiBold(), - subtitle: Align( - alignment: Alignment.centerLeft, - child: ArtistLink( - artists: track.artists, - onOverflowArtistClick: () => - context.navigateTo( - TrackRoute(trackId: track.id), - ), - ), - ), - ), - const Divider(), - TrackOptions( - track: track, - userPlaylist: userPlaylist, - playlistId: playlistId, - ), - ], - ), - ); - }, - ); - } - }, + return TrackOptionsButton( + track: track, + userPlaylist: userPlaylist, + playlistId: playlistId, ); }, ), diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index e55192e1..44453ebd 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -11,7 +11,7 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/components/track_tile/track_options.dart'; +import 'package:spotube/components/track_tile/track_options_button.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/list.dart'; import 'package:spotube/models/metadata/metadata.dart'; @@ -231,7 +231,7 @@ class TrackPage extends HookConsumerWidget { else const Gap(20), TrackHeartButton(track: track), - TrackOptions( + TrackOptionsButton( track: track, userPlaylist: false, ),