mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-21 02:19:30 +00:00
feat: add player settings page and enhance settings UI (#33)
This commit is contained in:
parent
8049a660e6
commit
150e5c9025
12 changed files with 761 additions and 157 deletions
|
|
@ -44,6 +44,9 @@ class PlayerSettings with _$PlayerSettings {
|
|||
@Default(1) double preferredDefaultVolume,
|
||||
@Default(1) double preferredDefaultSpeed,
|
||||
@Default([0.75, 1, 1.25, 1.5, 1.75, 2]) List<double> speedOptions,
|
||||
@Default(0.05) double speedIncrement,
|
||||
@Default(0.1) double minSpeed,
|
||||
@Default(4) double maxSpeed,
|
||||
@Default(SleepTimerSettings()) SleepTimerSettings sleepTimerSettings,
|
||||
@Default(Duration(seconds: 10)) Duration minimumPositionForReporting,
|
||||
@Default(Duration(seconds: 10)) Duration playbackReportInterval,
|
||||
|
|
|
|||
|
|
@ -512,6 +512,9 @@ mixin _$PlayerSettings {
|
|||
double get preferredDefaultVolume => throw _privateConstructorUsedError;
|
||||
double get preferredDefaultSpeed => throw _privateConstructorUsedError;
|
||||
List<double> get speedOptions => throw _privateConstructorUsedError;
|
||||
double get speedIncrement => throw _privateConstructorUsedError;
|
||||
double get minSpeed => throw _privateConstructorUsedError;
|
||||
double get maxSpeed => throw _privateConstructorUsedError;
|
||||
SleepTimerSettings get sleepTimerSettings =>
|
||||
throw _privateConstructorUsedError;
|
||||
Duration get minimumPositionForReporting =>
|
||||
|
|
@ -542,6 +545,9 @@ abstract class $PlayerSettingsCopyWith<$Res> {
|
|||
double preferredDefaultVolume,
|
||||
double preferredDefaultSpeed,
|
||||
List<double> speedOptions,
|
||||
double speedIncrement,
|
||||
double minSpeed,
|
||||
double maxSpeed,
|
||||
SleepTimerSettings sleepTimerSettings,
|
||||
Duration minimumPositionForReporting,
|
||||
Duration playbackReportInterval,
|
||||
|
|
@ -573,6 +579,9 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
Object? preferredDefaultVolume = null,
|
||||
Object? preferredDefaultSpeed = null,
|
||||
Object? speedOptions = null,
|
||||
Object? speedIncrement = null,
|
||||
Object? minSpeed = null,
|
||||
Object? maxSpeed = null,
|
||||
Object? sleepTimerSettings = null,
|
||||
Object? minimumPositionForReporting = null,
|
||||
Object? playbackReportInterval = null,
|
||||
|
|
@ -600,6 +609,18 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
? _value.speedOptions
|
||||
: speedOptions // ignore: cast_nullable_to_non_nullable
|
||||
as List<double>,
|
||||
speedIncrement: null == speedIncrement
|
||||
? _value.speedIncrement
|
||||
: speedIncrement // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
minSpeed: null == minSpeed
|
||||
? _value.minSpeed
|
||||
: minSpeed // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
maxSpeed: null == maxSpeed
|
||||
? _value.maxSpeed
|
||||
: maxSpeed // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
sleepTimerSettings: null == sleepTimerSettings
|
||||
? _value.sleepTimerSettings
|
||||
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -671,6 +692,9 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res>
|
|||
double preferredDefaultVolume,
|
||||
double preferredDefaultSpeed,
|
||||
List<double> speedOptions,
|
||||
double speedIncrement,
|
||||
double minSpeed,
|
||||
double maxSpeed,
|
||||
SleepTimerSettings sleepTimerSettings,
|
||||
Duration minimumPositionForReporting,
|
||||
Duration playbackReportInterval,
|
||||
|
|
@ -703,6 +727,9 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
Object? preferredDefaultVolume = null,
|
||||
Object? preferredDefaultSpeed = null,
|
||||
Object? speedOptions = null,
|
||||
Object? speedIncrement = null,
|
||||
Object? minSpeed = null,
|
||||
Object? maxSpeed = null,
|
||||
Object? sleepTimerSettings = null,
|
||||
Object? minimumPositionForReporting = null,
|
||||
Object? playbackReportInterval = null,
|
||||
|
|
@ -730,6 +757,18 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
? _value._speedOptions
|
||||
: speedOptions // ignore: cast_nullable_to_non_nullable
|
||||
as List<double>,
|
||||
speedIncrement: null == speedIncrement
|
||||
? _value.speedIncrement
|
||||
: speedIncrement // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
minSpeed: null == minSpeed
|
||||
? _value.minSpeed
|
||||
: minSpeed // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
maxSpeed: null == maxSpeed
|
||||
? _value.maxSpeed
|
||||
: maxSpeed // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
sleepTimerSettings: null == sleepTimerSettings
|
||||
? _value.sleepTimerSettings
|
||||
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -763,6 +802,9 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
this.preferredDefaultVolume = 1,
|
||||
this.preferredDefaultSpeed = 1,
|
||||
final List<double> speedOptions = const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
this.speedIncrement = 0.05,
|
||||
this.minSpeed = 0.1,
|
||||
this.maxSpeed = 4,
|
||||
this.sleepTimerSettings = const SleepTimerSettings(),
|
||||
this.minimumPositionForReporting = const Duration(seconds: 10),
|
||||
this.playbackReportInterval = const Duration(seconds: 10),
|
||||
|
|
@ -794,6 +836,15 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
return EqualUnmodifiableListView(_speedOptions);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final double speedIncrement;
|
||||
@override
|
||||
@JsonKey()
|
||||
final double minSpeed;
|
||||
@override
|
||||
@JsonKey()
|
||||
final double maxSpeed;
|
||||
@override
|
||||
@JsonKey()
|
||||
final SleepTimerSettings sleepTimerSettings;
|
||||
|
|
@ -812,7 +863,7 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings, minimumPositionForReporting: $minimumPositionForReporting, playbackReportInterval: $playbackReportInterval, markCompleteWhenTimeLeft: $markCompleteWhenTimeLeft, configurePlayerForEveryBook: $configurePlayerForEveryBook)';
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, speedIncrement: $speedIncrement, minSpeed: $minSpeed, maxSpeed: $maxSpeed, sleepTimerSettings: $sleepTimerSettings, minimumPositionForReporting: $minimumPositionForReporting, playbackReportInterval: $playbackReportInterval, markCompleteWhenTimeLeft: $markCompleteWhenTimeLeft, configurePlayerForEveryBook: $configurePlayerForEveryBook)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -830,6 +881,12 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
other.preferredDefaultSpeed == preferredDefaultSpeed) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._speedOptions, _speedOptions) &&
|
||||
(identical(other.speedIncrement, speedIncrement) ||
|
||||
other.speedIncrement == speedIncrement) &&
|
||||
(identical(other.minSpeed, minSpeed) ||
|
||||
other.minSpeed == minSpeed) &&
|
||||
(identical(other.maxSpeed, maxSpeed) ||
|
||||
other.maxSpeed == maxSpeed) &&
|
||||
(identical(other.sleepTimerSettings, sleepTimerSettings) ||
|
||||
other.sleepTimerSettings == sleepTimerSettings) &&
|
||||
(identical(other.minimumPositionForReporting,
|
||||
|
|
@ -856,6 +913,9 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
preferredDefaultVolume,
|
||||
preferredDefaultSpeed,
|
||||
const DeepCollectionEquality().hash(_speedOptions),
|
||||
speedIncrement,
|
||||
minSpeed,
|
||||
maxSpeed,
|
||||
sleepTimerSettings,
|
||||
minimumPositionForReporting,
|
||||
playbackReportInterval,
|
||||
|
|
@ -886,6 +946,9 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
final double preferredDefaultVolume,
|
||||
final double preferredDefaultSpeed,
|
||||
final List<double> speedOptions,
|
||||
final double speedIncrement,
|
||||
final double minSpeed,
|
||||
final double maxSpeed,
|
||||
final SleepTimerSettings sleepTimerSettings,
|
||||
final Duration minimumPositionForReporting,
|
||||
final Duration playbackReportInterval,
|
||||
|
|
@ -906,6 +969,12 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
@override
|
||||
List<double> get speedOptions;
|
||||
@override
|
||||
double get speedIncrement;
|
||||
@override
|
||||
double get minSpeed;
|
||||
@override
|
||||
double get maxSpeed;
|
||||
@override
|
||||
SleepTimerSettings get sleepTimerSettings;
|
||||
@override
|
||||
Duration get minimumPositionForReporting;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map<String, dynamic> json) =>
|
|||
?.map((e) => (e as num).toDouble())
|
||||
.toList() ??
|
||||
const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
speedIncrement: (json['speedIncrement'] as num?)?.toDouble() ?? 0.05,
|
||||
minSpeed: (json['minSpeed'] as num?)?.toDouble() ?? 0.1,
|
||||
maxSpeed: (json['maxSpeed'] as num?)?.toDouble() ?? 4,
|
||||
sleepTimerSettings: json['sleepTimerSettings'] == null
|
||||
? const SleepTimerSettings()
|
||||
: SleepTimerSettings.fromJson(
|
||||
|
|
@ -98,6 +101,9 @@ Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
|||
'preferredDefaultVolume': instance.preferredDefaultVolume,
|
||||
'preferredDefaultSpeed': instance.preferredDefaultSpeed,
|
||||
'speedOptions': instance.speedOptions,
|
||||
'speedIncrement': instance.speedIncrement,
|
||||
'minSpeed': instance.minSpeed,
|
||||
'maxSpeed': instance.maxSpeed,
|
||||
'sleepTimerSettings': instance.sleepTimerSettings,
|
||||
'minimumPositionForReporting':
|
||||
instance.minimumPositionForReporting.inMicroseconds,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,16 @@ class AppSettingsPage extends HookConsumerWidget {
|
|||
context.pushNamed(Routes.notificationSettings.name);
|
||||
},
|
||||
),
|
||||
SettingsTile(
|
||||
title: const Text('Player Settings'),
|
||||
leading: const Icon(Icons.play_arrow),
|
||||
description: const Text(
|
||||
'Customize the player settings',
|
||||
),
|
||||
onPressed: (context) {
|
||||
context.pushNamed(Routes.playerSettings.name);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
// Appearance section
|
||||
|
|
|
|||
34
lib/settings/view/buttons.dart
Normal file
34
lib/settings/view/buttons.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class OkButton<T> extends StatelessWidget {
|
||||
const OkButton({
|
||||
super.key,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
final void Function()? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
child: const Text('OK'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CancelButton extends StatelessWidget {
|
||||
const CancelButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:vaani/settings/app_settings_provider.dart';
|
||||
import 'package:vaani/settings/models/app_settings.dart';
|
||||
import 'package:vaani/settings/view/buttons.dart';
|
||||
import 'package:vaani/settings/view/simple_settings_page.dart';
|
||||
|
||||
class NotificationSettingsPage extends HookConsumerWidget {
|
||||
|
|
@ -220,17 +221,11 @@ class MediaControlsPicker extends HookConsumerWidget {
|
|||
return AlertDialog(
|
||||
title: const Text('Media Controls'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
const CancelButton(),
|
||||
OkButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(selectedMediaControls.value);
|
||||
},
|
||||
child: const Text('OK'),
|
||||
}
|
||||
),
|
||||
],
|
||||
// a list of chips to easily select the media controls to display
|
||||
|
|
@ -333,17 +328,11 @@ class NotificationTitlePicker extends HookConsumerWidget {
|
|||
return AlertDialog(
|
||||
title: Text(title),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
const CancelButton(),
|
||||
OkButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(selectedTitle.value);
|
||||
},
|
||||
child: const Text('OK'),
|
||||
}
|
||||
),
|
||||
],
|
||||
// a list of chips to easily insert available fields into the text field
|
||||
|
|
|
|||
410
lib/settings/view/player_settings_page.dart
Normal file
410
lib/settings/view/player_settings_page.dart
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
import 'package:duration_picker/duration_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:vaani/settings/app_settings_provider.dart';
|
||||
import 'package:vaani/settings/view/buttons.dart';
|
||||
import 'package:vaani/settings/view/simple_settings_page.dart';
|
||||
import 'package:vaani/shared/extensions/duration_format.dart';
|
||||
|
||||
class PlayerSettingsPage extends HookConsumerWidget {
|
||||
const PlayerSettingsPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final appSettings = ref.watch(appSettingsProvider);
|
||||
final playerSettings = appSettings.playerSettings;
|
||||
|
||||
return SimpleSettingsPage(
|
||||
title: const Text('Player Settings'),
|
||||
sections: [
|
||||
SettingsSection(
|
||||
margin: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
tiles: [
|
||||
// preferred settings for every book
|
||||
SettingsTile.switchTile(
|
||||
title: const Text('Remember Player Settings for Every Book'),
|
||||
leading: const Icon(Icons.settings_applications),
|
||||
description: const Text(
|
||||
'Settings like speed, loudness, etc. will be remembered for every book',
|
||||
),
|
||||
initialValue: playerSettings.configurePlayerForEveryBook,
|
||||
onToggle: (value) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings(
|
||||
configurePlayerForEveryBook: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// preferred default speed
|
||||
SettingsTile(
|
||||
title: const Text('Default Speed'),
|
||||
description: Text('${playerSettings.preferredDefaultSpeed}x'),
|
||||
leading: const Icon(Icons.speed),
|
||||
onPressed: (context) async {
|
||||
final newSpeed = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => SpeedPicker(
|
||||
initialValue: playerSettings.preferredDefaultSpeed,
|
||||
),
|
||||
);
|
||||
if (newSpeed != null) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings(
|
||||
preferredDefaultSpeed: newSpeed,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
// preferred speed options
|
||||
SettingsTile(
|
||||
title: const Text('Speed Options'),
|
||||
description: Text(
|
||||
playerSettings.speedOptions.map((e) => '${e}x').join(', '),
|
||||
),
|
||||
leading: const Icon(Icons.speed),
|
||||
onPressed: (context) async {
|
||||
final newSpeedOptions = await showDialog<List<double>?>(
|
||||
context: context,
|
||||
builder: (context) => SpeedOptionsPicker(
|
||||
initialValue: playerSettings.speedOptions,
|
||||
),
|
||||
);
|
||||
if (newSpeedOptions != null) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings(
|
||||
speedOptions: newSpeedOptions..sort(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Playback Reporting
|
||||
SettingsSection(
|
||||
title: const Text('Playback Reporting'),
|
||||
tiles: [
|
||||
SettingsTile(
|
||||
title: const Text('Minimum Position to Report'),
|
||||
description: Text.rich(
|
||||
TextSpan(
|
||||
text: 'Do not report playback for the first ',
|
||||
children: [
|
||||
TextSpan(
|
||||
text: playerSettings
|
||||
.minimumPositionForReporting.smartBinaryFormat,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(text: ' of the book'),
|
||||
],
|
||||
),
|
||||
),
|
||||
leading: const Icon(Icons.timer),
|
||||
onPressed: (context) async {
|
||||
final newDuration = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return TimeDurationSelector(
|
||||
title: const Text('Ignore Playback Position Less Than'),
|
||||
baseUnit: BaseUnit.second,
|
||||
initialValue: playerSettings.minimumPositionForReporting,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (newDuration != null) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings(
|
||||
minimumPositionForReporting: newDuration,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
// when to mark complete
|
||||
SettingsTile(
|
||||
title: const Text('Mark Complete When Time Left'),
|
||||
description: Text.rich(
|
||||
TextSpan(
|
||||
text: 'Mark complete when less than ',
|
||||
children: [
|
||||
TextSpan(
|
||||
text: playerSettings
|
||||
.markCompleteWhenTimeLeft.smartBinaryFormat,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(text: ' left in the book'),
|
||||
],
|
||||
),
|
||||
),
|
||||
leading: const Icon(Icons.cloud_done),
|
||||
onPressed: (context) async {
|
||||
final newDuration = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return TimeDurationSelector(
|
||||
title: const Text('Mark Complete When Time Left'),
|
||||
baseUnit: BaseUnit.second,
|
||||
initialValue: playerSettings.markCompleteWhenTimeLeft,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (newDuration != null) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings(
|
||||
markCompleteWhenTimeLeft: newDuration,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
// playback report interval
|
||||
SettingsTile(
|
||||
title: const Text('Playback Report Interval'),
|
||||
description: Text.rich(
|
||||
TextSpan(
|
||||
text: 'Report progress every ',
|
||||
children: [
|
||||
TextSpan(
|
||||
text: playerSettings
|
||||
.playbackReportInterval.smartBinaryFormat,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(text: ' to the server'),
|
||||
],
|
||||
),
|
||||
),
|
||||
leading: const Icon(Icons.change_circle_outlined),
|
||||
onPressed: (context) async {
|
||||
final newDuration = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return TimeDurationSelector(
|
||||
title: const Text('Playback Report Interval'),
|
||||
baseUnit: BaseUnit.second,
|
||||
initialValue: playerSettings.playbackReportInterval,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (newDuration != null) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings(
|
||||
playbackReportInterval: newDuration,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
// Display Settings
|
||||
SettingsSection(
|
||||
title: const Text('Display Settings'),
|
||||
tiles: [
|
||||
// show total progress
|
||||
SettingsTile.switchTile(
|
||||
title: const Text('Show Total Progress'),
|
||||
leading: const Icon(Icons.show_chart),
|
||||
description: const Text(
|
||||
'Show the total progress of the book in the player',
|
||||
),
|
||||
initialValue:
|
||||
playerSettings.expandedPlayerSettings.showTotalProgress,
|
||||
onToggle: (value) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings
|
||||
.expandedPlayerSettings(showTotalProgress: value),
|
||||
);
|
||||
},
|
||||
),
|
||||
// show chapter progress
|
||||
SettingsTile.switchTile(
|
||||
title: const Text('Show Chapter Progress'),
|
||||
leading: const Icon(Icons.show_chart),
|
||||
description: const Text(
|
||||
'Show the progress of the current chapter in the player',
|
||||
),
|
||||
initialValue:
|
||||
playerSettings.expandedPlayerSettings.showChapterProgress,
|
||||
onToggle: (value) {
|
||||
ref.read(appSettingsProvider.notifier).update(
|
||||
appSettings.copyWith.playerSettings(
|
||||
expandedPlayerSettings: playerSettings
|
||||
.expandedPlayerSettings
|
||||
.copyWith(showChapterProgress: value),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeDurationSelector extends HookConsumerWidget {
|
||||
const TimeDurationSelector({
|
||||
super.key,
|
||||
this.title = const Text('Select Duration'),
|
||||
this.baseUnit = BaseUnit.second,
|
||||
this.initialValue = Duration.zero,
|
||||
});
|
||||
|
||||
final Widget title;
|
||||
final BaseUnit baseUnit;
|
||||
final Duration initialValue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final duration = useState(initialValue);
|
||||
return AlertDialog(
|
||||
title: title,
|
||||
content: DurationPicker(
|
||||
duration: duration.value,
|
||||
baseUnit: baseUnit,
|
||||
onChange: (value) {
|
||||
duration.value = value;
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
OkButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(duration.value);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpeedPicker extends HookConsumerWidget {
|
||||
const SpeedPicker({
|
||||
super.key,
|
||||
this.initialValue = 1,
|
||||
});
|
||||
|
||||
final double initialValue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final speedController =
|
||||
useTextEditingController(text: initialValue.toString());
|
||||
final speed = useState<double?>(initialValue);
|
||||
return AlertDialog(
|
||||
title: const Text('Select Speed'),
|
||||
content: TextField(
|
||||
controller: speedController,
|
||||
onChanged: (value) => speed.value = double.tryParse(value),
|
||||
onSubmitted: (value) {
|
||||
Navigator.of(context).pop(speed.value);
|
||||
},
|
||||
autofocus: true,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Speed',
|
||||
helper: Text(
|
||||
'Enter the speed you want to set when playing for the first time',
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
OkButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(speed.value);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpeedOptionsPicker extends HookConsumerWidget {
|
||||
const SpeedOptionsPicker({
|
||||
super.key,
|
||||
this.initialValue = const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
});
|
||||
|
||||
final List<double> initialValue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final speedOptionAddController = useTextEditingController();
|
||||
final speedOptions = useState<List<double>>(initialValue);
|
||||
final focusNode = useFocusNode();
|
||||
return AlertDialog(
|
||||
title: const Text('Select Speed Options'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: speedOptions.value
|
||||
.map(
|
||||
(speed) => Chip(
|
||||
label: Text('${speed}x'),
|
||||
onDeleted: speed == 1
|
||||
? null
|
||||
: () {
|
||||
speedOptions.value =
|
||||
speedOptions.value.where((element) {
|
||||
// speed option 1 can't be removed
|
||||
return element != speed;
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
..sort((a, b) {
|
||||
// if (a.label == const Text('1x')) {
|
||||
// return -1;
|
||||
// } else if (b.label == const Text('1x')) {
|
||||
// return 1;
|
||||
// }
|
||||
return a.label.toString().compareTo(b.label.toString());
|
||||
}),
|
||||
),
|
||||
TextField(
|
||||
focusNode: focusNode,
|
||||
autofocus: true,
|
||||
controller: speedOptionAddController,
|
||||
onSubmitted: (value) {
|
||||
final newSpeed = double.tryParse(value);
|
||||
if (newSpeed != null && !speedOptions.value.contains(newSpeed)) {
|
||||
speedOptions.value = [...speedOptions.value, newSpeed];
|
||||
}
|
||||
speedOptionAddController.clear();
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Add Speed Option',
|
||||
helper: Text('Enter a new speed option to add'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
OkButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(speedOptions.value);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,8 @@ class SimpleSettingsPage extends HookConsumerWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
// some padding at the bottom
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: 20)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue