mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-06 19:19:28 +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",
|
"audioplayers",
|
||||||
"Autovalidate",
|
"Autovalidate",
|
||||||
"fullscreen",
|
"fullscreen",
|
||||||
|
"Lerp",
|
||||||
"miniplayer",
|
"miniplayer",
|
||||||
"mocktail",
|
"mocktail",
|
||||||
"riverpod",
|
"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:just_audio_background/just_audio_background.dart';
|
||||||
import 'package:shelfsdk/audiobookshelf_api.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
|
/// will manage the audio player instance
|
||||||
class AudiobookPlayer extends AudioPlayer {
|
class AudiobookPlayer extends AudioPlayer {
|
||||||
// constructor which takes in the BookExpanded object
|
// constructor which takes in the BookExpanded object
|
||||||
|
|
@ -18,6 +42,9 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
/// the [BookExpanded] being played
|
/// the [BookExpanded] being played
|
||||||
BookExpanded? _book;
|
BookExpanded? _book;
|
||||||
|
|
||||||
|
// /// the [BookExpanded] trying to be played
|
||||||
|
// BookExpanded? _intended_book;
|
||||||
|
|
||||||
/// the [BookExpanded] being played
|
/// the [BookExpanded] being played
|
||||||
///
|
///
|
||||||
/// to set the book, use [setSourceAudioBook]
|
/// to set the book, use [setSourceAudioBook]
|
||||||
|
|
@ -36,7 +63,12 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
int? get availableTracks => _book?.tracks.length;
|
int? get availableTracks => _book?.tracks.length;
|
||||||
|
|
||||||
/// sets the current [AudioTrack] as the source of the player
|
/// 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 the book is null, stop the player
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
_book = null;
|
_book = null;
|
||||||
|
|
@ -51,7 +83,24 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
// first stop the player and clear the source
|
// first stop the player and clear the source
|
||||||
await stop();
|
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(
|
await setAudioSource(
|
||||||
|
preload: preload,
|
||||||
|
initialIndex: initialIndex,
|
||||||
|
initialPosition: initialPositionInTrack,
|
||||||
ConcatenatingAudioSource(
|
ConcatenatingAudioSource(
|
||||||
useLazyPreparation: true,
|
useLazyPreparation: true,
|
||||||
children: book.tracks.map((track) {
|
children: book.tracks.map((track) {
|
||||||
|
|
@ -73,8 +122,6 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
).catchError((error) {
|
).catchError((error) {
|
||||||
debugPrint('Error: $error');
|
debugPrint('Error: $error');
|
||||||
});
|
});
|
||||||
|
|
||||||
_book = book;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// toggles the player between play and pause
|
/// toggles the player between play and pause
|
||||||
|
|
@ -84,7 +131,7 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
throw StateError('No book is set');
|
throw StateError('No book is set');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! refactor this
|
// TODO refactor this
|
||||||
return switch (playerState) {
|
return switch (playerState) {
|
||||||
PlayerState(playing: var isPlaying) => isPlaying ? pause() : play(),
|
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
|
/// 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
|
/// 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
|
/// 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
|
@override
|
||||||
// Future<Duration?> getCurrentPosition() async {
|
Future<void> seek(Duration? position, {int? index}) async {
|
||||||
// if (_book == null) {
|
if (_book == null) {
|
||||||
// return null;
|
return;
|
||||||
// }
|
}
|
||||||
// var currentTrack = _book!.tracks[_currentIndex];
|
if (position == null) {
|
||||||
// var currentTrackDuration = currentTrack.duration;
|
return;
|
||||||
// var currentTrackPosition = await super.getCurrentPosition();
|
}
|
||||||
// return currentTrackPosition != null
|
final trackToPlay = getTrackToPlay(_book!, position);
|
||||||
// ? currentTrackPosition + currentTrackDuration
|
final index = _book!.tracks.indexOf(trackToPlay);
|
||||||
// : null;
|
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);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
return player.book;
|
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?>;
|
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: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// 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';
|
part 'player_form.g.dart';
|
||||||
|
|
||||||
const double playerMinHeight = 70;
|
const double playerMinHeight = 70;
|
||||||
const miniplayerPercentageDeclaration = 0.2;
|
// const miniplayerPercentageDeclaration = 0.2;
|
||||||
|
|
||||||
extension on Ref {
|
extension on Ref {
|
||||||
// We can move the previous logic to a Ref extension.
|
// 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:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/audiobook_player.dart';
|
||||||
import 'package:whispering_pages/features/player/providers/currently_playing_provider.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/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/shared/widgets/shelves/book_shelf.dart';
|
||||||
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
|
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
|
||||||
|
|
||||||
import 'player_when_expanded.dart';
|
import 'player_when_expanded.dart';
|
||||||
import 'player_when_minimized.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 {
|
class AudiobookPlayer extends HookConsumerWidget {
|
||||||
const AudiobookPlayer({super.key});
|
const AudiobookPlayer({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final appSettings = ref.watch(appSettingsProvider);
|
||||||
final currentBook = ref.watch(currentlyPlayingBookProvider);
|
final currentBook = ref.watch(currentlyPlayingBookProvider);
|
||||||
if (currentBook == null) {
|
if (currentBook == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
|
|
@ -67,11 +57,8 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final playPauseButton = AudiobookPlayerPlayPauseButton(
|
|
||||||
playPauseController: playPauseController,
|
|
||||||
);
|
|
||||||
|
|
||||||
const progressBar = AudiobookTotalProgressBar();
|
const progressBar = AudiobookTotalProgressBar();
|
||||||
|
const chapterProgressBar = AudiobookChapterProgressBar();
|
||||||
|
|
||||||
// theme from image
|
// theme from image
|
||||||
final imageTheme = ref.watch(
|
final imageTheme = ref.watch(
|
||||||
|
|
@ -84,14 +71,25 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
// max height of the player is the height of the screen
|
// max height of the player is the height of the screen
|
||||||
final playerMaxHeight = MediaQuery.of(context).size.height;
|
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(
|
return Theme(
|
||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
colorScheme: imageTheme.valueOrNull ?? Theme.of(context).colorScheme,
|
colorScheme: imageTheme.valueOrNull ?? Theme.of(context).colorScheme,
|
||||||
),
|
),
|
||||||
child: Miniplayer(
|
child: Miniplayer(
|
||||||
valueNotifier: ref.watch(playerExpandProgressNotifierProvider),
|
valueNotifier: ref.watch(playerExpandProgressNotifierProvider),
|
||||||
|
onDragDown: (percentage) async {
|
||||||
|
// preferred volume
|
||||||
|
// set volume to 0 when dragging down
|
||||||
|
await player.setVolume(preferredVolume * (1 - percentage));
|
||||||
|
},
|
||||||
minHeight: playerMinHeight,
|
minHeight: playerMinHeight,
|
||||||
|
// subtract the height of notches and other system UI
|
||||||
maxHeight: playerMaxHeight,
|
maxHeight: playerMaxHeight,
|
||||||
controller: ref.watch(miniplayerControllerProvider),
|
controller: ref.watch(miniplayerControllerProvider),
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
|
|
@ -102,81 +100,47 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
builder: (height, percentage) {
|
builder: (height, percentage) {
|
||||||
// return SafeArea(
|
// return SafeArea(
|
||||||
// child: Text(
|
// 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 =
|
final bool isFormMiniplayer =
|
||||||
percentage < miniplayerPercentageDeclaration;
|
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) {
|
if (!isFormMiniplayer) {
|
||||||
var percentageExpandedPlayer = percentageFromValueInRange(
|
// this calculation needs a refactor
|
||||||
min: playerMaxHeight * miniplayerPercentageDeclaration +
|
var percentageExpandedPlayer = percentage
|
||||||
playerMinHeight,
|
.inverseLerp(
|
||||||
max: playerMaxHeight,
|
miniplayerPercentageDeclaration,
|
||||||
value: height,
|
1,
|
||||||
);
|
)
|
||||||
if (percentageExpandedPlayer < 0) percentageExpandedPlayer = 0;
|
.clamp(0.0, 1.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;
|
|
||||||
|
|
||||||
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(
|
return PlayerWhenExpanded(
|
||||||
imgPaddingLeft: paddingLeft,
|
imageSize: maxImgSize,
|
||||||
imgPaddingVertical: paddingVertical,
|
|
||||||
imageSize: imageSize,
|
|
||||||
img: imgWidget,
|
img: imgWidget,
|
||||||
percentageExpandedPlayer: percentageExpandedPlayer,
|
percentageExpandedPlayer: percentageExpandedPlayer,
|
||||||
text: bookTitle,
|
playPauseController: playPauseController,
|
||||||
buttonSkipBackwards: buttonSkipBackwards,
|
|
||||||
playPauseButton: playPauseButton,
|
|
||||||
buttonSkipForward: buttonSkipForward,
|
|
||||||
progressIndicator: progressBar,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Miniplayer
|
//Miniplayer
|
||||||
final percentageMiniplayer = percentageFromValueInRange(
|
final percentageMiniplayer = percentage.inverseLerp(
|
||||||
min: playerMinHeight,
|
0,
|
||||||
max: playerMaxHeight * miniplayerPercentageDeclaration +
|
miniplayerPercentageDeclaration,
|
||||||
playerMinHeight,
|
|
||||||
value: height,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final elementOpacity = 1 - 1 * percentageMiniplayer;
|
|
||||||
|
|
||||||
return PlayerWhenMinimized(
|
return PlayerWhenMinimized(
|
||||||
maxImgSize: maxImgSize,
|
maxImgSize: maxImgSize,
|
||||||
|
availWidth: availWidth,
|
||||||
imgWidget: imgWidget,
|
imgWidget: imgWidget,
|
||||||
elementOpacity: elementOpacity,
|
playPauseController: playPauseController,
|
||||||
playPauseButton: playPauseButton,
|
percentageMiniplayer: percentageMiniplayer,
|
||||||
progressIndicator: progressBar,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -188,32 +152,37 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
|
||||||
const AudiobookPlayerPlayPauseButton({
|
const AudiobookPlayerPlayPauseButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.playPauseController,
|
required this.playPauseController,
|
||||||
|
this.iconSize = 48.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final double iconSize;
|
||||||
final AnimationController playPauseController;
|
final AnimationController playPauseController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
|
|
||||||
return switch (player.processingState) {
|
return switch (player.processingState) {
|
||||||
ProcessingState.loading ||
|
ProcessingState.loading || ProcessingState.buffering => const Padding(
|
||||||
ProcessingState.buffering =>
|
padding: EdgeInsets.all(8.0),
|
||||||
const CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
ProcessingState.completed => IconButton(
|
ProcessingState.completed => IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await player.seek(const Duration(seconds: 0));
|
await player.seek(const Duration(seconds: 0));
|
||||||
await player.play();
|
await player.play();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.replay),
|
icon: const Icon(
|
||||||
|
Icons.replay,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ProcessingState.ready => IconButton(
|
ProcessingState.ready => IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await player.togglePlayPause();
|
await player.togglePlayPause();
|
||||||
},
|
},
|
||||||
|
iconSize: iconSize,
|
||||||
icon: AnimatedIcon(
|
icon: AnimatedIcon(
|
||||||
icon: AnimatedIcons.play_pause,
|
icon: AnimatedIcons.play_pause,
|
||||||
progress: playPauseController,
|
progress: playPauseController,
|
||||||
size: 50,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ProcessingState.idle => const SizedBox.shrink(),
|
ProcessingState.idle => const SizedBox.shrink(),
|
||||||
|
|
@ -227,56 +196,130 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
|
||||||
class AudiobookTotalProgressBar extends HookConsumerWidget {
|
class AudiobookTotalProgressBar extends HookConsumerWidget {
|
||||||
const AudiobookTotalProgressBar({
|
const AudiobookTotalProgressBar({
|
||||||
super.key,
|
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
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
// final playerState = useState(player.processingState);
|
final position = useStream(
|
||||||
// add a listener to the player state
|
player.positionStream,
|
||||||
// player.processingStateStream.listen((state) {
|
initialData: const Duration(seconds: 0),
|
||||||
// playerState.value = state;
|
);
|
||||||
// });
|
final buffered = useStream(
|
||||||
return StreamBuilder(
|
player.bufferedPositionStream,
|
||||||
stream: player.currentIndexStream,
|
initialData: const Duration(seconds: 0),
|
||||||
builder: (context, currentTrackIndex) {
|
);
|
||||||
return StreamBuilder(
|
final currentIndex = useStream(
|
||||||
stream: player.positionStream,
|
player.currentIndexStream,
|
||||||
builder: (context, progress) {
|
initialData: 0,
|
||||||
// totalProgress is the sum of the duration of all the tracks before the current track + the current track position
|
);
|
||||||
final totalProgress =
|
final durationOfPreviousTracks =
|
||||||
player.book?.tracks.sublist(0, currentTrackIndex.data).fold(
|
player.book?.tracks.sublist(0, currentIndex.data).fold(
|
||||||
const Duration(seconds: 0),
|
const Duration(seconds: 0),
|
||||||
(previousValue, element) =>
|
(previousValue, element) => previousValue + element.duration,
|
||||||
previousValue + element.duration,
|
|
||||||
) ??
|
) ??
|
||||||
const Duration(seconds: 0) +
|
const Duration(seconds: 0);
|
||||||
(progress.data ?? const Duration(seconds: 0));
|
final totalProgress = durationOfPreviousTracks +
|
||||||
|
(position.data ?? const Duration(seconds: 0));
|
||||||
return StreamBuilder(
|
final totalBuffered = durationOfPreviousTracks +
|
||||||
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));
|
(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);
|
||||||
|
|
||||||
|
final currentChapterBuffered =
|
||||||
|
currentChapter == null ? null : (totalBuffered - currentChapter.start);
|
||||||
|
|
||||||
return ProgressBar(
|
return ProgressBar(
|
||||||
progress: totalProgress,
|
progress: currentChapterProgress ?? totalProgress,
|
||||||
total: player.book?.duration ?? const Duration(seconds: 0),
|
total: currentChapter == null
|
||||||
onSeek: player.seek,
|
? 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,
|
thumbRadius: 8,
|
||||||
buffered: totalBuffered,
|
buffered: currentChapterBuffered ?? totalBuffered,
|
||||||
bufferedBarColor: Theme.of(context).colorScheme.secondary,
|
bufferedBarColor: Theme.of(context).colorScheme.secondary,
|
||||||
);
|
timeLabelType: TimeLabelType.remainingTime,
|
||||||
},
|
timeLabelLocation: TimeLabelLocation.below,
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,331 @@
|
||||||
import 'package:flutter/material.dart';
|
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({
|
const PlayerWhenExpanded({
|
||||||
super.key,
|
super.key,
|
||||||
required this.imgPaddingLeft,
|
|
||||||
required this.imgPaddingVertical,
|
|
||||||
required this.imageSize,
|
required this.imageSize,
|
||||||
required this.img,
|
required this.img,
|
||||||
required this.percentageExpandedPlayer,
|
required this.percentageExpandedPlayer,
|
||||||
required this.text,
|
required this.playPauseController,
|
||||||
required this.buttonSkipBackwards,
|
|
||||||
required this.playPauseButton,
|
|
||||||
required this.buttonSkipForward,
|
|
||||||
required this.progressIndicator,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// padding values control the position of the image
|
/// padding values control the position of the image
|
||||||
final double imgPaddingLeft;
|
|
||||||
final double imgPaddingVertical;
|
|
||||||
final double imageSize;
|
final double imageSize;
|
||||||
final Widget img;
|
final Widget img;
|
||||||
final double percentageExpandedPlayer;
|
final double percentageExpandedPlayer;
|
||||||
final Text text;
|
final AnimationController playPauseController;
|
||||||
final IconButton buttonSkipBackwards;
|
|
||||||
final Widget playPauseButton;
|
|
||||||
final IconButton buttonSkipForward;
|
|
||||||
final Widget progressIndicator;
|
|
||||||
|
|
||||||
@override
|
@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(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
// sized box for system status bar
|
||||||
alignment: Alignment.centerLeft,
|
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(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 8.0 * earlyPercentage),
|
||||||
left: imgPaddingLeft,
|
child: Row(
|
||||||
top: imgPaddingVertical,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
// bottom: paddingVertical,
|
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: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: imageSize,
|
height: imageSize,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppElementSizes.borderRadiusRegular * earlyPercentage,
|
||||||
|
),
|
||||||
child: img,
|
child: img,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// the chapter title
|
||||||
|
currentChapter == null
|
||||||
|
? const SizedBox()
|
||||||
|
: Opacity(
|
||||||
|
opacity: earlyPercentage,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 33),
|
padding: EdgeInsets.only(
|
||||||
child: Opacity(
|
top: AppElementSizes.paddingRegular * 4 * earlyPercentage,
|
||||||
opacity: percentageExpandedPlayer,
|
// horizontal: 16.0,
|
||||||
child: Column(
|
),
|
||||||
|
// 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,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
Flexible(child: text),
|
// speed control
|
||||||
Flexible(
|
IconButton(
|
||||||
child: Row(
|
icon: const Icon(Icons.speed),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
onPressed: () {},
|
||||||
children: [
|
),
|
||||||
buttonSkipBackwards,
|
// sleep timer
|
||||||
playPauseButton,
|
IconButton(
|
||||||
buttonSkipForward,
|
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: () {},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(child: progressIndicator),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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/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: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/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 {
|
class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
const PlayerWhenMinimized({
|
const PlayerWhenMinimized({
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.availWidth,
|
||||||
required this.maxImgSize,
|
required this.maxImgSize,
|
||||||
required this.imgWidget,
|
required this.imgWidget,
|
||||||
required this.elementOpacity,
|
required this.playPauseController,
|
||||||
required this.playPauseButton,
|
required this.percentageMiniplayer,
|
||||||
required this.progressIndicator,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final double availWidth;
|
||||||
final double maxImgSize;
|
final double maxImgSize;
|
||||||
final Widget imgWidget;
|
final Widget imgWidget;
|
||||||
final double elementOpacity;
|
final AnimationController playPauseController;
|
||||||
final Widget playPauseButton;
|
|
||||||
final Widget progressIndicator;
|
/// 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
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
final controller = ref.watch(miniplayerControllerProvider);
|
final vanishingPercentage = 1 - percentageMiniplayer;
|
||||||
return Column(
|
final progress =
|
||||||
|
useStream(player.positionStream, initialData: Duration.zero);
|
||||||
|
|
||||||
|
final bookMetaExpanded = ref.watch(currentBookMetadataProvider);
|
||||||
|
|
||||||
|
var barHeight = vanishingPercentage * 3;
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Row(
|
||||||
child: Row(
|
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
// image
|
||||||
constraints: BoxConstraints(maxHeight: maxImgSize),
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal:
|
||||||
|
((availWidth - maxImgSize) / 2) * percentageMiniplayer,
|
||||||
|
),
|
||||||
|
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,
|
child: imgWidget,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// author and title of the book
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 10),
|
padding: const EdgeInsets.only(left: 8),
|
||||||
child: Opacity(
|
|
||||||
opacity: elementOpacity,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
// AutoScrollText(
|
||||||
Text(
|
Text(
|
||||||
player.book?.metadata.title ?? '',
|
bookMetaExpanded?.title ?? '',
|
||||||
style: Theme.of(context)
|
maxLines: 1, overflow: TextOverflow.ellipsis,
|
||||||
.textTheme
|
// velocity:
|
||||||
.bodyMedium!
|
// const Velocity(pixelsPerSecond: Offset(16, 0)),
|
||||||
.copyWith(fontSize: 16),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'audioObject.subtitle',
|
bookMetaExpanded?.authorName ?? '',
|
||||||
style:
|
maxLines: 1,
|
||||||
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.textTheme
|
.colorScheme
|
||||||
.bodyMedium!
|
.onBackground
|
||||||
.color!
|
.withOpacity(0.7),
|
||||||
.withOpacity(0.55),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
// IconButton(
|
// IconButton(
|
||||||
// icon: const Icon(Icons.fullscreen),
|
// icon: const Icon(Icons.fullscreen),
|
||||||
// onPressed: () {
|
// onPressed: () {
|
||||||
// controller.animateToHeight(state: PanelState.MAX);
|
// controller.animateToHeight(state: PanelState.MAX);
|
||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
Padding(
|
// rewind button
|
||||||
padding: const EdgeInsets.all(8),
|
Opacity(
|
||||||
child: Opacity(
|
opacity: vanishingPercentage,
|
||||||
opacity: elementOpacity,
|
child: Padding(
|
||||||
child: playPauseButton,
|
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({
|
const factory PlayerSettings({
|
||||||
@Default(MinimizedPlayerSettings())
|
@Default(MinimizedPlayerSettings())
|
||||||
MinimizedPlayerSettings miniPlayerSettings,
|
MinimizedPlayerSettings miniPlayerSettings,
|
||||||
|
@Default(ExpandedPlayerSettings())
|
||||||
|
ExpandedPlayerSettings expandedPlayerSettings,
|
||||||
|
@Default(1) double preferredVolume,
|
||||||
|
@Default(1) double preferredSpeed,
|
||||||
|
@Default(Duration(minutes: 15)) Duration sleepTimer,
|
||||||
}) = _PlayerSettings;
|
}) = _PlayerSettings;
|
||||||
|
|
||||||
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PlayerSettingsFromJson(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
|
@freezed
|
||||||
class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
|
class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
|
||||||
const factory MinimizedPlayerSettings({
|
const factory MinimizedPlayerSettings({
|
||||||
@Default(false) bool useChapterInfo,
|
@Default(false) bool useChapterInfo,
|
||||||
}) = _MiniPlayerSettings;
|
}) = _MinimizedPlayerSettings;
|
||||||
|
|
||||||
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
_$MinimizedPlayerSettingsFromJson(json);
|
_$MinimizedPlayerSettingsFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,11 @@ PlayerSettings _$PlayerSettingsFromJson(Map<String, dynamic> json) {
|
||||||
mixin _$PlayerSettings {
|
mixin _$PlayerSettings {
|
||||||
MinimizedPlayerSettings get miniPlayerSettings =>
|
MinimizedPlayerSettings get miniPlayerSettings =>
|
||||||
throw _privateConstructorUsedError;
|
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;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
|
@ -237,9 +242,15 @@ abstract class $PlayerSettingsCopyWith<$Res> {
|
||||||
PlayerSettings value, $Res Function(PlayerSettings) then) =
|
PlayerSettings value, $Res Function(PlayerSettings) then) =
|
||||||
_$PlayerSettingsCopyWithImpl<$Res, PlayerSettings>;
|
_$PlayerSettingsCopyWithImpl<$Res, PlayerSettings>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({MinimizedPlayerSettings miniPlayerSettings});
|
$Res call(
|
||||||
|
{MinimizedPlayerSettings miniPlayerSettings,
|
||||||
|
ExpandedPlayerSettings expandedPlayerSettings,
|
||||||
|
double preferredVolume,
|
||||||
|
double preferredSpeed,
|
||||||
|
Duration sleepTimer});
|
||||||
|
|
||||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||||
|
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -256,12 +267,32 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? miniPlayerSettings = null,
|
Object? miniPlayerSettings = null,
|
||||||
|
Object? expandedPlayerSettings = null,
|
||||||
|
Object? preferredVolume = null,
|
||||||
|
Object? preferredSpeed = null,
|
||||||
|
Object? sleepTimer = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
miniPlayerSettings: null == miniPlayerSettings
|
miniPlayerSettings: null == miniPlayerSettings
|
||||||
? _value.miniPlayerSettings
|
? _value.miniPlayerSettings
|
||||||
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
as MinimizedPlayerSettings,
|
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);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +304,15 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
||||||
return _then(_value.copyWith(miniPlayerSettings: value) as $Val);
|
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
|
/// @nodoc
|
||||||
|
|
@ -283,10 +323,17 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res>
|
||||||
__$$PlayerSettingsImplCopyWithImpl<$Res>;
|
__$$PlayerSettingsImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({MinimizedPlayerSettings miniPlayerSettings});
|
$Res call(
|
||||||
|
{MinimizedPlayerSettings miniPlayerSettings,
|
||||||
|
ExpandedPlayerSettings expandedPlayerSettings,
|
||||||
|
double preferredVolume,
|
||||||
|
double preferredSpeed,
|
||||||
|
Duration sleepTimer});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||||
|
@override
|
||||||
|
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -301,12 +348,32 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? miniPlayerSettings = null,
|
Object? miniPlayerSettings = null,
|
||||||
|
Object? expandedPlayerSettings = null,
|
||||||
|
Object? preferredVolume = null,
|
||||||
|
Object? preferredSpeed = null,
|
||||||
|
Object? sleepTimer = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$PlayerSettingsImpl(
|
return _then(_$PlayerSettingsImpl(
|
||||||
miniPlayerSettings: null == miniPlayerSettings
|
miniPlayerSettings: null == miniPlayerSettings
|
||||||
? _value.miniPlayerSettings
|
? _value.miniPlayerSettings
|
||||||
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
as MinimizedPlayerSettings,
|
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()
|
@JsonSerializable()
|
||||||
class _$PlayerSettingsImpl implements _PlayerSettings {
|
class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
const _$PlayerSettingsImpl(
|
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) =>
|
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$PlayerSettingsImplFromJson(json);
|
_$$PlayerSettingsImplFromJson(json);
|
||||||
|
|
@ -323,10 +394,22 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final MinimizedPlayerSettings miniPlayerSettings;
|
final MinimizedPlayerSettings miniPlayerSettings;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final ExpandedPlayerSettings expandedPlayerSettings;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final double preferredVolume;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final double preferredSpeed;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final Duration sleepTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings)';
|
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredVolume: $preferredVolume, preferredSpeed: $preferredSpeed, sleepTimer: $sleepTimer)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -335,12 +418,21 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$PlayerSettingsImpl &&
|
other is _$PlayerSettingsImpl &&
|
||||||
(identical(other.miniPlayerSettings, miniPlayerSettings) ||
|
(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)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, miniPlayerSettings);
|
int get hashCode => Object.hash(runtimeType, miniPlayerSettings,
|
||||||
|
expandedPlayerSettings, preferredVolume, preferredSpeed, sleepTimer);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
|
|
@ -359,8 +451,11 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
|
|
||||||
abstract class _PlayerSettings implements PlayerSettings {
|
abstract class _PlayerSettings implements PlayerSettings {
|
||||||
const factory _PlayerSettings(
|
const factory _PlayerSettings(
|
||||||
{final MinimizedPlayerSettings miniPlayerSettings}) =
|
{final MinimizedPlayerSettings miniPlayerSettings,
|
||||||
_$PlayerSettingsImpl;
|
final ExpandedPlayerSettings expandedPlayerSettings,
|
||||||
|
final double preferredVolume,
|
||||||
|
final double preferredSpeed,
|
||||||
|
final Duration sleepTimer}) = _$PlayerSettingsImpl;
|
||||||
|
|
||||||
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||||
_$PlayerSettingsImpl.fromJson;
|
_$PlayerSettingsImpl.fromJson;
|
||||||
|
|
@ -368,14 +463,188 @@ abstract class _PlayerSettings implements PlayerSettings {
|
||||||
@override
|
@override
|
||||||
MinimizedPlayerSettings get miniPlayerSettings;
|
MinimizedPlayerSettings get miniPlayerSettings;
|
||||||
@override
|
@override
|
||||||
|
ExpandedPlayerSettings get expandedPlayerSettings;
|
||||||
|
@override
|
||||||
|
double get preferredVolume;
|
||||||
|
@override
|
||||||
|
double get preferredSpeed;
|
||||||
|
@override
|
||||||
|
Duration get sleepTimer;
|
||||||
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
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(
|
MinimizedPlayerSettings _$MinimizedPlayerSettingsFromJson(
|
||||||
Map<String, dynamic> json) {
|
Map<String, dynamic> json) {
|
||||||
return _MiniPlayerSettings.fromJson(json);
|
return _MinimizedPlayerSettings.fromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -423,23 +692,25 @@ class _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract class _$$MiniPlayerSettingsImplCopyWith<$Res>
|
abstract class _$$MinimizedPlayerSettingsImplCopyWith<$Res>
|
||||||
implements $MinimizedPlayerSettingsCopyWith<$Res> {
|
implements $MinimizedPlayerSettingsCopyWith<$Res> {
|
||||||
factory _$$MiniPlayerSettingsImplCopyWith(_$MiniPlayerSettingsImpl value,
|
factory _$$MinimizedPlayerSettingsImplCopyWith(
|
||||||
$Res Function(_$MiniPlayerSettingsImpl) then) =
|
_$MinimizedPlayerSettingsImpl value,
|
||||||
__$$MiniPlayerSettingsImplCopyWithImpl<$Res>;
|
$Res Function(_$MinimizedPlayerSettingsImpl) then) =
|
||||||
|
__$$MinimizedPlayerSettingsImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool useChapterInfo});
|
$Res call({bool useChapterInfo});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
|
class __$$MinimizedPlayerSettingsImplCopyWithImpl<$Res>
|
||||||
extends _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
extends _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
||||||
_$MiniPlayerSettingsImpl>
|
_$MinimizedPlayerSettingsImpl>
|
||||||
implements _$$MiniPlayerSettingsImplCopyWith<$Res> {
|
implements _$$MinimizedPlayerSettingsImplCopyWith<$Res> {
|
||||||
__$$MiniPlayerSettingsImplCopyWithImpl(_$MiniPlayerSettingsImpl _value,
|
__$$MinimizedPlayerSettingsImplCopyWithImpl(
|
||||||
$Res Function(_$MiniPlayerSettingsImpl) _then)
|
_$MinimizedPlayerSettingsImpl _value,
|
||||||
|
$Res Function(_$MinimizedPlayerSettingsImpl) _then)
|
||||||
: super(_value, _then);
|
: super(_value, _then);
|
||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
|
|
@ -447,7 +718,7 @@ class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? useChapterInfo = null,
|
Object? useChapterInfo = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$MiniPlayerSettingsImpl(
|
return _then(_$MinimizedPlayerSettingsImpl(
|
||||||
useChapterInfo: null == useChapterInfo
|
useChapterInfo: null == useChapterInfo
|
||||||
? _value.useChapterInfo
|
? _value.useChapterInfo
|
||||||
: useChapterInfo // ignore: cast_nullable_to_non_nullable
|
: useChapterInfo // ignore: cast_nullable_to_non_nullable
|
||||||
|
|
@ -458,11 +729,11 @@ class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
|
class _$MinimizedPlayerSettingsImpl implements _MinimizedPlayerSettings {
|
||||||
const _$MiniPlayerSettingsImpl({this.useChapterInfo = false});
|
const _$MinimizedPlayerSettingsImpl({this.useChapterInfo = false});
|
||||||
|
|
||||||
factory _$MiniPlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$MinimizedPlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$MiniPlayerSettingsImplFromJson(json);
|
_$$MinimizedPlayerSettingsImplFromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
|
|
@ -477,7 +748,7 @@ class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$MiniPlayerSettingsImpl &&
|
other is _$MinimizedPlayerSettingsImpl &&
|
||||||
(identical(other.useChapterInfo, useChapterInfo) ||
|
(identical(other.useChapterInfo, useChapterInfo) ||
|
||||||
other.useChapterInfo == useChapterInfo));
|
other.useChapterInfo == useChapterInfo));
|
||||||
}
|
}
|
||||||
|
|
@ -489,29 +760,29 @@ class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
|
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||||
__$$MiniPlayerSettingsImplCopyWithImpl<_$MiniPlayerSettingsImpl>(
|
get copyWith => __$$MinimizedPlayerSettingsImplCopyWithImpl<
|
||||||
this, _$identity);
|
_$MinimizedPlayerSettingsImpl>(this, _$identity);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return _$$MiniPlayerSettingsImplToJson(
|
return _$$MinimizedPlayerSettingsImplToJson(
|
||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _MiniPlayerSettings implements MinimizedPlayerSettings {
|
abstract class _MinimizedPlayerSettings implements MinimizedPlayerSettings {
|
||||||
const factory _MiniPlayerSettings({final bool useChapterInfo}) =
|
const factory _MinimizedPlayerSettings({final bool useChapterInfo}) =
|
||||||
_$MiniPlayerSettingsImpl;
|
_$MinimizedPlayerSettingsImpl;
|
||||||
|
|
||||||
factory _MiniPlayerSettings.fromJson(Map<String, dynamic> json) =
|
factory _MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||||
_$MiniPlayerSettingsImpl.fromJson;
|
_$MinimizedPlayerSettingsImpl.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get useChapterInfo;
|
bool get useChapterInfo;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
|
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||||
throw _privateConstructorUsedError;
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,22 +30,49 @@ _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map<String, dynamic> json) =>
|
||||||
? const MinimizedPlayerSettings()
|
? const MinimizedPlayerSettings()
|
||||||
: MinimizedPlayerSettings.fromJson(
|
: MinimizedPlayerSettings.fromJson(
|
||||||
json['miniPlayerSettings'] as Map<String, dynamic>),
|
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(
|
Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
||||||
_$PlayerSettingsImpl instance) =>
|
_$PlayerSettingsImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'miniPlayerSettings': instance.miniPlayerSettings,
|
'miniPlayerSettings': instance.miniPlayerSettings,
|
||||||
|
'expandedPlayerSettings': instance.expandedPlayerSettings,
|
||||||
|
'preferredVolume': instance.preferredVolume,
|
||||||
|
'preferredSpeed': instance.preferredSpeed,
|
||||||
|
'sleepTimer': instance.sleepTimer.inMicroseconds,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$MiniPlayerSettingsImpl _$$MiniPlayerSettingsImplFromJson(
|
_$ExpandedPlayerSettingsImpl _$$ExpandedPlayerSettingsImplFromJson(
|
||||||
Map<String, dynamic> json) =>
|
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,
|
useChapterInfo: json['useChapterInfo'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$MiniPlayerSettingsImplToJson(
|
Map<String, dynamic> _$$MinimizedPlayerSettingsImplToJson(
|
||||||
_$MiniPlayerSettingsImpl instance) =>
|
_$MinimizedPlayerSettingsImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'useChapterInfo': instance.useChapterInfo,
|
'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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
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:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -390,6 +398,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.2"
|
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:
|
flutter_hooks:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -406,6 +422,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
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:
|
flutter_riverpod:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -552,6 +584,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.7"
|
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:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -751,6 +799,14 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.0.3"
|
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:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ isar_version: &isar_version ^4.0.0-dev.13 # define the version to be used
|
||||||
dependencies:
|
dependencies:
|
||||||
animated_list_plus: ^0.5.2
|
animated_list_plus: ^0.5.2
|
||||||
animated_theme_switcher: ^2.0.10
|
animated_theme_switcher: ^2.0.10
|
||||||
|
flutter_material_pickers: ^3.6.0
|
||||||
audio_session: ^0.1.19
|
audio_session: ^0.1.19
|
||||||
audio_video_progress_bar: ^2.0.2
|
audio_video_progress_bar: ^2.0.2
|
||||||
auto_scroll_text: ^0.0.7
|
auto_scroll_text: ^0.0.7
|
||||||
|
|
@ -62,6 +63,7 @@ dependencies:
|
||||||
media_kit_libs_windows_audio: any
|
media_kit_libs_windows_audio: any
|
||||||
miniplayer:
|
miniplayer:
|
||||||
path: ../miniplayer
|
path: ../miniplayer
|
||||||
|
numberpicker: ^2.1.2
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
path_provider: ^2.1.0
|
path_provider: ^2.1.0
|
||||||
riverpod_annotation: ^2.3.5
|
riverpod_annotation: ^2.3.5
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue