From b1db0059ebaa61f949b3d6bfa2d8ec08222eea71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Tue, 28 May 2024 19:59:09 +0200 Subject: [PATCH] feat: implement basic seekbar --- lib/api/audio/audioplayer_service.dart | 14 +++ lib/main.dart | 10 +- lib/widgets/player.dart | 168 ++++++++++++++++++------- pubspec.lock | 8 ++ pubspec.yaml | 1 + 5 files changed, 156 insertions(+), 45 deletions(-) diff --git a/lib/api/audio/audioplayer_service.dart b/lib/api/audio/audioplayer_service.dart index 520ea48..d328e3d 100644 --- a/lib/api/audio/audioplayer_service.dart +++ b/lib/api/audio/audioplayer_service.dart @@ -26,11 +26,19 @@ class AudioPlayerService { /// Registers listeners on the player void setup() { if (_setUp) return; + _player.createPositionStream().listen((d) { + if (!isPlaying) return; + progressNotifier.value = [ + d.inSeconds, + _songList[_player.currentIndex!].duration, + ]; + }); _player.currentIndexStream.listen((index) { logger.d("Done fired"); songNotifier.value = index == null ? null : _songList[index]; _setColorScheme(); }); + _setUp = true; } @@ -70,10 +78,16 @@ class AudioPlayerService { logger.d("Playing ${_player.currentIndex}"); } + /// Moves to the specified number of seconds + Future seek(double position) async { + await _player.seek(Duration(seconds: position.toInt())); + } + /// Sets color scheme from image Future _setColorScheme() async { if (AudioPlayerService().song == null) { themeNotifier.value = ColorScheme.fromSeed(seedColor: Colors.deepPurple); + return; } themeNotifier.value = await ColorScheme.fromImageProvider( provider: CachedNetworkImageProvider( diff --git a/lib/main.dart b/lib/main.dart index 17f0771..1f52b39 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,6 +35,9 @@ final ValueNotifier themeNotifier = /// Notifier to change theme from inside the app final ValueNotifier songNotifier = ValueNotifier(null); +/// Notifier for the song progress bar +final ValueNotifier> progressNotifier = ValueNotifier([0, 1]); + /// Main app class class MyApp extends StatelessWidget { /// Main app class @@ -58,8 +61,11 @@ class MyApp extends StatelessWidget { return Stack( children: [ child!, - Player( - key: playerKey, + Material( + type: MaterialType.transparency, + child: Player( + key: playerKey, + ), ), ], ); diff --git a/lib/widgets/player.dart b/lib/widgets/player.dart index efeda87..2f82d96 100644 --- a/lib/widgets/player.dart +++ b/lib/widgets/player.dart @@ -1,6 +1,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_xlider/flutter_xlider.dart'; import 'package:ocarina/api/audio/audioplayer_service.dart'; import 'package:ocarina/api/subsonic/song.dart'; import 'package:ocarina/main.dart'; @@ -208,57 +209,138 @@ class PlayerState extends State { ), ), ), - SizedBox( + Container( + // Full player controls here height: 90.h, + color: Theme.of(context).colorScheme.primaryContainer, width: 100.w, child: Center( child: Column( children: [ - ClipRRect( - child: (AudioPlayerService().song == null) - ? ColoredBox( - color: Theme.of(context) - .colorScheme - .primaryContainer, - child: Center( - child: Icon( - Icons.music_note, - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, - ), - ), - ) - : (CachedNetworkImage( - cacheKey: AudioPlayerService().song!.coverArtId, - imageUrl: - AudioPlayerService().song!.coverArtUrl, - placeholder: (c, d) => Shimmer.fromColors( - baseColor: Colors.grey.shade300, - highlightColor: Colors.grey.shade100, - child: Container( - color: Colors.grey, - ), - ), - errorWidget: (c, _, __) { - logger - ..e(_) - ..e(__); - return ColoredBox( - color: Theme.of(context) - .colorScheme - .primaryContainer, - child: Center( - child: Icon( - Icons.music_note, - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, - ), + const SizedBox( + height: 30, + ), + SizedBox( + width: 80.w, + height: 80.w, + child: ClipRRect( + // borderRadius: BorderRadius.circular(16), + child: (AudioPlayerService().song == null) + ? ColoredBox( + color: Theme.of(context) + .colorScheme + .primaryContainer, + child: Center( + child: Icon( + Icons.music_note, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, ), + ), + ) + : (CachedNetworkImage( + cacheKey: + AudioPlayerService().song!.coverArtId, + imageUrl: + AudioPlayerService().song!.coverArtUrl, + placeholder: (c, d) => Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: Container( + color: Colors.grey, + ), + ), + errorWidget: (c, _, __) { + logger + ..e(_) + ..e(__); + return ColoredBox( + color: Theme.of(context) + .colorScheme + .primaryContainer, + child: Center( + child: Icon( + Icons.music_note, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + ), + ), + ); + }, + )), + ), + ), + const SizedBox( + height: 10, + ), + SizedBox( + width: 70.w, + height: 40, + child: AutoSizeText( + AudioPlayerService().song?.title ?? "Nothing", + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.sp, + ), + overflowReplacement: TextScroll( + AudioPlayerService().song?.title ?? "Nothing", + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.sp, + ), + ), + ), + ), + SizedBox( + width: 70.w, + height: 50, + child: AutoSizeText( + AudioPlayerService().song?.artistName ?? "Nobody", + style: TextStyle(fontSize: 16.sp), + textAlign: TextAlign.center, + overflowReplacement: TextScroll( + AudioPlayerService().song?.artistName ?? "Nobody", + style: TextStyle( + fontSize: 16.sp, + ), + ), + ), + ), + const SizedBox( + height: 10, + ), + SizedBox( + width: 60.w, + height: 40, + child: Overlay( + initialEntries: [ + OverlayEntry( + builder: (c) => ValueListenableBuilder>( + valueListenable: progressNotifier, + builder: (c, v, _) { + return FlutterSlider( + values: [v[0].toDouble()], + max: v[1].toDouble(), + min: 0, + onDragCompleted: + (handlerIndex, lowerValue, upperValue) { + if (AudioPlayerService().song == null) { + return; + } + logger.d(lowerValue); + AudioPlayerService() + .seek(lowerValue as double); + }, ); }, - )), + ), + ), + ], + ), ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 622ca68..cffa820 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -368,6 +368,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_xlider: + dependency: "direct main" + description: + name: flutter_xlider + sha256: b83da229b8a2153adeefc5d9e08e0060689c8dc2187b30e3502cf67c1a6495be + url: "https://pub.dev" + source: hosted + version: "3.5.0" frontend_server_client: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 35015e8..d154ab9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: shared_preferences: ^2.2.3 dynamic_color: ^1.7.0 cached_network_image: ^3.3.1 + flutter_xlider: ^3.5.0 dev_dependencies: build_runner: ^2.4.9