mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-02-04 07:52:55 +00:00
The MusicBrainz metadata plugin doesn't provide follower counts, so artist.followers is always null. Previously this caused "Infinity Followers" to be displayed. Now the followers line is hidden when the data is unavailable.
229 lines
8.5 KiB
Dart
229 lines
8.5 KiB
Dart
import 'package:auto_size_text/auto_size_text.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer;
|
|
import 'package:skeletonizer/skeletonizer.dart';
|
|
import 'package:spotube/collections/fake.dart';
|
|
import 'package:spotube/collections/spotube_icons.dart';
|
|
import 'package:spotube/components/image/universal_image.dart';
|
|
import 'package:spotube/extensions/constrains.dart';
|
|
import 'package:spotube/extensions/context.dart';
|
|
import 'package:spotube/models/database/database.dart';
|
|
import 'package:spotube/models/metadata/metadata.dart';
|
|
import 'package:spotube/provider/blacklist_provider.dart';
|
|
import 'package:spotube/provider/metadata_plugin/artist/artist.dart';
|
|
import 'package:spotube/provider/metadata_plugin/core/auth.dart';
|
|
import 'package:spotube/provider/metadata_plugin/library/artists.dart';
|
|
import 'package:spotube/utils/primitive_utils.dart';
|
|
|
|
class ArtistPageHeader extends HookConsumerWidget {
|
|
final String artistId;
|
|
const ArtistPageHeader({super.key, required this.artistId});
|
|
|
|
@override
|
|
Widget build(BuildContext context, ref) {
|
|
final artistQuery = ref.watch(metadataPluginArtistProvider(artistId));
|
|
final artist = artistQuery.asData?.value ?? FakeData.artist;
|
|
|
|
final theme = Theme.of(context);
|
|
final ThemeData(:typography) = theme;
|
|
|
|
final authenticated = ref.watch(metadataPluginAuthenticatedProvider);
|
|
ref.watch(blacklistProvider);
|
|
final blacklistNotifier = ref.watch(blacklistProvider.notifier);
|
|
final isBlackListed = blacklistNotifier.containsArtist(artist.id);
|
|
|
|
final image = artist.images.asUrlString(
|
|
placeholder: ImagePlaceholder.artist,
|
|
);
|
|
|
|
final actions = Skeleton.keep(
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (authenticated.asData?.value == true)
|
|
Consumer(
|
|
builder: (context, ref, _) {
|
|
final isFollowingQuery = ref.watch(
|
|
metadataPluginIsSavedArtistProvider(artist.id),
|
|
);
|
|
final followingArtistNotifier =
|
|
ref.watch(metadataPluginSavedArtistsProvider.notifier);
|
|
|
|
return switch (isFollowingQuery) {
|
|
AsyncData(value: final following) => Builder(
|
|
builder: (context) {
|
|
if (following) {
|
|
return Button.outline(
|
|
onPressed: () async {
|
|
await followingArtistNotifier
|
|
.removeFavorite([artist]);
|
|
},
|
|
child: Text(context.l10n.following),
|
|
);
|
|
}
|
|
|
|
return Button.primary(
|
|
onPressed: () async {
|
|
await followingArtistNotifier.addFavorite([artist]);
|
|
},
|
|
child: Text(context.l10n.follow),
|
|
);
|
|
},
|
|
),
|
|
AsyncError() => const SizedBox(),
|
|
_ => const SizedBox.square(
|
|
dimension: 20,
|
|
child: CircularProgressIndicator(),
|
|
)
|
|
};
|
|
},
|
|
),
|
|
const SizedBox(width: 5),
|
|
Tooltip(
|
|
tooltip: TooltipContainer(
|
|
child: Text(context.l10n.add_artist_to_blacklist),
|
|
).call,
|
|
child: IconButton(
|
|
icon: Icon(
|
|
SpotubeIcons.userRemove,
|
|
color: !isBlackListed ? Colors.red[400] : null,
|
|
),
|
|
variance: isBlackListed
|
|
? ButtonVariance.destructive
|
|
: ButtonVariance.ghost,
|
|
onPressed: () async {
|
|
if (isBlackListed) {
|
|
await ref.read(blacklistProvider.notifier).remove(artist.id);
|
|
} else {
|
|
await ref.read(blacklistProvider.notifier).add(
|
|
BlacklistTableCompanion.insert(
|
|
name: artist.name,
|
|
elementId: artist.id,
|
|
elementType: BlacklistedType.artist,
|
|
),
|
|
);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
IconButton.ghost(
|
|
icon: const Icon(SpotubeIcons.share),
|
|
onPressed: () async {
|
|
await Clipboard.setData(
|
|
ClipboardData(
|
|
text: artist.externalUri,
|
|
),
|
|
);
|
|
|
|
if (!context.mounted) return;
|
|
|
|
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),
|
|
if (artist.followers != null)
|
|
Flexible(
|
|
child: AutoSizeText(
|
|
context.l10n.followers(
|
|
PrimitiveUtils.toReadableNumber(
|
|
artist.followers!.toDouble(),
|
|
),
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
minFontSize: 12,
|
|
).muted(),
|
|
),
|
|
if (constrains.mdAndUp) ...[
|
|
const Gap(20),
|
|
actions,
|
|
]
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (constrains.smAndDown) ...[
|
|
const Gap(20),
|
|
actions,
|
|
]
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|