diff --git a/lib/components/PlayerScreen/player_buttons_more.dart b/lib/components/PlayerScreen/player_buttons_more.dart index a4c637e53..f227ee39b 100644 --- a/lib/components/PlayerScreen/player_buttons_more.dart +++ b/lib/components/PlayerScreen/player_buttons_more.dart @@ -24,8 +24,9 @@ class PlayerButtonsMore extends StatelessWidget { Radius.circular(15), ), ), - icon: const Icon( + icon: Icon( TablerIcons.menu_2, + color: IconTheme.of(context).color!, ), itemBuilder: (BuildContext context) => >[ diff --git a/lib/components/PlayerScreen/queue_list.dart b/lib/components/PlayerScreen/queue_list.dart index c66e0c59d..b97529822 100644 --- a/lib/components/PlayerScreen/queue_list.dart +++ b/lib/components/PlayerScreen/queue_list.dart @@ -29,12 +29,10 @@ import 'queue_list_item.dart'; class _QueueListStreamState { _QueueListStreamState( this.mediaState, - this.playbackPosition, this.queueInfo, ); final MediaState mediaState; - final Duration playbackPosition; final QueueInfo queueInfo; } @@ -59,7 +57,7 @@ class QueueList extends StatefulWidget { void scrollDown() { scrollController.animateTo( scrollController.position.maxScrollExtent, - duration: Duration(seconds: 2), + duration: const Duration(seconds: 2), curve: Curves.fastOutSlowIn, ); } @@ -398,6 +396,7 @@ class _PreviousTracksListState extends State _previousTracks ??= snapshot.data!.previousTracks; return SliverReorderableList( + autoScrollerVelocityScalar: 20.0, onReorder: (oldIndex, newIndex) { int draggingOffset = -(_previousTracks!.length - oldIndex); int newPositionOffset = -(_previousTracks!.length - newIndex); @@ -419,13 +418,21 @@ class _PreviousTracksListState extends State // Feedback.forLongPress(context); Vibrate.feedback(FeedbackType.selection); }, + findChildIndexCallback: (Key key) { + key = key as GlobalObjectKey; + final ValueKey valueKey = key.value as ValueKey; + // search from the back as this is probably more efficient for previous tracks + final index = _previousTracks!.lastIndexWhere((item) => item.id == valueKey.value); + if (index == -1) return null; + return index; + }, itemCount: _previousTracks?.length ?? 0, itemBuilder: (context, index) { final item = _previousTracks![index]; final actualIndex = index; final indexOffset = -((_previousTracks?.length ?? 0) - index); return QueueListItem( - key: ValueKey(_previousTracks![actualIndex].id), + key: ValueKey(item.id), item: item, listIndex: index, actualIndex: actualIndex, @@ -481,10 +488,10 @@ class _NextUpTracksListState extends State { return SliverPadding( padding: const EdgeInsets.only(top: 0.0, left: 8.0, right: 8.0), sliver: SliverReorderableList( + autoScrollerVelocityScalar: 20.0, onReorder: (oldIndex, newIndex) { int draggingOffset = oldIndex + 1; int newPositionOffset = newIndex + 1; - print("$draggingOffset -> $newPositionOffset"); if (mounted) { Vibrate.feedback(FeedbackType.impact); setState(() { @@ -501,13 +508,20 @@ class _NextUpTracksListState extends State { onReorderStart: (p0) { Vibrate.feedback(FeedbackType.selection); }, + findChildIndexCallback: (Key key) { + key = key as GlobalObjectKey; + final ValueKey valueKey = key.value as ValueKey; + final index = _nextUp!.indexWhere((item) => item.id == valueKey.value); + if (index == -1) return null; + return index; + }, itemCount: _nextUp?.length ?? 0, itemBuilder: (context, index) { final item = _nextUp![index]; final actualIndex = index; final indexOffset = index + 1; return QueueListItem( - key: ValueKey(_nextUp![actualIndex].id), + key: ValueKey(item.id), item: item, listIndex: index, actualIndex: actualIndex, @@ -560,20 +574,21 @@ class _QueueTracksListState extends State { _nextUp ??= snapshot.data!.nextUp; return SliverReorderableList( + autoScrollerVelocityScalar: 20.0, onReorder: (oldIndex, newIndex) { int draggingOffset = oldIndex + (_nextUp?.length ?? 0) + 1; int newPositionOffset = newIndex + (_nextUp?.length ?? 0) + 1; print("$draggingOffset -> $newPositionOffset"); if (mounted) { + // update external queue to commit changes, but don't await it + _queueService.reorderByOffset( + draggingOffset, newPositionOffset); Vibrate.feedback(FeedbackType.impact); setState(() { // temporarily update internal queue QueueItem tmp = _queue!.removeAt(oldIndex); _queue!.insert( newIndex < oldIndex ? newIndex : newIndex - 1, tmp); - // update external queue to commit changes, results in a rebuild - _queueService.reorderByOffset( - draggingOffset, newPositionOffset); }); } }, @@ -581,12 +596,20 @@ class _QueueTracksListState extends State { Vibrate.feedback(FeedbackType.selection); }, itemCount: _queue?.length ?? 0, + findChildIndexCallback: (Key key) { + key = key as GlobalObjectKey; + final ValueKey valueKey = key.value as ValueKey; + final index = _queue!.indexWhere((item) => item.id == valueKey.value); + if (index == -1) return null; + return index; + }, itemBuilder: (context, index) { final item = _queue![index]; final actualIndex = index; final indexOffset = index + _nextUp!.length + 1; + return QueueListItem( - key: ValueKey(_queue![actualIndex].id), + key: ValueKey(item.id), item: item, listIndex: index, actualIndex: actualIndex, @@ -642,18 +665,16 @@ class _CurrentTrackState extends State { Duration? playbackPosition; return StreamBuilder<_QueueListStreamState>( - stream: Rx.combineLatest3( mediaStateStream, - AudioService.position - .startWith(_audioHandler.playbackState.value.position), _queueService.getQueueStream(), - (a, b, c) => _QueueListStreamState(a, b, c)), + (a, b) => _QueueListStreamState(a, b)), builder: (context, snapshot) { if (snapshot.hasData) { currentTrack = snapshot.data!.queueInfo.currentTrack; mediaState = snapshot.data!.mediaState; - playbackPosition = snapshot.data!.playbackPosition; + // playbackPosition = snapshot.data!.playbackPosition; jellyfin_models.BaseItemDto? baseItem = currentTrack!.item.extras?["itemJson"] == null ? null @@ -725,7 +746,7 @@ class _CurrentTrackState extends State { TablerIcons.player_play, size: 32, ), - color: Color.fromRGBO(255, 255, 255, 1.0), + color: Colors.white, )), ], ), @@ -736,25 +757,34 @@ class _CurrentTrackState extends State { left: 0, top: 0, // child: RepaintBoundary( - child: Container( - width: 298 * - (playbackPosition!.inMilliseconds / - (mediaState?.mediaItem?.duration ?? - const Duration(seconds: 0)) - .inMilliseconds), - height: 70.0, - decoration: ShapeDecoration( - // color: Color.fromRGBO(188, 136, 86, 0.75), - color: IconTheme.of(context).color!.withOpacity(0.75), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topRight: Radius.circular(8), - bottomRight: Radius.circular(8), - ), - ), - ), + child: StreamBuilder( + stream: AudioService.position + .startWith(_audioHandler.playbackState.value.position), + builder: (context, snapshot) { + if (snapshot.hasData) { + playbackPosition = snapshot.data; + return Container( + width: 298 * + (playbackPosition!.inMilliseconds / + (mediaState?.mediaItem?.duration ?? + const Duration(seconds: 0)) + .inMilliseconds), + height: 70.0, + decoration: ShapeDecoration( + color: IconTheme.of(context).color!.withOpacity(0.75), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topRight: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + ), + ), + ); + } else { + return Container(); + } + } ), - // ), ), Row( mainAxisSize: MainAxisSize.max, @@ -795,18 +825,32 @@ class _CurrentTrackState extends State { overflow: TextOverflow.ellipsis), ), Row(children: [ - Text( - // '0:00', - playbackPosition!.inHours >= 1.0 - ? "${playbackPosition?.inHours.toString()}:${((playbackPosition?.inMinutes ?? 0) % 60).toString().padLeft(2, '0')}:${((playbackPosition?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}" - : "${playbackPosition?.inMinutes.toString()}:${((playbackPosition?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}", - style: TextStyle( + StreamBuilder( + stream: AudioService.position + .startWith(_audioHandler.playbackState.value.position), + builder: (context, snapshot) { + final TextStyle style = TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 14, fontFamily: 'Lexend Deca', fontWeight: FontWeight.w400, - ), - ), + ); + if (snapshot.hasData) { + playbackPosition = snapshot.data; + return Text( + // '0:00', + playbackPosition!.inHours >= 1.0 + ? "${playbackPosition?.inHours.toString()}:${((playbackPosition?.inMinutes ?? 0) % 60).toString().padLeft(2, '0')}:${((playbackPosition?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}" + : "${playbackPosition?.inMinutes.toString()}:${((playbackPosition?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}", + style: style, + ); + } else { + return Text( + "0:00", + style: style, + ); + } + }), const SizedBox(width: 2), Text( '/', @@ -835,7 +879,7 @@ class _CurrentTrackState extends State { ), ],) ], - ) + ), ], ), // ), @@ -904,7 +948,7 @@ class _CurrentTrackState extends State { void showSongMenu(QueueItem currentTrack) async { final item = jellyfin_models.BaseItemDto.fromJson( - currentTrack?.item.extras?["itemJson"]); + currentTrack.item.extras?["itemJson"]); final canGoToAlbum = _isAlbumDownloadedIfOffline(item.parentId); diff --git a/lib/components/PlayerScreen/queue_list_item.dart b/lib/components/PlayerScreen/queue_list_item.dart index cb32a4a7a..ceb5c1e6d 100644 --- a/lib/components/PlayerScreen/queue_list_item.dart +++ b/lib/components/PlayerScreen/queue_list_item.dart @@ -7,7 +7,6 @@ import 'package:finamp/services/audio_service_helper.dart'; import 'package:finamp/services/downloads_helper.dart'; import 'package:finamp/services/finamp_settings_helper.dart'; import 'package:finamp/services/jellyfin_api_helper.dart'; -import 'package:finamp/services/music_player_background_task.dart'; import 'package:finamp/services/process_artist.dart'; import 'package:flutter/material.dart' hide ReorderableList; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -19,17 +18,17 @@ import 'package:flutter_vibrate/flutter_vibrate.dart'; import 'package:get_it/get_it.dart'; class QueueListItem extends StatefulWidget { - late QueueItem item; - late int listIndex; - late int actualIndex; - late int indexOffset; - late List subqueue; - late bool isCurrentTrack; - late bool isPreviousTrack; - late bool allowReorder; - late void Function() onTap; - - QueueListItem({ + final QueueItem item; + final int listIndex; + final int actualIndex; + final int indexOffset; + final List subqueue; + final bool isCurrentTrack; + final bool isPreviousTrack; + final bool allowReorder; + final void Function() onTap; + + const QueueListItem({ Key? key, required this.item, required this.listIndex, @@ -45,14 +44,18 @@ class QueueListItem extends StatefulWidget { State createState() => _QueueListItemState(); } -class _QueueListItemState extends State { - final _audioHandler = GetIt.instance(); +class _QueueListItemState extends State with AutomaticKeepAliveClientMixin { final _audioServiceHelper = GetIt.instance(); final _queueService = GetIt.instance(); final _jellyfinApiHelper = GetIt.instance(); + @override + bool get wantKeepAlive => true; + @override Widget build(BuildContext context) { + super.build(context); + return Dismissible( key: Key(widget.item.id), onDismissed: (direction) async { @@ -89,11 +92,6 @@ class _QueueListItemState extends State { widget.item.item.extras?["itemJson"]), borderRadius: BorderRadius.zero, ), - // leading: Container( - // height: 60.0, - // width: 60.0, - // color: Colors.white, - // ), title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -149,7 +147,7 @@ class _QueueListItemState extends State { margin: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 6.0), // width: widget.allowReorder ? 145.0 : 115.0, - width: widget.allowReorder ? 70.0 : 35.0, + width: widget.allowReorder ? 70.0 : 40.0, height: 50.0, child: Row( mainAxisSize: MainAxisSize.min, @@ -189,9 +187,9 @@ class _QueueListItemState extends State { if (widget.allowReorder) ReorderableDragStartListener( index: widget.listIndex, - child: Padding( + child: const Padding( padding: EdgeInsets.only(bottom: 5.0, left: 6.0), - child: const Icon( + child: Icon( TablerIcons.grip_horizontal, color: Colors.white, size: 28.0, diff --git a/pubspec.lock b/pubspec.lock index 194934fdc..edb47cf23 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -547,10 +547,10 @@ packages: dependency: "direct main" description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" io: dependency: transitive description: @@ -636,18 +636,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -962,10 +962,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqflite: dependency: transitive description: @@ -1042,10 +1042,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timing: dependency: transitive description: @@ -1126,6 +1126,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1167,5 +1175,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.7.0"