diff --git a/CHANGELOG.md b/CHANGELOG.md index 9143d6d7..a8cb395c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # Changelog +## [2.2.7] - 2026-01-11 + +### Added + +- **Deezer Metadata Support**: Enhanced metadata viewer for Deezer tracks + - "Open in Deezer" button for Deezer-sourced tracks (opens app or web) + - Displays "Deezer ID" instead of "Spotify ID" when applicable + +### Changed + +- **UI Modernization**: Major UI consistency updates across the app + - **Unified App Bars**: Home, History, and Settings now share identical behavior + - Lowered expanded header for easier one-handed reachability + - Dynamic title text scaling (20px to 34px) + - **Appearance Settings**: Completely redesigned appearance page + - New "Theme Preview" card showing visualizing current theme + - Modern color palette picker replacing old color dots + - Clean, grouped layout + - **App Logo**: Refined logo style on Home and About screens + - Inverted colors: Filled primary color circle with on-color icon + - Removed padding for a cleaner, bolder look + - **Material 3 Switches**: Added checkmark icon to active switches + +## [2.2.6] - 2026-01-11 + +### Fixed + +- **Release Mode Logging**: Flutter app logs now properly captured in release builds + - Previously only Go backend logs appeared when "Detailed Logging" was enabled + - Now both Flutter and Go logs are captured in release mode + - Bypasses Logger package which filters logs in release mode + +### Added + +- **Detailed Deezer Search Logging**: Better debugging for search issues + - Logs API URLs, response counts, and errors + - Helps diagnose geo-restriction and API issues + - Detects Deezer API error responses + +### Changed + +- **Home Screen Logo**: Replaced music note icon with app logo + - Uses `assets/images/logo.png` + - Rounded corners (24px radius) + - Fallback to music note icon if logo fails to load +- **About Page Logo**: Removed shadow/border from logo + - Cleaner appearance without background container +- **About Page Icon Alignment**: Icons now aligned with contributor avatars + - DoubleDouble and DAB Music icons use 40x40 area + - Text now properly aligned with contributor items + ## [2.2.5] - 2026-01-10 ### Added diff --git a/assets/images/logo-transparant.png b/assets/images/logo-transparant.png new file mode 100644 index 00000000..afe8ad94 Binary files /dev/null and b/assets/images/logo-transparant.png differ diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index c8e353d3..bf96e6f2 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -289,6 +289,7 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient final colorScheme = Theme.of(context).colorScheme; final hasResults = _isTyping || tracks.isNotEmpty || (searchArtists != null && searchArtists.isNotEmpty) || isLoading; final screenHeight = MediaQuery.of(context).size.height; + final topPadding = MediaQuery.of(context).padding.top; final historyItems = ref.watch(downloadHistoryProvider.select((s) => s.items)); return Scaffold( @@ -297,24 +298,32 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient slivers: [ // App Bar - always present SliverAppBar( - expandedHeight: 130, + expandedHeight: 120 + topPadding, collapsedHeight: kToolbarHeight, floating: false, pinned: true, backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, automaticallyImplyLeading: false, - flexibleSpace: FlexibleSpaceBar( - expandedTitleScale: 1.3, - titlePadding: const EdgeInsets.only(left: 24, bottom: 16), - title: Text( - 'Home', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), + 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); + + return FlexibleSpaceBar( + expandedTitleScale: 1.0, + titlePadding: const EdgeInsets.only(left: 24, bottom: 16), + title: Text( + 'Home', + style: TextStyle( + fontSize: 20 + (14 * expandRatio), // 20 -> 34 + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, ), ), @@ -328,24 +337,25 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient : Column( children: [ SizedBox(height: screenHeight * 0.06), - ClipRRect( - borderRadius: BorderRadius.circular(24), + Container( + width: 96, + height: 96, + decoration: BoxDecoration( + color: colorScheme.primary, + shape: BoxShape.circle, + ), child: Image.asset( - 'assets/images/logo.png', - width: 96, - height: 96, - fit: BoxFit.cover, - errorBuilder: (_, _, _) => Container( - width: 96, - height: 96, - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(24), - ), - child: Icon( - Icons.music_note, - size: 48, - color: colorScheme.onPrimaryContainer, + 'assets/images/logo-transparant.png', + color: colorScheme.onPrimary, // Tint with onPrimary color + fit: BoxFit.contain, + errorBuilder: (_, _, _) => ClipRRect( + // Fallback to original logo if transparent one is missing + borderRadius: BorderRadius.circular(24), + child: Image.asset( + 'assets/images/logo.png', + width: 96, + height: 96, + fit: BoxFit.cover, ), ), ), diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 082e3085..cbd10da6 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -99,29 +99,38 @@ class _QueueTabState extends ConsumerState { final historyItems = ref.watch(downloadHistoryProvider.select((s) => s.items)); final historyViewMode = ref.watch(settingsProvider.select((s) => s.historyViewMode)); final colorScheme = Theme.of(context).colorScheme; + final topPadding = MediaQuery.of(context).padding.top; return CustomScrollView( slivers: [ // Collapsing App Bar - Simplified for performance SliverAppBar( - expandedHeight: 130, + expandedHeight: 120 + topPadding, collapsedHeight: kToolbarHeight, floating: false, pinned: true, backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, automaticallyImplyLeading: false, - flexibleSpace: FlexibleSpaceBar( - expandedTitleScale: 1.3, - titlePadding: const EdgeInsets.only(left: 24, bottom: 16), - title: Text( - 'History', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), + 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); + + return FlexibleSpaceBar( + expandedTitleScale: 1.0, + titlePadding: const EdgeInsets.only(left: 24, bottom: 16), + title: Text( + 'History', + style: TextStyle( + fontSize: 20 + (14 * expandRatio), // 20 -> 34 + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, ), ), diff --git a/lib/screens/settings/about_page.dart b/lib/screens/settings/about_page.dart index 36f055ac..c29298f5 100644 --- a/lib/screens/settings/about_page.dart +++ b/lib/screens/settings/about_page.dart @@ -250,24 +250,25 @@ class _AppHeaderCard extends StatelessWidget { child: Column( children: [ // App logo - ClipRRect( - borderRadius: BorderRadius.circular(24), + // App logo + Container( + width: 88, + height: 88, + decoration: BoxDecoration( + color: colorScheme.primary, + shape: BoxShape.circle, + ), child: Image.asset( - 'assets/images/logo.png', - width: 88, - height: 88, - fit: BoxFit.cover, - errorBuilder: (_, _, _) => Container( - width: 88, - height: 88, - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(24), - ), - child: Icon( - Icons.music_note, - size: 48, - color: colorScheme.onPrimaryContainer, + 'assets/images/logo-transparant.png', + color: colorScheme.onPrimary, // Tint with onPrimary color + fit: BoxFit.contain, + errorBuilder: (_, _, _) => ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Image.asset( + 'assets/images/logo.png', + width: 88, + height: 88, + fit: BoxFit.cover, ), ), ), diff --git a/lib/screens/settings/appearance_settings_page.dart b/lib/screens/settings/appearance_settings_page.dart index 107be1b2..18daa3de 100644 --- a/lib/screens/settings/appearance_settings_page.dart +++ b/lib/screens/settings/appearance_settings_page.dart @@ -31,6 +31,42 @@ class AppearanceSettingsPage extends ConsumerWidget { flexibleSpace: _AppBarTitle(title: 'Appearance', topPadding: topPadding), ), + // Preview Section + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: _ThemePreviewCard(), + ), + ), + + // Color section + const SliverToBoxAdapter(child: SettingsSectionHeader(title: 'Color')), + + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.wallpaper, + title: 'Dynamic Color', + subtitle: 'Use colors from your wallpaper', + value: themeSettings.useDynamicColor, + onChanged: (value) => ref.read(themeProvider.notifier).setUseDynamicColor(value), + showDivider: false, + ), + ], + ), + ), + if (!themeSettings.useDynamicColor) + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), + child: _ColorPalettePicker( + currentColor: themeSettings.seedColorValue, + onColorSelected: (color) => ref.read(themeProvider.notifier).setSeedColor(color), + ), + ), + ), + // Theme section const SliverToBoxAdapter(child: SettingsSectionHeader(title: 'Theme')), SliverToBoxAdapter( @@ -43,7 +79,7 @@ class AppearanceSettingsPage extends ConsumerWidget { SettingsSwitchItem( icon: Icons.brightness_2, title: 'AMOLED Dark', - subtitle: 'Pure black background for OLED screens', + subtitle: 'Pure black background', value: themeSettings.useAmoled, onChanged: (value) => ref.read(themeProvider.notifier).setUseAmoled(value), showDivider: false, @@ -52,28 +88,6 @@ class AppearanceSettingsPage extends ConsumerWidget { ), ), - // Color section - const SliverToBoxAdapter(child: SettingsSectionHeader(title: 'Color')), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - SettingsSwitchItem( - icon: Icons.auto_awesome, - title: 'Dynamic Color', - subtitle: 'Use colors from your wallpaper', - value: themeSettings.useDynamicColor, - onChanged: (value) => ref.read(themeProvider.notifier).setUseDynamicColor(value), - showDivider: !themeSettings.useDynamicColor, - ), - if (!themeSettings.useDynamicColor) - _ColorPicker( - currentColor: themeSettings.seedColorValue, - onColorSelected: (color) => ref.read(themeProvider.notifier).setSeedColor(color), - ), - ], - ), - ), - // Layout section const SliverToBoxAdapter(child: SettingsSectionHeader(title: 'Layout')), SliverToBoxAdapter( @@ -88,7 +102,7 @@ class AppearanceSettingsPage extends ConsumerWidget { ), // Fill remaining for scroll - const SliverFillRemaining(hasScrollBody: false, child: SizedBox()), + const SliverFillRemaining(hasScrollBody: false, child: SizedBox(height: 32)), ], ), ), @@ -96,6 +110,230 @@ class AppearanceSettingsPage extends ConsumerWidget { } } +/// A simplified preview of how the app looks with current settings +class _ThemePreviewCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest, // Background similar to reference + borderRadius: BorderRadius.circular(28), + ), + clipBehavior: Clip.antiAlias, + child: Stack( + children: [ + // Decorative background blobs + Positioned( + top: -50, + right: -50, + child: Container( + width: 200, height: 200, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: colorScheme.primaryContainer.withValues(alpha: 0.5), + ), + ), + ), + Positioned( + bottom: -30, + left: -30, + child: Container( + width: 150, height: 150, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: colorScheme.tertiaryContainer.withValues(alpha: 0.5), + ), + ), + ), + + // Foreground "fake UI" + Center( + child: Container( + width: 260, + height: 140, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: colorScheme.surface, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: Row( + children: [ + // Fake Album Art + Container( + width: 108, + height: 108, + decoration: BoxDecoration( + color: colorScheme.primary, + borderRadius: BorderRadius.circular(16), + ), + child: Icon(Icons.music_note, color: colorScheme.onPrimary, size: 48), + ), + const SizedBox(width: 16), + + // Fake Text Info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: double.infinity, height: 14, + decoration: BoxDecoration( + color: colorScheme.onSurface, + borderRadius: BorderRadius.circular(4), + ), + ), + const SizedBox(height: 8), + Container( + width: 80, height: 10, + decoration: BoxDecoration( + color: colorScheme.primary, + borderRadius: BorderRadius.circular(4), + ), + ), + const SizedBox(height: 24), + Row( + children: [ + Icon(Icons.skip_previous, size: 24, color: colorScheme.onSurfaceVariant), + const SizedBox(width: 12), + Icon(Icons.play_circle_fill, size: 32, color: colorScheme.primary), + const SizedBox(width: 12), + Icon(Icons.skip_next, size: 24, color: colorScheme.onSurfaceVariant), + ], + ), + ], + ), + ), + ], + ), + ), + ), + + // Label badge + Positioned( + bottom: 12, + right: 12, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + isDark ? 'Dark Mode' : 'Light Mode', + style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ); + } +} + + + +class _ColorPalettePicker extends StatelessWidget { + final int currentColor; + final ValueChanged onColorSelected; + const _ColorPalettePicker({required this.currentColor, required this.onColorSelected}); + + static const _colors = [ + Color(0xFF1DB954), Color(0xFF6750A4), Color(0xFF0061A4), Color(0xFF006E1C), + Color(0xFFBA1A1A), Color(0xFF984061), Color(0xFF7D5260), Color(0xFF006874), + ]; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: _colors.map((color) { + final isSelected = color.toARGB32() == currentColor; + return Padding( + padding: const EdgeInsets.only(right: 12), + child: GestureDetector( + onTap: () => onColorSelected(color), + child: _ColorPaletteItem(color: color, isSelected: isSelected), + ), + ); + }).toList(), + ), + ); + } +} + +class _ColorPaletteItem extends StatelessWidget { + final Color color; + final bool isSelected; + + const _ColorPaletteItem({required this.color, required this.isSelected}); + + @override + Widget build(BuildContext context) { + final scheme = ColorScheme.fromSeed(seedColor: color, brightness: Theme.of(context).brightness); + final size = 64.0; + + return Stack( + children: [ + Container( + width: size, + height: size, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + ), + clipBehavior: Clip.antiAlias, + child: Column( + children: [ + Expanded( + child: Row( + children: [ + Expanded(child: Container(color: scheme.primaryContainer)), + Expanded(child: Container(color: scheme.tertiaryContainer)), + ], + ), + ), + Expanded( + child: Row( + children: [ + Expanded(child: Container(color: scheme.secondaryContainer)), + Expanded(child: Container(color: scheme.surfaceContainer)), + ], + ), + ), + ], + ), + ), + if (isSelected) + Positioned.fill( + child: Center( + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: Icon(Icons.check, size: 16, color: scheme.primary), + ), + ), + ), + ], + ); + } +} + /// Optimized app bar title with animation class _AppBarTitle extends StatelessWidget { final String title; @@ -200,45 +438,6 @@ class _ThemeModeChip extends StatelessWidget { } } -class _ColorPicker extends StatelessWidget { - final int currentColor; - final ValueChanged onColorSelected; - const _ColorPicker({required this.currentColor, required this.onColorSelected}); - - static const _colors = [ - Color(0xFF1DB954), Color(0xFF6750A4), Color(0xFF0061A4), Color(0xFF006E1C), - Color(0xFFBA1A1A), Color(0xFF984061), Color(0xFF7D5260), Color(0xFF006874), Color(0xFFFF6F00), - ]; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return Padding( - padding: const EdgeInsets.fromLTRB(20, 8, 20, 16), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Accent Color', style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: colorScheme.onSurfaceVariant)), - const SizedBox(height: 12), - Wrap(spacing: 12, runSpacing: 12, children: _colors.map((color) { - final isSelected = color.toARGB32() == currentColor; - return GestureDetector( - onTap: () => onColorSelected(color), - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: 44, height: 44, - decoration: BoxDecoration( - color: color, shape: BoxShape.circle, - border: isSelected ? Border.all(color: colorScheme.onSurface, width: 3) : null, - boxShadow: isSelected ? [BoxShadow(color: color.withValues(alpha: 0.4), blurRadius: 8, spreadRadius: 2)] : null, - ), - child: isSelected ? const Icon(Icons.check, color: Colors.white, size: 20) : null, - ), - ); - }).toList()), - ]), - ); - } -} - class _HistoryViewSelector extends StatelessWidget { final String currentMode; final ValueChanged onChanged; diff --git a/lib/screens/settings/settings_tab.dart b/lib/screens/settings/settings_tab.dart index 8d63dd82..9c30f67d 100644 --- a/lib/screens/settings/settings_tab.dart +++ b/lib/screens/settings/settings_tab.dart @@ -14,29 +14,38 @@ class SettingsTab extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; + final topPadding = MediaQuery.of(context).padding.top; return CustomScrollView( slivers: [ // Collapsing App Bar SliverAppBar( - expandedHeight: 130, + expandedHeight: 120 + topPadding, collapsedHeight: kToolbarHeight, floating: false, pinned: true, backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, automaticallyImplyLeading: false, - flexibleSpace: FlexibleSpaceBar( - expandedTitleScale: 1.3, - titlePadding: const EdgeInsets.only(left: 24, bottom: 16), - title: Text( - 'Settings', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), + 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); + + return FlexibleSpaceBar( + expandedTitleScale: 1.0, + titlePadding: const EdgeInsets.only(left: 24, bottom: 16), + title: Text( + 'Settings', + style: TextStyle( + fontSize: 20 + (14 * expandRatio), // 20 -> 34 + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, ), ), diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index 90480f0a..3c2294c6 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -353,19 +353,24 @@ class _TrackMetadataScreenState extends ConsumerState { // Metadata grid _buildMetadataGrid(context, colorScheme), - // Spotify link button + // Streaming service link button if (item.spotifyId != null && item.spotifyId!.isNotEmpty) ...[ const SizedBox(height: 8), - OutlinedButton.icon( - onPressed: () => _openSpotifyUrl(context), - icon: const Icon(Icons.open_in_new, size: 18), - label: const Text('Open in Spotify'), - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), + Builder( + builder: (context) { + final isDeezer = item.spotifyId!.contains('deezer'); + return OutlinedButton.icon( + onPressed: () => _openServiceUrl(context), + icon: const Icon(Icons.open_in_new, size: 18), + label: Text(isDeezer ? 'Open in Deezer' : 'Open in Spotify'), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } ), ], ], @@ -374,16 +379,24 @@ class _TrackMetadataScreenState extends ConsumerState { ); } - Future _openSpotifyUrl(BuildContext context) async { + Future _openServiceUrl(BuildContext context) async { if (item.spotifyId == null) return; - final webUrl = 'https://open.spotify.com/track/${item.spotifyId}'; - final spotifyUri = Uri.parse('spotify:track:${item.spotifyId}'); + final isDeezer = item.spotifyId!.contains('deezer'); + final rawId = item.spotifyId!.replaceAll('deezer:', ''); + + final webUrl = isDeezer + ? 'https://www.deezer.com/track/$rawId' + : 'https://open.spotify.com/track/$rawId'; + + final appUri = isDeezer + ? Uri.parse('deezer://www.deezer.com/track/$rawId') + : Uri.parse('spotify:track:$rawId'); try { - // Try to open in Spotify app first using URI scheme + // Try to open in App first using URI scheme final launched = await launchUrl( - spotifyUri, + appUri, mode: LaunchMode.externalApplication, ); @@ -406,7 +419,7 @@ class _TrackMetadataScreenState extends ConsumerState { if (context.mounted) { _copyToClipboard(context, webUrl); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Spotify URL copied to clipboard')), + SnackBar(content: Text('${isDeezer ? 'Deezer' : 'Spotify'} URL copied to clipboard')), ); } } @@ -439,11 +452,18 @@ class _TrackMetadataScreenState extends ConsumerState { _MetadataItem('Release date', releaseDate!), if (isrc != null && isrc!.isNotEmpty) _MetadataItem('ISRC', isrc!), - if (item.spotifyId != null && item.spotifyId!.isNotEmpty) - _MetadataItem('Spotify ID', item.spotifyId!), + ]; + + if (item.spotifyId != null && item.spotifyId!.isNotEmpty) { + final isDeezer = item.spotifyId!.contains('deezer'); + final cleanId = item.spotifyId!.replaceAll('deezer:', ''); + items.add(_MetadataItem(isDeezer ? 'Deezer ID' : 'Spotify ID', cleanId)); + } + + items.addAll([ _MetadataItem('Service', item.service.toUpperCase()), _MetadataItem('Downloaded', _formatFullDate(item.downloadedAt)), - ]; + ]); return Column( children: items.map((metadata) { diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 9d08b720..f9dc3014 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -221,6 +221,12 @@ class AppTheme { } return scheme.surfaceContainerHighest; }), + thumbIcon: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return Icon(Icons.check, color: scheme.primary); + } + return null; + }), ); /// Chip theme