Files
SpotiFLAC-Mobile/lib/screens/favorite_artists_screen.dart
T

201 lines
7.5 KiB
Dart

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/providers/library_collections_provider.dart';
import 'package:spotiflac_android/screens/artist_screen.dart';
import 'package:spotiflac_android/services/cover_cache_manager.dart';
import 'package:spotiflac_android/utils/app_bar_layout.dart';
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
import 'package:spotiflac_android/widgets/animation_utils.dart';
class FavoriteArtistsScreen extends ConsumerWidget {
const FavoriteArtistsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final artists = ref.watch(
libraryCollectionsProvider.select((state) => state.favoriteArtists),
);
final colorScheme = Theme.of(context).colorScheme;
final topPadding = normalizedHeaderTopPadding(context);
final bottomInset = context.navBarBottomInset;
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 120 + topPadding,
collapsedHeight: kToolbarHeight,
floating: false,
pinned: true,
backgroundColor: colorScheme.surface,
surfaceTintColor: Colors.transparent,
leading: IconButton(
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
flexibleSpace: LayoutBuilder(
builder: (context, constraints) {
final maxHeight = 120 + topPadding;
final minHeight = kToolbarHeight + topPadding;
final expandRatio =
((constraints.maxHeight - minHeight) /
(maxHeight - minHeight))
.clamp(0.0, 1.0);
final leftPadding = 56 - (32 * expandRatio);
return FlexibleSpaceBar(
expandedTitleScale: 1.0,
titlePadding: EdgeInsets.only(left: leftPadding, bottom: 16),
title: Text(
context.l10n.collectionFavoriteArtists,
style: TextStyle(
fontSize: 20 + (8 * expandRatio),
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
);
},
),
),
if (artists.isEmpty)
SliverFillRemaining(
hasScrollBody: false,
child: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.favorite,
size: 60,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: 12),
Text(
context.l10n.collectionFavoriteArtistsEmptyTitle,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
context.l10n.collectionFavoriteArtistsEmptySubtitle,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
),
)
else
SliverList.separated(
itemCount: artists.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final artist = artists[index];
return ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
leading: _ArtistThumbnail(artist: artist),
title: Text(
artist.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle:
artist.providerId == null || artist.providerId!.isEmpty
? null
: Text(
artist.providerId!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: IconButton(
tooltip: context.l10n.artistOptionRemoveFromFavorites,
icon: Icon(Icons.favorite, color: colorScheme.error),
onPressed: () async {
await ref
.read(libraryCollectionsProvider.notifier)
.removeFavoriteArtist(artist.key);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
context.l10n.collectionRemovedFromFavoriteArtists(
artist.name,
),
),
),
);
},
),
onTap: () {
Navigator.of(context).push(
slidePageRoute<void>(
page: ArtistScreen(
artistId: artist.artistId,
artistName: artist.name,
coverUrl: artist.imageUrl,
extensionId:
artist.providerId != null &&
artist.providerId!.isNotEmpty
? artist.providerId
: null,
),
),
);
},
);
},
),
SliverToBoxAdapter(child: SizedBox(height: bottomInset)),
],
),
);
}
}
class _ArtistThumbnail extends StatelessWidget {
final CollectionArtistEntry artist;
const _ArtistThumbnail({required this.artist});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final imageUrl = artist.imageUrl;
return ClipOval(
child: imageUrl != null && imageUrl.isNotEmpty
? CachedNetworkImage(
imageUrl: imageUrl,
width: 56,
height: 56,
fit: BoxFit.cover,
memCacheWidth: 112,
memCacheHeight: 112,
cacheManager: CoverCacheManager.instance,
errorWidget: (_, _, _) => _placeholder(colorScheme),
)
: _placeholder(colorScheme),
);
}
Widget _placeholder(ColorScheme colorScheme) {
return Container(
width: 56,
height: 56,
color: colorScheme.surfaceContainerHighest,
child: Icon(Icons.person, color: colorScheme.onSurfaceVariant),
);
}
}