mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
refactor: artist page
This commit is contained in:
parent
bbad701c07
commit
4afe0cca68
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart' hide Page;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
@ -30,7 +30,7 @@ class ArtistAlbumList extends HookConsumerWidget {
|
||||
onFetchMore: albumsQueryNotifier.fetchMore,
|
||||
title: Text(
|
||||
context.l10n.albums,
|
||||
style: theme.textTheme.headlineSmall,
|
||||
style: theme.typography.h4,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/components/button/back_button.dart';
|
||||
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
import 'package:spotube/modules/artist/artist_album_list.dart';
|
||||
@ -30,12 +30,14 @@ class ArtistPage extends HookConsumerWidget {
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Scaffold(
|
||||
appBar: const TitleBar(
|
||||
headers: const [
|
||||
TitleBar(
|
||||
leading: [BackButton()],
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
extendBodyBehindAppBar: true,
|
||||
body: Builder(builder: (context) {
|
||||
)
|
||||
],
|
||||
floatingHeader: true,
|
||||
child: Builder(builder: (context) {
|
||||
if (artistQuery.hasError && artistQuery.asData?.value == null) {
|
||||
return Center(child: Text(artistQuery.error.toString()));
|
||||
}
|
||||
@ -50,31 +52,26 @@ class ArtistPage extends HookConsumerWidget {
|
||||
child: ArtistPageHeader(artistId: artistId),
|
||||
),
|
||||
),
|
||||
const SliverGap(50),
|
||||
ArtistPageTopTracks(artistId: artistId),
|
||||
const SliverGap(50),
|
||||
SliverToBoxAdapter(child: ArtistAlbumList(artistId)),
|
||||
const SliverGap(20),
|
||||
ArtistPageTopTracks(artistId: artistId),
|
||||
const SliverGap(20),
|
||||
SliverToBoxAdapter(child: ArtistAlbumList(artistId)),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
context.l10n.fans_also_like,
|
||||
style: theme.textTheme.headlineSmall,
|
||||
style: theme.typography.h4,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverSafeArea(
|
||||
sliver: ArtistPageRelatedArtists(artistId: artistId),
|
||||
),
|
||||
ArtistPageRelatedArtists(artistId: artistId),
|
||||
const SliverGap(20),
|
||||
if (artistQuery.asData?.value != null)
|
||||
SliverSafeArea(
|
||||
top: false,
|
||||
sliver: SliverToBoxAdapter(
|
||||
child:
|
||||
ArtistPageFooter(artist: artistQuery.asData!.value),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: ArtistPageFooter(artist: artistQuery.asData!.value),
|
||||
),
|
||||
const SliverSafeArea(sliver: SliverGap(10)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -26,7 +26,7 @@ class ArtistPageFooter extends ConsumerWidget {
|
||||
if (summary.asData?.value == null) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.all(8),
|
||||
padding: mediaQuery.smAndDown
|
||||
? const EdgeInsets.all(20)
|
||||
: const EdgeInsets.all(30),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
@ -9,7 +9,6 @@ import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/blacklist_provider.dart';
|
||||
@ -25,19 +24,8 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
final artistQuery = ref.watch(artistProvider(artistId));
|
||||
final artist = artistQuery.asData?.value ?? FakeData.artist;
|
||||
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final ThemeData(:textTheme) = theme;
|
||||
|
||||
final chipTextVariant = useBreakpointValue(
|
||||
xs: textTheme.bodySmall,
|
||||
sm: textTheme.bodySmall,
|
||||
md: textTheme.bodyMedium,
|
||||
lg: textTheme.bodyLarge,
|
||||
xl: textTheme.titleSmall,
|
||||
xxl: textTheme.titleMedium,
|
||||
);
|
||||
final ThemeData(:typography) = theme;
|
||||
|
||||
final auth = ref.watch(authenticationProvider);
|
||||
ref.watch(blacklistProvider);
|
||||
@ -48,106 +36,25 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constrains) {
|
||||
return Center(
|
||||
child: Flex(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: constrains.smAndDown
|
||||
? CrossAxisAlignment.start
|
||||
: CrossAxisAlignment.center,
|
||||
direction: constrains.smAndDown ? Axis.vertical : Axis.horizontal,
|
||||
children: [
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: kElevationToShadow[2],
|
||||
borderRadius: BorderRadius.circular(35),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(35),
|
||||
child: UniversalImage(
|
||||
path: image,
|
||||
width: 250,
|
||||
height: 250,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(20),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(50)),
|
||||
child: Skeleton.keep(
|
||||
child: Text(
|
||||
artist.type!.toUpperCase(),
|
||||
style: chipTextVariant.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isBlackListed) ...[
|
||||
const SizedBox(width: 5),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red[400],
|
||||
borderRadius: BorderRadius.circular(50)),
|
||||
child: Text(
|
||||
context.l10n.blacklisted,
|
||||
style: chipTextVariant.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
Text(
|
||||
artist.name!,
|
||||
style: mediaQuery.smAndDown
|
||||
? textTheme.headlineSmall
|
||||
: textTheme.headlineMedium,
|
||||
),
|
||||
Text(
|
||||
context.l10n.followers(
|
||||
PrimitiveUtils.toReadableNumber(
|
||||
artist.followers!.total!.toDouble(),
|
||||
),
|
||||
),
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: mediaQuery.mdAndUp ? FontWeight.bold : null,
|
||||
),
|
||||
),
|
||||
const Gap(20),
|
||||
Skeleton.keep(
|
||||
final actions = Skeleton.keep(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (auth.asData?.value != null)
|
||||
Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final isFollowingQuery = ref
|
||||
.watch(artistIsFollowingProvider(artist.id!));
|
||||
final followingArtistNotifier =
|
||||
ref.watch(followedArtistsProvider.notifier);
|
||||
final isFollowingQuery = ref.watch(
|
||||
artistIsFollowingProvider(artist.id!),
|
||||
);
|
||||
final followingArtistNotifier = ref.watch(
|
||||
followedArtistsProvider.notifier,
|
||||
);
|
||||
|
||||
return switch (isFollowingQuery) {
|
||||
AsyncData(value: final following) => Builder(
|
||||
builder: (context) {
|
||||
if (following) {
|
||||
return OutlinedButton(
|
||||
return Button.outline(
|
||||
onPressed: () async {
|
||||
await followingArtistNotifier
|
||||
.removeArtists([artist.id!]);
|
||||
@ -156,7 +63,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return FilledButton(
|
||||
return Button.primary(
|
||||
onPressed: () async {
|
||||
await followingArtistNotifier
|
||||
.saveArtists([artist.id!]);
|
||||
@ -174,22 +81,21 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
IconButton(
|
||||
tooltip: context.l10n.add_artist_to_blacklist,
|
||||
Tooltip(
|
||||
tooltip: TooltipContainer(
|
||||
child: Text(context.l10n.add_artist_to_blacklist),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
SpotubeIcons.userRemove,
|
||||
color:
|
||||
!isBlackListed ? Colors.red[400] : Colors.white,
|
||||
),
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor:
|
||||
isBlackListed ? Colors.red[400] : null,
|
||||
color: !isBlackListed ? Colors.red[400] : null,
|
||||
),
|
||||
variance: isBlackListed
|
||||
? ButtonVariance.destructive
|
||||
: ButtonVariance.ghost,
|
||||
onPressed: () async {
|
||||
if (isBlackListed) {
|
||||
await ref
|
||||
.read(blacklistProvider.notifier)
|
||||
.remove(artist.id!);
|
||||
await ref.read(blacklistProvider.notifier).remove(artist.id!);
|
||||
} else {
|
||||
await ref.read(blacklistProvider.notifier).add(
|
||||
BlacklistTableCompanion.insert(
|
||||
@ -201,7 +107,8 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
),
|
||||
IconButton.ghost(
|
||||
icon: const Icon(SpotubeIcons.share),
|
||||
onPressed: () async {
|
||||
if (artist.externalUrls?.spotify != null) {
|
||||
@ -214,25 +121,108 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
width: 300,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(
|
||||
showToast(
|
||||
context: context,
|
||||
location: ToastLocation.topRight,
|
||||
dismissible: true,
|
||||
builder: (context, overlay) {
|
||||
return SurfaceCard(
|
||||
child: Text(
|
||||
context.l10n.artist_url_copied,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constrains) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: theme.borderRadiusXl,
|
||||
child: UniversalImage(
|
||||
path: image,
|
||||
width: constrains.mdAndUp ? 200 : 120,
|
||||
height: constrains.mdAndUp ? 200 : 120,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
const Gap(20),
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
OutlineBadge(
|
||||
child:
|
||||
Text(context.l10n.artist).small().muted(),
|
||||
),
|
||||
if (isBlackListed) ...[
|
||||
const Gap(5),
|
||||
DestructiveBadge(
|
||||
child: Text(context.l10n.blacklisted).small(),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
const Gap(10),
|
||||
Flexible(
|
||||
child: AutoSizeText(
|
||||
artist.name!,
|
||||
style: constrains.smAndDown
|
||||
? typography.h4
|
||||
: typography.h3,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
minFontSize: 14,
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Flexible(
|
||||
child: AutoSizeText(
|
||||
context.l10n.followers(
|
||||
PrimitiveUtils.toReadableNumber(
|
||||
artist.followers!.total!.toDouble(),
|
||||
),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
minFontSize: 12,
|
||||
).muted(),
|
||||
),
|
||||
if (constrains.mdAndUp) ...[
|
||||
const Gap(20),
|
||||
actions,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (constrains.smAndDown) ...[
|
||||
const Gap(20),
|
||||
actions,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
@ -19,7 +19,6 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final theme = Theme.of(context);
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
|
||||
final playlist = ref.watch(audioPlayerProvider);
|
||||
final playlistNotifier = ref.watch(audioPlayerProvider.notifier);
|
||||
@ -93,46 +92,46 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
context.l10n.top_tracks,
|
||||
style: theme.textTheme.headlineSmall,
|
||||
style: theme.typography.h4,
|
||||
),
|
||||
),
|
||||
if (!isPlaylistPlaying)
|
||||
IconButton(
|
||||
IconButton.outline(
|
||||
icon: const Icon(
|
||||
SpotubeIcons.queueAdd,
|
||||
),
|
||||
onPressed: () {
|
||||
playlistNotifier.addTracks(topTracks.toList());
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
width: 300,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(
|
||||
showToast(
|
||||
context: context,
|
||||
location: ToastLocation.topRight,
|
||||
builder: (context, overlay) {
|
||||
return SurfaceCard(
|
||||
child: Text(
|
||||
context.l10n.added_to_queue(
|
||||
topTracks.length,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
IconButton(
|
||||
IconButton.primary(
|
||||
shape: ButtonShape.circle,
|
||||
enabled: !isPlaylistPlaying,
|
||||
icon: Skeleton.keep(
|
||||
child: Icon(
|
||||
isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play,
|
||||
color: Colors.white,
|
||||
isPlaylistPlaying ? SpotubeIcons.pause : SpotubeIcons.play,
|
||||
),
|
||||
),
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
onPressed: () => playPlaylist(topTracks.toList()),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SliverGap(10),
|
||||
SliverList.builder(
|
||||
itemCount: topTracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
Loading…
Reference in New Issue
Block a user