ocarina2/lib/widgets/player.dart

406 lines
19 KiB
Dart

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';
import 'package:ocarina/util/util.dart';
import 'package:responsive_sizer/responsive_sizer.dart';
import 'package:shimmer/shimmer.dart';
import 'package:text_scroll/text_scroll.dart';
/// The player control widget
///
/// Showcases the playing song's details and features playback controls
class Player extends StatefulWidget {
/// The player control widget
///
/// Showcases the playing song's details and features playback controls
const Player({super.key});
@override
State<Player> createState() => PlayerState();
}
/// State of [Player]
class PlayerState extends State<Player> {
void update() {
logger.d(AudioPlayerService().song?.coverArtUrl);
setState(() {});
}
var _showFullControls = false;
final _sheetController = DraggableScrollableController();
@override
Widget build(BuildContext context) {
return NotificationListener<DraggableScrollableNotification>(
onNotification: (n) {
if (n.extent > 0.3) {
_showFullControls = true;
} else {
_showFullControls = false;
}
setState(() {});
return false;
},
child: DraggableScrollableSheet(
controller: _sheetController,
initialChildSize: 0.1,
snap: true,
snapSizes: const [0.1, 1],
minChildSize: 0.1,
builder: (c, s) => SingleChildScrollView(
controller: s,
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: GestureDetector(
onTap: () {
_sheetController.animateTo(
(_sheetController.size == 1) ? 0.1 : 1,
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
},
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
height: 10.h,
width: 100.w,
child: AnimatedOpacity(
opacity: _showFullControls ? 0 : 1,
duration: const Duration(milliseconds: 300),
child: Padding(
padding: const EdgeInsets.all(16),
child: ValueListenableBuilder<Song?>(
valueListenable: songNotifier,
builder: (c, t, w) {
return Row(
children: [
SizedBox(
height: 10.h,
width: 10.h,
child: Padding(
padding: const EdgeInsets.all(16),
child: ClipRRect(
child: (t == null)
? ColoredBox(
color: Theme.of(context)
.colorScheme
.primaryContainer,
child: Center(
child: Icon(
Icons.music_note,
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
)
: CachedNetworkImage(
cacheKey: t.coverArtId,
imageUrl: t.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(
width: 5,
),
Expanded(
child: AutoSizeText(
AudioPlayerService().song == null
? "Nothing"
: AudioPlayerService().song!.title,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
fontSize: 14,
decoration: TextDecoration.none,
),
overflowReplacement: TextScroll(
AudioPlayerService().song == null
? "Nothing"
: AudioPlayerService().song!.title,
),
),
),
SizedBox(
width: 35.w,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
AudioPlayerService().previous();
},
icon: const Icon(Icons.skip_previous),
),
IconButton(
onPressed: () async {
if (AudioPlayerService().song ==
null) {
return;
}
if (AudioPlayerService().isPlaying) {
await AudioPlayerService().pause();
} else {
AudioPlayerService().resume();
}
setState(() {});
},
icon: AnimatedCrossFade(
firstChild:
const Icon(Icons.play_arrow),
secondChild: const Icon(Icons.pause),
crossFadeState:
(AudioPlayerService().isPlaying)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration:
const Duration(milliseconds: 300),
),
),
IconButton(
onPressed: () {
AudioPlayerService().next();
},
icon: const Icon(Icons.skip_next),
),
],
),
),
],
);
},
),
),
),
),
),
),
Container(
// Full player controls here
height: 90.h,
color: Theme.of(context).colorScheme.primaryContainer,
width: 100.w,
child: Center(
child: Column(
children: [
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
.secondaryContainer,
child: Center(
child: Icon(
Icons.music_note,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
)
: (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
.secondaryContainer,
child: Center(
child: Icon(
Icons.music_note,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
);
},
)),
),
),
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: 30,
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,
),
),
),
),
SizedBox(
width: 60.w,
height: 40,
child: Overlay(
initialEntries: [
OverlayEntry(
builder: (c) => ValueListenableBuilder<List<int>>(
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);
},
);
},
),
),
],
),
),
const SizedBox(
height: 10,
),
SizedBox(
width: 60.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
AudioPlayerService().previous();
},
icon: const Icon(
Icons.skip_previous,
size: 36,
),
),
IconButton(
onPressed: () async {
if (AudioPlayerService().song == null) {
return;
}
if (AudioPlayerService().isPlaying) {
await AudioPlayerService().pause();
} else {
AudioPlayerService().resume();
}
setState(() {});
},
icon: AnimatedCrossFade(
firstChild: const Icon(
Icons.play_arrow,
size: 36, // TODO: adapt to display size
),
secondChild: const Icon(Icons.pause, size: 36),
crossFadeState: (AudioPlayerService().isPlaying)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 300),
),
),
IconButton(
onPressed: () {
AudioPlayerService().next();
},
icon: const Icon(
Icons.skip_next,
size: 36,
),
),
],
),
),
],
),
),
),
],
),
),
),
);
}
}