mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: track page
This commit is contained in:
parent
dd0bb01af5
commit
b734985199
@ -110,7 +110,6 @@ class AdaptivePopSheetList<T> extends StatelessWidget {
|
|||||||
backgroundColor: context.theme.colorScheme.card,
|
backgroundColor: context.theme.colorScheme.card,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: childrenModified.length,
|
itemCount: childrenModified.length,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart' hide Page;
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.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:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:spotify/spotify.dart' hide Offset;
|
import 'package:spotify/spotify.dart' hide Offset;
|
||||||
@ -69,16 +69,20 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
void actionShare(BuildContext context, Track track) {
|
void actionShare(BuildContext context, Track track) {
|
||||||
final data = "https://open.spotify.com/track/${track.id}";
|
final data = "https://open.spotify.com/track/${track.id}";
|
||||||
Clipboard.setData(ClipboardData(text: data)).then((_) {
|
Clipboard.setData(ClipboardData(text: data)).then((_) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (context.mounted) {
|
||||||
SnackBar(
|
showToast(
|
||||||
width: 300,
|
context: context,
|
||||||
behavior: SnackBarBehavior.floating,
|
location: ToastLocation.topRight,
|
||||||
content: Text(
|
builder: (context, overlay) {
|
||||||
|
return SurfaceCard(
|
||||||
|
child: Text(
|
||||||
context.l10n.copied_to_clipboard(data),
|
context.l10n.copied_to_clipboard(data),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +165,6 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final router = GoRouter.of(context);
|
final router = GoRouter.of(context);
|
||||||
final ThemeData(:colorScheme) = Theme.of(context);
|
final ThemeData(:colorScheme) = Theme.of(context);
|
||||||
@ -220,36 +223,57 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
case TrackOptionValue.addToQueue:
|
case TrackOptionValue.addToQueue:
|
||||||
await playback.addTrack(track);
|
await playback.addTrack(track);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
scaffoldMessenger.showSnackBar(
|
showToast(
|
||||||
SnackBar(
|
context: context,
|
||||||
content: Text(
|
location: ToastLocation.topRight,
|
||||||
|
builder: (context, overlay) {
|
||||||
|
return SurfaceCard(
|
||||||
|
child: Text(
|
||||||
context.l10n.added_track_to_queue(track.name!),
|
context.l10n.added_track_to_queue(track.name!),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.playNext:
|
case TrackOptionValue.playNext:
|
||||||
playback.addTracksAtFirst([track]);
|
playback.addTracksAtFirst([track]);
|
||||||
scaffoldMessenger.showSnackBar(
|
|
||||||
SnackBar(
|
if (context.mounted) {
|
||||||
content: Text(
|
showToast(
|
||||||
|
context: context,
|
||||||
|
location: ToastLocation.topRight,
|
||||||
|
builder: (context, overlay) {
|
||||||
|
return SurfaceCard(
|
||||||
|
child: Text(
|
||||||
context.l10n.track_will_play_next(track.name!),
|
context.l10n.track_will_play_next(track.name!),
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.removeFromQueue:
|
case TrackOptionValue.removeFromQueue:
|
||||||
playback.removeTrack(track.id!);
|
playback.removeTrack(track.id!);
|
||||||
scaffoldMessenger.showSnackBar(
|
|
||||||
SnackBar(
|
if (context.mounted) {
|
||||||
content: Text(
|
showToast(
|
||||||
|
context: context,
|
||||||
|
location: ToastLocation.topRight,
|
||||||
|
builder: (context, overlay) {
|
||||||
|
return SurfaceCard(
|
||||||
|
child: Text(
|
||||||
context.l10n.removed_track_from_queue(
|
context.l10n.removed_track_from_queue(
|
||||||
track.name!,
|
track.name!,
|
||||||
),
|
),
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.favorite:
|
case TrackOptionValue.favorite:
|
||||||
favorites.toggleTrackLike(track);
|
favorites.toggleTrackLike(track);
|
||||||
@ -286,7 +310,10 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
case TrackOptionValue.details:
|
case TrackOptionValue.details:
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => TrackDetailsDialog(track: track),
|
builder: (context) => ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 400),
|
||||||
|
child: TrackDetailsDialog(track: track),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.download:
|
case TrackOptionValue.download:
|
||||||
@ -299,8 +326,7 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
icon: icon ?? const Icon(SpotubeIcons.moreHorizontal),
|
icon: icon ?? const Icon(SpotubeIcons.moreHorizontal),
|
||||||
headings: [
|
headings: [
|
||||||
ListTile(
|
Basic(
|
||||||
dense: true,
|
|
||||||
leading: AspectRatio(
|
leading: AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
@ -316,8 +342,7 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
track.name!,
|
track.name!,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
).semiBold(),
|
||||||
),
|
|
||||||
subtitle: Align(
|
subtitle: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: ArtistLink(
|
child: ArtistLink(
|
||||||
@ -449,7 +474,7 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
leading: Assets.logos.songlinkTransparent.image(
|
leading: Assets.logos.songlinkTransparent.image(
|
||||||
width: 22,
|
width: 22,
|
||||||
height: 22,
|
height: 22,
|
||||||
color: colorScheme.onSurface.withOpacity(0.5),
|
color: colorScheme.foreground.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
child: Text(context.l10n.song_link),
|
child: Text(context.l10n.song_link),
|
||||||
),
|
),
|
||||||
@ -471,11 +496,6 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
adaptivePopSheetList.showDropdownMenu(context, offsetFromRect);
|
adaptivePopSheetList.showDropdownMenu(context, offsetFromRect);
|
||||||
};
|
};
|
||||||
|
|
||||||
return ListTileTheme(
|
return adaptivePopSheetList;
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: adaptivePopSheetList,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
@ -32,7 +32,7 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme, :colorScheme) = Theme.of(context);
|
final ThemeData(:typography, :colorScheme) = Theme.of(context);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
final playlist = ref.watch(audioPlayerProvider);
|
final playlist = ref.watch(audioPlayerProvider);
|
||||||
@ -53,12 +53,15 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: const TitleBar(
|
headers: const [
|
||||||
|
TitleBar(
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
),
|
surfaceBlur: 0,
|
||||||
extendBodyBehindAppBar: true,
|
)
|
||||||
body: Stack(
|
],
|
||||||
|
floatingHeader: true,
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Container(
|
child: Container(
|
||||||
@ -71,7 +74,7 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
colorFilter: ColorFilter.mode(
|
colorFilter: ColorFilter.mode(
|
||||||
colorScheme.surface.withOpacity(0.5),
|
colorScheme.background.withOpacity(0.5),
|
||||||
BlendMode.srcOver,
|
BlendMode.srcOver,
|
||||||
),
|
),
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
@ -89,7 +92,7 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
colorScheme.surface,
|
colorScheme.background,
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
],
|
],
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
@ -125,8 +128,7 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
track.name!,
|
track.name!,
|
||||||
style: textTheme.titleLarge,
|
).large().semiBold(),
|
||||||
),
|
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -170,9 +172,10 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
if (!isActive &&
|
if (!isActive &&
|
||||||
!playlist.tracks
|
!playlist.tracks
|
||||||
.containsBy(track, (t) => t.id))
|
.containsBy(track, (t) => t.id))
|
||||||
OutlinedButton.icon(
|
Button.outline(
|
||||||
icon: const Icon(SpotubeIcons.queueAdd),
|
leading:
|
||||||
label: Text(context.l10n.queue),
|
const Icon(SpotubeIcons.queueAdd),
|
||||||
|
child: Text(context.l10n.queue),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
playlistNotifier.addTrack(track);
|
playlistNotifier.addTrack(track);
|
||||||
},
|
},
|
||||||
@ -181,28 +184,38 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
if (!isActive &&
|
if (!isActive &&
|
||||||
!playlist.tracks
|
!playlist.tracks
|
||||||
.containsBy(track, (t) => t.id))
|
.containsBy(track, (t) => t.id))
|
||||||
IconButton.outlined(
|
Tooltip(
|
||||||
icon:
|
tooltip: TooltipContainer(
|
||||||
const Icon(SpotubeIcons.lightning),
|
child: Text(context.l10n.play_next),
|
||||||
tooltip: context.l10n.play_next,
|
),
|
||||||
|
child: IconButton.outline(
|
||||||
|
icon: const Icon(
|
||||||
|
SpotubeIcons.lightning),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
playlistNotifier
|
playlistNotifier
|
||||||
.addTracksAtFirst([track]);
|
.addTracksAtFirst([track]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const Gap(5),
|
const Gap(5),
|
||||||
IconButton.filled(
|
Tooltip(
|
||||||
tooltip: isActive
|
tooltip: TooltipContainer(
|
||||||
|
child: Text(
|
||||||
|
isActive
|
||||||
? context.l10n.pause_playback
|
? context.l10n.pause_playback
|
||||||
: context.l10n.play,
|
: context.l10n.play,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton.primary(
|
||||||
|
shape: ButtonShape.circle,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
isActive
|
isActive
|
||||||
? SpotubeIcons.pause
|
? SpotubeIcons.pause
|
||||||
: SpotubeIcons.play,
|
: SpotubeIcons.play,
|
||||||
color: colorScheme.onPrimary,
|
|
||||||
),
|
),
|
||||||
onPressed: onPlay,
|
onPressed: onPlay,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const Gap(5),
|
const Gap(5),
|
||||||
if (mediaQuery.smAndDown)
|
if (mediaQuery.smAndDown)
|
||||||
const Spacer()
|
const Spacer()
|
||||||
|
Loading…
Reference in New Issue
Block a user