✨ Alternative tracks
This commit is contained in:
parent
3f41573f00
commit
2134500089
26
lib/screens/player/siblings.dart
Normal file
26
lib/screens/player/siblings.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:rhythm_box/widgets/player/sibling_tracks.dart';
|
||||||
|
|
||||||
|
class SiblingTracksPopup extends StatelessWidget {
|
||||||
|
const SiblingTracksPopup({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.85,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Alternative Sources',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
|
||||||
|
const Expanded(
|
||||||
|
child: SiblingTracks(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -10,8 +10,10 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
import 'package:rhythm_box/providers/audio_player.dart';
|
import 'package:rhythm_box/providers/audio_player.dart';
|
||||||
import 'package:rhythm_box/screens/player/queue.dart';
|
import 'package:rhythm_box/screens/player/queue.dart';
|
||||||
|
import 'package:rhythm_box/screens/player/siblings.dart';
|
||||||
import 'package:rhythm_box/services/artist.dart';
|
import 'package:rhythm_box/services/artist.dart';
|
||||||
import 'package:rhythm_box/services/audio_player/audio_player.dart';
|
import 'package:rhythm_box/services/audio_player/audio_player.dart';
|
||||||
|
import 'package:rhythm_box/services/duration.dart';
|
||||||
import 'package:rhythm_box/widgets/auto_cache_image.dart';
|
import 'package:rhythm_box/widgets/auto_cache_image.dart';
|
||||||
import 'package:rhythm_box/services/audio_services/image.dart';
|
import 'package:rhythm_box/services/audio_services/image.dart';
|
||||||
import 'package:rhythm_box/widgets/tracks/querying_track_info.dart';
|
import 'package:rhythm_box/widgets/tracks/querying_track_info.dart';
|
||||||
@ -53,14 +55,6 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDuration(Duration duration) {
|
|
||||||
String negativeSign = duration.isNegative ? '-' : '';
|
|
||||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
|
||||||
String twoDigitMinutes = twoDigits(duration.inMinutes.abs());
|
|
||||||
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60).abs());
|
|
||||||
return '$negativeSign$twoDigitMinutes:$twoDigitSeconds';
|
|
||||||
}
|
|
||||||
|
|
||||||
double? _draggingValue;
|
double? _draggingValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -173,11 +167,11 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_formatDuration(_durationCurrent),
|
_durationCurrent.toHumanReadableString(),
|
||||||
style: GoogleFonts.robotoMono(fontSize: 12),
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_formatDuration(_durationTotal),
|
_durationTotal.toHumanReadableString(),
|
||||||
style: GoogleFonts.robotoMono(fontSize: 12),
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -302,7 +296,18 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
icon: const Icon(Icons.merge),
|
icon: const Icon(Icons.merge),
|
||||||
label: const Text('Sources'),
|
label: const Text('Sources'),
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const SiblingTracksPopup(),
|
||||||
|
).then((_) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
26
lib/services/duration.dart
Normal file
26
lib/services/duration.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
extension DurationToHumanReadableString on Duration {
|
||||||
|
String toHumanReadableString({padZero = true}) {
|
||||||
|
final mm = inMinutes
|
||||||
|
.remainder(60)
|
||||||
|
.toString()
|
||||||
|
.padLeft(2, !padZero && inHours == 0 ? '' : '0');
|
||||||
|
final ss = inSeconds.remainder(60).toString().padLeft(2, '0');
|
||||||
|
|
||||||
|
if (inHours > 0) {
|
||||||
|
final hh = inHours.toString().padLeft(2, !padZero ? '' : '0');
|
||||||
|
return '$hh:$mm:$ss';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '$mm:$ss';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ParseDuration on Duration {
|
||||||
|
static Duration fromString(String duration) {
|
||||||
|
final parts = duration.split(':').reversed.toList();
|
||||||
|
final seconds = int.parse(parts[0]);
|
||||||
|
final minutes = parts.length > 1 ? int.parse(parts[1]) : 0;
|
||||||
|
final hours = parts.length > 2 ? int.parse(parts[2]) : 0;
|
||||||
|
return Duration(hours: hours, minutes: minutes, seconds: seconds);
|
||||||
|
}
|
||||||
|
}
|
@ -249,7 +249,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
final query = SourcedTrack.getSearchTerm(track);
|
final query = SourcedTrack.getSearchTerm(track);
|
||||||
|
|
||||||
final searchResults = await youtubeClient.search.search(
|
final searchResults = await youtubeClient.search.search(
|
||||||
'$query - Topic',
|
query,
|
||||||
|
// '$query - Topic',
|
||||||
filter: TypeFilters.video,
|
filter: TypeFilters.video,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
99
lib/widgets/player/sibling_tracks.dart
Normal file
99
lib/widgets/player/sibling_tracks.dart
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:rhythm_box/providers/audio_player.dart';
|
||||||
|
import 'package:rhythm_box/services/duration.dart';
|
||||||
|
import 'package:rhythm_box/services/server/active_sourced_track.dart';
|
||||||
|
import 'package:rhythm_box/services/sourced_track/models/source_info.dart';
|
||||||
|
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
|
||||||
|
import 'package:rhythm_box/services/sourced_track/sources/piped.dart';
|
||||||
|
import 'package:rhythm_box/services/sourced_track/sources/youtube.dart';
|
||||||
|
import 'package:rhythm_box/widgets/auto_cache_image.dart';
|
||||||
|
import 'package:rhythm_box/widgets/tracks/querying_track_info.dart';
|
||||||
|
|
||||||
|
class SiblingTracks extends StatefulWidget {
|
||||||
|
const SiblingTracks({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SiblingTracks> createState() => _SiblingTracksState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SiblingTracksState extends State<SiblingTracks> {
|
||||||
|
late final QueryingTrackInfoProvider _query = Get.find();
|
||||||
|
late final ActiveSourcedTrackProvider _activeSource = Get.find();
|
||||||
|
late final AudioPlayerProvider _playback = Get.find();
|
||||||
|
|
||||||
|
get _activeTrack =>
|
||||||
|
_activeSource.state.value ?? _playback.state.value.activeTrack;
|
||||||
|
|
||||||
|
List<SourceInfo> get _siblings => !_query.isQueryingTrackInfo.value
|
||||||
|
? [
|
||||||
|
(_activeTrack as SourcedTrack).sourceInfo,
|
||||||
|
..._activeSource.state.value!.siblings,
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
final sourceInfoToLabelMap = {
|
||||||
|
YoutubeSourceInfo: 'YouTube',
|
||||||
|
PipedSourceInfo: 'Piped',
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(
|
||||||
|
() => ListView.builder(
|
||||||
|
itemCount: _siblings.length,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final item = _siblings[idx];
|
||||||
|
final src = sourceInfoToLabelMap[item.runtimeType];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
item.title,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: AutoCacheImage(
|
||||||
|
item.thumbnail,
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
trailing: Text(
|
||||||
|
item.duration.toHumanReadableString(),
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
),
|
||||||
|
subtitle: Row(
|
||||||
|
children: [
|
||||||
|
if (src != null) Text(src),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
' · ${item.artist}',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
enabled: !_query.isQueryingTrackInfo.value,
|
||||||
|
tileColor: !_query.isQueryingTrackInfo.value &&
|
||||||
|
item.id == (_activeTrack as SourcedTrack).sourceInfo.id
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: null,
|
||||||
|
onTap: () {
|
||||||
|
if (!_query.isQueryingTrackInfo.value &&
|
||||||
|
item.id != (_activeTrack as SourcedTrack).sourceInfo.id) {
|
||||||
|
_activeSource.swapSibling(item);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -366,6 +366,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
duration:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: duration
|
||||||
|
sha256: "13e5d20723c9c1dde8fb318cf86716d10ce294734e81e44ae1a817f3ae714501"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.3"
|
||||||
encrypt:
|
encrypt:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -85,6 +85,7 @@ dependencies:
|
|||||||
scroll_to_index: ^3.0.1
|
scroll_to_index: ^3.0.1
|
||||||
animations: ^2.0.11
|
animations: ^2.0.11
|
||||||
flutter_animate: ^4.5.0
|
flutter_animate: ^4.5.0
|
||||||
|
duration: ^4.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user