mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-07 03:29:29 +00:00
player seek and chapter change
This commit is contained in:
parent
01b3dead49
commit
d01855c218
17 changed files with 1721 additions and 305 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -12,6 +12,7 @@
|
|||
"audioplayers",
|
||||
"Autovalidate",
|
||||
"fullscreen",
|
||||
"Lerp",
|
||||
"miniplayer",
|
||||
"mocktail",
|
||||
"riverpod",
|
||||
|
|
|
|||
15
lib/constants/sizes.dart
Normal file
15
lib/constants/sizes.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
class AppElementSizes {
|
||||
// paddings
|
||||
static const double paddingRegular = 8.0;
|
||||
static const double paddingSmall = paddingRegular / 2;
|
||||
static const double paddingLarge = paddingRegular * 2;
|
||||
|
||||
// border radius
|
||||
static const double borderRadiusRegular = 12.0;
|
||||
static const double borderRadiusSmall = borderRadiusRegular / 2;
|
||||
|
||||
// icon sizes
|
||||
static const double iconSizeRegular = 48.0;
|
||||
static const double iconSizeSmall = 36.0;
|
||||
static const double iconSizeLarge = 64.0;
|
||||
}
|
||||
29
lib/db/player_prefs/book_prefs.dart
Normal file
29
lib/db/player_prefs/book_prefs.dart
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// a table to track preferences of player for each book
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'book_prefs.g.dart';
|
||||
|
||||
/// stores the preferences of the player for a book
|
||||
@Collection()
|
||||
@Name('BookPrefs')
|
||||
class BookPrefs {
|
||||
@Id()
|
||||
int libItemId;
|
||||
|
||||
double? speed;
|
||||
// double? volume;
|
||||
// Duration? sleepTimer;
|
||||
// bool? showTotalProgress;
|
||||
// bool? showChapterProgress;
|
||||
// bool? useChapterInfo;
|
||||
|
||||
BookPrefs({
|
||||
required this.libItemId,
|
||||
this.speed,
|
||||
// this.volume,
|
||||
// this.sleepTimer,
|
||||
// this.showTotalProgress,
|
||||
// this.showChapterProgress,
|
||||
// this.useChapterInfo,
|
||||
});
|
||||
}
|
||||
496
lib/db/player_prefs/book_prefs.g.dart
Normal file
496
lib/db/player_prefs/book_prefs.g.dart
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'book_prefs.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// _IsarCollectionGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, invalid_use_of_protected_member, lines_longer_than_80_chars, constant_identifier_names, avoid_js_rounded_ints, no_leading_underscores_for_local_identifiers, require_trailing_commas, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_in_if_null_operators, library_private_types_in_public_api, prefer_const_constructors
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
extension GetBookPrefsCollection on Isar {
|
||||
IsarCollection<int, BookPrefs> get bookPrefs => this.collection();
|
||||
}
|
||||
|
||||
const BookPrefsSchema = IsarGeneratedSchema(
|
||||
schema: IsarSchema(
|
||||
name: 'BookPrefs',
|
||||
idName: 'libItemId',
|
||||
embedded: false,
|
||||
properties: [
|
||||
IsarPropertySchema(
|
||||
name: 'speed',
|
||||
type: IsarType.double,
|
||||
),
|
||||
],
|
||||
indexes: [],
|
||||
),
|
||||
converter: IsarObjectConverter<int, BookPrefs>(
|
||||
serialize: serializeBookPrefs,
|
||||
deserialize: deserializeBookPrefs,
|
||||
deserializeProperty: deserializeBookPrefsProp,
|
||||
),
|
||||
embeddedSchemas: [],
|
||||
);
|
||||
|
||||
@isarProtected
|
||||
int serializeBookPrefs(IsarWriter writer, BookPrefs object) {
|
||||
IsarCore.writeDouble(writer, 1, object.speed ?? double.nan);
|
||||
return object.libItemId;
|
||||
}
|
||||
|
||||
@isarProtected
|
||||
BookPrefs deserializeBookPrefs(IsarReader reader) {
|
||||
final int _libItemId;
|
||||
_libItemId = IsarCore.readId(reader);
|
||||
final double? _speed;
|
||||
{
|
||||
final value = IsarCore.readDouble(reader, 1);
|
||||
if (value.isNaN) {
|
||||
_speed = null;
|
||||
} else {
|
||||
_speed = value;
|
||||
}
|
||||
}
|
||||
final object = BookPrefs(
|
||||
libItemId: _libItemId,
|
||||
speed: _speed,
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
||||
@isarProtected
|
||||
dynamic deserializeBookPrefsProp(IsarReader reader, int property) {
|
||||
switch (property) {
|
||||
case 0:
|
||||
return IsarCore.readId(reader);
|
||||
case 1:
|
||||
{
|
||||
final value = IsarCore.readDouble(reader, 1);
|
||||
if (value.isNaN) {
|
||||
return null;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw ArgumentError('Unknown property: $property');
|
||||
}
|
||||
}
|
||||
|
||||
sealed class _BookPrefsUpdate {
|
||||
bool call({
|
||||
required int libItemId,
|
||||
double? speed,
|
||||
});
|
||||
}
|
||||
|
||||
class _BookPrefsUpdateImpl implements _BookPrefsUpdate {
|
||||
const _BookPrefsUpdateImpl(this.collection);
|
||||
|
||||
final IsarCollection<int, BookPrefs> collection;
|
||||
|
||||
@override
|
||||
bool call({
|
||||
required int libItemId,
|
||||
Object? speed = ignore,
|
||||
}) {
|
||||
return collection.updateProperties([
|
||||
libItemId
|
||||
], {
|
||||
if (speed != ignore) 1: speed as double?,
|
||||
}) >
|
||||
0;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class _BookPrefsUpdateAll {
|
||||
int call({
|
||||
required List<int> libItemId,
|
||||
double? speed,
|
||||
});
|
||||
}
|
||||
|
||||
class _BookPrefsUpdateAllImpl implements _BookPrefsUpdateAll {
|
||||
const _BookPrefsUpdateAllImpl(this.collection);
|
||||
|
||||
final IsarCollection<int, BookPrefs> collection;
|
||||
|
||||
@override
|
||||
int call({
|
||||
required List<int> libItemId,
|
||||
Object? speed = ignore,
|
||||
}) {
|
||||
return collection.updateProperties(libItemId, {
|
||||
if (speed != ignore) 1: speed as double?,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsUpdate on IsarCollection<int, BookPrefs> {
|
||||
_BookPrefsUpdate get update => _BookPrefsUpdateImpl(this);
|
||||
|
||||
_BookPrefsUpdateAll get updateAll => _BookPrefsUpdateAllImpl(this);
|
||||
}
|
||||
|
||||
sealed class _BookPrefsQueryUpdate {
|
||||
int call({
|
||||
double? speed,
|
||||
});
|
||||
}
|
||||
|
||||
class _BookPrefsQueryUpdateImpl implements _BookPrefsQueryUpdate {
|
||||
const _BookPrefsQueryUpdateImpl(this.query, {this.limit});
|
||||
|
||||
final IsarQuery<BookPrefs> query;
|
||||
final int? limit;
|
||||
|
||||
@override
|
||||
int call({
|
||||
Object? speed = ignore,
|
||||
}) {
|
||||
return query.updateProperties(limit: limit, {
|
||||
if (speed != ignore) 1: speed as double?,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQueryUpdate on IsarQuery<BookPrefs> {
|
||||
_BookPrefsQueryUpdate get updateFirst =>
|
||||
_BookPrefsQueryUpdateImpl(this, limit: 1);
|
||||
|
||||
_BookPrefsQueryUpdate get updateAll => _BookPrefsQueryUpdateImpl(this);
|
||||
}
|
||||
|
||||
class _BookPrefsQueryBuilderUpdateImpl implements _BookPrefsQueryUpdate {
|
||||
const _BookPrefsQueryBuilderUpdateImpl(this.query, {this.limit});
|
||||
|
||||
final QueryBuilder<BookPrefs, BookPrefs, QOperations> query;
|
||||
final int? limit;
|
||||
|
||||
@override
|
||||
int call({
|
||||
Object? speed = ignore,
|
||||
}) {
|
||||
final q = query.build();
|
||||
try {
|
||||
return q.updateProperties(limit: limit, {
|
||||
if (speed != ignore) 1: speed as double?,
|
||||
});
|
||||
} finally {
|
||||
q.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQueryBuilderUpdate
|
||||
on QueryBuilder<BookPrefs, BookPrefs, QOperations> {
|
||||
_BookPrefsQueryUpdate get updateFirst =>
|
||||
_BookPrefsQueryBuilderUpdateImpl(this, limit: 1);
|
||||
|
||||
_BookPrefsQueryUpdate get updateAll => _BookPrefsQueryBuilderUpdateImpl(this);
|
||||
}
|
||||
|
||||
extension BookPrefsQueryFilter
|
||||
on QueryBuilder<BookPrefs, BookPrefs, QFilterCondition> {
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> libItemIdEqualTo(
|
||||
int value,
|
||||
) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
EqualCondition(
|
||||
property: 0,
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition>
|
||||
libItemIdGreaterThan(
|
||||
int value,
|
||||
) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
GreaterCondition(
|
||||
property: 0,
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition>
|
||||
libItemIdGreaterThanOrEqualTo(
|
||||
int value,
|
||||
) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
GreaterOrEqualCondition(
|
||||
property: 0,
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> libItemIdLessThan(
|
||||
int value,
|
||||
) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
LessCondition(
|
||||
property: 0,
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition>
|
||||
libItemIdLessThanOrEqualTo(
|
||||
int value,
|
||||
) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
LessOrEqualCondition(
|
||||
property: 0,
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> libItemIdBetween(
|
||||
int lower,
|
||||
int upper,
|
||||
) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
BetweenCondition(
|
||||
property: 0,
|
||||
lower: lower,
|
||||
upper: upper,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> speedIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const IsNullCondition(property: 1));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> speedIsNotNull() {
|
||||
return QueryBuilder.apply(not(), (query) {
|
||||
return query.addFilterCondition(const IsNullCondition(property: 1));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> speedEqualTo(
|
||||
double? value, {
|
||||
double epsilon = Filter.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
EqualCondition(
|
||||
property: 1,
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> speedGreaterThan(
|
||||
double? value, {
|
||||
double epsilon = Filter.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
GreaterCondition(
|
||||
property: 1,
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition>
|
||||
speedGreaterThanOrEqualTo(
|
||||
double? value, {
|
||||
double epsilon = Filter.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
GreaterOrEqualCondition(
|
||||
property: 1,
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> speedLessThan(
|
||||
double? value, {
|
||||
double epsilon = Filter.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
LessCondition(
|
||||
property: 1,
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition>
|
||||
speedLessThanOrEqualTo(
|
||||
double? value, {
|
||||
double epsilon = Filter.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
LessOrEqualCondition(
|
||||
property: 1,
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterFilterCondition> speedBetween(
|
||||
double? lower,
|
||||
double? upper, {
|
||||
double epsilon = Filter.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(
|
||||
BetweenCondition(
|
||||
property: 1,
|
||||
lower: lower,
|
||||
upper: upper,
|
||||
epsilon: epsilon,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQueryObject
|
||||
on QueryBuilder<BookPrefs, BookPrefs, QFilterCondition> {}
|
||||
|
||||
extension BookPrefsQuerySortBy on QueryBuilder<BookPrefs, BookPrefs, QSortBy> {
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> sortByLibItemId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(0);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> sortByLibItemIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(0, sort: Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> sortBySpeed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(1);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> sortBySpeedDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(1, sort: Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQuerySortThenBy
|
||||
on QueryBuilder<BookPrefs, BookPrefs, QSortThenBy> {
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> thenByLibItemId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(0);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> thenByLibItemIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(0, sort: Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> thenBySpeed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(1);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterSortBy> thenBySpeedDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(1, sort: Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQueryWhereDistinct
|
||||
on QueryBuilder<BookPrefs, BookPrefs, QDistinct> {
|
||||
QueryBuilder<BookPrefs, BookPrefs, QAfterDistinct> distinctBySpeed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQueryProperty1
|
||||
on QueryBuilder<BookPrefs, BookPrefs, QProperty> {
|
||||
QueryBuilder<BookPrefs, int, QAfterProperty> libItemIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addProperty(0);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, double?, QAfterProperty> speedProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addProperty(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQueryProperty2<R>
|
||||
on QueryBuilder<BookPrefs, R, QAfterProperty> {
|
||||
QueryBuilder<BookPrefs, (R, int), QAfterProperty> libItemIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addProperty(0);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, (R, double?), QAfterProperty> speedProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addProperty(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension BookPrefsQueryProperty3<R1, R2>
|
||||
on QueryBuilder<BookPrefs, (R1, R2), QAfterProperty> {
|
||||
QueryBuilder<BookPrefs, (R1, R2, int), QOperations> libItemIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addProperty(0);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<BookPrefs, (R1, R2, double?), QOperations> speedProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addProperty(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,30 @@ import 'package:just_audio/just_audio.dart';
|
|||
import 'package:just_audio_background/just_audio_background.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
|
||||
/// returns the sum of the duration of all the previous tracks before the [index]
|
||||
Duration sumOfTracks(BookExpanded book, int? index) {
|
||||
// return 0 if index is less than 0
|
||||
if (index == null || index < 0) {
|
||||
return Duration.zero;
|
||||
}
|
||||
return book.tracks.sublist(0, index).fold<Duration>(Duration.zero,
|
||||
(previousValue, element) {
|
||||
return previousValue + element.duration;
|
||||
});
|
||||
}
|
||||
|
||||
/// returns the [AudioTrack] to play based on the [position] in the [book]
|
||||
AudioTrack getTrackToPlay(BookExpanded book, Duration position) {
|
||||
var totalDuration = Duration.zero;
|
||||
for (var track in book.tracks) {
|
||||
totalDuration += track.duration;
|
||||
if (totalDuration >= position) {
|
||||
return track;
|
||||
}
|
||||
}
|
||||
return book.tracks.last;
|
||||
}
|
||||
|
||||
/// will manage the audio player instance
|
||||
class AudiobookPlayer extends AudioPlayer {
|
||||
// constructor which takes in the BookExpanded object
|
||||
|
|
@ -18,6 +42,9 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
/// the [BookExpanded] being played
|
||||
BookExpanded? _book;
|
||||
|
||||
// /// the [BookExpanded] trying to be played
|
||||
// BookExpanded? _intended_book;
|
||||
|
||||
/// the [BookExpanded] being played
|
||||
///
|
||||
/// to set the book, use [setSourceAudioBook]
|
||||
|
|
@ -36,7 +63,12 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
int? get availableTracks => _book?.tracks.length;
|
||||
|
||||
/// sets the current [AudioTrack] as the source of the player
|
||||
Future<void> setSourceAudioBook(BookExpanded? book) async {
|
||||
Future<void> setSourceAudioBook(
|
||||
BookExpanded? book, {
|
||||
bool preload = true,
|
||||
// int? initialIndex,
|
||||
Duration? initialPosition,
|
||||
}) async {
|
||||
// if the book is null, stop the player
|
||||
if (book == null) {
|
||||
_book = null;
|
||||
|
|
@ -51,7 +83,24 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
// first stop the player and clear the source
|
||||
await stop();
|
||||
|
||||
_book = book;
|
||||
|
||||
// some calculations to set the initial index and position
|
||||
// initialPosition is of the entire book not just the current track
|
||||
// hence first we need to calculate the current track which will be used to set the initial position
|
||||
// then we set the initial index to the current track index and position as the remaining duration from the position
|
||||
// after subtracting the duration of all the previous tracks
|
||||
|
||||
final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero);
|
||||
final initialIndex = book.tracks.indexOf(trackToPlay);
|
||||
final initialPositionInTrack = initialPosition != null
|
||||
? initialPosition - sumOfTracks(book, initialIndex - 1)
|
||||
: null;
|
||||
|
||||
await setAudioSource(
|
||||
preload: preload,
|
||||
initialIndex: initialIndex,
|
||||
initialPosition: initialPositionInTrack,
|
||||
ConcatenatingAudioSource(
|
||||
useLazyPreparation: true,
|
||||
children: book.tracks.map((track) {
|
||||
|
|
@ -73,8 +122,6 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
).catchError((error) {
|
||||
debugPrint('Error: $error');
|
||||
});
|
||||
|
||||
_book = book;
|
||||
}
|
||||
|
||||
/// toggles the player between play and pause
|
||||
|
|
@ -84,7 +131,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
throw StateError('No book is set');
|
||||
}
|
||||
|
||||
// ! refactor this
|
||||
// TODO refactor this
|
||||
return switch (playerState) {
|
||||
PlayerState(playing: var isPlaying) => isPlaying ? pause() : play(),
|
||||
};
|
||||
|
|
@ -93,27 +140,58 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
/// need to override getDuration and getCurrentPosition to return according to the book instead of the current track
|
||||
/// this is because the book can be a list of audio files and the player is only aware of the current track
|
||||
/// so we need to calculate the duration and current position based on the book
|
||||
// @override
|
||||
// Future<Duration?> getDuration() async {
|
||||
// if (_book == null) {
|
||||
// return null;
|
||||
// }
|
||||
// return _book!.tracks.fold<Duration>(
|
||||
// Duration.zero,
|
||||
// (previousValue, element) => previousValue + element.duration,
|
||||
// );
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Future<Duration?> getCurrentPosition() async {
|
||||
// if (_book == null) {
|
||||
// return null;
|
||||
// }
|
||||
// var currentTrack = _book!.tracks[_currentIndex];
|
||||
// var currentTrackDuration = currentTrack.duration;
|
||||
// var currentTrackPosition = await super.getCurrentPosition();
|
||||
// return currentTrackPosition != null
|
||||
// ? currentTrackPosition + currentTrackDuration
|
||||
// : null;
|
||||
// }
|
||||
@override
|
||||
Future<void> seek(Duration? position, {int? index}) async {
|
||||
if (_book == null) {
|
||||
return;
|
||||
}
|
||||
if (position == null) {
|
||||
return;
|
||||
}
|
||||
final trackToPlay = getTrackToPlay(_book!, position);
|
||||
final index = _book!.tracks.indexOf(trackToPlay);
|
||||
final positionInTrack = position - sumOfTracks(_book!, index - 1);
|
||||
return super.seek(positionInTrack, index: index);
|
||||
}
|
||||
|
||||
/// streams to override to suit the book instead of the current track
|
||||
// - positionStream
|
||||
// - bufferedPositionStream
|
||||
|
||||
@override
|
||||
Stream<Duration> get positionStream {
|
||||
return super.positionStream.map((position) {
|
||||
if (_book == null) {
|
||||
return Duration.zero;
|
||||
}
|
||||
return position + sumOfTracks(_book!, sequenceState!.currentIndex);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Duration> get bufferedPositionStream {
|
||||
return super.bufferedPositionStream.map((position) {
|
||||
if (_book == null) {
|
||||
return Duration.zero;
|
||||
}
|
||||
return position + sumOfTracks(_book!, sequenceState!.currentIndex);
|
||||
});
|
||||
}
|
||||
|
||||
/// get current chapter
|
||||
BookChapter? get currentChapter {
|
||||
if (_book == null) {
|
||||
return null;
|
||||
}
|
||||
return _book!.chapters.firstWhere(
|
||||
(element) {
|
||||
return element.start <= position && element.end >= position;
|
||||
},
|
||||
orElse: () => _book!.chapters.first,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,3 +9,30 @@ BookExpanded? currentlyPlayingBook(CurrentlyPlayingBookRef ref) {
|
|||
final player = ref.watch(audiobookPlayerProvider);
|
||||
return player.book;
|
||||
}
|
||||
|
||||
/// provided the current chapter of the book being played
|
||||
@riverpod
|
||||
BookChapter? currentPlayingChapter(CurrentPlayingChapterRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
// get the current timestamp
|
||||
final currentTimestamp = player.position;
|
||||
// get the chapter that contains the current timestamp
|
||||
return player.book?.chapters.firstWhere(
|
||||
(element) =>
|
||||
element.start <= currentTimestamp && element.end >= currentTimestamp,
|
||||
);
|
||||
}
|
||||
|
||||
/// provides the book metadata of the currently playing book
|
||||
@riverpod
|
||||
BookMetadataExpanded? currentBookMetadata(CurrentBookMetadataRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
if (player.book == null) return null;
|
||||
return BookMetadataExpanded.fromJson(player.book!.metadata.toJson());
|
||||
}
|
||||
|
||||
// /// volume of the player [0, 1]
|
||||
// @riverpod
|
||||
// double currentVolume(CurrentVolumeRef ref) {
|
||||
// return 1;
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -23,5 +23,43 @@ final currentlyPlayingBookProvider =
|
|||
);
|
||||
|
||||
typedef CurrentlyPlayingBookRef = AutoDisposeProviderRef<BookExpanded?>;
|
||||
String _$currentPlayingChapterHash() =>
|
||||
r'562416b7e0068aaba9138cb8e0ed7a5ddba8e6c6';
|
||||
|
||||
/// provided the current chapter of the book being played
|
||||
///
|
||||
/// Copied from [currentPlayingChapter].
|
||||
@ProviderFor(currentPlayingChapter)
|
||||
final currentPlayingChapterProvider =
|
||||
AutoDisposeProvider<BookChapter?>.internal(
|
||||
currentPlayingChapter,
|
||||
name: r'currentPlayingChapterProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$currentPlayingChapterHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef CurrentPlayingChapterRef = AutoDisposeProviderRef<BookChapter?>;
|
||||
String _$currentBookMetadataHash() =>
|
||||
r'02b462a051fce5bcbdad6fdb708b60256fbb588c';
|
||||
|
||||
/// provides the book metadata of the currently playing book
|
||||
///
|
||||
/// Copied from [currentBookMetadata].
|
||||
@ProviderFor(currentBookMetadata)
|
||||
final currentBookMetadataProvider =
|
||||
AutoDisposeProvider<BookMetadataExpanded?>.internal(
|
||||
currentBookMetadata,
|
||||
name: r'currentBookMetadataProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$currentBookMetadataHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef CurrentBookMetadataRef = AutoDisposeProviderRef<BookMetadataExpanded?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||
part 'player_form.g.dart';
|
||||
|
||||
const double playerMinHeight = 70;
|
||||
const miniplayerPercentageDeclaration = 0.2;
|
||||
// const miniplayerPercentageDeclaration = 0.2;
|
||||
|
||||
extension on Ref {
|
||||
// We can move the previous logic to a Ref extension.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
|
@ -9,31 +10,20 @@ import 'package:whispering_pages/api/library_item_provider.dart';
|
|||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart';
|
||||
import 'package:whispering_pages/features/player/providers/player_form.dart';
|
||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||
import 'package:whispering_pages/shared/extensions/inverse_lerp.dart';
|
||||
import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart';
|
||||
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
|
||||
|
||||
import 'player_when_expanded.dart';
|
||||
import 'player_when_minimized.dart';
|
||||
|
||||
double valueFromPercentageInRange({
|
||||
required final double min,
|
||||
max,
|
||||
percentage,
|
||||
}) {
|
||||
return percentage * (max - min) + min;
|
||||
}
|
||||
|
||||
double percentageFromValueInRange({required final double min, max, value}) {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AudiobookPlayer extends HookConsumerWidget {
|
||||
const AudiobookPlayer({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final appSettings = ref.watch(appSettingsProvider);
|
||||
final currentBook = ref.watch(currentlyPlayingBookProvider);
|
||||
if (currentBook == null) {
|
||||
return const SizedBox.shrink();
|
||||
|
|
@ -67,11 +57,8 @@ class AudiobookPlayer extends HookConsumerWidget {
|
|||
}
|
||||
});
|
||||
|
||||
final playPauseButton = AudiobookPlayerPlayPauseButton(
|
||||
playPauseController: playPauseController,
|
||||
);
|
||||
|
||||
const progressBar = AudiobookTotalProgressBar();
|
||||
const chapterProgressBar = AudiobookChapterProgressBar();
|
||||
|
||||
// theme from image
|
||||
final imageTheme = ref.watch(
|
||||
|
|
@ -84,14 +71,25 @@ class AudiobookPlayer extends HookConsumerWidget {
|
|||
// max height of the player is the height of the screen
|
||||
final playerMaxHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
final availWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
// the image width when the player is expanded
|
||||
final maxImgSize = availWidth * 0.9;
|
||||
|
||||
final preferredVolume = appSettings.playerSettings.preferredVolume;
|
||||
return Theme(
|
||||
data: ThemeData(
|
||||
colorScheme: imageTheme.valueOrNull ?? Theme.of(context).colorScheme,
|
||||
),
|
||||
child: Miniplayer(
|
||||
valueNotifier: ref.watch(playerExpandProgressNotifierProvider),
|
||||
onDragDown: (percentage) async {
|
||||
// preferred volume
|
||||
// set volume to 0 when dragging down
|
||||
await player.setVolume(preferredVolume * (1 - percentage));
|
||||
},
|
||||
minHeight: playerMinHeight,
|
||||
// subtract the height of notches and other system UI
|
||||
maxHeight: playerMaxHeight,
|
||||
controller: ref.watch(miniplayerControllerProvider),
|
||||
elevation: 4,
|
||||
|
|
@ -102,81 +100,47 @@ class AudiobookPlayer extends HookConsumerWidget {
|
|||
builder: (height, percentage) {
|
||||
// return SafeArea(
|
||||
// child: Text(
|
||||
// 'percentage: ${percentage.toStringAsFixed(2)}, height: ${height.toStringAsFixed(2)}',
|
||||
// 'percentage: ${percentage.toStringAsFixed(2)}, height: ${height.toStringAsFixed(2)} volume: ${player.volume.toStringAsFixed(2)}',
|
||||
// ),
|
||||
// );
|
||||
|
||||
// at what point should the player switch from miniplayer to expanded player
|
||||
// at this point the image should be at its max size and in the center of the player
|
||||
final miniplayerPercentageDeclaration =
|
||||
(maxImgSize - playerMinHeight) /
|
||||
(playerMaxHeight - playerMinHeight);
|
||||
final bool isFormMiniplayer =
|
||||
percentage < miniplayerPercentageDeclaration;
|
||||
final double availWidth = MediaQuery.of(context).size.width;
|
||||
final maxImgSize = availWidth * 0.4;
|
||||
|
||||
final bookTitle = Text(player.book?.metadata.title ?? '');
|
||||
|
||||
//Declare additional widgets (eg. SkipButton) and variables
|
||||
if (!isFormMiniplayer) {
|
||||
var percentageExpandedPlayer = percentageFromValueInRange(
|
||||
min: playerMaxHeight * miniplayerPercentageDeclaration +
|
||||
playerMinHeight,
|
||||
max: playerMaxHeight,
|
||||
value: height,
|
||||
);
|
||||
if (percentageExpandedPlayer < 0) percentageExpandedPlayer = 0;
|
||||
final paddingVertical = valueFromPercentageInRange(
|
||||
min: 0,
|
||||
max: 16,
|
||||
percentage: percentageExpandedPlayer,
|
||||
);
|
||||
final double heightWithoutPadding = height - paddingVertical * 2;
|
||||
final double imageSize = heightWithoutPadding > maxImgSize
|
||||
? maxImgSize
|
||||
: heightWithoutPadding;
|
||||
final paddingLeft = valueFromPercentageInRange(
|
||||
min: 0,
|
||||
max: availWidth - imageSize,
|
||||
percentage: percentageExpandedPlayer,
|
||||
) /
|
||||
2;
|
||||
// this calculation needs a refactor
|
||||
var percentageExpandedPlayer = percentage
|
||||
.inverseLerp(
|
||||
miniplayerPercentageDeclaration,
|
||||
1,
|
||||
)
|
||||
.clamp(0.0, 1.0);
|
||||
|
||||
const buttonSkipForward = IconButton(
|
||||
icon: Icon(Icons.forward_30),
|
||||
iconSize: 33,
|
||||
onPressed: onTap,
|
||||
);
|
||||
const buttonSkipBackwards = IconButton(
|
||||
icon: Icon(Icons.replay_10),
|
||||
iconSize: 33,
|
||||
onPressed: onTap,
|
||||
);
|
||||
return PlayerWhenExpanded(
|
||||
imgPaddingLeft: paddingLeft,
|
||||
imgPaddingVertical: paddingVertical,
|
||||
imageSize: imageSize,
|
||||
imageSize: maxImgSize,
|
||||
img: imgWidget,
|
||||
percentageExpandedPlayer: percentageExpandedPlayer,
|
||||
text: bookTitle,
|
||||
buttonSkipBackwards: buttonSkipBackwards,
|
||||
playPauseButton: playPauseButton,
|
||||
buttonSkipForward: buttonSkipForward,
|
||||
progressIndicator: progressBar,
|
||||
playPauseController: playPauseController,
|
||||
);
|
||||
}
|
||||
|
||||
//Miniplayer
|
||||
final percentageMiniplayer = percentageFromValueInRange(
|
||||
min: playerMinHeight,
|
||||
max: playerMaxHeight * miniplayerPercentageDeclaration +
|
||||
playerMinHeight,
|
||||
value: height,
|
||||
final percentageMiniplayer = percentage.inverseLerp(
|
||||
0,
|
||||
miniplayerPercentageDeclaration,
|
||||
);
|
||||
|
||||
final elementOpacity = 1 - 1 * percentageMiniplayer;
|
||||
|
||||
return PlayerWhenMinimized(
|
||||
maxImgSize: maxImgSize,
|
||||
availWidth: availWidth,
|
||||
imgWidget: imgWidget,
|
||||
elementOpacity: elementOpacity,
|
||||
playPauseButton: playPauseButton,
|
||||
progressIndicator: progressBar,
|
||||
playPauseController: playPauseController,
|
||||
percentageMiniplayer: percentageMiniplayer,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
@ -188,32 +152,37 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
|
|||
const AudiobookPlayerPlayPauseButton({
|
||||
super.key,
|
||||
required this.playPauseController,
|
||||
this.iconSize = 48.0,
|
||||
});
|
||||
|
||||
final double iconSize;
|
||||
final AnimationController playPauseController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
|
||||
return switch (player.processingState) {
|
||||
ProcessingState.loading ||
|
||||
ProcessingState.buffering =>
|
||||
const CircularProgressIndicator(),
|
||||
ProcessingState.loading || ProcessingState.buffering => const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
ProcessingState.completed => IconButton(
|
||||
onPressed: () async {
|
||||
await player.seek(const Duration(seconds: 0));
|
||||
await player.play();
|
||||
},
|
||||
icon: const Icon(Icons.replay),
|
||||
icon: const Icon(
|
||||
Icons.replay,
|
||||
),
|
||||
),
|
||||
ProcessingState.ready => IconButton(
|
||||
onPressed: () async {
|
||||
await player.togglePlayPause();
|
||||
},
|
||||
iconSize: iconSize,
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.play_pause,
|
||||
progress: playPauseController,
|
||||
size: 50,
|
||||
),
|
||||
),
|
||||
ProcessingState.idle => const SizedBox.shrink(),
|
||||
|
|
@ -227,56 +196,130 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
|
|||
class AudiobookTotalProgressBar extends HookConsumerWidget {
|
||||
const AudiobookTotalProgressBar({
|
||||
super.key,
|
||||
this.barHeight = 5.0,
|
||||
this.barCapShape = BarCapShape.round,
|
||||
this.thumbRadius = 10.0,
|
||||
this.thumbGlowRadius = 30.0,
|
||||
this.thumbCanPaintOutsideBar = true,
|
||||
this.timeLabelLocation,
|
||||
this.timeLabelType,
|
||||
this.timeLabelTextStyle,
|
||||
this.timeLabelPadding = 0.0,
|
||||
});
|
||||
|
||||
final double barHeight;
|
||||
final BarCapShape barCapShape;
|
||||
final double thumbRadius;
|
||||
final double thumbGlowRadius;
|
||||
final bool thumbCanPaintOutsideBar;
|
||||
final TimeLabelLocation? timeLabelLocation;
|
||||
final TimeLabelType? timeLabelType;
|
||||
final TextStyle? timeLabelTextStyle;
|
||||
final double timeLabelPadding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
final position = useStream(
|
||||
player.positionStream,
|
||||
initialData: const Duration(seconds: 0),
|
||||
);
|
||||
final buffered = useStream(
|
||||
player.bufferedPositionStream,
|
||||
initialData: const Duration(seconds: 0),
|
||||
);
|
||||
final currentIndex = useStream(
|
||||
player.currentIndexStream,
|
||||
initialData: 0,
|
||||
);
|
||||
var durationOfPreviousTracks =
|
||||
player.book?.tracks.sublist(0, currentIndex.data).fold(
|
||||
const Duration(seconds: 0),
|
||||
(previousValue, element) => previousValue + element.duration,
|
||||
) ??
|
||||
const Duration(seconds: 0);
|
||||
final totalProgress = durationOfPreviousTracks +
|
||||
(position.data ?? const Duration(seconds: 0));
|
||||
final totalBuffered = durationOfPreviousTracks +
|
||||
(buffered.data ?? const Duration(seconds: 0));
|
||||
|
||||
return ProgressBar(
|
||||
progress: totalProgress,
|
||||
total: player.book?.duration ?? const Duration(seconds: 0),
|
||||
onSeek: player.seek,
|
||||
buffered: totalBuffered,
|
||||
bufferedBarColor: Theme.of(context).colorScheme.secondary,
|
||||
thumbRadius: thumbRadius,
|
||||
thumbGlowRadius: thumbGlowRadius,
|
||||
thumbCanPaintOutsideBar: thumbCanPaintOutsideBar,
|
||||
barHeight: barHeight,
|
||||
barCapShape: barCapShape,
|
||||
timeLabelLocation: timeLabelLocation,
|
||||
timeLabelType: timeLabelType,
|
||||
timeLabelTextStyle: timeLabelTextStyle,
|
||||
timeLabelPadding: timeLabelPadding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AudiobookChapterProgressBar extends HookConsumerWidget {
|
||||
const AudiobookChapterProgressBar({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
// final playerState = useState(player.processingState);
|
||||
// add a listener to the player state
|
||||
// player.processingStateStream.listen((state) {
|
||||
// playerState.value = state;
|
||||
// });
|
||||
return StreamBuilder(
|
||||
stream: player.currentIndexStream,
|
||||
builder: (context, currentTrackIndex) {
|
||||
return StreamBuilder(
|
||||
stream: player.positionStream,
|
||||
builder: (context, progress) {
|
||||
// totalProgress is the sum of the duration of all the tracks before the current track + the current track position
|
||||
final totalProgress =
|
||||
player.book?.tracks.sublist(0, currentTrackIndex.data).fold(
|
||||
const Duration(seconds: 0),
|
||||
(previousValue, element) =>
|
||||
previousValue + element.duration,
|
||||
) ??
|
||||
const Duration(seconds: 0) +
|
||||
(progress.data ?? const Duration(seconds: 0));
|
||||
final position = useStream(
|
||||
player.positionStream,
|
||||
initialData: const Duration(seconds: 0),
|
||||
);
|
||||
final buffered = useStream(
|
||||
player.bufferedPositionStream,
|
||||
initialData: const Duration(seconds: 0),
|
||||
);
|
||||
final currentIndex = useStream(
|
||||
player.currentIndexStream,
|
||||
initialData: 0,
|
||||
);
|
||||
final durationOfPreviousTracks =
|
||||
player.book?.tracks.sublist(0, currentIndex.data).fold(
|
||||
const Duration(seconds: 0),
|
||||
(previousValue, element) => previousValue + element.duration,
|
||||
) ??
|
||||
const Duration(seconds: 0);
|
||||
final totalProgress = durationOfPreviousTracks +
|
||||
(position.data ?? const Duration(seconds: 0));
|
||||
final totalBuffered = durationOfPreviousTracks +
|
||||
(buffered.data ?? const Duration(seconds: 0));
|
||||
// now find the chapter that corresponds to the current time
|
||||
// and calculate the progress of the current chapter
|
||||
final currentChapter = player.book?.chapters.firstWhereOrNull(
|
||||
(element) =>
|
||||
(element.start <= totalProgress) && (element.end >= totalProgress),
|
||||
);
|
||||
final currentChapterProgress =
|
||||
currentChapter == null ? null : (totalProgress - currentChapter.start);
|
||||
|
||||
return StreamBuilder(
|
||||
stream: player.bufferedPositionStream,
|
||||
builder: (context, buffered) {
|
||||
final totalBuffered =
|
||||
player.book?.tracks.sublist(0, currentTrackIndex.data).fold(
|
||||
const Duration(seconds: 0),
|
||||
(previousValue, element) =>
|
||||
previousValue + element.duration,
|
||||
) ??
|
||||
const Duration(seconds: 0) +
|
||||
(buffered.data ?? const Duration(seconds: 0));
|
||||
return ProgressBar(
|
||||
progress: totalProgress,
|
||||
total: player.book?.duration ?? const Duration(seconds: 0),
|
||||
onSeek: player.seek,
|
||||
thumbRadius: 8,
|
||||
buffered: totalBuffered,
|
||||
bufferedBarColor: Theme.of(context).colorScheme.secondary,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
final currentChapterBuffered =
|
||||
currentChapter == null ? null : (totalBuffered - currentChapter.start);
|
||||
|
||||
return ProgressBar(
|
||||
progress: currentChapterProgress ?? totalProgress,
|
||||
total: currentChapter == null
|
||||
? player.book?.duration ?? const Duration(seconds: 0)
|
||||
: currentChapter.end - currentChapter.start,
|
||||
// ! TODO add onSeek
|
||||
onSeek: (duration) {
|
||||
player.seek(
|
||||
duration + (currentChapter?.start ?? const Duration(seconds: 0)),
|
||||
);
|
||||
},
|
||||
thumbRadius: 8,
|
||||
buffered: currentChapterBuffered ?? totalBuffered,
|
||||
bufferedBarColor: Theme.of(context).colorScheme.secondary,
|
||||
timeLabelType: TimeLabelType.remainingTime,
|
||||
timeLabelLocation: TimeLabelLocation.below,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,75 +1,257 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:miniplayer/miniplayer.dart';
|
||||
import 'package:whispering_pages/constants/sizes.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart';
|
||||
import 'package:whispering_pages/features/player/providers/player_form.dart';
|
||||
import 'package:whispering_pages/features/player/view/audiobook_player.dart';
|
||||
import 'package:whispering_pages/shared/extensions/inverse_lerp.dart';
|
||||
|
||||
class PlayerWhenExpanded extends StatelessWidget {
|
||||
class PlayerWhenExpanded extends HookConsumerWidget {
|
||||
const PlayerWhenExpanded({
|
||||
super.key,
|
||||
required this.imgPaddingLeft,
|
||||
required this.imgPaddingVertical,
|
||||
required this.imageSize,
|
||||
required this.img,
|
||||
required this.percentageExpandedPlayer,
|
||||
required this.text,
|
||||
required this.buttonSkipBackwards,
|
||||
required this.playPauseButton,
|
||||
required this.buttonSkipForward,
|
||||
required this.progressIndicator,
|
||||
required this.playPauseController,
|
||||
});
|
||||
|
||||
/// padding values control the position of the image
|
||||
final double imgPaddingLeft;
|
||||
final double imgPaddingVertical;
|
||||
final double imageSize;
|
||||
final Widget img;
|
||||
final double percentageExpandedPlayer;
|
||||
final Text text;
|
||||
final IconButton buttonSkipBackwards;
|
||||
final Widget playPauseButton;
|
||||
final IconButton buttonSkipForward;
|
||||
final Widget progressIndicator;
|
||||
final AnimationController playPauseController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
/// all the properties that help in building the widget are calculated from the [percentageExpandedPlayer]
|
||||
/// however, some properties need to start later than 0% and end before 100%
|
||||
const lateStart = 0.4;
|
||||
const earlyEnd = 1;
|
||||
final earlyPercentage = percentageExpandedPlayer
|
||||
.inverseLerp(
|
||||
lateStart,
|
||||
earlyEnd,
|
||||
)
|
||||
.clamp(0.0, 1.0);
|
||||
final currentBook = ref.watch(currentlyPlayingBookProvider);
|
||||
final currentChapter = ref.watch(currentPlayingChapterProvider);
|
||||
final currentBookMetadata = ref.watch(currentBookMetadataProvider);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: imgPaddingLeft,
|
||||
top: imgPaddingVertical,
|
||||
// bottom: paddingVertical,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: imageSize,
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: img,
|
||||
// sized box for system status bar
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).padding.top * earlyPercentage,
|
||||
),
|
||||
// a row with a down arrow to minimize the player, a pill shaped container to drag the player, and a cast button
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 100 * earlyPercentage,
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: earlyPercentage,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 8.0 * earlyPercentage),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// the down arrow
|
||||
IconButton(
|
||||
iconSize: 30,
|
||||
icon: const Icon(Icons.keyboard_arrow_down),
|
||||
onPressed: () {
|
||||
// minimize the player
|
||||
ref.read(miniplayerControllerProvider).animateToHeight(
|
||||
state: PanelState.MIN,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// the pill shaped container
|
||||
// SizedBox(
|
||||
// height: 6,
|
||||
// width: 32,
|
||||
// child: Container(
|
||||
// decoration: BoxDecoration(
|
||||
// color: Theme.of(context).colorScheme.secondary,
|
||||
// borderRadius: BorderRadius.circular(32),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// the cast button
|
||||
IconButton(
|
||||
icon: const Icon(Icons.cast),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 33),
|
||||
child: Opacity(
|
||||
opacity: percentageExpandedPlayer,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Flexible(child: text),
|
||||
Flexible(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
buttonSkipBackwards,
|
||||
playPauseButton,
|
||||
buttonSkipForward,
|
||||
],
|
||||
),
|
||||
// the image
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8.0 * earlyPercentage),
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
// add a shadow to the image elevation hovering effect
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
blurRadius: 32 * earlyPercentage,
|
||||
spreadRadius: 8 * earlyPercentage,
|
||||
// offset: Offset(0, 16 * earlyPercentage),
|
||||
),
|
||||
Flexible(child: progressIndicator),
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
height: imageSize,
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppElementSizes.borderRadiusRegular * earlyPercentage,
|
||||
),
|
||||
child: img,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// the chapter title
|
||||
currentChapter == null
|
||||
? const SizedBox()
|
||||
: Opacity(
|
||||
opacity: earlyPercentage,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: AppElementSizes.paddingRegular * 4 * earlyPercentage,
|
||||
// horizontal: 16.0,
|
||||
),
|
||||
// child: SizedBox(
|
||||
// same as the image width
|
||||
// width: imageSize,
|
||||
child: Text(
|
||||
currentChapter.title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
// ),
|
||||
),
|
||||
),
|
||||
|
||||
// the book name and author
|
||||
Opacity(
|
||||
opacity: earlyPercentage,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: AppElementSizes.paddingRegular * earlyPercentage,
|
||||
// horizontal: 16.0,
|
||||
),
|
||||
// child: SizedBox(
|
||||
// same as the image width
|
||||
// width: imageSize,
|
||||
child: Text(
|
||||
[
|
||||
currentBookMetadata?.title ?? '',
|
||||
currentBookMetadata?.authorName ?? '',
|
||||
].join(' - '),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
// ),
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
// the progress bar
|
||||
Opacity(
|
||||
opacity: earlyPercentage,
|
||||
child: SizedBox(
|
||||
width: imageSize,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: AppElementSizes.paddingRegular * earlyPercentage,
|
||||
left: AppElementSizes.paddingRegular * earlyPercentage,
|
||||
right: AppElementSizes.paddingRegular * earlyPercentage,
|
||||
),
|
||||
child: const AudiobookChapterProgressBar(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
|
||||
// the chapter skip buttons, seek 30 seconds back and forward, and play/pause button
|
||||
Opacity(
|
||||
opacity: earlyPercentage,
|
||||
child: SizedBox(
|
||||
width: imageSize,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// previous chapter
|
||||
const AudiobookPlayerSeekChapterButton(isForward: false),
|
||||
// buttonSkipBackwards
|
||||
const AudiobookPlayerSeekButton(isForward: false),
|
||||
AudiobookPlayerPlayPauseButton(
|
||||
playPauseController: playPauseController,
|
||||
),
|
||||
// buttonSkipForwards
|
||||
const AudiobookPlayerSeekButton(isForward: true),
|
||||
// next chapter
|
||||
const AudiobookPlayerSeekChapterButton(isForward: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
|
||||
// speed control, sleep timer, chapter list, and settings
|
||||
Opacity(
|
||||
opacity: earlyPercentage,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: AppElementSizes.paddingRegular * 4 * earlyPercentage,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// speed control
|
||||
IconButton(
|
||||
icon: const Icon(Icons.speed),
|
||||
onPressed: () {},
|
||||
),
|
||||
// sleep timer
|
||||
IconButton(
|
||||
icon: const Icon(Icons.timer),
|
||||
onPressed: () {},
|
||||
),
|
||||
// chapter list
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu_book_rounded),
|
||||
onPressed: () {},
|
||||
),
|
||||
// settings
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -77,3 +259,73 @@ class PlayerWhenExpanded extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AudiobookPlayerSeekButton extends HookConsumerWidget {
|
||||
const AudiobookPlayerSeekButton({
|
||||
super.key,
|
||||
required this.isForward,
|
||||
});
|
||||
|
||||
/// if true, the button seeks forward, else it seeks backwards
|
||||
final bool isForward;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
isForward ? Icons.forward_30 : Icons.replay_30,
|
||||
size: AppElementSizes.iconSizeSmall,
|
||||
),
|
||||
onPressed: () {
|
||||
if (isForward) {
|
||||
player.seek(player.position + const Duration(seconds: 30));
|
||||
} else {
|
||||
player.seek(player.position - const Duration(seconds: 30));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
|
||||
const AudiobookPlayerSeekChapterButton({
|
||||
super.key,
|
||||
required this.isForward,
|
||||
});
|
||||
|
||||
/// if true, the button seeks forward, else it seeks backwards
|
||||
final bool isForward;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
isForward ? Icons.skip_next : Icons.skip_previous,
|
||||
size: AppElementSizes.iconSizeSmall,
|
||||
),
|
||||
onPressed: () {
|
||||
if (player.book == null) {
|
||||
return;
|
||||
}
|
||||
if (isForward) {
|
||||
player.seek(player.currentChapter!.end);
|
||||
} else {
|
||||
// if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||
final chapterPosition =
|
||||
player.position - player.currentChapter!.start;
|
||||
if (chapterPosition < const Duration(seconds: 5)) {
|
||||
final index = player.book!.chapters.indexOf(player.currentChapter!);
|
||||
if (index > 0) {
|
||||
player.seek(player.book!.chapters[index - 1].start);
|
||||
}
|
||||
} else {
|
||||
player.seek(player.currentChapter!.start);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +1,146 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:whispering_pages/constants/sizes.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/player/providers/player_form.dart';
|
||||
import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart';
|
||||
import 'package:whispering_pages/features/player/view/audiobook_player.dart';
|
||||
import 'package:whispering_pages/router/router.dart';
|
||||
|
||||
class PlayerWhenMinimized extends HookConsumerWidget {
|
||||
const PlayerWhenMinimized({
|
||||
super.key,
|
||||
required this.availWidth,
|
||||
required this.maxImgSize,
|
||||
required this.imgWidget,
|
||||
required this.elementOpacity,
|
||||
required this.playPauseButton,
|
||||
required this.progressIndicator,
|
||||
required this.playPauseController,
|
||||
required this.percentageMiniplayer,
|
||||
});
|
||||
|
||||
final double availWidth;
|
||||
final double maxImgSize;
|
||||
final Widget imgWidget;
|
||||
final double elementOpacity;
|
||||
final Widget playPauseButton;
|
||||
final Widget progressIndicator;
|
||||
final AnimationController playPauseController;
|
||||
|
||||
/// 0 - 1, from minimized to when switched to expanded player
|
||||
///
|
||||
/// by the time 1 is reached only image should be visible in the center of the widget
|
||||
final double percentageMiniplayer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
final controller = ref.watch(miniplayerControllerProvider);
|
||||
return Column(
|
||||
final vanishingPercentage = 1 - percentageMiniplayer;
|
||||
final progress =
|
||||
useStream(player.positionStream, initialData: Duration.zero);
|
||||
|
||||
final bookMetaExpanded = ref.watch(currentBookMetadataProvider);
|
||||
|
||||
var barHeight = vanishingPercentage * 3;
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: maxImgSize),
|
||||
child: imgWidget,
|
||||
Row(
|
||||
children: [
|
||||
// image
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
((availWidth - maxImgSize) / 2) * percentageMiniplayer,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Opacity(
|
||||
opacity: elementOpacity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
player.book?.metadata.title ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
'audioObject.subtitle',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.color!
|
||||
.withOpacity(0.55),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// navigate to item page
|
||||
context.pushNamed(
|
||||
Routes.libraryItem.name,
|
||||
pathParameters: {
|
||||
Routes.libraryItem.pathParamName!:
|
||||
player.book!.libraryItemId,
|
||||
},
|
||||
);
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: maxImgSize,
|
||||
),
|
||||
child: imgWidget,
|
||||
),
|
||||
),
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.fullscreen),
|
||||
// onPressed: () {
|
||||
// controller.animateToHeight(state: PanelState.MAX);
|
||||
// },
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Opacity(
|
||||
opacity: elementOpacity,
|
||||
child: playPauseButton,
|
||||
),
|
||||
// author and title of the book
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// AutoScrollText(
|
||||
Text(
|
||||
bookMetaExpanded?.title ?? '',
|
||||
maxLines: 1, overflow: TextOverflow.ellipsis,
|
||||
// velocity:
|
||||
// const Velocity(pixelsPerSecond: Offset(16, 0)),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
bookMetaExpanded?.authorName ?? '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.fullscreen),
|
||||
// onPressed: () {
|
||||
// controller.animateToHeight(state: PanelState.MAX);
|
||||
// },
|
||||
// ),
|
||||
// rewind button
|
||||
Opacity(
|
||||
opacity: vanishingPercentage,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.replay_30,
|
||||
size: AppElementSizes.iconSizeSmall,
|
||||
),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
// play/pause button
|
||||
Opacity(
|
||||
opacity: vanishingPercentage,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: AudiobookPlayerPlayPauseButton(
|
||||
playPauseController: playPauseController,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: barHeight,
|
||||
child: LinearProgressIndicator(
|
||||
value: (progress.data ?? Duration.zero).inSeconds /
|
||||
player.book!.duration.inSeconds,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
),
|
||||
// SizedBox(
|
||||
// height: progressIndicatorHeight,
|
||||
// child: Opacity(
|
||||
// opacity: elementOpacity,
|
||||
// child: progressIndicator,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,17 +25,32 @@ class PlayerSettings with _$PlayerSettings {
|
|||
const factory PlayerSettings({
|
||||
@Default(MinimizedPlayerSettings())
|
||||
MinimizedPlayerSettings miniPlayerSettings,
|
||||
@Default(ExpandedPlayerSettings())
|
||||
ExpandedPlayerSettings expandedPlayerSettings,
|
||||
@Default(1) double preferredVolume,
|
||||
@Default(1) double preferredSpeed,
|
||||
@Default(Duration(minutes: 15)) Duration sleepTimer,
|
||||
}) = _PlayerSettings;
|
||||
|
||||
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$PlayerSettingsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ExpandedPlayerSettings with _$ExpandedPlayerSettings {
|
||||
const factory ExpandedPlayerSettings({
|
||||
@Default(false) bool showTotalProgress,
|
||||
@Default(true) bool showChapterProgress,
|
||||
}) = _ExpandedPlayerSettings;
|
||||
|
||||
factory ExpandedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$ExpandedPlayerSettingsFromJson(json);
|
||||
}
|
||||
@freezed
|
||||
class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
|
||||
const factory MinimizedPlayerSettings({
|
||||
@Default(false) bool useChapterInfo,
|
||||
}) = _MiniPlayerSettings;
|
||||
}) = _MinimizedPlayerSettings;
|
||||
|
||||
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$MinimizedPlayerSettingsFromJson(json);
|
||||
|
|
|
|||
|
|
@ -224,6 +224,11 @@ PlayerSettings _$PlayerSettingsFromJson(Map<String, dynamic> json) {
|
|||
mixin _$PlayerSettings {
|
||||
MinimizedPlayerSettings get miniPlayerSettings =>
|
||||
throw _privateConstructorUsedError;
|
||||
ExpandedPlayerSettings get expandedPlayerSettings =>
|
||||
throw _privateConstructorUsedError;
|
||||
double get preferredVolume => throw _privateConstructorUsedError;
|
||||
double get preferredSpeed => throw _privateConstructorUsedError;
|
||||
Duration get sleepTimer => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
|
@ -237,9 +242,15 @@ abstract class $PlayerSettingsCopyWith<$Res> {
|
|||
PlayerSettings value, $Res Function(PlayerSettings) then) =
|
||||
_$PlayerSettingsCopyWithImpl<$Res, PlayerSettings>;
|
||||
@useResult
|
||||
$Res call({MinimizedPlayerSettings miniPlayerSettings});
|
||||
$Res call(
|
||||
{MinimizedPlayerSettings miniPlayerSettings,
|
||||
ExpandedPlayerSettings expandedPlayerSettings,
|
||||
double preferredVolume,
|
||||
double preferredSpeed,
|
||||
Duration sleepTimer});
|
||||
|
||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -256,12 +267,32 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
@override
|
||||
$Res call({
|
||||
Object? miniPlayerSettings = null,
|
||||
Object? expandedPlayerSettings = null,
|
||||
Object? preferredVolume = null,
|
||||
Object? preferredSpeed = null,
|
||||
Object? sleepTimer = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
miniPlayerSettings: null == miniPlayerSettings
|
||||
? _value.miniPlayerSettings
|
||||
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as MinimizedPlayerSettings,
|
||||
expandedPlayerSettings: null == expandedPlayerSettings
|
||||
? _value.expandedPlayerSettings
|
||||
: expandedPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as ExpandedPlayerSettings,
|
||||
preferredVolume: null == preferredVolume
|
||||
? _value.preferredVolume
|
||||
: preferredVolume // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
preferredSpeed: null == preferredSpeed
|
||||
? _value.preferredSpeed
|
||||
: preferredSpeed // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
sleepTimer: null == sleepTimer
|
||||
? _value.sleepTimer
|
||||
: sleepTimer // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
|
|
@ -273,6 +304,15 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
return _then(_value.copyWith(miniPlayerSettings: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings {
|
||||
return $ExpandedPlayerSettingsCopyWith<$Res>(_value.expandedPlayerSettings,
|
||||
(value) {
|
||||
return _then(_value.copyWith(expandedPlayerSettings: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -283,10 +323,17 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res>
|
|||
__$$PlayerSettingsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({MinimizedPlayerSettings miniPlayerSettings});
|
||||
$Res call(
|
||||
{MinimizedPlayerSettings miniPlayerSettings,
|
||||
ExpandedPlayerSettings expandedPlayerSettings,
|
||||
double preferredVolume,
|
||||
double preferredSpeed,
|
||||
Duration sleepTimer});
|
||||
|
||||
@override
|
||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||
@override
|
||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -301,12 +348,32 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
@override
|
||||
$Res call({
|
||||
Object? miniPlayerSettings = null,
|
||||
Object? expandedPlayerSettings = null,
|
||||
Object? preferredVolume = null,
|
||||
Object? preferredSpeed = null,
|
||||
Object? sleepTimer = null,
|
||||
}) {
|
||||
return _then(_$PlayerSettingsImpl(
|
||||
miniPlayerSettings: null == miniPlayerSettings
|
||||
? _value.miniPlayerSettings
|
||||
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as MinimizedPlayerSettings,
|
||||
expandedPlayerSettings: null == expandedPlayerSettings
|
||||
? _value.expandedPlayerSettings
|
||||
: expandedPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as ExpandedPlayerSettings,
|
||||
preferredVolume: null == preferredVolume
|
||||
? _value.preferredVolume
|
||||
: preferredVolume // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
preferredSpeed: null == preferredSpeed
|
||||
? _value.preferredSpeed
|
||||
: preferredSpeed // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
sleepTimer: null == sleepTimer
|
||||
? _value.sleepTimer
|
||||
: sleepTimer // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -315,7 +382,11 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
@JsonSerializable()
|
||||
class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||
const _$PlayerSettingsImpl(
|
||||
{this.miniPlayerSettings = const MinimizedPlayerSettings()});
|
||||
{this.miniPlayerSettings = const MinimizedPlayerSettings(),
|
||||
this.expandedPlayerSettings = const ExpandedPlayerSettings(),
|
||||
this.preferredVolume = 1,
|
||||
this.preferredSpeed = 1,
|
||||
this.sleepTimer = const Duration(minutes: 15)});
|
||||
|
||||
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PlayerSettingsImplFromJson(json);
|
||||
|
|
@ -323,10 +394,22 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
@override
|
||||
@JsonKey()
|
||||
final MinimizedPlayerSettings miniPlayerSettings;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ExpandedPlayerSettings expandedPlayerSettings;
|
||||
@override
|
||||
@JsonKey()
|
||||
final double preferredVolume;
|
||||
@override
|
||||
@JsonKey()
|
||||
final double preferredSpeed;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration sleepTimer;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings)';
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredVolume: $preferredVolume, preferredSpeed: $preferredSpeed, sleepTimer: $sleepTimer)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -335,12 +418,21 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
(other.runtimeType == runtimeType &&
|
||||
other is _$PlayerSettingsImpl &&
|
||||
(identical(other.miniPlayerSettings, miniPlayerSettings) ||
|
||||
other.miniPlayerSettings == miniPlayerSettings));
|
||||
other.miniPlayerSettings == miniPlayerSettings) &&
|
||||
(identical(other.expandedPlayerSettings, expandedPlayerSettings) ||
|
||||
other.expandedPlayerSettings == expandedPlayerSettings) &&
|
||||
(identical(other.preferredVolume, preferredVolume) ||
|
||||
other.preferredVolume == preferredVolume) &&
|
||||
(identical(other.preferredSpeed, preferredSpeed) ||
|
||||
other.preferredSpeed == preferredSpeed) &&
|
||||
(identical(other.sleepTimer, sleepTimer) ||
|
||||
other.sleepTimer == sleepTimer));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, miniPlayerSettings);
|
||||
int get hashCode => Object.hash(runtimeType, miniPlayerSettings,
|
||||
expandedPlayerSettings, preferredVolume, preferredSpeed, sleepTimer);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
|
|
@ -359,8 +451,11 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
|
||||
abstract class _PlayerSettings implements PlayerSettings {
|
||||
const factory _PlayerSettings(
|
||||
{final MinimizedPlayerSettings miniPlayerSettings}) =
|
||||
_$PlayerSettingsImpl;
|
||||
{final MinimizedPlayerSettings miniPlayerSettings,
|
||||
final ExpandedPlayerSettings expandedPlayerSettings,
|
||||
final double preferredVolume,
|
||||
final double preferredSpeed,
|
||||
final Duration sleepTimer}) = _$PlayerSettingsImpl;
|
||||
|
||||
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||
_$PlayerSettingsImpl.fromJson;
|
||||
|
|
@ -368,14 +463,188 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
@override
|
||||
MinimizedPlayerSettings get miniPlayerSettings;
|
||||
@override
|
||||
ExpandedPlayerSettings get expandedPlayerSettings;
|
||||
@override
|
||||
double get preferredVolume;
|
||||
@override
|
||||
double get preferredSpeed;
|
||||
@override
|
||||
Duration get sleepTimer;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
ExpandedPlayerSettings _$ExpandedPlayerSettingsFromJson(
|
||||
Map<String, dynamic> json) {
|
||||
return _ExpandedPlayerSettings.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ExpandedPlayerSettings {
|
||||
bool get showTotalProgress => throw _privateConstructorUsedError;
|
||||
bool get showChapterProgress => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ExpandedPlayerSettingsCopyWith<ExpandedPlayerSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ExpandedPlayerSettingsCopyWith<$Res> {
|
||||
factory $ExpandedPlayerSettingsCopyWith(ExpandedPlayerSettings value,
|
||||
$Res Function(ExpandedPlayerSettings) then) =
|
||||
_$ExpandedPlayerSettingsCopyWithImpl<$Res, ExpandedPlayerSettings>;
|
||||
@useResult
|
||||
$Res call({bool showTotalProgress, bool showChapterProgress});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ExpandedPlayerSettingsCopyWithImpl<$Res,
|
||||
$Val extends ExpandedPlayerSettings>
|
||||
implements $ExpandedPlayerSettingsCopyWith<$Res> {
|
||||
_$ExpandedPlayerSettingsCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? showTotalProgress = null,
|
||||
Object? showChapterProgress = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
showTotalProgress: null == showTotalProgress
|
||||
? _value.showTotalProgress
|
||||
: showTotalProgress // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
showChapterProgress: null == showChapterProgress
|
||||
? _value.showChapterProgress
|
||||
: showChapterProgress // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ExpandedPlayerSettingsImplCopyWith<$Res>
|
||||
implements $ExpandedPlayerSettingsCopyWith<$Res> {
|
||||
factory _$$ExpandedPlayerSettingsImplCopyWith(
|
||||
_$ExpandedPlayerSettingsImpl value,
|
||||
$Res Function(_$ExpandedPlayerSettingsImpl) then) =
|
||||
__$$ExpandedPlayerSettingsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool showTotalProgress, bool showChapterProgress});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ExpandedPlayerSettingsImplCopyWithImpl<$Res>
|
||||
extends _$ExpandedPlayerSettingsCopyWithImpl<$Res,
|
||||
_$ExpandedPlayerSettingsImpl>
|
||||
implements _$$ExpandedPlayerSettingsImplCopyWith<$Res> {
|
||||
__$$ExpandedPlayerSettingsImplCopyWithImpl(
|
||||
_$ExpandedPlayerSettingsImpl _value,
|
||||
$Res Function(_$ExpandedPlayerSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? showTotalProgress = null,
|
||||
Object? showChapterProgress = null,
|
||||
}) {
|
||||
return _then(_$ExpandedPlayerSettingsImpl(
|
||||
showTotalProgress: null == showTotalProgress
|
||||
? _value.showTotalProgress
|
||||
: showTotalProgress // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
showChapterProgress: null == showChapterProgress
|
||||
? _value.showChapterProgress
|
||||
: showChapterProgress // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ExpandedPlayerSettingsImpl implements _ExpandedPlayerSettings {
|
||||
const _$ExpandedPlayerSettingsImpl(
|
||||
{this.showTotalProgress = false, this.showChapterProgress = true});
|
||||
|
||||
factory _$ExpandedPlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ExpandedPlayerSettingsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool showTotalProgress;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool showChapterProgress;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ExpandedPlayerSettings(showTotalProgress: $showTotalProgress, showChapterProgress: $showChapterProgress)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ExpandedPlayerSettingsImpl &&
|
||||
(identical(other.showTotalProgress, showTotalProgress) ||
|
||||
other.showTotalProgress == showTotalProgress) &&
|
||||
(identical(other.showChapterProgress, showChapterProgress) ||
|
||||
other.showChapterProgress == showChapterProgress));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, showTotalProgress, showChapterProgress);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ExpandedPlayerSettingsImplCopyWith<_$ExpandedPlayerSettingsImpl>
|
||||
get copyWith => __$$ExpandedPlayerSettingsImplCopyWithImpl<
|
||||
_$ExpandedPlayerSettingsImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ExpandedPlayerSettingsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ExpandedPlayerSettings implements ExpandedPlayerSettings {
|
||||
const factory _ExpandedPlayerSettings(
|
||||
{final bool showTotalProgress,
|
||||
final bool showChapterProgress}) = _$ExpandedPlayerSettingsImpl;
|
||||
|
||||
factory _ExpandedPlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||
_$ExpandedPlayerSettingsImpl.fromJson;
|
||||
|
||||
@override
|
||||
bool get showTotalProgress;
|
||||
@override
|
||||
bool get showChapterProgress;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ExpandedPlayerSettingsImplCopyWith<_$ExpandedPlayerSettingsImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
MinimizedPlayerSettings _$MinimizedPlayerSettingsFromJson(
|
||||
Map<String, dynamic> json) {
|
||||
return _MiniPlayerSettings.fromJson(json);
|
||||
return _MinimizedPlayerSettings.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
|
@ -423,23 +692,25 @@ class _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
|||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$MiniPlayerSettingsImplCopyWith<$Res>
|
||||
abstract class _$$MinimizedPlayerSettingsImplCopyWith<$Res>
|
||||
implements $MinimizedPlayerSettingsCopyWith<$Res> {
|
||||
factory _$$MiniPlayerSettingsImplCopyWith(_$MiniPlayerSettingsImpl value,
|
||||
$Res Function(_$MiniPlayerSettingsImpl) then) =
|
||||
__$$MiniPlayerSettingsImplCopyWithImpl<$Res>;
|
||||
factory _$$MinimizedPlayerSettingsImplCopyWith(
|
||||
_$MinimizedPlayerSettingsImpl value,
|
||||
$Res Function(_$MinimizedPlayerSettingsImpl) then) =
|
||||
__$$MinimizedPlayerSettingsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool useChapterInfo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
|
||||
class __$$MinimizedPlayerSettingsImplCopyWithImpl<$Res>
|
||||
extends _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
||||
_$MiniPlayerSettingsImpl>
|
||||
implements _$$MiniPlayerSettingsImplCopyWith<$Res> {
|
||||
__$$MiniPlayerSettingsImplCopyWithImpl(_$MiniPlayerSettingsImpl _value,
|
||||
$Res Function(_$MiniPlayerSettingsImpl) _then)
|
||||
_$MinimizedPlayerSettingsImpl>
|
||||
implements _$$MinimizedPlayerSettingsImplCopyWith<$Res> {
|
||||
__$$MinimizedPlayerSettingsImplCopyWithImpl(
|
||||
_$MinimizedPlayerSettingsImpl _value,
|
||||
$Res Function(_$MinimizedPlayerSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
|
|
@ -447,7 +718,7 @@ class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
|
|||
$Res call({
|
||||
Object? useChapterInfo = null,
|
||||
}) {
|
||||
return _then(_$MiniPlayerSettingsImpl(
|
||||
return _then(_$MinimizedPlayerSettingsImpl(
|
||||
useChapterInfo: null == useChapterInfo
|
||||
? _value.useChapterInfo
|
||||
: useChapterInfo // ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -458,11 +729,11 @@ class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
|
|||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
|
||||
const _$MiniPlayerSettingsImpl({this.useChapterInfo = false});
|
||||
class _$MinimizedPlayerSettingsImpl implements _MinimizedPlayerSettings {
|
||||
const _$MinimizedPlayerSettingsImpl({this.useChapterInfo = false});
|
||||
|
||||
factory _$MiniPlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$MiniPlayerSettingsImplFromJson(json);
|
||||
factory _$MinimizedPlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$MinimizedPlayerSettingsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
|
|
@ -477,7 +748,7 @@ class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
|
|||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$MiniPlayerSettingsImpl &&
|
||||
other is _$MinimizedPlayerSettingsImpl &&
|
||||
(identical(other.useChapterInfo, useChapterInfo) ||
|
||||
other.useChapterInfo == useChapterInfo));
|
||||
}
|
||||
|
|
@ -489,29 +760,29 @@ class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
|
|||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
|
||||
__$$MiniPlayerSettingsImplCopyWithImpl<_$MiniPlayerSettingsImpl>(
|
||||
this, _$identity);
|
||||
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||
get copyWith => __$$MinimizedPlayerSettingsImplCopyWithImpl<
|
||||
_$MinimizedPlayerSettingsImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$MiniPlayerSettingsImplToJson(
|
||||
return _$$MinimizedPlayerSettingsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _MiniPlayerSettings implements MinimizedPlayerSettings {
|
||||
const factory _MiniPlayerSettings({final bool useChapterInfo}) =
|
||||
_$MiniPlayerSettingsImpl;
|
||||
abstract class _MinimizedPlayerSettings implements MinimizedPlayerSettings {
|
||||
const factory _MinimizedPlayerSettings({final bool useChapterInfo}) =
|
||||
_$MinimizedPlayerSettingsImpl;
|
||||
|
||||
factory _MiniPlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||
_$MiniPlayerSettingsImpl.fromJson;
|
||||
factory _MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||
_$MinimizedPlayerSettingsImpl.fromJson;
|
||||
|
||||
@override
|
||||
bool get useChapterInfo;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,22 +30,49 @@ _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map<String, dynamic> json) =>
|
|||
? const MinimizedPlayerSettings()
|
||||
: MinimizedPlayerSettings.fromJson(
|
||||
json['miniPlayerSettings'] as Map<String, dynamic>),
|
||||
expandedPlayerSettings: json['expandedPlayerSettings'] == null
|
||||
? const ExpandedPlayerSettings()
|
||||
: ExpandedPlayerSettings.fromJson(
|
||||
json['expandedPlayerSettings'] as Map<String, dynamic>),
|
||||
preferredVolume: (json['preferredVolume'] as num?)?.toDouble() ?? 1,
|
||||
preferredSpeed: (json['preferredSpeed'] as num?)?.toDouble() ?? 1,
|
||||
sleepTimer: json['sleepTimer'] == null
|
||||
? const Duration(minutes: 15)
|
||||
: Duration(microseconds: (json['sleepTimer'] as num).toInt()),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
||||
_$PlayerSettingsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'miniPlayerSettings': instance.miniPlayerSettings,
|
||||
'expandedPlayerSettings': instance.expandedPlayerSettings,
|
||||
'preferredVolume': instance.preferredVolume,
|
||||
'preferredSpeed': instance.preferredSpeed,
|
||||
'sleepTimer': instance.sleepTimer.inMicroseconds,
|
||||
};
|
||||
|
||||
_$MiniPlayerSettingsImpl _$$MiniPlayerSettingsImplFromJson(
|
||||
_$ExpandedPlayerSettingsImpl _$$ExpandedPlayerSettingsImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$MiniPlayerSettingsImpl(
|
||||
_$ExpandedPlayerSettingsImpl(
|
||||
showTotalProgress: json['showTotalProgress'] as bool? ?? false,
|
||||
showChapterProgress: json['showChapterProgress'] as bool? ?? true,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ExpandedPlayerSettingsImplToJson(
|
||||
_$ExpandedPlayerSettingsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'showTotalProgress': instance.showTotalProgress,
|
||||
'showChapterProgress': instance.showChapterProgress,
|
||||
};
|
||||
|
||||
_$MinimizedPlayerSettingsImpl _$$MinimizedPlayerSettingsImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$MinimizedPlayerSettingsImpl(
|
||||
useChapterInfo: json['useChapterInfo'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$MiniPlayerSettingsImplToJson(
|
||||
_$MiniPlayerSettingsImpl instance) =>
|
||||
Map<String, dynamic> _$$MinimizedPlayerSettingsImplToJson(
|
||||
_$MinimizedPlayerSettingsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'useChapterInfo': instance.useChapterInfo,
|
||||
};
|
||||
|
|
|
|||
13
lib/shared/extensions/inverse_lerp.dart
Normal file
13
lib/shared/extensions/inverse_lerp.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
extension InverseLerp on num {
|
||||
/// Returns the fraction of this value between [min] and [max].
|
||||
double inverseLerp(num min, num max) {
|
||||
return (this - min) / (max - min);
|
||||
}
|
||||
}
|
||||
|
||||
extension Lerp on double {
|
||||
/// Returns the value between [min] and [max] given the fraction [t].
|
||||
double lerp(double min, double max) {
|
||||
return min + ((max - min) * this);
|
||||
}
|
||||
}
|
||||
56
pubspec.lock
56
pubspec.lock
|
|
@ -361,6 +361,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
file_picker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -390,6 +398,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.2"
|
||||
flutter_colorpicker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_colorpicker
|
||||
sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
flutter_hooks:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -406,6 +422,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_material_pickers:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_material_pickers
|
||||
sha256: "1100bfd9a296a6680578aba8c51a0db114fb8ef94708fe320fe6da92b1f8c0e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.19"
|
||||
flutter_riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -552,6 +584,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.7"
|
||||
infinite_listview:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: infinite_listview
|
||||
sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -751,6 +799,14 @@ packages:
|
|||
relative: true
|
||||
source: path
|
||||
version: "1.0.3"
|
||||
numberpicker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: numberpicker
|
||||
sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ isar_version: &isar_version ^4.0.0-dev.13 # define the version to be used
|
|||
dependencies:
|
||||
animated_list_plus: ^0.5.2
|
||||
animated_theme_switcher: ^2.0.10
|
||||
flutter_material_pickers: ^3.6.0
|
||||
audio_session: ^0.1.19
|
||||
audio_video_progress_bar: ^2.0.2
|
||||
auto_scroll_text: ^0.0.7
|
||||
|
|
@ -62,6 +63,7 @@ dependencies:
|
|||
media_kit_libs_windows_audio: any
|
||||
miniplayer:
|
||||
path: ../miniplayer
|
||||
numberpicker: ^2.1.2
|
||||
path: ^1.9.0
|
||||
path_provider: ^2.1.0
|
||||
riverpod_annotation: ^2.3.5
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue