♻️ Better current track metadata

This commit is contained in:
2025-12-20 00:25:48 +08:00
parent cb4cca2917
commit a86e8b1cab
8 changed files with 299 additions and 247 deletions

View File

@@ -1,7 +1,11 @@
import 'dart:typed_data';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:media_kit/media_kit.dart' as media_kit; import 'package:media_kit/media_kit.dart' as media_kit;
import 'package:groovybox/data/db.dart'; import 'package:groovybox/data/db.dart';
import 'package:groovybox/logic/metadata_service.dart';
import 'package:groovybox/providers/audio_provider.dart';
import 'package:groovybox/providers/theme_provider.dart'; import 'package:groovybox/providers/theme_provider.dart';
import 'package:groovybox/providers/remote_provider.dart'; import 'package:groovybox/providers/remote_provider.dart';
import 'package:groovybox/providers/db_provider.dart'; import 'package:groovybox/providers/db_provider.dart';
@@ -47,11 +51,13 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
_container = container; _container = container;
} }
// Update theme color based on current track's album art // Update theme color based on current track's album art and set current metadata
void _updateThemeFromCurrentTrack(MediaItem mediaItem) async { void _updateThemeFromCurrentTrack(MediaItem mediaItem) async {
if (_container == null) return; if (_container == null) return;
try { try {
TrackMetadata? metadata;
// For remote tracks, get metadata from database // For remote tracks, get metadata from database
final urlResolver = _container!.read(remoteUrlResolverProvider); final urlResolver = _container!.read(remoteUrlResolverProvider);
if (urlResolver.isProtocolUrl(mediaItem.id)) { if (urlResolver.isProtocolUrl(mediaItem.id)) {
@@ -60,22 +66,59 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
database.tracks, database.tracks,
)..where((t) => t.path.equals(mediaItem.id))).getSingleOrNull(); )..where((t) => t.path.equals(mediaItem.id))).getSingleOrNull();
if (track != null && track.artUri != null) { if (track != null) {
// Fetch album art bytes for remote tracks // Fetch album art bytes for remote tracks
// TODO: Implement remote album art fetching for theme Uint8List? artBytes;
if (track.artUri != null) {
try {
final response = await http.get(Uri.parse(track.artUri!));
if (response.statusCode == 200) {
artBytes = response.bodyBytes;
}
} catch (e) {
// Ignore art fetching errors
}
}
metadata = TrackMetadata(
title: track.title,
artist: track.artist,
album: track.album,
artBytes: artBytes,
);
// Update theme from album art
final seedColorNotifier = _container!.read(
seedColorProvider.notifier,
);
seedColorNotifier.updateFromAlbumArtBytes(artBytes);
} }
} else { } else {
// For local tracks, use existing metadata service // For local tracks, use metadata service
// TODO: Get metadata service working final metadataService = MetadataService();
metadata = await metadataService.getMetadata(mediaItem.id);
// Update theme from album art
final seedColorNotifier = _container!.read(seedColorProvider.notifier);
seedColorNotifier.updateFromAlbumArtBytes(metadata.artBytes);
} }
// Reset to default for now // Set current track metadata
final seedColorNotifier = _container!.read(seedColorProvider.notifier); if (metadata != null) {
seedColorNotifier.resetToDefault(); final metadataNotifier = _container!.read(
currentTrackMetadataProvider.notifier,
);
metadataNotifier.setMetadata(metadata);
}
} catch (e) { } catch (e) {
// If metadata retrieval fails, reset to default color // If metadata retrieval fails, reset to default color and clear metadata
final seedColorNotifier = _container!.read(seedColorProvider.notifier); final seedColorNotifier = _container!.read(seedColorProvider.notifier);
seedColorNotifier.resetToDefault(); seedColorNotifier.resetToDefault();
final metadataNotifier = _container!.read(
currentTrackMetadataProvider.notifier,
);
metadataNotifier.clear();
} }
} }

View File

@@ -1,10 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_media_metadata/flutter_media_metadata.dart'; import 'package:flutter_media_metadata/flutter_media_metadata.dart';
import 'package:http/http.dart' as http;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:groovybox/providers/remote_provider.dart';
import 'package:groovybox/providers/db_provider.dart'; import 'package:groovybox/providers/db_provider.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart' as http;
class TrackMetadata { class TrackMetadata {
final String? title; final String? title;
@@ -38,20 +37,16 @@ class MetadataService {
} }
} }
@Riverpod(keepAlive: true) final trackMetadataProvider = FutureProvider.family<TrackMetadata, String>((
MetadataService metadataService(Ref ref) { ref,
return MetadataService(); path,
} ) async {
try {
// Import the database provider directly
final db = ref.watch(databaseProvider);
@riverpod final track = await (db.select(
Future<TrackMetadata> trackMetadata(Ref ref, String path) async { db.tracks,
// Check if this is a remote track (protocol URL)
final urlResolver = ref.watch(remoteUrlResolverProvider);
if (urlResolver.isProtocolUrl(path)) {
// For remote tracks, get metadata from database
final database = ref.watch(databaseProvider);
final track = await (database.select(
database.tracks,
)..where((t) => t.path.equals(path))).getSingleOrNull(); )..where((t) => t.path.equals(path))).getSingleOrNull();
if (track != null) { if (track != null) {
@@ -75,11 +70,21 @@ Future<TrackMetadata> trackMetadata(Ref ref, String path) async {
album: track.album, album: track.album,
artBytes: artBytes, artBytes: artBytes,
); );
} else {
return TrackMetadata(
title: 'Unknown Title',
artist: 'Unknown Artist',
album: 'Unknown Album',
artBytes: null,
);
} }
return TrackMetadata(); } catch (e) {
} else { debugPrint('Error fetching metadata for $path: $e');
// For local tracks, use file metadata return TrackMetadata(
final service = MetadataService(); title: 'Unknown Title',
return service.getMetadata(path); artist: 'Unknown Artist',
album: 'Unknown Album',
artBytes: null,
);
} }
} });

View File

@@ -1,4 +1,5 @@
import 'package:groovybox/logic/audio_handler.dart'; import 'package:groovybox/logic/audio_handler.dart';
import 'package:groovybox/logic/metadata_service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'audio_provider.g.dart'; part 'audio_provider.g.dart';
@@ -15,3 +16,19 @@ AudioHandler audioHandler(Ref ref) {
void setAudioHandler(AudioHandler handler) { void setAudioHandler(AudioHandler handler) {
_audioHandler = handler; _audioHandler = handler;
} }
@Riverpod(keepAlive: true)
class CurrentTrackMetadataNotifier extends _$CurrentTrackMetadataNotifier {
@override
TrackMetadata? build() {
return null;
}
void setMetadata(TrackMetadata metadata) {
state = metadata;
}
void clear() {
state = null;
}
}

View File

@@ -49,3 +49,58 @@ final class AudioHandlerProvider
} }
String _$audioHandlerHash() => r'65fbd92e049fe4f3a0763516f1e68e1614f7630f'; String _$audioHandlerHash() => r'65fbd92e049fe4f3a0763516f1e68e1614f7630f';
@ProviderFor(CurrentTrackMetadataNotifier)
const currentTrackMetadataProvider = CurrentTrackMetadataNotifierProvider._();
final class CurrentTrackMetadataNotifierProvider
extends $NotifierProvider<CurrentTrackMetadataNotifier, TrackMetadata?> {
const CurrentTrackMetadataNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'currentTrackMetadataProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$currentTrackMetadataNotifierHash();
@$internal
@override
CurrentTrackMetadataNotifier create() => CurrentTrackMetadataNotifier();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(TrackMetadata? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<TrackMetadata?>(value),
);
}
}
String _$currentTrackMetadataNotifierHash() =>
r'0a491bd4edda2b010ed3d6f7dd459f4ac8689a5f';
abstract class _$CurrentTrackMetadataNotifier
extends $Notifier<TrackMetadata?> {
TrackMetadata? build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<TrackMetadata?, TrackMetadata?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<TrackMetadata?, TrackMetadata?>,
TrackMetadata?,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -48,46 +48,37 @@ class PlayerScreen extends HookConsumerWidget {
} }
final media = medias[index]; final media = medias[index];
final path = Uri.decodeFull(Uri.parse(media.uri).path); final currentMetadata = ref.watch(currentTrackMetadataProvider);
// For now, skip metadata loading to avoid provider issues
final AsyncValue<TrackMetadata> metadataAsync = AsyncValue.data(
TrackMetadata(),
);
// Build blurred background if cover art is available // Build blurred background if cover art is available
Widget? background; Widget? background;
metadataAsync.when( final artBytes = currentMetadata?.artBytes;
data: (meta) { if (artBytes != null) {
if (meta.artBytes != null) { background = Positioned.fill(
background = Positioned.fill( child: Stack(
child: Stack( children: [
children: [ Container(
Container( decoration: BoxDecoration(
decoration: BoxDecoration( image: DecorationImage(
image: DecorationImage( image: MemoryImage(artBytes),
image: MemoryImage(meta.artBytes!), fit: BoxFit.cover,
fit: BoxFit.cover,
),
),
), ),
BackdropFilter( ),
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: Container(
color: Theme.of(
context,
).colorScheme.surface.withValues(alpha: 0.6),
),
),
],
), ),
); BackdropFilter(
} else { filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
background = null; child: Container(
} color: Theme.of(
}, context,
loading: () => background = null, ).colorScheme.surface.withValues(alpha: 0.6),
error: (_, _) => background = null, ),
); ),
],
),
);
} else {
background = null;
}
final devicePadding = MediaQuery.paddingOf(context); final devicePadding = MediaQuery.paddingOf(context);
@@ -116,7 +107,7 @@ class PlayerScreen extends HookConsumerWidget {
body: ClipRect( body: ClipRect(
child: Stack( child: Stack(
children: [ children: [
...background != null ? [background!] : [], ...background != null ? [background] : [],
// Main content (StreamBuilder) // Main content (StreamBuilder)
Builder( Builder(
builder: (context) { builder: (context) {
@@ -126,18 +117,16 @@ class PlayerScreen extends HookConsumerWidget {
child: _MobileLayout( child: _MobileLayout(
player: player, player: player,
viewMode: viewMode, viewMode: viewMode,
metadataAsync: metadataAsync,
media: media, media: media,
trackPath: path, trackPath: media.uri,
), ),
); );
} else { } else {
return _DesktopLayout( return _DesktopLayout(
player: player, player: player,
viewMode: viewMode, viewMode: viewMode,
metadataAsync: metadataAsync,
media: media, media: media,
trackPath: path, trackPath: media.uri,
); );
} }
}, },
@@ -164,30 +153,30 @@ class PlayerScreen extends HookConsumerWidget {
} }
} }
class _MobileLayout extends StatelessWidget { class _MobileLayout extends HookConsumerWidget {
final Player player; final Player player;
final ValueNotifier<ViewMode> viewMode; final ValueNotifier<ViewMode> viewMode;
final AsyncValue<TrackMetadata> metadataAsync;
final Media media; final Media media;
final String trackPath; final String trackPath;
const _MobileLayout({ const _MobileLayout({
required this.player, required this.player,
required this.viewMode, required this.viewMode,
required this.metadataAsync,
required this.media, required this.media,
required this.trackPath, required this.trackPath,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final currentMetadata = ref.watch(currentTrackMetadataProvider);
return AnimatedSwitcher( return AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: switch (viewMode.value) { child: switch (viewMode.value) {
ViewMode.cover => _CoverView( ViewMode.cover => _CoverView(
key: const ValueKey('cover'), key: const ValueKey('cover'),
player: player, player: player,
metadataAsync: metadataAsync, currentMetadata: currentMetadata,
media: media, media: media,
trackPath: trackPath, trackPath: trackPath,
).padding(bottom: MediaQuery.paddingOf(context).bottom), ).padding(bottom: MediaQuery.paddingOf(context).bottom),
@@ -207,13 +196,13 @@ class _MobileLayout extends StatelessWidget {
class _PlayerCoverControlsPanel extends StatelessWidget { class _PlayerCoverControlsPanel extends StatelessWidget {
final Player player; final Player player;
final AsyncValue<TrackMetadata> metadataAsync; final TrackMetadata? currentMetadata;
final Media media; final Media media;
final String trackPath; final String trackPath;
const _PlayerCoverControlsPanel({ const _PlayerCoverControlsPanel({
required this.player, required this.player,
required this.metadataAsync, required this.currentMetadata,
required this.media, required this.media,
required this.trackPath, required this.trackPath,
}); });
@@ -235,7 +224,7 @@ class _PlayerCoverControlsPanel extends StatelessWidget {
constraints: const BoxConstraints(maxWidth: 400), constraints: const BoxConstraints(maxWidth: 400),
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: _PlayerCoverArt(metadataAsync: metadataAsync), child: _PlayerCoverArt(currentMetadata: currentMetadata),
), ),
), ),
), ),
@@ -243,7 +232,7 @@ class _PlayerCoverControlsPanel extends StatelessWidget {
), ),
_PlayerControls( _PlayerControls(
player: player, player: player,
metadataAsync: metadataAsync, currentMetadata: currentMetadata,
media: media, media: media,
trackPath: trackPath, trackPath: trackPath,
), ),
@@ -254,23 +243,23 @@ class _PlayerCoverControlsPanel extends StatelessWidget {
} }
} }
class _DesktopLayout extends StatelessWidget { class _DesktopLayout extends HookConsumerWidget {
final Player player; final Player player;
final ValueNotifier<ViewMode> viewMode; final ValueNotifier<ViewMode> viewMode;
final AsyncValue<TrackMetadata> metadataAsync;
final Media media; final Media media;
final String trackPath; final String trackPath;
const _DesktopLayout({ const _DesktopLayout({
required this.player, required this.player,
required this.viewMode, required this.viewMode,
required this.metadataAsync,
required this.media, required this.media,
required this.trackPath, required this.trackPath,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final currentMetadata = ref.watch(currentTrackMetadataProvider);
return AnimatedSwitcher( return AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: switch (viewMode.value) { child: switch (viewMode.value) {
@@ -278,7 +267,7 @@ class _DesktopLayout extends StatelessWidget {
key: const ValueKey('cover'), key: const ValueKey('cover'),
child: _PlayerCoverControlsPanel( child: _PlayerCoverControlsPanel(
player: player, player: player,
metadataAsync: metadataAsync, currentMetadata: currentMetadata,
media: media, media: media,
trackPath: trackPath, trackPath: trackPath,
), ),
@@ -294,7 +283,7 @@ class _DesktopLayout extends StatelessWidget {
child: Center( child: Center(
child: _PlayerCoverControlsPanel( child: _PlayerCoverControlsPanel(
player: player, player: player,
metadataAsync: metadataAsync, currentMetadata: currentMetadata,
media: media, media: media,
trackPath: trackPath, trackPath: trackPath,
), ),
@@ -328,7 +317,7 @@ class _DesktopLayout extends StatelessWidget {
child: Center( child: Center(
child: _PlayerCoverControlsPanel( child: _PlayerCoverControlsPanel(
player: player, player: player,
metadataAsync: metadataAsync, currentMetadata: currentMetadata,
media: media, media: media,
trackPath: trackPath, trackPath: trackPath,
), ),
@@ -355,14 +344,14 @@ class _DesktopLayout extends StatelessWidget {
class _CoverView extends StatelessWidget { class _CoverView extends StatelessWidget {
final Player player; final Player player;
final AsyncValue<TrackMetadata> metadataAsync; final TrackMetadata? currentMetadata;
final Media media; final Media media;
final String trackPath; final String trackPath;
const _CoverView({ const _CoverView({
super.key, super.key,
required this.player, required this.player,
required this.metadataAsync, required this.currentMetadata,
required this.media, required this.media,
required this.trackPath, required this.trackPath,
}); });
@@ -377,13 +366,13 @@ class _CoverView extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 32), padding: const EdgeInsets.only(bottom: 32),
child: Center( child: Center(
child: _PlayerCoverArt(metadataAsync: metadataAsync), child: _PlayerCoverArt(currentMetadata: currentMetadata),
), ),
), ),
), ),
_PlayerControls( _PlayerControls(
player: player, player: player,
metadataAsync: metadataAsync, currentMetadata: currentMetadata,
media: media, media: media,
trackPath: trackPath, trackPath: trackPath,
), ),
@@ -417,64 +406,49 @@ class _LyricsView extends StatelessWidget {
} }
class _PlayerCoverArt extends StatelessWidget { class _PlayerCoverArt extends StatelessWidget {
final AsyncValue<TrackMetadata> metadataAsync; final TrackMetadata? currentMetadata;
const _PlayerCoverArt({required this.metadataAsync}); const _PlayerCoverArt({required this.currentMetadata});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return metadataAsync.when( return Center(
data: (meta) => Center( child: AspectRatio(
child: AspectRatio( aspectRatio: 1,
aspectRatio: 1, child: Container(
child: Container( decoration: BoxDecoration(
decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainer,
color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(24),
borderRadius: BorderRadius.circular(24), boxShadow: [
boxShadow: [ BoxShadow(
BoxShadow( color: Theme.of(
color: Theme.of( context,
context, ).colorScheme.shadow.withValues(alpha: 0.3),
).colorScheme.shadow.withValues(alpha: 0.3), blurRadius: 20,
blurRadius: 20, offset: const Offset(0, 10),
offset: const Offset(0, 10), ),
), ],
], image: () {
image: meta.artBytes != null final artBytes = currentMetadata?.artBytes;
return artBytes != null
? DecorationImage( ? DecorationImage(
image: MemoryImage(meta.artBytes!), image: MemoryImage(artBytes),
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: null, : null;
), }(),
child: meta.artBytes == null
? Center(
child: Icon(
Icons.music_note,
size: 80,
color: Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.7),
),
)
: null,
),
),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (_, _) => Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(24),
),
child: Center(
child: Icon(
Icons.error_outline,
size: 80,
color: Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.7),
), ),
child: currentMetadata?.artBytes == null
? Center(
child: Icon(
Icons.music_note,
size: 80,
color: Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.7),
),
)
: null,
), ),
), ),
); );
@@ -1161,7 +1135,7 @@ class _QueueView extends HookConsumerWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final media = playlist.medias[index]; final media = playlist.medias[index];
final isCurrent = index == playlist.index; final isCurrent = index == playlist.index;
final trackPath = Uri.decodeFull(Uri.parse(media.uri).path); final trackPath = media.uri;
final trackAsync = ref.watch(trackByPathProvider(trackPath)); final trackAsync = ref.watch(trackByPathProvider(trackPath));
return trackAsync.when( return trackAsync.when(
@@ -1525,13 +1499,13 @@ class _TimedLyricsView extends HookConsumerWidget {
class _PlayerControls extends HookWidget { class _PlayerControls extends HookWidget {
final Player player; final Player player;
final AsyncValue<TrackMetadata> metadataAsync; final TrackMetadata? currentMetadata;
final Media media; final Media media;
final String trackPath; final String trackPath;
const _PlayerControls({ const _PlayerControls({
required this.player, required this.player,
required this.metadataAsync, required this.currentMetadata,
required this.media, required this.media,
required this.trackPath, required this.trackPath,
}); });
@@ -1547,27 +1521,19 @@ class _PlayerControls extends HookWidget {
// Title & Artist // Title & Artist
Column( Column(
children: [ children: [
metadataAsync.when( Text(
data: (meta) => Text( currentMetadata?.title ?? Uri.parse(media.uri).pathSegments.last,
meta.title ?? Uri.parse(media.uri).pathSegments.last, style: Theme.of(context).textTheme.headlineSmall,
style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center,
textAlign: TextAlign.center, maxLines: 1,
maxLines: 1, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
),
loading: () => const SizedBox(height: 32),
error: (_, _) => Text(Uri.parse(media.uri).pathSegments.last),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
metadataAsync.when( Text(
data: (meta) => Text( currentMetadata?.artist ?? 'Unknown Artist',
meta.artist ?? 'Unknown Artist', style: Theme.of(context).textTheme.bodyLarge?.copyWith(
style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.primary,
),
), ),
loading: () => const SizedBox(height: 24),
error: (_, _) => const SizedBox.shrink(),
), ),
], ],
), ),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:groovybox/data/db.dart' as db; import 'package:groovybox/data/db.dart' as db;
import 'package:groovybox/logic/metadata_service.dart';
import 'package:groovybox/providers/audio_provider.dart'; import 'package:groovybox/providers/audio_provider.dart';
import 'package:groovybox/ui/screens/player_screen.dart'; import 'package:groovybox/ui/screens/player_screen.dart';
import 'package:groovybox/ui/widgets/track_tile.dart'; import 'package:groovybox/ui/widgets/track_tile.dart';
@@ -53,10 +53,7 @@ class _MobileMiniPlayer extends HookConsumerWidget {
final devicePadding = MediaQuery.paddingOf(context); final devicePadding = MediaQuery.paddingOf(context);
// For now, skip metadata loading to avoid provider issues final currentMetadata = ref.watch(currentTrackMetadataProvider);
final AsyncValue<TrackMetadata> metadataAsync = AsyncValue.data(
TrackMetadata(),
);
Widget content = Container( Widget content = Container(
height: 72 + devicePadding.bottom, height: 72 + devicePadding.bottom,
@@ -132,19 +129,18 @@ class _MobileMiniPlayer extends HookConsumerWidget {
// Cover Art // Cover Art
AspectRatio( AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: metadataAsync.when( child: currentMetadata?.artBytes != null
data: (meta) => meta.artBytes != null ? Image.memory(
? Image.memory(meta.artBytes!, fit: BoxFit.cover) currentMetadata!.artBytes!,
: Container( fit: BoxFit.cover,
color: Colors.grey[800], )
child: const Icon( : Container(
Icons.music_note, color: Colors.grey[800],
color: Colors.white54, child: const Icon(
), Icons.music_note,
color: Colors.white54,
), ),
loading: () => Container(color: Colors.grey[800]), ),
error: (_, _) => Container(color: Colors.grey[800]),
),
), ),
const Gap(8), const Gap(8),
// Title & Artist // Title & Artist
@@ -155,28 +151,19 @@ class _MobileMiniPlayer extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
metadataAsync.when( Text(
data: (meta) => Text( currentMetadata?.title ??
meta.title ?? Uri.parse(media.uri).pathSegments.last,
Uri.parse(media.uri).pathSegments.last, style: Theme.of(context).textTheme.bodyMedium
style: Theme.of(context).textTheme.bodyMedium ?.copyWith(fontWeight: FontWeight.bold),
?.copyWith(fontWeight: FontWeight.bold), maxLines: 1,
maxLines: 1, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
),
loading: () => const Text('Loading...'),
error: (_, _) =>
Text(Uri.parse(media.uri).pathSegments.last),
), ),
metadataAsync.when( Text(
data: (meta) => Text( currentMetadata?.artist ?? 'Unknown Artist',
meta.artist ?? 'Unknown Artist', style: Theme.of(context).textTheme.bodySmall,
style: Theme.of(context).textTheme.bodySmall, maxLines: 1,
maxLines: 1, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
),
loading: () => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(),
), ),
], ],
), ),
@@ -272,10 +259,7 @@ class _DesktopMiniPlayer extends HookConsumerWidget {
final devicePadding = MediaQuery.paddingOf(context); final devicePadding = MediaQuery.paddingOf(context);
// For now, skip metadata loading to avoid provider issues final currentMetadata = ref.watch(currentTrackMetadataProvider);
final AsyncValue<TrackMetadata> metadataAsync = AsyncValue.data(
TrackMetadata(),
);
Widget content = Container( Widget content = Container(
height: 72 + devicePadding.bottom, height: 72 + devicePadding.bottom,
@@ -356,23 +340,18 @@ class _DesktopMiniPlayer extends HookConsumerWidget {
// Cover Art // Cover Art
AspectRatio( AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: metadataAsync.when( child: currentMetadata?.artBytes != null
data: (meta) => meta.artBytes != null ? Image.memory(
? Image.memory( currentMetadata!.artBytes!,
meta.artBytes!, fit: BoxFit.cover,
fit: BoxFit.cover, )
) : Container(
: Container( color: Colors.grey[800],
color: Colors.grey[800], child: const Icon(
child: const Icon( Icons.music_note,
Icons.music_note, color: Colors.white54,
color: Colors.white54,
),
), ),
loading: () => Container(color: Colors.grey[800]), ),
error: (_, _) =>
Container(color: Colors.grey[800]),
),
), ),
const Gap(8), const Gap(8),
// Title & Artist // Title & Artist
@@ -384,33 +363,19 @@ class _DesktopMiniPlayer extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
metadataAsync.when( Text(
data: (meta) => Text( currentMetadata?.title ??
meta.title ?? Uri.parse(media.uri).pathSegments.last,
Uri.parse(media.uri).pathSegments.last, style: Theme.of(context).textTheme.bodyMedium
style: Theme.of(context) ?.copyWith(fontWeight: FontWeight.bold),
.textTheme maxLines: 1,
.bodyMedium overflow: TextOverflow.ellipsis,
?.copyWith(fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
loading: () => const Text('Loading...'),
error: (_, _) => Text(
Uri.parse(media.uri).pathSegments.last,
),
), ),
metadataAsync.when( Text(
data: (meta) => Text( currentMetadata?.artist ?? 'Unknown Artist',
meta.artist ?? 'Unknown Artist', style: Theme.of(context).textTheme.bodySmall,
style: Theme.of( maxLines: 1,
context, overflow: TextOverflow.ellipsis,
).textTheme.bodySmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
loading: () => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(),
), ),
], ],
), ),

View File

@@ -537,7 +537,7 @@ packages:
source: hosted source: hosted
version: "0.15.6" version: "0.15.6"
http: http:
dependency: transitive dependency: "direct main"
description: description:
name: http name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"

View File

@@ -51,6 +51,7 @@ dependencies:
styled_widget: ^0.4.1 styled_widget: ^0.4.1
super_sliver_list: ^0.4.1 super_sliver_list: ^0.4.1
dio: ^5.0.0 dio: ^5.0.0
http: ^1.2.2
audio_service: ^0.18.18 audio_service: ^0.18.18
palette_generator: ^0.3.3+4 palette_generator: ^0.3.3+4
watcher: ^1.2.0 watcher: ^1.2.0