✨ Infinite playing
💄 Optimized UX
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:media_kit/media_kit.dart' as media_kit;
|
import 'package:media_kit/media_kit.dart' as media_kit;
|
||||||
@@ -9,6 +10,7 @@ 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';
|
||||||
|
import 'package:groovybox/providers/settings_provider.dart';
|
||||||
|
|
||||||
class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||||
final media_kit.Player _player;
|
final media_kit.Player _player;
|
||||||
@@ -44,6 +46,26 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_player.stream.completed.listen((completed) async {
|
||||||
|
if (completed && _container != null) {
|
||||||
|
final continuePlays = _container!
|
||||||
|
.read(settingsProvider)
|
||||||
|
.when(
|
||||||
|
data: (settings) => settings.continuePlays,
|
||||||
|
loading: () => false,
|
||||||
|
error: (_, __) => false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (continuePlays && _queueIndex == _queue.length - 1) {
|
||||||
|
final oldLength = _queue.length;
|
||||||
|
await _addRandomTracksToQueue();
|
||||||
|
_queueIndex = oldLength; // Point to first new track
|
||||||
|
await _updatePlaylist();
|
||||||
|
_broadcastPlaybackState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to set the provider container for theme updates
|
// Method to set the provider container for theme updates
|
||||||
@@ -364,6 +386,40 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
await updateQueue(mediaItems);
|
await updateQueue(mediaItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _addRandomTracksToQueue() async {
|
||||||
|
if (_container == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final database = _container!.read(databaseProvider);
|
||||||
|
|
||||||
|
// Get paths of tracks already in queue to avoid duplicates
|
||||||
|
final existingPaths = _queue.map((item) => item.id).toSet();
|
||||||
|
|
||||||
|
// Query for tracks not in current queue
|
||||||
|
final allTracks = await (database.select(
|
||||||
|
database.tracks,
|
||||||
|
)..where((t) => t.path.isNotIn(existingPaths))).get();
|
||||||
|
|
||||||
|
// Shuffle and take 10 random tracks
|
||||||
|
allTracks.shuffle();
|
||||||
|
final tracks = allTracks.take(10).toList();
|
||||||
|
|
||||||
|
if (tracks.isEmpty) return;
|
||||||
|
|
||||||
|
// Convert to MediaItems
|
||||||
|
final newMediaItems = await Future.wait(tracks.map(_trackToMediaItem));
|
||||||
|
|
||||||
|
// Add to queue
|
||||||
|
_queue.addAll(newMediaItems);
|
||||||
|
|
||||||
|
// Update the broadcasted queue
|
||||||
|
queue.add(_queue);
|
||||||
|
} catch (e) {
|
||||||
|
// Silently handle errors to avoid interrupting playback
|
||||||
|
debugPrint('Error adding random tracks to queue: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _extractTitleFromPath(String path) {
|
String _extractTitleFromPath(String path) {
|
||||||
return path.split('/').last.split('.').first;
|
return path.split('/').last.split('.').first;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class SettingsState {
|
|||||||
final bool watchForChanges;
|
final bool watchForChanges;
|
||||||
final DefaultPlayerScreen defaultPlayerScreen;
|
final DefaultPlayerScreen defaultPlayerScreen;
|
||||||
final LyricsMode lyricsMode;
|
final LyricsMode lyricsMode;
|
||||||
|
final bool continuePlays;
|
||||||
final Set<String> supportedFormats;
|
final Set<String> supportedFormats;
|
||||||
|
|
||||||
const SettingsState({
|
const SettingsState({
|
||||||
@@ -44,6 +45,7 @@ class SettingsState {
|
|||||||
this.watchForChanges = true,
|
this.watchForChanges = true,
|
||||||
this.defaultPlayerScreen = DefaultPlayerScreen.cover,
|
this.defaultPlayerScreen = DefaultPlayerScreen.cover,
|
||||||
this.lyricsMode = LyricsMode.auto,
|
this.lyricsMode = LyricsMode.auto,
|
||||||
|
this.continuePlays = false,
|
||||||
this.supportedFormats = const {
|
this.supportedFormats = const {
|
||||||
'.mp3',
|
'.mp3',
|
||||||
'.flac',
|
'.flac',
|
||||||
@@ -62,6 +64,7 @@ class SettingsState {
|
|||||||
bool? watchForChanges,
|
bool? watchForChanges,
|
||||||
DefaultPlayerScreen? defaultPlayerScreen,
|
DefaultPlayerScreen? defaultPlayerScreen,
|
||||||
LyricsMode? lyricsMode,
|
LyricsMode? lyricsMode,
|
||||||
|
bool? continuePlays,
|
||||||
Set<String>? supportedFormats,
|
Set<String>? supportedFormats,
|
||||||
}) {
|
}) {
|
||||||
return SettingsState(
|
return SettingsState(
|
||||||
@@ -70,6 +73,7 @@ class SettingsState {
|
|||||||
watchForChanges: watchForChanges ?? this.watchForChanges,
|
watchForChanges: watchForChanges ?? this.watchForChanges,
|
||||||
defaultPlayerScreen: defaultPlayerScreen ?? this.defaultPlayerScreen,
|
defaultPlayerScreen: defaultPlayerScreen ?? this.defaultPlayerScreen,
|
||||||
lyricsMode: lyricsMode ?? this.lyricsMode,
|
lyricsMode: lyricsMode ?? this.lyricsMode,
|
||||||
|
continuePlays: continuePlays ?? this.continuePlays,
|
||||||
supportedFormats: supportedFormats ?? this.supportedFormats,
|
supportedFormats: supportedFormats ?? this.supportedFormats,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -82,6 +86,7 @@ class SettingsNotifier extends _$SettingsNotifier {
|
|||||||
static const String _watchForChangesKey = 'watch_for_changes';
|
static const String _watchForChangesKey = 'watch_for_changes';
|
||||||
static const String _defaultPlayerScreenKey = 'default_player_screen';
|
static const String _defaultPlayerScreenKey = 'default_player_screen';
|
||||||
static const String _lyricsModeKey = 'lyrics_mode';
|
static const String _lyricsModeKey = 'lyrics_mode';
|
||||||
|
static const String _continuePlaysKey = 'continue_plays';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SettingsState> build() async {
|
Future<SettingsState> build() async {
|
||||||
@@ -101,12 +106,15 @@ class SettingsNotifier extends _$SettingsNotifier {
|
|||||||
prefs.getInt(_lyricsModeKey) ?? 2; // Auto is default
|
prefs.getInt(_lyricsModeKey) ?? 2; // Auto is default
|
||||||
final lyricsMode = LyricsMode.values[lyricsModeIndex];
|
final lyricsMode = LyricsMode.values[lyricsModeIndex];
|
||||||
|
|
||||||
|
final continuePlays = prefs.getBool(_continuePlaysKey) ?? false;
|
||||||
|
|
||||||
return SettingsState(
|
return SettingsState(
|
||||||
importMode: importMode,
|
importMode: importMode,
|
||||||
autoScan: autoScan,
|
autoScan: autoScan,
|
||||||
watchForChanges: watchForChanges,
|
watchForChanges: watchForChanges,
|
||||||
defaultPlayerScreen: defaultPlayerScreen,
|
defaultPlayerScreen: defaultPlayerScreen,
|
||||||
lyricsMode: lyricsMode,
|
lyricsMode: lyricsMode,
|
||||||
|
continuePlays: continuePlays,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +164,15 @@ class SettingsNotifier extends _$SettingsNotifier {
|
|||||||
state = AsyncValue.data(state.value!.copyWith(lyricsMode: mode));
|
state = AsyncValue.data(state.value!.copyWith(lyricsMode: mode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setContinuePlays(bool enabled) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool(_continuePlaysKey, enabled);
|
||||||
|
|
||||||
|
if (state.hasValue) {
|
||||||
|
state = AsyncValue.data(state.value!.copyWith(continuePlays: enabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience providers for specific settings
|
// Convenience providers for specific settings
|
||||||
@@ -248,3 +265,21 @@ class LyricsModeNotifier extends _$LyricsModeNotifier {
|
|||||||
await ref.read(settingsProvider.notifier).setLyricsMode(mode);
|
await ref.read(settingsProvider.notifier).setLyricsMode(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ContinuePlaysNotifier extends _$ContinuePlaysNotifier {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return ref
|
||||||
|
.watch(settingsProvider)
|
||||||
|
.when(
|
||||||
|
data: (settings) => settings.continuePlays,
|
||||||
|
loading: () => false,
|
||||||
|
error: (_, _) => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> update(bool enabled) async {
|
||||||
|
await ref.read(settingsProvider.notifier).setContinuePlays(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ final class SettingsNotifierProvider
|
|||||||
SettingsNotifier create() => SettingsNotifier();
|
SettingsNotifier create() => SettingsNotifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$settingsNotifierHash() => r'4099dd1aa3dfc971c0761f314d196f3bc97315e7';
|
String _$settingsNotifierHash() => r'7c3a92d9ac94e175b79a3a4485bd9bbcc1e860f9';
|
||||||
|
|
||||||
abstract class _$SettingsNotifier extends $AsyncNotifier<SettingsState> {
|
abstract class _$SettingsNotifier extends $AsyncNotifier<SettingsState> {
|
||||||
FutureOr<SettingsState> build();
|
FutureOr<SettingsState> build();
|
||||||
@@ -324,3 +324,57 @@ abstract class _$LyricsModeNotifier extends $Notifier<LyricsMode> {
|
|||||||
element.handleValue(ref, created);
|
element.handleValue(ref, created);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ProviderFor(ContinuePlaysNotifier)
|
||||||
|
const continuePlaysProvider = ContinuePlaysNotifierProvider._();
|
||||||
|
|
||||||
|
final class ContinuePlaysNotifierProvider
|
||||||
|
extends $NotifierProvider<ContinuePlaysNotifier, bool> {
|
||||||
|
const ContinuePlaysNotifierProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'continuePlaysProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$continuePlaysNotifierHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
ContinuePlaysNotifier create() => ContinuePlaysNotifier();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(bool value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<bool>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$continuePlaysNotifierHash() =>
|
||||||
|
r'17e5f9c933d67837301775ac5beda25462130775';
|
||||||
|
|
||||||
|
abstract class _$ContinuePlaysNotifier extends $Notifier<bool> {
|
||||||
|
bool build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<bool, bool>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<bool, bool>,
|
||||||
|
bool,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,7 +103,11 @@ class AlbumDetailScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _playAlbum(WidgetRef ref, List<Track> tracks, {int initialIndex = 0}) {
|
void _playAlbum(WidgetRef ref, List<Track> tracks, {int initialIndex = 0}) {
|
||||||
|
final loadingNotifier = ref.read(remoteTrackLoadingProvider.notifier);
|
||||||
final audioHandler = ref.read(audioHandlerProvider);
|
final audioHandler = ref.read(audioHandlerProvider);
|
||||||
audioHandler.playTracks(tracks, initialIndex: initialIndex);
|
loadingNotifier.setLoading(true);
|
||||||
|
audioHandler.playTracks(tracks, initialIndex: initialIndex).then((_) {
|
||||||
|
loadingNotifier.setLoading(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,8 +525,14 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
onTrailingPressed: () =>
|
onTrailingPressed: () =>
|
||||||
_showTrackOptions(context, ref, track),
|
_showTrackOptions(context, ref, track),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
final loadingNotifier = ref.read(
|
||||||
|
remoteTrackLoadingProvider.notifier,
|
||||||
|
);
|
||||||
final audio = ref.read(audioHandlerProvider);
|
final audio = ref.read(audioHandlerProvider);
|
||||||
audio.playTrack(track);
|
loadingNotifier.setLoading(true);
|
||||||
|
audio.playTrack(track).then((_) {
|
||||||
|
loadingNotifier.setLoading(false);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
|
|||||||
@@ -108,19 +108,35 @@ class PlayerScreen extends HookConsumerWidget {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
onKeyEvent: (node, event) {
|
onKeyEvent: (node, event) {
|
||||||
if (event is KeyDownEvent) {
|
if (event is KeyDownEvent) {
|
||||||
if (event.logicalKey == LogicalKeyboardKey.space) {
|
switch (event.logicalKey) {
|
||||||
if (player.state.playing) {
|
case LogicalKeyboardKey.space:
|
||||||
player.pause();
|
if (player.state.playing) {
|
||||||
} else {
|
player.pause();
|
||||||
player.play();
|
} else {
|
||||||
}
|
player.play();
|
||||||
return KeyEventResult.handled;
|
}
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.bracketLeft) {
|
return KeyEventResult.handled;
|
||||||
player.previous();
|
case LogicalKeyboardKey.bracketLeft:
|
||||||
return KeyEventResult.handled;
|
player.previous();
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.bracketRight) {
|
return KeyEventResult.handled;
|
||||||
player.next();
|
case LogicalKeyboardKey.bracketRight:
|
||||||
return KeyEventResult.handled;
|
player.next();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.escape:
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.arrowUp:
|
||||||
|
player.setVolume(
|
||||||
|
(player.state.volume + 10).clamp(0, 100),
|
||||||
|
); // Increase volume
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
case LogicalKeyboardKey.arrowDown:
|
||||||
|
player.setVolume(
|
||||||
|
(player.state.volume - 10).clamp(0, 100),
|
||||||
|
); // Decrease volume
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
default:
|
||||||
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
@@ -1700,7 +1716,10 @@ class _PlayerControls extends HookWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
playing ? Symbols.pause : Symbols.play_arrow,
|
playing
|
||||||
|
? Symbols.pause_rounded
|
||||||
|
: Symbols.play_arrow_rounded,
|
||||||
|
fill: 1,
|
||||||
key: ValueKey<bool>(playing),
|
key: ValueKey<bool>(playing),
|
||||||
size: 48,
|
size: 48,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -114,7 +114,11 @@ class PlaylistDetailScreen extends HookConsumerWidget {
|
|||||||
List<Track> tracks, {
|
List<Track> tracks, {
|
||||||
int initialIndex = 0,
|
int initialIndex = 0,
|
||||||
}) {
|
}) {
|
||||||
|
final loadingNotifier = ref.read(remoteTrackLoadingProvider.notifier);
|
||||||
final audioHandler = ref.read(audioHandlerProvider);
|
final audioHandler = ref.read(audioHandlerProvider);
|
||||||
audioHandler.playTracks(tracks, initialIndex: initialIndex);
|
loadingNotifier.setLoading(true);
|
||||||
|
audioHandler.playTracks(tracks, initialIndex: initialIndex).then((_) {
|
||||||
|
loadingNotifier.setLoading(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,6 +379,21 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: const Text('Continue Playing'),
|
||||||
|
subtitle: const Text(
|
||||||
|
'Continue playing music after the queue is empty',
|
||||||
|
),
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
value: settings.continuePlays,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref
|
||||||
|
.read(continuePlaysProvider.notifier)
|
||||||
|
.update(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -156,8 +156,7 @@ class _MobileMiniPlayer extends HookConsumerWidget {
|
|||||||
color: Colors.white54,
|
color: Colors.white54,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).clipRRect(all: 8).padding(left: 8, vertical: 8),
|
||||||
const Gap(8),
|
|
||||||
// Title & Artist
|
// Title & Artist
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -184,6 +183,14 @@ class _MobileMiniPlayer extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Next Button
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.skip_previous),
|
||||||
|
onPressed: player.previous,
|
||||||
|
iconSize: 24,
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4),
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
),
|
||||||
// Play/Pause Button
|
// Play/Pause Button
|
||||||
StreamBuilder<bool>(
|
StreamBuilder<bool>(
|
||||||
stream: player.stream.playing,
|
stream: player.stream.playing,
|
||||||
@@ -206,8 +213,11 @@ class _MobileMiniPlayer extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
playing ? Symbols.pause : Symbols.play_arrow,
|
playing
|
||||||
|
? Symbols.pause_rounded
|
||||||
|
: Symbols.play_arrow_rounded,
|
||||||
key: ValueKey<bool>(playing),
|
key: ValueKey<bool>(playing),
|
||||||
|
fill: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: playing ? player.pause : player.play,
|
onPressed: playing ? player.pause : player.play,
|
||||||
@@ -215,12 +225,7 @@ class _MobileMiniPlayer extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// Next Button
|
const Gap(12),
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Symbols.skip_next),
|
|
||||||
onPressed: player.next,
|
|
||||||
iconSize: 24,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -381,8 +386,7 @@ class _DesktopMiniPlayer extends HookConsumerWidget {
|
|||||||
color: Colors.white54,
|
color: Colors.white54,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).clipRRect(all: 8).padding(left: 8, vertical: 8),
|
||||||
const Gap(8),
|
|
||||||
// Title & Artist
|
// Title & Artist
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -501,8 +505,9 @@ class _DesktopMiniPlayer extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
playing
|
playing
|
||||||
? Symbols.pause
|
? Symbols.pause_rounded
|
||||||
: Symbols.play_arrow,
|
: Symbols.play_arrow_rounded,
|
||||||
|
fill: 1,
|
||||||
key: ValueKey<bool>(playing),
|
key: ValueKey<bool>(playing),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user