Skip to content

Commit

Permalink
Prevented saving of empty queues.
Browse files Browse the repository at this point in the history
Reversed queue restore screen.
Added saving queue on startPlayback().
Added saving global queue source.
Added ability to easily retry queue load if all items fail.
Delayed overwriting latest until user initiates playback or modifies queue after load.
  • Loading branch information
Komodo5197 committed Dec 8, 2023
1 parent 7738ebe commit 69589c0
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 109 deletions.
48 changes: 29 additions & 19 deletions lib/components/QueueRestoreScreen/queue_restore_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,54 @@ class QueueRestoreTile extends StatelessWidget {
Widget build(BuildContext context) {
final queuesBox = Hive.box<FinampStorableQueueInfo>("Queues");
final queueService = GetIt.instance<QueueService>();
int itemCount = info.queue.length + info.nextUp.length + ((info.currentTrack == null)?0:1);
Future<BaseItemDto?> track = (info.currentTrack == null)?Future.value(null):queueService.getTrackFromId(info.currentTrack!);
int itemCount = info.queue.length +
info.nextUp.length +
((info.currentTrack == null) ? 0 : 1);
Future<BaseItemDto?> track = (info.currentTrack == null)
? Future.value(null)
: queueService.getTrackFromId(info.currentTrack!);

return ListTile(
title: Text(AppLocalizations.of(context)!.queueRestoreTitle(
DateTime.fromMillisecondsSinceEpoch(info.creation)
)),
DateTime.fromMillisecondsSinceEpoch(info.creation))),
leading: FutureBuilder<BaseItemDto?>(
future: track,
builder: (context, snapshot) => AlbumImage(item: snapshot.data)
),
builder: (context, snapshot) => AlbumImage(item: snapshot.data)),
isThreeLine: true,
//dense: true,
subtitle: FutureBuilder<BaseItemDto?>(
future: track,
initialData: null,
builder: (context, snapshot) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:((snapshot.data?.name == null)?<Text>[]:[// exclude subtitle line 1 if song name is null
Text(
AppLocalizations.of(context)!.queueRestoreSubtitle1(snapshot.data!.name!),
overflow: TextOverflow.ellipsis
)])+[
Text(AppLocalizations.of(context)!.queueRestoreSubtitle2(itemCount))
]
)
),
children: ((snapshot.data?.name == null)
? <Text>[]
: [
// exclude subtitle line 1 if song name is null
Text(
AppLocalizations.of(context)!
.queueRestoreSubtitle1(snapshot.data!.name!),
overflow: TextOverflow.ellipsis)
]) +
[
Text(AppLocalizations.of(context)!
.queueRestoreSubtitle2(itemCount))
])),
trailing: IconButton(
icon: const Icon(Icons.arrow_circle_right_outlined),
onPressed: () {
var latest = queuesBox.get("latest");
if ( latest != null ){
if (latest != null && latest.songCount != 0) {
queuesBox.put(latest.creation.toString(), latest);
}
BuildContext parentcontext = Navigator.of(context).context;
queueService.loadSavedQueue(info).catchError((x) => errorSnackbar(x,parentcontext));
Navigator.of(context).popUntil((route) => route.isFirst && ! route.willHandlePopInternally);
queueService
.loadSavedQueue(info)
.catchError((x) => errorSnackbar(x, parentcontext));
Navigator.of(context).popUntil(
(route) => route.isFirst && !route.willHandlePopInternally);
}),
);
}
}
}
137 changes: 77 additions & 60 deletions lib/components/now_playing_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,67 +29,79 @@ class NowPlayingBar extends ConsumerWidget {
Key? key,
}) : super(key: key);

Widget buildLoadingQueueBar(BuildContext context) {
Widget buildLoadingQueueBar(BuildContext context, Function()? retryCallback) {
const elevation = 16.0;
const albumImageSize = 70.0;

return Padding(
padding: const EdgeInsets.only(left: 12.0, bottom: 12.0, right: 12.0),
child: Material(
shadowColor: Theme.of(context).colorScheme.primary.withOpacity(0.75),
borderRadius: BorderRadius.circular(12.0),
clipBehavior: Clip.antiAlias,
color: Theme.of(context).brightness == Brightness.dark
? IconTheme.of(context).color!.withOpacity(0.1)
: Theme.of(context).cardColor,
elevation: elevation,
child: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
height: albumImageSize,
padding: EdgeInsets.zero,
child: Container(
return SimpleGestureDetector(
onVerticalSwipe: (direction) {
if (direction == SwipeDirection.up && retryCallback != null) {
retryCallback();
}
},
onTap: retryCallback,
child: Padding(
padding: const EdgeInsets.only(left: 12.0, bottom: 12.0, right: 12.0),
child: Material(
shadowColor:
Theme.of(context).colorScheme.primary.withOpacity(0.75),
borderRadius: BorderRadius.circular(12.0),
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: Color.alphaBlend(
Theme.of(context).brightness == Brightness.dark
? IconTheme.of(context).color!.withOpacity(0.35)
: IconTheme.of(context).color!.withOpacity(0.5),
Theme.of(context).brightness == Brightness.dark
? Colors.black
: Colors.white),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12.0)),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: albumImageSize,
height: albumImageSize,
decoration: const ShapeDecoration(
shape: Border(),
color: Color.fromRGBO(0, 0, 0, 0.3),
color: Theme.of(context).brightness == Brightness.dark
? IconTheme.of(context).color!.withOpacity(0.1)
: Theme.of(context).cardColor,
elevation: elevation,
child: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
height: albumImageSize,
padding: EdgeInsets.zero,
child: Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: Color.alphaBlend(
Theme.of(context).brightness == Brightness.dark
? IconTheme.of(context).color!.withOpacity(0.35)
: IconTheme.of(context).color!.withOpacity(0.5),
Theme.of(context).brightness == Brightness.dark
? Colors.black
: Colors.white),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12.0)),
),
child: Center(child: CircularProgressIndicator.adaptive()),
),
Expanded(
child: Container(
height: albumImageSize,
padding: const EdgeInsets.only(left: 12, right: 4),
alignment: Alignment.centerLeft,
child: Text(
AppLocalizations.of(context)!.queueLoadingMessage)),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: albumImageSize,
height: albumImageSize,
decoration: const ShapeDecoration(
shape: Border(),
color: Color.fromRGBO(0, 0, 0, 0.3),
),
child: (retryCallback != null)
? const Icon(Icons.refresh, size: albumImageSize)
: const Center(
child: CircularProgressIndicator.adaptive())),
Expanded(
child: Container(
height: albumImageSize,
padding: const EdgeInsets.only(left: 12, right: 4),
alignment: Alignment.centerLeft,
child: Text((retryCallback != null)
? AppLocalizations.of(context)!.queueRetryMessage
: AppLocalizations.of(context)!
.queueLoadingMessage)),
),
],
),
],
),
),
)),
),
)),
),
);
));
}

Widget buildNowPlayingBar(
Expand Down Expand Up @@ -183,11 +195,12 @@ class NowPlayingBar extends ConsumerWidget {
children: [
AlbumImage(
updateProvider: true,
placeholderBuilder: (_) => const SizedBox.shrink(),
placeholderBuilder: (_) =>
const SizedBox.shrink(),
item: currentTrackBaseItem,
borderRadius: BorderRadius.zero,
itemsToPrecache: queueService
.getNextXTracksInQueue(3,reverse: 1)
.getNextXTracksInQueue(3, reverse: 1)
.map((e) {
final item = e.item.extras?["itemJson"] !=
null
Expand Down Expand Up @@ -489,13 +502,14 @@ class NowPlayingBar extends ConsumerWidget {
);
}


return Hero(
tag: "nowplaying",
createRectTween: (from, to) => RectTween(begin: from, end: from),
child: AnimatedTheme(
// immediately apply new theme if in background to avoid showing wrong theme during transition
duration: ModalRoute.of(context)!.isCurrent?const Duration(milliseconds: 1000):const Duration(milliseconds: 0),
duration: ModalRoute.of(context)!.isCurrent
? const Duration(milliseconds: 1000)
: const Duration(milliseconds: 0),
data: ThemeData(
fontFamily: "LexendDeca",
colorScheme: imageTheme.copyWith(
Expand All @@ -507,12 +521,15 @@ class NowPlayingBar extends ConsumerWidget {
),
child: StreamBuilder<FinampQueueInfo?>(
stream: queueService.getQueueStream(),
initialData:
queueService.getQueue(),
initialData: queueService.getQueue(),
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.data!.saveState == SavedQueueState.loading) {
return buildLoadingQueueBar(context);
return buildLoadingQueueBar(context, null);
} else if (snapshot.hasData &&
snapshot.data!.saveState == SavedQueueState.failed) {
return buildLoadingQueueBar(
context, queueService.retryQueueLoad);
} else if (snapshot.hasData &&
snapshot.data!.currentTrack != null) {
return buildNowPlayingBar(
Expand Down
4 changes: 4 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,10 @@
"@queueLoadingMessage": {
"description": "Message displayed on now-playing bar when a saved queue is loading."
},
"queueRetryMessage": "Failed to load queue. Retry?",
"@queueRetryMessage": {
"description": "Message displayed on now-playing bar when all items in a saved queue fail to load."
},
"autoloadLastQueueOnStartup": "Auto-restore Last Queue",
"@autoloadLastQueueOnStartup": {
"description": "Setting to restore last queue on startup"
Expand Down
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ Future<void> setupHive() async {
Hive.registerAdapter(LocaleAdapter());
Hive.registerAdapter(FinampLoopModeAdapter());
Hive.registerAdapter(FinampStorableQueueInfoAdapter());
Hive.registerAdapter(QueueItemSourceAdapter());
Hive.registerAdapter(QueueItemSourceTypeAdapter());
Hive.registerAdapter(QueueItemSourceNameAdapter());
Hive.registerAdapter(QueueItemSourceNameTypeAdapter());
await Future.wait([
Hive.openBox<DownloadedParent>("DownloadedParents"),
Hive.openBox<DownloadedSong>("DownloadedItems"),
Expand Down
17 changes: 15 additions & 2 deletions lib/models/finamp_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,7 @@ class FinampStorableQueueInfo {
required this.nextUp,
required this.queue,
required this.creation,
required this.source,
});

FinampStorableQueueInfo.fromQueueInfo(FinampQueueInfo info, int? seek):
Expand All @@ -826,7 +827,8 @@ class FinampStorableQueueInfo {
currentTrackSeek=seek,
nextUp=info.nextUp.map<String>((track) => track.item.extras?["itemJson"]["Id"]).toList(),
queue=info.queue.map<String>((track) => track.item.extras?["itemJson"]["Id"]).toList(),
creation=DateTime.now().millisecondsSinceEpoch
creation=DateTime.now().millisecondsSinceEpoch,
source=info.source
;

@HiveField(0)
Expand All @@ -848,9 +850,16 @@ class FinampStorableQueueInfo {
// timestamp, milliseconds since epoch
int creation;

@HiveField(6)
QueueItemSource? source;

String toString(){
return "previous:$previousTracks current:$currentTrack seek:$currentTrackSeek next:$nextUp queue:$queue";
}

int get songCount{
return previousTracks.length + ((currentTrack == null)?0:1) + nextUp.length + queue.length;
}
}

@HiveType(typeId: 62)
Expand All @@ -862,5 +871,9 @@ enum SavedQueueState {
@HiveField(2)
loading,
@HiveField(3)
saving
saving,
@HiveField(4)
failed,
@HiveField(5)
pendingSave,
}
17 changes: 15 additions & 2 deletions lib/models/finamp_models.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 69589c0

Please sign in to comment.