mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-02-17 14:59:35 +00:00
增加跳过片头片尾,上一章下一章移动到AudioPlayer对象中
This commit is contained in:
parent
e06c834d0e
commit
620a1eb7a2
29 changed files with 1080 additions and 179 deletions
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:vaani/api/library_item_provider.dart';
|
import 'package:vaani/api/library_item_provider.dart';
|
||||||
import 'package:vaani/features/downloads/providers/download_manager.dart';
|
import 'package:vaani/features/downloads/providers/download_manager.dart';
|
||||||
|
import 'package:vaani/generated/l10n.dart';
|
||||||
|
|
||||||
class DownloadsPage extends HookConsumerWidget {
|
class DownloadsPage extends HookConsumerWidget {
|
||||||
const DownloadsPage({super.key});
|
const DownloadsPage({super.key});
|
||||||
|
|
@ -13,7 +14,7 @@ class DownloadsPage extends HookConsumerWidget {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Downloads'),
|
title: Text(S.of(context).bookDownloads),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
// history of downloads
|
// history of downloads
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import 'package:vaani/api/library_item_provider.dart';
|
||||||
import 'package:vaani/constants/hero_tag_conventions.dart';
|
import 'package:vaani/constants/hero_tag_conventions.dart';
|
||||||
import 'package:vaani/features/explore/providers/search_controller.dart';
|
import 'package:vaani/features/explore/providers/search_controller.dart';
|
||||||
import 'package:vaani/features/explore/view/search_result_page.dart';
|
import 'package:vaani/features/explore/view/search_result_page.dart';
|
||||||
|
import 'package:vaani/generated/l10n.dart';
|
||||||
import 'package:vaani/router/router.dart';
|
import 'package:vaani/router/router.dart';
|
||||||
import 'package:vaani/settings/api_settings_provider.dart';
|
import 'package:vaani/settings/api_settings_provider.dart';
|
||||||
import 'package:vaani/settings/app_settings_provider.dart';
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
|
|
@ -29,7 +30,7 @@ class ExplorePage extends HookConsumerWidget {
|
||||||
final api = ref.watch(authenticatedApiProvider);
|
final api = ref.watch(authenticatedApiProvider);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Explore'),
|
title: Text(S.of(context).explore),
|
||||||
),
|
),
|
||||||
body: const MySearchBar(),
|
body: const MySearchBar(),
|
||||||
);
|
);
|
||||||
|
|
@ -61,8 +62,8 @@ class MySearchBar extends HookConsumerWidget {
|
||||||
currentQuery = query;
|
currentQuery = query;
|
||||||
|
|
||||||
// In a real application, there should be some error handling here.
|
// In a real application, there should be some error handling here.
|
||||||
final options = await api.libraries
|
final options =
|
||||||
.search(libraryId: settings.activeLibraryId!, query: query, limit: 3);
|
await api.libraries.search(libraryId: settings.activeLibraryId!, query: query, limit: 3);
|
||||||
|
|
||||||
// If another search happened after this one, throw away these options.
|
// If another search happened after this one, throw away these options.
|
||||||
if (currentQuery != query) {
|
if (currentQuery != query) {
|
||||||
|
|
@ -93,14 +94,11 @@ class MySearchBar extends HookConsumerWidget {
|
||||||
// "Seek and you shall find... your next book!"
|
// "Seek and you shall find... your next book!"
|
||||||
// "Let's uncover your next favorite book..."
|
// "Let's uncover your next favorite book..."
|
||||||
// "Ready to dive into a new story?"
|
// "Ready to dive into a new story?"
|
||||||
hintText: 'Seek and you shall discover...',
|
hintText: S.of(context).exploreHint,
|
||||||
// opacity: 0.5 for the hint text
|
// opacity: 0.5 for the hint text
|
||||||
hintStyle: WidgetStatePropertyAll(
|
hintStyle: WidgetStatePropertyAll(
|
||||||
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5),
|
||||||
.colorScheme
|
|
||||||
.onSurface
|
|
||||||
.withValues(alpha: 0.5),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.search,
|
textInputAction: TextInputAction.search,
|
||||||
|
|
@ -137,7 +135,7 @@ class MySearchBar extends HookConsumerWidget {
|
||||||
// debugPrint('options: $options');
|
// debugPrint('options: $options');
|
||||||
if (options == null) {
|
if (options == null) {
|
||||||
// TODO: show loading indicator or failure message
|
// TODO: show loading indicator or failure message
|
||||||
return <Widget>[const ListTile(title: Text('Loading...'))];
|
return <Widget>[ListTile(title: Text(S.of(context).loading))];
|
||||||
}
|
}
|
||||||
// see if BookLibrarySearchResponse or PodcastLibrarySearchResponse
|
// see if BookLibrarySearchResponse or PodcastLibrarySearchResponse
|
||||||
if (options is BookLibrarySearchResponse) {
|
if (options is BookLibrarySearchResponse) {
|
||||||
|
|
@ -233,9 +231,8 @@ class BookSearchResultMini extends HookConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final item = ref.watch(libraryItemProvider(book.libraryItemId)).valueOrNull;
|
final item = ref.watch(libraryItemProvider(book.libraryItemId)).valueOrNull;
|
||||||
final image = item == null
|
final image =
|
||||||
? const AsyncValue.loading()
|
item == null ? const AsyncValue.loading() : ref.watch(coverImageProvider(item.id));
|
||||||
: ref.watch(coverImageProvider(item.id));
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: SizedBox(
|
leading: SizedBox(
|
||||||
width: 50,
|
width: 50,
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,7 @@ Future<void> libraryItemPlayButtonOnPressed({
|
||||||
}) async {
|
}) async {
|
||||||
appLogger.info('Pressed play/resume button');
|
appLogger.info('Pressed play/resume button');
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
|
// final bookSettings = ref.watch(bookSettingsProvider(book.libraryItemId));
|
||||||
|
|
||||||
final isCurrentBookSetInPlayer = player.book == book;
|
final isCurrentBookSetInPlayer = player.book == book;
|
||||||
final isPlayingThisBook = player.playing && isCurrentBookSetInPlayer;
|
final isPlayingThisBook = player.playing && isCurrentBookSetInPlayer;
|
||||||
|
|
@ -554,6 +555,7 @@ Future<void> libraryItemPlayButtonOnPressed({
|
||||||
? bookPlayerSettings.preferredDefaultSpeed ?? appPlayerSettings.preferredDefaultSpeed
|
? bookPlayerSettings.preferredDefaultSpeed ?? appPlayerSettings.preferredDefaultSpeed
|
||||||
: appPlayerSettings.preferredDefaultSpeed,
|
: appPlayerSettings.preferredDefaultSpeed,
|
||||||
),
|
),
|
||||||
|
// player.setClip(start: Duration(seconds: 10)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// toggle play/pause
|
// toggle play/pause
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:vaani/api/library_provider.dart' show currentLibraryProvider;
|
import 'package:vaani/api/library_provider.dart' show currentLibraryProvider;
|
||||||
import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'
|
import 'package:vaani/features/you/view/widgets/library_switch_chip.dart' show showLibrarySwitcher;
|
||||||
show showLibrarySwitcher;
|
import 'package:vaani/generated/l10n.dart';
|
||||||
import 'package:vaani/router/router.dart' show Routes;
|
import 'package:vaani/router/router.dart' show Routes;
|
||||||
import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons;
|
import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons;
|
||||||
import 'package:vaani/shared/widgets/not_implemented.dart'
|
import 'package:vaani/shared/widgets/not_implemented.dart' show showNotImplementedToast;
|
||||||
show showNotImplementedToast;
|
|
||||||
|
|
||||||
class LibraryBrowserPage extends HookConsumerWidget {
|
class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
const LibraryBrowserPage({super.key});
|
const LibraryBrowserPage({super.key});
|
||||||
|
|
@ -20,7 +19,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
AbsIcons.getIconByName(currentLibrary?.icon) ?? Icons.library_books;
|
AbsIcons.getIconByName(currentLibrary?.icon) ?? Icons.library_books;
|
||||||
|
|
||||||
// Determine the title text
|
// Determine the title text
|
||||||
final String appBarTitle = '${currentLibrary?.name ?? 'Your'} Library';
|
final String appBarTitle = currentLibrary?.name ?? S.of(context).library;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
// Use CustomScrollView to enable slivers
|
// Use CustomScrollView to enable slivers
|
||||||
|
|
@ -33,7 +32,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
// true, // Optional: uncomment if you want snapping behavior (usually with floating: true)
|
// true, // Optional: uncomment if you want snapping behavior (usually with floating: true)
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(libraryIconData),
|
icon: Icon(libraryIconData),
|
||||||
tooltip: 'Switch Library', // Helpful tooltip for users
|
tooltip: S.of(context).librarySwitchTooltip, // Helpful tooltip for users
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showLibrarySwitcher(context, ref);
|
showLibrarySwitcher(context, ref);
|
||||||
},
|
},
|
||||||
|
|
@ -44,7 +43,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
delegate: SliverChildListDelegate(
|
delegate: SliverChildListDelegate(
|
||||||
[
|
[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Authors'),
|
title: Text(S.of(context).bookAuthors),
|
||||||
leading: const Icon(Icons.person),
|
leading: const Icon(Icons.person),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
@ -52,7 +51,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Genres'),
|
title: Text(S.of(context).bookGenres),
|
||||||
leading: const Icon(Icons.category),
|
leading: const Icon(Icons.category),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
@ -60,7 +59,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Series'),
|
title: Text(S.of(context).bookSeries),
|
||||||
leading: const Icon(Icons.list),
|
leading: const Icon(Icons.list),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
@ -69,7 +68,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
// Downloads
|
// Downloads
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Downloads'),
|
title: Text(S.of(context).bookDownloads),
|
||||||
leading: const Icon(Icons.download),
|
leading: const Icon(Icons.download),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ class NullablePlayerSettings with _$NullablePlayerSettings {
|
||||||
List<double>? speedOptions,
|
List<double>? speedOptions,
|
||||||
SleepTimerSettings? sleepTimerSettings,
|
SleepTimerSettings? sleepTimerSettings,
|
||||||
Duration? playbackReportInterval,
|
Duration? playbackReportInterval,
|
||||||
|
@Default(Duration()) Duration skipChapterStart,
|
||||||
|
@Default(Duration()) Duration skipChapterEnd,
|
||||||
}) = _NullablePlayerSettings;
|
}) = _NullablePlayerSettings;
|
||||||
|
|
||||||
factory NullablePlayerSettings.fromJson(Map<String, dynamic> json) =>
|
factory NullablePlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ mixin _$NullablePlayerSettings {
|
||||||
SleepTimerSettings? get sleepTimerSettings =>
|
SleepTimerSettings? get sleepTimerSettings =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
Duration? get playbackReportInterval => throw _privateConstructorUsedError;
|
Duration? get playbackReportInterval => throw _privateConstructorUsedError;
|
||||||
|
Duration get skipChapterStart => throw _privateConstructorUsedError;
|
||||||
|
Duration get skipChapterEnd => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this NullablePlayerSettings to a JSON map.
|
/// Serializes this NullablePlayerSettings to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
@ -55,7 +57,9 @@ abstract class $NullablePlayerSettingsCopyWith<$Res> {
|
||||||
double? preferredDefaultSpeed,
|
double? preferredDefaultSpeed,
|
||||||
List<double>? speedOptions,
|
List<double>? speedOptions,
|
||||||
SleepTimerSettings? sleepTimerSettings,
|
SleepTimerSettings? sleepTimerSettings,
|
||||||
Duration? playbackReportInterval});
|
Duration? playbackReportInterval,
|
||||||
|
Duration skipChapterStart,
|
||||||
|
Duration skipChapterEnd});
|
||||||
|
|
||||||
$MinimizedPlayerSettingsCopyWith<$Res>? get miniPlayerSettings;
|
$MinimizedPlayerSettingsCopyWith<$Res>? get miniPlayerSettings;
|
||||||
$ExpandedPlayerSettingsCopyWith<$Res>? get expandedPlayerSettings;
|
$ExpandedPlayerSettingsCopyWith<$Res>? get expandedPlayerSettings;
|
||||||
|
|
@ -85,6 +89,8 @@ class _$NullablePlayerSettingsCopyWithImpl<$Res,
|
||||||
Object? speedOptions = freezed,
|
Object? speedOptions = freezed,
|
||||||
Object? sleepTimerSettings = freezed,
|
Object? sleepTimerSettings = freezed,
|
||||||
Object? playbackReportInterval = freezed,
|
Object? playbackReportInterval = freezed,
|
||||||
|
Object? skipChapterStart = null,
|
||||||
|
Object? skipChapterEnd = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
miniPlayerSettings: freezed == miniPlayerSettings
|
miniPlayerSettings: freezed == miniPlayerSettings
|
||||||
|
|
@ -115,6 +121,14 @@ class _$NullablePlayerSettingsCopyWithImpl<$Res,
|
||||||
? _value.playbackReportInterval
|
? _value.playbackReportInterval
|
||||||
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
|
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration?,
|
as Duration?,
|
||||||
|
skipChapterStart: null == skipChapterStart
|
||||||
|
? _value.skipChapterStart
|
||||||
|
: skipChapterStart // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
|
skipChapterEnd: null == skipChapterEnd
|
||||||
|
? _value.skipChapterEnd
|
||||||
|
: skipChapterEnd // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +194,9 @@ abstract class _$$NullablePlayerSettingsImplCopyWith<$Res>
|
||||||
double? preferredDefaultSpeed,
|
double? preferredDefaultSpeed,
|
||||||
List<double>? speedOptions,
|
List<double>? speedOptions,
|
||||||
SleepTimerSettings? sleepTimerSettings,
|
SleepTimerSettings? sleepTimerSettings,
|
||||||
Duration? playbackReportInterval});
|
Duration? playbackReportInterval,
|
||||||
|
Duration skipChapterStart,
|
||||||
|
Duration skipChapterEnd});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$MinimizedPlayerSettingsCopyWith<$Res>? get miniPlayerSettings;
|
$MinimizedPlayerSettingsCopyWith<$Res>? get miniPlayerSettings;
|
||||||
|
|
@ -212,6 +228,8 @@ class __$$NullablePlayerSettingsImplCopyWithImpl<$Res>
|
||||||
Object? speedOptions = freezed,
|
Object? speedOptions = freezed,
|
||||||
Object? sleepTimerSettings = freezed,
|
Object? sleepTimerSettings = freezed,
|
||||||
Object? playbackReportInterval = freezed,
|
Object? playbackReportInterval = freezed,
|
||||||
|
Object? skipChapterStart = null,
|
||||||
|
Object? skipChapterEnd = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$NullablePlayerSettingsImpl(
|
return _then(_$NullablePlayerSettingsImpl(
|
||||||
miniPlayerSettings: freezed == miniPlayerSettings
|
miniPlayerSettings: freezed == miniPlayerSettings
|
||||||
|
|
@ -242,6 +260,14 @@ class __$$NullablePlayerSettingsImplCopyWithImpl<$Res>
|
||||||
? _value.playbackReportInterval
|
? _value.playbackReportInterval
|
||||||
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
|
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
|
||||||
as Duration?,
|
as Duration?,
|
||||||
|
skipChapterStart: null == skipChapterStart
|
||||||
|
? _value.skipChapterStart
|
||||||
|
: skipChapterStart // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
|
skipChapterEnd: null == skipChapterEnd
|
||||||
|
? _value.skipChapterEnd
|
||||||
|
: skipChapterEnd // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +282,9 @@ class _$NullablePlayerSettingsImpl implements _NullablePlayerSettings {
|
||||||
this.preferredDefaultSpeed,
|
this.preferredDefaultSpeed,
|
||||||
final List<double>? speedOptions,
|
final List<double>? speedOptions,
|
||||||
this.sleepTimerSettings,
|
this.sleepTimerSettings,
|
||||||
this.playbackReportInterval})
|
this.playbackReportInterval,
|
||||||
|
this.skipChapterStart = const Duration(),
|
||||||
|
this.skipChapterEnd = const Duration()})
|
||||||
: _speedOptions = speedOptions;
|
: _speedOptions = speedOptions;
|
||||||
|
|
||||||
factory _$NullablePlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$NullablePlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
@ -284,10 +312,16 @@ class _$NullablePlayerSettingsImpl implements _NullablePlayerSettings {
|
||||||
final SleepTimerSettings? sleepTimerSettings;
|
final SleepTimerSettings? sleepTimerSettings;
|
||||||
@override
|
@override
|
||||||
final Duration? playbackReportInterval;
|
final Duration? playbackReportInterval;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final Duration skipChapterStart;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final Duration skipChapterEnd;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'NullablePlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings, playbackReportInterval: $playbackReportInterval)';
|
return 'NullablePlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings, playbackReportInterval: $playbackReportInterval, skipChapterStart: $skipChapterStart, skipChapterEnd: $skipChapterEnd)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -308,7 +342,11 @@ class _$NullablePlayerSettingsImpl implements _NullablePlayerSettings {
|
||||||
(identical(other.sleepTimerSettings, sleepTimerSettings) ||
|
(identical(other.sleepTimerSettings, sleepTimerSettings) ||
|
||||||
other.sleepTimerSettings == sleepTimerSettings) &&
|
other.sleepTimerSettings == sleepTimerSettings) &&
|
||||||
(identical(other.playbackReportInterval, playbackReportInterval) ||
|
(identical(other.playbackReportInterval, playbackReportInterval) ||
|
||||||
other.playbackReportInterval == playbackReportInterval));
|
other.playbackReportInterval == playbackReportInterval) &&
|
||||||
|
(identical(other.skipChapterStart, skipChapterStart) ||
|
||||||
|
other.skipChapterStart == skipChapterStart) &&
|
||||||
|
(identical(other.skipChapterEnd, skipChapterEnd) ||
|
||||||
|
other.skipChapterEnd == skipChapterEnd));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
|
@ -321,7 +359,9 @@ class _$NullablePlayerSettingsImpl implements _NullablePlayerSettings {
|
||||||
preferredDefaultSpeed,
|
preferredDefaultSpeed,
|
||||||
const DeepCollectionEquality().hash(_speedOptions),
|
const DeepCollectionEquality().hash(_speedOptions),
|
||||||
sleepTimerSettings,
|
sleepTimerSettings,
|
||||||
playbackReportInterval);
|
playbackReportInterval,
|
||||||
|
skipChapterStart,
|
||||||
|
skipChapterEnd);
|
||||||
|
|
||||||
/// Create a copy of NullablePlayerSettings
|
/// Create a copy of NullablePlayerSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
@ -348,7 +388,9 @@ abstract class _NullablePlayerSettings implements NullablePlayerSettings {
|
||||||
final double? preferredDefaultSpeed,
|
final double? preferredDefaultSpeed,
|
||||||
final List<double>? speedOptions,
|
final List<double>? speedOptions,
|
||||||
final SleepTimerSettings? sleepTimerSettings,
|
final SleepTimerSettings? sleepTimerSettings,
|
||||||
final Duration? playbackReportInterval}) = _$NullablePlayerSettingsImpl;
|
final Duration? playbackReportInterval,
|
||||||
|
final Duration skipChapterStart,
|
||||||
|
final Duration skipChapterEnd}) = _$NullablePlayerSettingsImpl;
|
||||||
|
|
||||||
factory _NullablePlayerSettings.fromJson(Map<String, dynamic> json) =
|
factory _NullablePlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||||
_$NullablePlayerSettingsImpl.fromJson;
|
_$NullablePlayerSettingsImpl.fromJson;
|
||||||
|
|
@ -367,6 +409,10 @@ abstract class _NullablePlayerSettings implements NullablePlayerSettings {
|
||||||
SleepTimerSettings? get sleepTimerSettings;
|
SleepTimerSettings? get sleepTimerSettings;
|
||||||
@override
|
@override
|
||||||
Duration? get playbackReportInterval;
|
Duration? get playbackReportInterval;
|
||||||
|
@override
|
||||||
|
Duration get skipChapterStart;
|
||||||
|
@override
|
||||||
|
Duration get skipChapterEnd;
|
||||||
|
|
||||||
/// Create a copy of NullablePlayerSettings
|
/// Create a copy of NullablePlayerSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,12 @@ _$NullablePlayerSettingsImpl _$$NullablePlayerSettingsImplFromJson(
|
||||||
? null
|
? null
|
||||||
: Duration(
|
: Duration(
|
||||||
microseconds: (json['playbackReportInterval'] as num).toInt()),
|
microseconds: (json['playbackReportInterval'] as num).toInt()),
|
||||||
|
skipChapterStart: json['skipChapterStart'] == null
|
||||||
|
? const Duration()
|
||||||
|
: Duration(microseconds: (json['skipChapterStart'] as num).toInt()),
|
||||||
|
skipChapterEnd: json['skipChapterEnd'] == null
|
||||||
|
? const Duration()
|
||||||
|
: Duration(microseconds: (json['skipChapterEnd'] as num).toInt()),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$NullablePlayerSettingsImplToJson(
|
Map<String, dynamic> _$$NullablePlayerSettingsImplToJson(
|
||||||
|
|
@ -44,4 +50,6 @@ Map<String, dynamic> _$$NullablePlayerSettingsImplToJson(
|
||||||
'speedOptions': instance.speedOptions,
|
'speedOptions': instance.speedOptions,
|
||||||
'sleepTimerSettings': instance.sleepTimerSettings,
|
'sleepTimerSettings': instance.sleepTimerSettings,
|
||||||
'playbackReportInterval': instance.playbackReportInterval?.inMicroseconds,
|
'playbackReportInterval': instance.playbackReportInterval?.inMicroseconds,
|
||||||
|
'skipChapterStart': instance.skipChapterStart.inMicroseconds,
|
||||||
|
'skipChapterEnd': instance.skipChapterEnd.inMicroseconds,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:vaani/db/available_boxes.dart';
|
import 'package:vaani/db/available_boxes.dart';
|
||||||
import 'package:vaani/features/per_book_settings/models/book_settings.dart'
|
import 'package:vaani/features/per_book_settings/models/book_settings.dart' as model;
|
||||||
as model;
|
|
||||||
import 'package:vaani/features/per_book_settings/models/nullable_player_settings.dart';
|
import 'package:vaani/features/per_book_settings/models/nullable_player_settings.dart';
|
||||||
|
|
||||||
part 'book_settings_provider.g.dart';
|
part 'book_settings_provider.g.dart';
|
||||||
|
|
@ -52,6 +51,7 @@ class BookSettings extends _$BookSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(model.BookSettings newSettings, {bool force = false}) {
|
void update(model.BookSettings newSettings, {bool force = false}) {
|
||||||
|
state = newSettings;
|
||||||
updateState(newSettings, force: force);
|
updateState(newSettings, force: force);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'book_settings_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$bookSettingsHash() => r'b976df954edf98ec6ccb3eb41e9d07dd4a9193eb';
|
String _$bookSettingsHash() => r'ef4316367513b1b2b3971e53609e8f0f29de8667';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,18 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
AudiobookPlayer(this.token, this.baseUrl) : super() {
|
AudiobookPlayer(this.token, this.baseUrl) : super() {
|
||||||
// set the source of the player to the first track in the book
|
// set the source of the player to the first track in the book
|
||||||
_logger.config('Setting up audiobook player');
|
_logger.config('Setting up audiobook player');
|
||||||
|
// currentIndexStream.listen((index) {
|
||||||
|
// print('播放器已切换到第 $index 首曲目');
|
||||||
|
// skip = true;
|
||||||
|
// });
|
||||||
|
// positionStream.listen((position) {
|
||||||
|
// if (skip != null && skip! && position.inSeconds < 20) {
|
||||||
|
// super.seek(Duration(seconds: 20));
|
||||||
|
// print('播放 $position');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
bool? skip;
|
||||||
|
|
||||||
/// the [BookExpanded] being played
|
/// the [BookExpanded] being played
|
||||||
BookExpanded? _book;
|
BookExpanded? _book;
|
||||||
|
|
@ -114,9 +125,8 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
// initialPosition ;
|
// initialPosition ;
|
||||||
final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero);
|
final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero);
|
||||||
final initialIndex = book.tracks.indexOf(trackToPlay);
|
final initialIndex = book.tracks.indexOf(trackToPlay);
|
||||||
final initialPositionInTrack = initialPosition != null
|
final initialPositionInTrack =
|
||||||
? initialPosition - trackToPlay.startOffset
|
initialPosition != null ? initialPosition - trackToPlay.startOffset : null;
|
||||||
: null;
|
|
||||||
|
|
||||||
_logger.finer('Setting audioSource');
|
_logger.finer('Setting audioSource');
|
||||||
await setAudioSource(
|
await setAudioSource(
|
||||||
|
|
@ -126,8 +136,7 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
ConcatenatingAudioSource(
|
ConcatenatingAudioSource(
|
||||||
useLazyPreparation: true,
|
useLazyPreparation: true,
|
||||||
children: book.tracks.map((track) {
|
children: book.tracks.map((track) {
|
||||||
final retrievedUri =
|
final retrievedUri = _getUri(track, downloadedUris, baseUrl: baseUrl, token: token);
|
||||||
_getUri(track, downloadedUris, baseUrl: baseUrl, token: token);
|
|
||||||
_logger.fine(
|
_logger.fine(
|
||||||
'Setting source for track: ${track.title}, URI: ${retrievedUri.obfuscate()}',
|
'Setting source for track: ${track.title}, URI: ${retrievedUri.obfuscate()}',
|
||||||
);
|
);
|
||||||
|
|
@ -137,10 +146,8 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
// Specify a unique ID for each media item:
|
// Specify a unique ID for each media item:
|
||||||
id: book.libraryItemId + track.index.toString(),
|
id: book.libraryItemId + track.index.toString(),
|
||||||
// Metadata to display in the notification:
|
// Metadata to display in the notification:
|
||||||
title: appSettings.notificationSettings.primaryTitle
|
title: appSettings.notificationSettings.primaryTitle.formatNotificationTitle(book),
|
||||||
.formatNotificationTitle(book),
|
album: appSettings.notificationSettings.secondaryTitle.formatNotificationTitle(book),
|
||||||
album: appSettings.notificationSettings.secondaryTitle
|
|
||||||
.formatNotificationTitle(book),
|
|
||||||
artUri: artworkUri ??
|
artUri: artworkUri ??
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
'$baseUrl/api/items/${book.libraryItemId}/cover?token=$token&width=800',
|
'$baseUrl/api/items/${book.libraryItemId}/cover?token=$token&width=800',
|
||||||
|
|
@ -172,7 +179,10 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
/// 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
|
@override
|
||||||
Future<void> seek(Duration? positionInBook, {int? index}) async {
|
Future<void> seek(Duration? positionInBook, {int? index, bool b = true}) async {
|
||||||
|
if (!b) {
|
||||||
|
return super.seek(positionInBook, index: index);
|
||||||
|
}
|
||||||
if (_book == null) {
|
if (_book == null) {
|
||||||
_logger.warning('No book is set, not seeking');
|
_logger.warning('No book is set, not seeking');
|
||||||
return;
|
return;
|
||||||
|
|
@ -183,9 +193,43 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
}
|
}
|
||||||
final tracks = _book!.tracks;
|
final tracks = _book!.tracks;
|
||||||
final trackToPlay = getTrackToPlay(_book!, positionInBook);
|
final trackToPlay = getTrackToPlay(_book!, positionInBook);
|
||||||
final index = tracks.indexOf(trackToPlay);
|
final i = tracks.indexOf(trackToPlay);
|
||||||
final positionInTrack = positionInBook - trackToPlay.startOffset;
|
final positionInTrack = positionInBook - trackToPlay.startOffset;
|
||||||
return super.seek(positionInTrack, index: index);
|
return super.seek(positionInTrack, index: i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a small offset so the display does not show the previous chapter for a split second
|
||||||
|
final offset = Duration(milliseconds: 10);
|
||||||
|
|
||||||
|
/// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
||||||
|
final doNotSeekBackIfLessThan = Duration(seconds: 5);
|
||||||
|
|
||||||
|
/// seek forward to the next chapter
|
||||||
|
void seekForward() {
|
||||||
|
final index = _book!.chapters.indexOf(currentChapter!);
|
||||||
|
if (index < _book!.chapters.length - 1) {
|
||||||
|
seek(
|
||||||
|
_book!.chapters[index + 1].start + offset,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
seek(currentChapter!.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// seek backward to the previous chapter or the start of the current chapter
|
||||||
|
void seekBackward() {
|
||||||
|
final currentPlayingChapterIndex = _book!.chapters.indexOf(currentChapter!);
|
||||||
|
final chapterPosition = positionInBook - currentChapter!.start;
|
||||||
|
BookChapter chapterToSeekTo;
|
||||||
|
// if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||||
|
if (chapterPosition < doNotSeekBackIfLessThan && currentPlayingChapterIndex > 0) {
|
||||||
|
chapterToSeekTo = _book!.chapters[currentPlayingChapterIndex - 1];
|
||||||
|
} else {
|
||||||
|
chapterToSeekTo = currentChapter!;
|
||||||
|
}
|
||||||
|
seek(
|
||||||
|
chapterToSeekTo.start + offset,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a convenience method to get position in the book instead of the current track position
|
/// a convenience method to get position in the book instead of the current track position
|
||||||
|
|
@ -201,8 +245,7 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
if (_book == null) {
|
if (_book == null) {
|
||||||
return Duration.zero;
|
return Duration.zero;
|
||||||
}
|
}
|
||||||
return bufferedPosition +
|
return bufferedPosition + _book!.tracks[sequenceState!.currentIndex].startOffset;
|
||||||
_book!.tracks[sequenceState!.currentIndex].startOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// streams to override to suit the book instead of the current track
|
/// streams to override to suit the book instead of the current track
|
||||||
|
|
@ -277,8 +320,7 @@ Uri _getUri(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return uri ??
|
return uri ?? Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token');
|
||||||
Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FormatNotificationTitle on String {
|
extension FormatNotificationTitle on String {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:vaani/constants/sizes.dart';
|
||||||
import 'package:vaani/features/player/providers/currently_playing_provider.dart';
|
import 'package:vaani/features/player/providers/currently_playing_provider.dart';
|
||||||
import 'package:vaani/features/player/providers/player_form.dart';
|
import 'package:vaani/features/player/providers/player_form.dart';
|
||||||
import 'package:vaani/features/player/view/audiobook_player.dart';
|
import 'package:vaani/features/player/view/audiobook_player.dart';
|
||||||
|
import 'package:vaani/features/skip_start_end/player_skip_chapter_start_end.dart';
|
||||||
import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart';
|
import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart';
|
||||||
import 'package:vaani/shared/extensions/inverse_lerp.dart';
|
import 'package:vaani/shared/extensions/inverse_lerp.dart';
|
||||||
import 'package:vaani/shared/widgets/not_implemented.dart';
|
import 'package:vaani/shared/widgets/not_implemented.dart';
|
||||||
|
|
@ -245,6 +246,9 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
// chapter list
|
// chapter list
|
||||||
const ChapterSelectionButton(),
|
const ChapterSelectionButton(),
|
||||||
|
const Spacer(),
|
||||||
|
// 跳过片头片尾
|
||||||
|
SkipChapterStartEndButton(),
|
||||||
// settings
|
// settings
|
||||||
// IconButton(
|
// IconButton(
|
||||||
// icon: const Icon(Icons.more_horiz),
|
// icon: const Icon(Icons.more_horiz),
|
||||||
|
|
|
||||||
|
|
@ -17,42 +17,42 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
|
|
||||||
// add a small offset so the display does not show the previous chapter for a split second
|
// // add a small offset so the display does not show the previous chapter for a split second
|
||||||
const offset = Duration(milliseconds: 10);
|
// const offset = Duration(milliseconds: 10);
|
||||||
|
|
||||||
/// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
// /// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
||||||
const doNotSeekBackIfLessThan = Duration(seconds: 5);
|
// const doNotSeekBackIfLessThan = Duration(seconds: 5);
|
||||||
|
|
||||||
/// seek forward to the next chapter
|
// /// seek forward to the next chapter
|
||||||
void seekForward() {
|
// void seekForward() {
|
||||||
final index = player.book!.chapters.indexOf(player.currentChapter!);
|
// final index = player.book!.chapters.indexOf(player.currentChapter!);
|
||||||
if (index < player.book!.chapters.length - 1) {
|
// if (index < player.book!.chapters.length - 1) {
|
||||||
player.seek(
|
// player.seek(
|
||||||
player.book!.chapters[index + 1].start + offset,
|
// player.book!.chapters[index + 1].start + offset,
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
player.seek(player.currentChapter!.end);
|
// player.seek(player.currentChapter!.end);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// seek backward to the previous chapter or the start of the current chapter
|
// /// seek backward to the previous chapter or the start of the current chapter
|
||||||
void seekBackward() {
|
// void seekBackward() {
|
||||||
final currentPlayingChapterIndex =
|
// final currentPlayingChapterIndex =
|
||||||
player.book!.chapters.indexOf(player.currentChapter!);
|
// player.book!.chapters.indexOf(player.currentChapter!);
|
||||||
final chapterPosition =
|
// final chapterPosition =
|
||||||
player.positionInBook - player.currentChapter!.start;
|
// player.positionInBook - player.currentChapter!.start;
|
||||||
BookChapter chapterToSeekTo;
|
// BookChapter chapterToSeekTo;
|
||||||
// if player position is less than 5 seconds into the chapter, go to the previous chapter
|
// // if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||||
if (chapterPosition < doNotSeekBackIfLessThan &&
|
// if (chapterPosition < doNotSeekBackIfLessThan &&
|
||||||
currentPlayingChapterIndex > 0) {
|
// currentPlayingChapterIndex > 0) {
|
||||||
chapterToSeekTo = player.book!.chapters[currentPlayingChapterIndex - 1];
|
// chapterToSeekTo = player.book!.chapters[currentPlayingChapterIndex - 1];
|
||||||
} else {
|
// } else {
|
||||||
chapterToSeekTo = player.currentChapter!;
|
// chapterToSeekTo = player.currentChapter!;
|
||||||
}
|
// }
|
||||||
player.seek(
|
// player.seek(
|
||||||
chapterToSeekTo.start + offset,
|
// chapterToSeekTo.start + offset,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
|
|
@ -69,9 +69,9 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isForward) {
|
if (isForward) {
|
||||||
seekForward();
|
player.seekForward();
|
||||||
} else {
|
} else {
|
||||||
seekBackward();
|
player.seekBackward();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:vaani/features/player/providers/audiobook_player.dart'
|
import 'package:vaani/features/player/providers/audiobook_player.dart' show audiobookPlayerProvider;
|
||||||
show audiobookPlayerProvider;
|
|
||||||
import 'package:vaani/features/player/providers/currently_playing_provider.dart'
|
import 'package:vaani/features/player/providers/currently_playing_provider.dart'
|
||||||
show currentPlayingChapterProvider, currentlyPlayingBookProvider;
|
show currentPlayingChapterProvider, currentlyPlayingBookProvider;
|
||||||
import 'package:vaani/features/player/view/player_when_expanded.dart'
|
import 'package:vaani/features/player/view/player_when_expanded.dart' show pendingPlayerModals;
|
||||||
show pendingPlayerModals;
|
|
||||||
import 'package:vaani/features/player/view/widgets/playing_indicator_icon.dart';
|
import 'package:vaani/features/player/view/widgets/playing_indicator_icon.dart';
|
||||||
import 'package:vaani/main.dart' show appLogger;
|
import 'package:vaani/main.dart' show appLogger;
|
||||||
import 'package:vaani/shared/extensions/chapter.dart' show ChapterDuration;
|
import 'package:vaani/shared/extensions/chapter.dart' show ChapterDuration;
|
||||||
import 'package:vaani/shared/extensions/duration_format.dart'
|
import 'package:vaani/shared/extensions/duration_format.dart' show DurationFormat;
|
||||||
show DurationFormat;
|
|
||||||
import 'package:vaani/shared/hooks.dart' show useTimer;
|
import 'package:vaani/shared/hooks.dart' show useTimer;
|
||||||
|
|
||||||
class ChapterSelectionButton extends HookConsumerWidget {
|
class ChapterSelectionButton extends HookConsumerWidget {
|
||||||
|
|
@ -70,7 +67,7 @@ class ChapterSelectionModal extends HookConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
useTimer(scrollToCurrentChapter, 500.ms);
|
useTimer(scrollToCurrentChapter, 100.ms);
|
||||||
// useInterval(scrollToCurrentChapter, 500.ms);
|
// useInterval(scrollToCurrentChapter, 500.ms);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Column(
|
return Column(
|
||||||
|
|
@ -84,19 +81,18 @@ class ChapterSelectionModal extends HookConsumerWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
primary: true,
|
||||||
child: currentBook?.chapters == null
|
child: currentBook?.chapters == null
|
||||||
? const Text('No chapters found')
|
? const Text('No chapters found')
|
||||||
: Column(
|
: Column(
|
||||||
children: currentBook!.chapters.map(
|
children: currentBook!.chapters.map(
|
||||||
(chapter) {
|
(chapter) {
|
||||||
final isCurrent = currentChapterIndex == chapter.id;
|
final isCurrent = currentChapterIndex == chapter.id;
|
||||||
final isPlayed = currentChapterIndex != null &&
|
final isPlayed =
|
||||||
chapter.id < currentChapterIndex;
|
currentChapterIndex != null && chapter.id < currentChapterIndex;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
autofocus: isCurrent,
|
autofocus: isCurrent,
|
||||||
iconColor: isPlayed && !isCurrent
|
iconColor: isPlayed && !isCurrent ? theme.disabledColor : null,
|
||||||
? theme.disabledColor
|
|
||||||
: null,
|
|
||||||
title: Text(
|
title: Text(
|
||||||
chapter.title,
|
chapter.title,
|
||||||
style: isPlayed && !isCurrent
|
style: isPlayed && !isCurrent
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
|
||||||
|
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
||||||
|
import 'package:vaani/features/player/view/player_when_expanded.dart';
|
||||||
|
import 'package:vaani/settings/view/notification_settings_page.dart';
|
||||||
|
|
||||||
|
class SkipChapterStartEndButton extends HookConsumerWidget {
|
||||||
|
const SkipChapterStartEndButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Tooltip(
|
||||||
|
message: "跳过片头片尾",
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.fast_forward_rounded),
|
||||||
|
onPressed: () async {
|
||||||
|
// show toast
|
||||||
|
pendingPlayerModals++;
|
||||||
|
await showModalBottomSheet<bool>(
|
||||||
|
context: context,
|
||||||
|
barrierLabel: '跳过片头片尾',
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
// 40% of the screen height
|
||||||
|
maxHeight: MediaQuery.of(context).size.height * 0.4,
|
||||||
|
),
|
||||||
|
builder: (context) {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: PlayerSkipChapterStartEnd(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
pendingPlayerModals--;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerSkipChapterStartEnd extends HookConsumerWidget {
|
||||||
|
const PlayerSkipChapterStartEnd({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
|
final bookId = player.book?.libraryItemId ?? '_';
|
||||||
|
final bookSettings = ref.watch(bookSettingsProvider(bookId));
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text('跳过片头 ${bookSettings.playerSettings.skipChapterStart.inSeconds}s'),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TimeIntervalSlider(
|
||||||
|
defaultValue: bookSettings.playerSettings.skipChapterStart,
|
||||||
|
// defaultValue: const Duration(seconds: 0),
|
||||||
|
min: const Duration(seconds: 0),
|
||||||
|
max: const Duration(seconds: 60),
|
||||||
|
step: const Duration(seconds: 1),
|
||||||
|
onChangedEnd: (interval) {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
bookSettingsProvider(bookId).notifier,
|
||||||
|
)
|
||||||
|
.update(
|
||||||
|
bookSettings.copyWith.playerSettings(skipChapterStart: interval),
|
||||||
|
);
|
||||||
|
ref.read(audiobookPlayerProvider).setClip(start: interval);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('跳过片尾 ${bookSettings.playerSettings.skipChapterEnd.inSeconds}s'),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TimeIntervalSlider(
|
||||||
|
defaultValue: bookSettings.playerSettings.skipChapterEnd,
|
||||||
|
// defaultValue: const Duration(seconds: 0),
|
||||||
|
min: const Duration(seconds: 0),
|
||||||
|
max: const Duration(seconds: 60),
|
||||||
|
step: const Duration(seconds: 1),
|
||||||
|
onChangedEnd: (interval) {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
bookSettingsProvider(bookId).notifier,
|
||||||
|
)
|
||||||
|
.update(
|
||||||
|
bookSettings.copyWith.playerSettings(skipChapterEnd: interval),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
lib/features/skip_start_end/skip_start_end.dart
Normal file
86
lib/features/skip_start_end/skip_start_end.dart
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:vaani/features/player/core/audiobook_player.dart';
|
||||||
|
|
||||||
|
class SkipStartEnd {
|
||||||
|
final Duration start;
|
||||||
|
final Duration end;
|
||||||
|
final AudiobookPlayer player;
|
||||||
|
int _index;
|
||||||
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
final throttler = Throttler(delay: Duration(seconds: 3));
|
||||||
|
// final StreamController<PlaybackEvent> _playbackController =
|
||||||
|
// StreamController<PlaybackEvent>.broadcast();
|
||||||
|
|
||||||
|
SkipStartEnd({required this.start, required this.end, required this.player}) : _index = 0 {
|
||||||
|
if (start > Duration()) {
|
||||||
|
_subscriptions.add(
|
||||||
|
player.currentIndexStream.listen((index) {
|
||||||
|
if (_index != index && player.position.inMilliseconds < 500) {
|
||||||
|
_index = index!;
|
||||||
|
Future.microtask(() {
|
||||||
|
player.seek(start, b: false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (end > Duration()) {
|
||||||
|
_subscriptions.add(
|
||||||
|
player.positionStream.distinct().listen((position) {
|
||||||
|
if (player.duration != null &&
|
||||||
|
player.duration!.inMilliseconds - player.position.inMilliseconds <
|
||||||
|
end.inMilliseconds) {
|
||||||
|
Future.microtask(() {
|
||||||
|
throttler.call(player.seekForward);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dispose the timer
|
||||||
|
void dispose() {
|
||||||
|
for (var sub in _subscriptions) {
|
||||||
|
sub.cancel();
|
||||||
|
}
|
||||||
|
throttler.dispose();
|
||||||
|
// _playbackController.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Throttler {
|
||||||
|
final Duration delay;
|
||||||
|
Timer? _timer;
|
||||||
|
DateTime? _lastRun;
|
||||||
|
|
||||||
|
Throttler({required this.delay});
|
||||||
|
|
||||||
|
void call(void Function() callback) {
|
||||||
|
// 如果是第一次调用,立即执行
|
||||||
|
if (_lastRun == null) {
|
||||||
|
callback();
|
||||||
|
_lastRun = DateTime.now();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果距离上次执行已经超过延迟时间,立即执行
|
||||||
|
if (DateTime.now().difference(_lastRun!) > delay) {
|
||||||
|
callback();
|
||||||
|
_lastRun = DateTime.now();
|
||||||
|
}
|
||||||
|
// 否则,安排在下个周期执行
|
||||||
|
else {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = Timer(delay, () {
|
||||||
|
callback();
|
||||||
|
_lastRun = DateTime.now();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
23
lib/features/skip_start_end/skip_start_end_provider.dart
Normal file
23
lib/features/skip_start_end/skip_start_end_provider.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
|
||||||
|
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
||||||
|
import 'package:vaani/features/skip_start_end/skip_start_end.dart' as core;
|
||||||
|
|
||||||
|
part 'skip_start_end_provider.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class SkipStartEnd extends _$SkipStartEnd {
|
||||||
|
@override
|
||||||
|
core.SkipStartEnd? build() {
|
||||||
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
|
final bookId = player.book?.libraryItemId ?? '_';
|
||||||
|
if (bookId == '_') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final bookSettings = ref.watch(bookSettingsProvider(bookId));
|
||||||
|
final start = bookSettings.playerSettings.skipChapterStart;
|
||||||
|
final end = bookSettings.playerSettings.skipChapterEnd;
|
||||||
|
|
||||||
|
return core.SkipStartEnd(start: start, end: end, player: player);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/features/skip_start_end/skip_start_end_provider.g.dart
Normal file
25
lib/features/skip_start_end/skip_start_end_provider.g.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'skip_start_end_provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$skipStartEndHash() => r'202cfb36fdb3d3fa12debfb188f87650473a88a9';
|
||||||
|
|
||||||
|
/// See also [SkipStartEnd].
|
||||||
|
@ProviderFor(SkipStartEnd)
|
||||||
|
final skipStartEndProvider =
|
||||||
|
NotifierProvider<SkipStartEnd, core.SkipStartEnd?>.internal(
|
||||||
|
SkipStartEnd.new,
|
||||||
|
name: r'skipStartEndProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$skipStartEndHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$SkipStartEnd = Notifier<core.SkipStartEnd?>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shelfsdk/audiobookshelf_api.dart' show Library;
|
import 'package:shelfsdk/audiobookshelf_api.dart' show Library;
|
||||||
import 'package:vaani/api/library_provider.dart';
|
import 'package:vaani/api/library_provider.dart';
|
||||||
import 'package:vaani/settings/api_settings_provider.dart'
|
import 'package:vaani/generated/l10n.dart';
|
||||||
show apiSettingsProvider;
|
import 'package:vaani/settings/api_settings_provider.dart' show apiSettingsProvider;
|
||||||
import 'package:vaani/shared/icons/abs_icons.dart';
|
import 'package:vaani/shared/icons/abs_icons.dart';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ class LibrarySwitchChip extends HookConsumerWidget {
|
||||||
: libraries.first.icon,
|
: libraries.first.icon,
|
||||||
),
|
),
|
||||||
), // Replace with your icon
|
), // Replace with your icon
|
||||||
label: const Text('Change Library'),
|
label: Text(S.of(context).libraryChange),
|
||||||
// Enable only if libraries are loaded and not empty
|
// Enable only if libraries are loaded and not empty
|
||||||
onPressed: libraries.isNotEmpty
|
onPressed: libraries.isNotEmpty
|
||||||
? () => showLibrarySwitcher(
|
? () => showLibrarySwitcher(
|
||||||
|
|
@ -69,7 +69,7 @@ void showLibrarySwitcher(
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
title: const Text('Select Library'),
|
title: Text(S.of(context).librarySelect),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
// Constrain size for dialogs
|
// Constrain size for dialogs
|
||||||
width: 300, // Adjust as needed
|
width: 300, // Adjust as needed
|
||||||
|
|
@ -83,11 +83,11 @@ void showLibrarySwitcher(
|
||||||
ref.invalidate(librariesProvider);
|
ref.invalidate(librariesProvider);
|
||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
},
|
},
|
||||||
child: const Text('Refresh'),
|
child: Text(S.of(context).refresh),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(dialogContext),
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
child: const Text('Cancel'),
|
child: Text(S.of(context).cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -99,8 +99,7 @@ void showLibrarySwitcher(
|
||||||
// Make it scrollable and control height
|
// Make it scrollable and control height
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxHeight:
|
maxHeight: MediaQuery.of(context).size.height * 0.6, // Max 60% of screen
|
||||||
MediaQuery.of(context).size.height * 0.6, // Max 60% of screen
|
|
||||||
),
|
),
|
||||||
builder: (sheetContext) => Padding(
|
builder: (sheetContext) => Padding(
|
||||||
// Add padding within the bottom sheet
|
// Add padding within the bottom sheet
|
||||||
|
|
@ -108,8 +107,8 @@ void showLibrarySwitcher(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min, // Take minimum necessary height
|
mainAxisSize: MainAxisSize.min, // Take minimum necessary height
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(
|
||||||
'Select Library',
|
S.of(context).librarySelect,
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
@ -121,7 +120,7 @@ void showLibrarySwitcher(
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
label: const Text('Refresh'),
|
label: Text(S.of(context).refresh),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Invalidate the provider to trigger a refetch
|
// Invalidate the provider to trigger a refetch
|
||||||
ref.invalidate(librariesProvider);
|
ref.invalidate(librariesProvider);
|
||||||
|
|
@ -157,14 +156,14 @@ class _LibrarySelectionContent extends ConsumerWidget {
|
||||||
Icon(Icons.error_outline, color: errorColor),
|
Icon(Icons.error_outline, color: errorColor),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'Error loading libraries: $error',
|
S.of(context).libraryLoadError('$error'),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: errorColor),
|
style: TextStyle(color: errorColor),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
label: const Text('Retry'),
|
label: Text(S.of(context).retry),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Invalidate the provider to trigger a refetch
|
// Invalidate the provider to trigger a refetch
|
||||||
ref.invalidate(librariesProvider);
|
ref.invalidate(librariesProvider);
|
||||||
|
|
@ -179,10 +178,10 @@ class _LibrarySelectionContent extends ConsumerWidget {
|
||||||
data: (libraries) {
|
data: (libraries) {
|
||||||
// Handle case where data loaded successfully but is empty
|
// Handle case where data loaded successfully but is empty
|
||||||
if (libraries.isEmpty) {
|
if (libraries.isEmpty) {
|
||||||
return const Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Text('No libraries available.'),
|
child: Text(S.of(context).libraryEmpty),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:vaani/api/api_provider.dart';
|
||||||
import 'package:vaani/api/library_provider.dart' show librariesProvider;
|
import 'package:vaani/api/library_provider.dart' show librariesProvider;
|
||||||
import 'package:vaani/features/player/view/mini_player_bottom_padding.dart';
|
import 'package:vaani/features/player/view/mini_player_bottom_padding.dart';
|
||||||
import 'package:vaani/features/you/view/widgets/library_switch_chip.dart';
|
import 'package:vaani/features/you/view/widgets/library_switch_chip.dart';
|
||||||
|
import 'package:vaani/generated/l10n.dart';
|
||||||
import 'package:vaani/router/router.dart';
|
import 'package:vaani/router/router.dart';
|
||||||
import 'package:vaani/settings/constants.dart';
|
import 'package:vaani/settings/constants.dart';
|
||||||
import 'package:vaani/shared/utils.dart';
|
import 'package:vaani/shared/utils.dart';
|
||||||
|
|
@ -25,7 +26,7 @@ class YouPage extends HookConsumerWidget {
|
||||||
// title: const Text('You'),
|
// title: const Text('You'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Logs',
|
tooltip: S.of(context).logs,
|
||||||
icon: const Icon(Icons.bug_report),
|
icon: const Icon(Icons.bug_report),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed(Routes.logs.name);
|
context.pushNamed(Routes.logs.name);
|
||||||
|
|
@ -38,7 +39,7 @@ class YouPage extends HookConsumerWidget {
|
||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Settings',
|
tooltip: S.of(context).settings,
|
||||||
icon: const Icon(Icons.settings),
|
icon: const Icon(Icons.settings),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed(Routes.settings.name);
|
context.pushNamed(Routes.settings.name);
|
||||||
|
|
@ -61,14 +62,13 @@ class YouPage extends HookConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
ActionChip(
|
ActionChip(
|
||||||
avatar: const Icon(Icons.switch_account_outlined),
|
avatar: const Icon(Icons.switch_account_outlined),
|
||||||
label: const Text('Switch Account'),
|
label: Text(S.of(context).accountSwitch),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed(Routes.userManagement.name);
|
context.pushNamed(Routes.userManagement.name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
librariesAsyncValue.when(
|
librariesAsyncValue.when(
|
||||||
data: (libraries) =>
|
data: (libraries) => LibrarySwitchChip(libraries: libraries),
|
||||||
LibrarySwitchChip(libraries: libraries),
|
|
||||||
loading: () => const ActionChip(
|
loading: () => const ActionChip(
|
||||||
avatar: SizedBox(
|
avatar: SizedBox(
|
||||||
width: 18,
|
width: 18,
|
||||||
|
|
@ -88,13 +88,13 @@ class YouPage extends HookConsumerWidget {
|
||||||
// Maybe show error details or allow retry
|
// Maybe show error details or allow retry
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content:
|
content: Text('Failed to load libraries: $error'),
|
||||||
Text('Failed to load libraries: $error'),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
), // ActionChip(
|
),
|
||||||
|
// ActionChip(
|
||||||
// avatar: const Icon(Icons.logout),
|
// avatar: const Icon(Icons.logout),
|
||||||
// label: const Text('Logout'),
|
// label: const Text('Logout'),
|
||||||
// onPressed: () {
|
// onPressed: () {
|
||||||
|
|
@ -113,7 +113,7 @@ class YouPage extends HookConsumerWidget {
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.playlist_play),
|
leading: const Icon(Icons.playlist_play),
|
||||||
title: const Text('My Playlists'),
|
title: Text(S.of(context).playlistsMine),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Handle navigation to playlists
|
// Handle navigation to playlists
|
||||||
showNotImplementedToast(context);
|
showNotImplementedToast(context);
|
||||||
|
|
@ -121,7 +121,7 @@ class YouPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.web),
|
leading: const Icon(Icons.web),
|
||||||
title: const Text('Web Version'),
|
title: Text(S.of(context).webVersion),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
handleLaunchUrl(
|
handleLaunchUrl(
|
||||||
// get url from api and launch it
|
// get url from api and launch it
|
||||||
|
|
@ -131,7 +131,7 @@ class YouPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.help),
|
leading: const Icon(Icons.help),
|
||||||
title: const Text('Help'),
|
title: Text(S.of(context).help),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Handle navigation to help website
|
// Handle navigation to help website
|
||||||
showNotImplementedToast(context);
|
showNotImplementedToast(context);
|
||||||
|
|
@ -141,8 +141,7 @@ class YouPage extends HookConsumerWidget {
|
||||||
icon: const Icon(Icons.info),
|
icon: const Icon(Icons.info),
|
||||||
applicationName: AppMetadata.appName,
|
applicationName: AppMetadata.appName,
|
||||||
applicationVersion: AppMetadata.version,
|
applicationVersion: AppMetadata.version,
|
||||||
applicationLegalese:
|
applicationLegalese: 'Made with ❤️ by ${AppMetadata.author}',
|
||||||
'Made with ❤️ by ${AppMetadata.author}',
|
|
||||||
aboutBoxChildren: [
|
aboutBoxChildren: [
|
||||||
// link to github repo
|
// link to github repo
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -217,8 +216,7 @@ class UserBar extends HookConsumerWidget {
|
||||||
Text(
|
Text(
|
||||||
api.baseUrl.toString(),
|
api.baseUrl.toString(),
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
color:
|
color: themeData.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||||
themeData.colorScheme.onSurface.withValues(alpha: 0.6),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
|
|
||||||
static String m1(item) => "Deleted ${item}";
|
static String m1(item) => "Deleted ${item}";
|
||||||
|
|
||||||
|
static String m2(error) => "Error loading libraries: ${error}";
|
||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
|
"account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||||
|
"accountSwitch": MessageLookupByLibrary.simpleMessage("Switch Account"),
|
||||||
"appSettings": MessageLookupByLibrary.simpleMessage("App Settings"),
|
"appSettings": MessageLookupByLibrary.simpleMessage("App Settings"),
|
||||||
"appearance": MessageLookupByLibrary.simpleMessage("Appearance"),
|
"appearance": MessageLookupByLibrary.simpleMessage("Appearance"),
|
||||||
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage(
|
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|
@ -42,12 +46,20 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"bookAboutDefault": MessageLookupByLibrary.simpleMessage(
|
"bookAboutDefault": MessageLookupByLibrary.simpleMessage(
|
||||||
"Sorry, no description found",
|
"Sorry, no description found",
|
||||||
),
|
),
|
||||||
|
"bookAuthors": MessageLookupByLibrary.simpleMessage("Authors"),
|
||||||
|
"bookDownloads": MessageLookupByLibrary.simpleMessage("Downloads"),
|
||||||
|
"bookGenres": MessageLookupByLibrary.simpleMessage("Genres"),
|
||||||
"bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("Abridged"),
|
"bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("Abridged"),
|
||||||
"bookMetadataLength": MessageLookupByLibrary.simpleMessage("Length"),
|
"bookMetadataLength": MessageLookupByLibrary.simpleMessage("Length"),
|
||||||
"bookMetadataPublished": MessageLookupByLibrary.simpleMessage("Published"),
|
"bookMetadataPublished": MessageLookupByLibrary.simpleMessage("Published"),
|
||||||
"bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage(
|
"bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage(
|
||||||
"Unabridged",
|
"Unabridged",
|
||||||
),
|
),
|
||||||
|
"bookSeries": MessageLookupByLibrary.simpleMessage("Series"),
|
||||||
|
"bookShelveEmpty": MessageLookupByLibrary.simpleMessage("Try again"),
|
||||||
|
"bookShelveEmptyText": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"No shelves to display",
|
||||||
|
),
|
||||||
"cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
|
"cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
|
||||||
"copyToClipboard": MessageLookupByLibrary.simpleMessage(
|
"copyToClipboard": MessageLookupByLibrary.simpleMessage(
|
||||||
"Copy to Clipboard",
|
"Copy to Clipboard",
|
||||||
|
|
@ -62,11 +74,30 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"deleteDialog": m0,
|
"deleteDialog": m0,
|
||||||
"deleted": m1,
|
"deleted": m1,
|
||||||
"explore": MessageLookupByLibrary.simpleMessage("explore"),
|
"explore": MessageLookupByLibrary.simpleMessage("explore"),
|
||||||
|
"exploreHint": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Seek and you shall discover...",
|
||||||
|
),
|
||||||
"exploreTooltip": MessageLookupByLibrary.simpleMessage(
|
"exploreTooltip": MessageLookupByLibrary.simpleMessage(
|
||||||
"Search and Explore",
|
"Search and Explore",
|
||||||
),
|
),
|
||||||
"general": MessageLookupByLibrary.simpleMessage("General"),
|
"general": MessageLookupByLibrary.simpleMessage("General"),
|
||||||
|
"help": MessageLookupByLibrary.simpleMessage("Help"),
|
||||||
"home": MessageLookupByLibrary.simpleMessage("Home"),
|
"home": MessageLookupByLibrary.simpleMessage("Home"),
|
||||||
|
"homeBookContinueListening": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Continue Listening",
|
||||||
|
),
|
||||||
|
"homeBookContinueSeries": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Continue Series",
|
||||||
|
),
|
||||||
|
"homeBookDiscover": MessageLookupByLibrary.simpleMessage("Discover"),
|
||||||
|
"homeBookListenAgain": MessageLookupByLibrary.simpleMessage("Listen Again"),
|
||||||
|
"homeBookNewestAuthors": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Newest Authors",
|
||||||
|
),
|
||||||
|
"homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Recently Added",
|
||||||
|
),
|
||||||
|
"homeBookRecommended": MessageLookupByLibrary.simpleMessage("Recommended"),
|
||||||
"homeContinueListening": MessageLookupByLibrary.simpleMessage(
|
"homeContinueListening": MessageLookupByLibrary.simpleMessage(
|
||||||
"Continue Listening",
|
"Continue Listening",
|
||||||
),
|
),
|
||||||
|
|
@ -85,10 +116,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"Language switch",
|
"Language switch",
|
||||||
),
|
),
|
||||||
"library": MessageLookupByLibrary.simpleMessage("Library"),
|
"library": MessageLookupByLibrary.simpleMessage("Library"),
|
||||||
|
"libraryChange": MessageLookupByLibrary.simpleMessage("Change Library"),
|
||||||
|
"libraryEmpty": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"No libraries available.",
|
||||||
|
),
|
||||||
|
"libraryLoadError": m2,
|
||||||
|
"librarySelect": MessageLookupByLibrary.simpleMessage("Select Library"),
|
||||||
|
"librarySwitchTooltip": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Switch Library",
|
||||||
|
),
|
||||||
"libraryTooltip": MessageLookupByLibrary.simpleMessage(
|
"libraryTooltip": MessageLookupByLibrary.simpleMessage(
|
||||||
"Browse your library",
|
"Browse your library",
|
||||||
),
|
),
|
||||||
|
"loading": MessageLookupByLibrary.simpleMessage("Loading..."),
|
||||||
|
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
|
||||||
"no": MessageLookupByLibrary.simpleMessage("No"),
|
"no": MessageLookupByLibrary.simpleMessage("No"),
|
||||||
|
"notImplemented": MessageLookupByLibrary.simpleMessage("Not implemented"),
|
||||||
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage(
|
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage(
|
||||||
"Notification Media Player",
|
"Notification Media Player",
|
||||||
),
|
),
|
||||||
|
|
@ -102,8 +145,38 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
|
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
|
||||||
"Customize the player settings",
|
"Customize the player settings",
|
||||||
),
|
),
|
||||||
|
"playerSettingsPlaybackReporting": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Playback Reporting",
|
||||||
|
),
|
||||||
|
"playerSettingsPlaybackReportingIgnore":
|
||||||
|
MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Ignore Playback Position Less Than",
|
||||||
|
),
|
||||||
|
"playerSettingsPlaybackReportingMinimum":
|
||||||
|
MessageLookupByLibrary.simpleMessage("Minimum Position to Report"),
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionHead":
|
||||||
|
MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Do not report playback for the first ",
|
||||||
|
),
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionTail":
|
||||||
|
MessageLookupByLibrary.simpleMessage("of the book"),
|
||||||
|
"playerSettingsRememberForEveryBook": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Remember Player Settings for Every Book",
|
||||||
|
),
|
||||||
|
"playerSettingsRememberForEveryBookDescription":
|
||||||
|
MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Settings like speed, loudness, etc. will be remembered for every book",
|
||||||
|
),
|
||||||
|
"playerSettingsSpeedDefault": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Default Speed",
|
||||||
|
),
|
||||||
|
"playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Speed Options",
|
||||||
|
),
|
||||||
|
"playlistsMine": MessageLookupByLibrary.simpleMessage("My Playlists"),
|
||||||
"readLess": MessageLookupByLibrary.simpleMessage("Read Less"),
|
"readLess": MessageLookupByLibrary.simpleMessage("Read Less"),
|
||||||
"readMore": MessageLookupByLibrary.simpleMessage("Read More"),
|
"readMore": MessageLookupByLibrary.simpleMessage("Read More"),
|
||||||
|
"refresh": MessageLookupByLibrary.simpleMessage("Refresh"),
|
||||||
"reset": MessageLookupByLibrary.simpleMessage("Reset"),
|
"reset": MessageLookupByLibrary.simpleMessage("Reset"),
|
||||||
"resetAppSettings": MessageLookupByLibrary.simpleMessage(
|
"resetAppSettings": MessageLookupByLibrary.simpleMessage(
|
||||||
"Reset App Settings",
|
"Reset App Settings",
|
||||||
|
|
@ -132,6 +205,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"Restore the app settings from the backup",
|
"Restore the app settings from the backup",
|
||||||
),
|
),
|
||||||
"resume": MessageLookupByLibrary.simpleMessage("Resume"),
|
"resume": MessageLookupByLibrary.simpleMessage("Resume"),
|
||||||
|
"retry": MessageLookupByLibrary.simpleMessage("Retry"),
|
||||||
|
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
|
||||||
"shakeDetector": MessageLookupByLibrary.simpleMessage("Shake Detector"),
|
"shakeDetector": MessageLookupByLibrary.simpleMessage("Shake Detector"),
|
||||||
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
|
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
|
||||||
"Customize the shake detector settings",
|
"Customize the shake detector settings",
|
||||||
|
|
@ -141,6 +216,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"Customize the app theme",
|
"Customize the app theme",
|
||||||
),
|
),
|
||||||
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
|
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
|
||||||
|
"webVersion": MessageLookupByLibrary.simpleMessage("Web Version"),
|
||||||
"yes": MessageLookupByLibrary.simpleMessage("Yes"),
|
"yes": MessageLookupByLibrary.simpleMessage("Yes"),
|
||||||
"you": MessageLookupByLibrary.simpleMessage("You"),
|
"you": MessageLookupByLibrary.simpleMessage("You"),
|
||||||
"youTooltip": MessageLookupByLibrary.simpleMessage(
|
"youTooltip": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
|
|
||||||
static String m1(item) => "已删除 ${item}";
|
static String m1(item) => "已删除 ${item}";
|
||||||
|
|
||||||
|
static String m2(error) => "加载库时出错:${error}";
|
||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
|
"account": MessageLookupByLibrary.simpleMessage("账户"),
|
||||||
|
"accountSwitch": MessageLookupByLibrary.simpleMessage("切换账户"),
|
||||||
"appSettings": MessageLookupByLibrary.simpleMessage("应用设置"),
|
"appSettings": MessageLookupByLibrary.simpleMessage("应用设置"),
|
||||||
"appearance": MessageLookupByLibrary.simpleMessage("外观"),
|
"appearance": MessageLookupByLibrary.simpleMessage("外观"),
|
||||||
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage("自动开启睡眠定时器"),
|
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage("自动开启睡眠定时器"),
|
||||||
|
|
@ -36,19 +40,38 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
"backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
||||||
"bookAbout": MessageLookupByLibrary.simpleMessage("关于本书"),
|
"bookAbout": MessageLookupByLibrary.simpleMessage("关于本书"),
|
||||||
"bookAboutDefault": MessageLookupByLibrary.simpleMessage("抱歉,找不到描述"),
|
"bookAboutDefault": MessageLookupByLibrary.simpleMessage("抱歉,找不到描述"),
|
||||||
|
"bookAuthors": MessageLookupByLibrary.simpleMessage("作者"),
|
||||||
|
"bookDownloads": MessageLookupByLibrary.simpleMessage("下载"),
|
||||||
|
"bookGenres": MessageLookupByLibrary.simpleMessage("风格"),
|
||||||
|
"bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("删节版"),
|
||||||
|
"bookMetadataLength": MessageLookupByLibrary.simpleMessage("持续时间"),
|
||||||
|
"bookMetadataPublished": MessageLookupByLibrary.simpleMessage("发布年份"),
|
||||||
|
"bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage("未删节版"),
|
||||||
|
"bookSeries": MessageLookupByLibrary.simpleMessage("系列"),
|
||||||
|
"bookShelveEmpty": MessageLookupByLibrary.simpleMessage("重试"),
|
||||||
|
"bookShelveEmptyText": MessageLookupByLibrary.simpleMessage("未查询到书架"),
|
||||||
"cancel": MessageLookupByLibrary.simpleMessage("取消"),
|
"cancel": MessageLookupByLibrary.simpleMessage("取消"),
|
||||||
"copyToClipboard": MessageLookupByLibrary.simpleMessage("复制到剪贴板"),
|
"copyToClipboard": MessageLookupByLibrary.simpleMessage("复制到剪贴板"),
|
||||||
"copyToClipboardDescription": MessageLookupByLibrary.simpleMessage(
|
"copyToClipboardDescription": MessageLookupByLibrary.simpleMessage(
|
||||||
"将应用程序设置复制到剪贴板",
|
"将应用程序设置复制到剪贴板",
|
||||||
),
|
),
|
||||||
"copyToClipboardToast": MessageLookupByLibrary.simpleMessage("设置已复制到剪贴板"),
|
"copyToClipboardToast": MessageLookupByLibrary.simpleMessage("设置已复制到剪贴板"),
|
||||||
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
|
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||||
"deleteDialog": m0,
|
"deleteDialog": m0,
|
||||||
"deleted": m1,
|
"deleted": m1,
|
||||||
"explore": MessageLookupByLibrary.simpleMessage("探索"),
|
"explore": MessageLookupByLibrary.simpleMessage("探索"),
|
||||||
|
"exploreHint": MessageLookupByLibrary.simpleMessage("搜索与探索..."),
|
||||||
"exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"),
|
"exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"),
|
||||||
"general": MessageLookupByLibrary.simpleMessage("通用"),
|
"general": MessageLookupByLibrary.simpleMessage("通用"),
|
||||||
|
"help": MessageLookupByLibrary.simpleMessage("Help"),
|
||||||
"home": MessageLookupByLibrary.simpleMessage("首页"),
|
"home": MessageLookupByLibrary.simpleMessage("首页"),
|
||||||
|
"homeBookContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"),
|
||||||
|
"homeBookContinueSeries": MessageLookupByLibrary.simpleMessage("继续系列"),
|
||||||
|
"homeBookDiscover": MessageLookupByLibrary.simpleMessage("发现"),
|
||||||
|
"homeBookListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"),
|
||||||
|
"homeBookNewestAuthors": MessageLookupByLibrary.simpleMessage("最新作者"),
|
||||||
|
"homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage("最近添加"),
|
||||||
|
"homeBookRecommended": MessageLookupByLibrary.simpleMessage("推荐"),
|
||||||
"homeContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"),
|
"homeContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"),
|
||||||
"homeListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"),
|
"homeListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"),
|
||||||
"homePageSettings": MessageLookupByLibrary.simpleMessage("主页设置"),
|
"homePageSettings": MessageLookupByLibrary.simpleMessage("主页设置"),
|
||||||
|
|
@ -59,8 +82,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||||
"languageDescription": MessageLookupByLibrary.simpleMessage("语言切换"),
|
"languageDescription": MessageLookupByLibrary.simpleMessage("语言切换"),
|
||||||
"library": MessageLookupByLibrary.simpleMessage("媒体库"),
|
"library": MessageLookupByLibrary.simpleMessage("媒体库"),
|
||||||
|
"libraryChange": MessageLookupByLibrary.simpleMessage("更改媒体库"),
|
||||||
|
"libraryEmpty": MessageLookupByLibrary.simpleMessage("没有可用的库。"),
|
||||||
|
"libraryLoadError": m2,
|
||||||
|
"librarySelect": MessageLookupByLibrary.simpleMessage("选择媒体库"),
|
||||||
|
"librarySwitchTooltip": MessageLookupByLibrary.simpleMessage("切换媒体库"),
|
||||||
"libraryTooltip": MessageLookupByLibrary.simpleMessage("浏览您的媒体库"),
|
"libraryTooltip": MessageLookupByLibrary.simpleMessage("浏览您的媒体库"),
|
||||||
|
"loading": MessageLookupByLibrary.simpleMessage("加载中..."),
|
||||||
|
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
||||||
"no": MessageLookupByLibrary.simpleMessage("否"),
|
"no": MessageLookupByLibrary.simpleMessage("否"),
|
||||||
|
"notImplemented": MessageLookupByLibrary.simpleMessage("未实现"),
|
||||||
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage("通知媒体播放器"),
|
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage("通知媒体播放器"),
|
||||||
"notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage(
|
"notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage(
|
||||||
"在通知中自定义媒体播放器",
|
"在通知中自定义媒体播放器",
|
||||||
|
|
@ -72,8 +103,32 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
|
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
|
||||||
"自定义播放器设置",
|
"自定义播放器设置",
|
||||||
),
|
),
|
||||||
|
"playerSettingsPlaybackReporting": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"回放报告",
|
||||||
|
),
|
||||||
|
"playerSettingsPlaybackReportingIgnore":
|
||||||
|
MessageLookupByLibrary.simpleMessage("忽略播放位置小于"),
|
||||||
|
"playerSettingsPlaybackReportingMinimum":
|
||||||
|
MessageLookupByLibrary.simpleMessage("回放报告最小位置"),
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionHead":
|
||||||
|
MessageLookupByLibrary.simpleMessage("不要报告本书前 "),
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionTail":
|
||||||
|
MessageLookupByLibrary.simpleMessage(" 的播放"),
|
||||||
|
"playerSettingsRememberForEveryBook": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"记住每本书的播放器设置",
|
||||||
|
),
|
||||||
|
"playerSettingsRememberForEveryBookDescription":
|
||||||
|
MessageLookupByLibrary.simpleMessage("每本书都会记住播放速度、音量等设置"),
|
||||||
|
"playerSettingsSpeedDefault": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"默认播放速度",
|
||||||
|
),
|
||||||
|
"playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"播放速度选项",
|
||||||
|
),
|
||||||
|
"playlistsMine": MessageLookupByLibrary.simpleMessage("播放列表"),
|
||||||
"readLess": MessageLookupByLibrary.simpleMessage("折叠"),
|
"readLess": MessageLookupByLibrary.simpleMessage("折叠"),
|
||||||
"readMore": MessageLookupByLibrary.simpleMessage("展开"),
|
"readMore": MessageLookupByLibrary.simpleMessage("展开"),
|
||||||
|
"refresh": MessageLookupByLibrary.simpleMessage("刷新"),
|
||||||
"reset": MessageLookupByLibrary.simpleMessage("重置"),
|
"reset": MessageLookupByLibrary.simpleMessage("重置"),
|
||||||
"resetAppSettings": MessageLookupByLibrary.simpleMessage("重置应用程序设置"),
|
"resetAppSettings": MessageLookupByLibrary.simpleMessage("重置应用程序设置"),
|
||||||
"resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage(
|
"resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|
@ -90,6 +145,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"restoreBackupValidator": MessageLookupByLibrary.simpleMessage("请将备份粘贴到此处"),
|
"restoreBackupValidator": MessageLookupByLibrary.simpleMessage("请将备份粘贴到此处"),
|
||||||
"restoreDescription": MessageLookupByLibrary.simpleMessage("从备份中还原应用程序设置"),
|
"restoreDescription": MessageLookupByLibrary.simpleMessage("从备份中还原应用程序设置"),
|
||||||
"resume": MessageLookupByLibrary.simpleMessage("继续"),
|
"resume": MessageLookupByLibrary.simpleMessage("继续"),
|
||||||
|
"retry": MessageLookupByLibrary.simpleMessage("重试"),
|
||||||
|
"settings": MessageLookupByLibrary.simpleMessage("设置"),
|
||||||
"shakeDetector": MessageLookupByLibrary.simpleMessage("抖动检测器"),
|
"shakeDetector": MessageLookupByLibrary.simpleMessage("抖动检测器"),
|
||||||
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
|
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
|
||||||
"自定义抖动检测器设置",
|
"自定义抖动检测器设置",
|
||||||
|
|
@ -97,6 +154,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"themeSettings": MessageLookupByLibrary.simpleMessage("主题设置"),
|
"themeSettings": MessageLookupByLibrary.simpleMessage("主题设置"),
|
||||||
"themeSettingsDescription": MessageLookupByLibrary.simpleMessage("自定义应用主题"),
|
"themeSettingsDescription": MessageLookupByLibrary.simpleMessage("自定义应用主题"),
|
||||||
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
||||||
|
"webVersion": MessageLookupByLibrary.simpleMessage("Web版本"),
|
||||||
"yes": MessageLookupByLibrary.simpleMessage("是"),
|
"yes": MessageLookupByLibrary.simpleMessage("是"),
|
||||||
"you": MessageLookupByLibrary.simpleMessage("我的"),
|
"you": MessageLookupByLibrary.simpleMessage("我的"),
|
||||||
"youTooltip": MessageLookupByLibrary.simpleMessage("您的个人资料和设置"),
|
"youTooltip": MessageLookupByLibrary.simpleMessage("您的个人资料和设置"),
|
||||||
|
|
|
||||||
|
|
@ -74,11 +74,21 @@ class S {
|
||||||
return Intl.message('Cancel', name: 'cancel', desc: '', args: []);
|
return Intl.message('Cancel', name: 'cancel', desc: '', args: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Refresh`
|
||||||
|
String get refresh {
|
||||||
|
return Intl.message('Refresh', name: 'refresh', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
/// `Reset`
|
/// `Reset`
|
||||||
String get reset {
|
String get reset {
|
||||||
return Intl.message('Reset', name: 'reset', desc: '', args: []);
|
return Intl.message('Reset', name: 'reset', desc: '', args: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Retry`
|
||||||
|
String get retry {
|
||||||
|
return Intl.message('Retry', name: 'retry', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
/// `Delete`
|
/// `Delete`
|
||||||
String get delete {
|
String get delete {
|
||||||
return Intl.message('Delete', name: 'delete', desc: '', args: []);
|
return Intl.message('Delete', name: 'delete', desc: '', args: []);
|
||||||
|
|
@ -134,6 +144,16 @@ class S {
|
||||||
return Intl.message('Read Less', name: 'readLess', desc: '', args: []);
|
return Intl.message('Read Less', name: 'readLess', desc: '', args: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Loading...`
|
||||||
|
String get loading {
|
||||||
|
return Intl.message('Loading...', name: 'loading', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Help`
|
||||||
|
String get help {
|
||||||
|
return Intl.message('Help', name: 'help', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
/// `Home`
|
/// `Home`
|
||||||
String get home {
|
String get home {
|
||||||
return Intl.message('Home', name: 'home', desc: '', args: []);
|
return Intl.message('Home', name: 'home', desc: '', args: []);
|
||||||
|
|
@ -169,6 +189,76 @@ class S {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Continue Listening`
|
||||||
|
String get homeBookContinueListening {
|
||||||
|
return Intl.message(
|
||||||
|
'Continue Listening',
|
||||||
|
name: 'homeBookContinueListening',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Continue Series`
|
||||||
|
String get homeBookContinueSeries {
|
||||||
|
return Intl.message(
|
||||||
|
'Continue Series',
|
||||||
|
name: 'homeBookContinueSeries',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Recently Added`
|
||||||
|
String get homeBookRecentlyAdded {
|
||||||
|
return Intl.message(
|
||||||
|
'Recently Added',
|
||||||
|
name: 'homeBookRecentlyAdded',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Recommended`
|
||||||
|
String get homeBookRecommended {
|
||||||
|
return Intl.message(
|
||||||
|
'Recommended',
|
||||||
|
name: 'homeBookRecommended',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Discover`
|
||||||
|
String get homeBookDiscover {
|
||||||
|
return Intl.message(
|
||||||
|
'Discover',
|
||||||
|
name: 'homeBookDiscover',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Listen Again`
|
||||||
|
String get homeBookListenAgain {
|
||||||
|
return Intl.message(
|
||||||
|
'Listen Again',
|
||||||
|
name: 'homeBookListenAgain',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Newest Authors`
|
||||||
|
String get homeBookNewestAuthors {
|
||||||
|
return Intl.message(
|
||||||
|
'Newest Authors',
|
||||||
|
name: 'homeBookNewestAuthors',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// `About the Book`
|
/// `About the Book`
|
||||||
String get bookAbout {
|
String get bookAbout {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
|
|
@ -229,6 +319,46 @@ class S {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Try again`
|
||||||
|
String get bookShelveEmpty {
|
||||||
|
return Intl.message(
|
||||||
|
'Try again',
|
||||||
|
name: 'bookShelveEmpty',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `No shelves to display`
|
||||||
|
String get bookShelveEmptyText {
|
||||||
|
return Intl.message(
|
||||||
|
'No shelves to display',
|
||||||
|
name: 'bookShelveEmptyText',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Authors`
|
||||||
|
String get bookAuthors {
|
||||||
|
return Intl.message('Authors', name: 'bookAuthors', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Genres`
|
||||||
|
String get bookGenres {
|
||||||
|
return Intl.message('Genres', name: 'bookGenres', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Series`
|
||||||
|
String get bookSeries {
|
||||||
|
return Intl.message('Series', name: 'bookSeries', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Downloads`
|
||||||
|
String get bookDownloads {
|
||||||
|
return Intl.message('Downloads', name: 'bookDownloads', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
/// `Library`
|
/// `Library`
|
||||||
String get library {
|
String get library {
|
||||||
return Intl.message('Library', name: 'library', desc: '', args: []);
|
return Intl.message('Library', name: 'library', desc: '', args: []);
|
||||||
|
|
@ -244,6 +374,56 @@ class S {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Switch Library`
|
||||||
|
String get librarySwitchTooltip {
|
||||||
|
return Intl.message(
|
||||||
|
'Switch Library',
|
||||||
|
name: 'librarySwitchTooltip',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Change Library`
|
||||||
|
String get libraryChange {
|
||||||
|
return Intl.message(
|
||||||
|
'Change Library',
|
||||||
|
name: 'libraryChange',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Select Library`
|
||||||
|
String get librarySelect {
|
||||||
|
return Intl.message(
|
||||||
|
'Select Library',
|
||||||
|
name: 'librarySelect',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `No libraries available.`
|
||||||
|
String get libraryEmpty {
|
||||||
|
return Intl.message(
|
||||||
|
'No libraries available.',
|
||||||
|
name: 'libraryEmpty',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Error loading libraries: {error}`
|
||||||
|
String libraryLoadError(String error) {
|
||||||
|
return Intl.message(
|
||||||
|
'Error loading libraries: $error',
|
||||||
|
name: 'libraryLoadError',
|
||||||
|
desc: '',
|
||||||
|
args: [error],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// `explore`
|
/// `explore`
|
||||||
String get explore {
|
String get explore {
|
||||||
return Intl.message('explore', name: 'explore', desc: '', args: []);
|
return Intl.message('explore', name: 'explore', desc: '', args: []);
|
||||||
|
|
@ -259,6 +439,16 @@ class S {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Seek and you shall discover...`
|
||||||
|
String get exploreHint {
|
||||||
|
return Intl.message(
|
||||||
|
'Seek and you shall discover...',
|
||||||
|
name: 'exploreHint',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// `You`
|
/// `You`
|
||||||
String get you {
|
String get you {
|
||||||
return Intl.message('You', name: 'you', desc: '', args: []);
|
return Intl.message('You', name: 'you', desc: '', args: []);
|
||||||
|
|
@ -274,6 +464,41 @@ class S {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Settings`
|
||||||
|
String get settings {
|
||||||
|
return Intl.message('Settings', name: 'settings', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Account`
|
||||||
|
String get account {
|
||||||
|
return Intl.message('Account', name: 'account', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Switch Account`
|
||||||
|
String get accountSwitch {
|
||||||
|
return Intl.message(
|
||||||
|
'Switch Account',
|
||||||
|
name: 'accountSwitch',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `My Playlists`
|
||||||
|
String get playlistsMine {
|
||||||
|
return Intl.message(
|
||||||
|
'My Playlists',
|
||||||
|
name: 'playlistsMine',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Web Version`
|
||||||
|
String get webVersion {
|
||||||
|
return Intl.message('Web Version', name: 'webVersion', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
/// `App Settings`
|
/// `App Settings`
|
||||||
String get appSettings {
|
String get appSettings {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
|
|
@ -324,6 +549,96 @@ class S {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Remember Player Settings for Every Book`
|
||||||
|
String get playerSettingsRememberForEveryBook {
|
||||||
|
return Intl.message(
|
||||||
|
'Remember Player Settings for Every Book',
|
||||||
|
name: 'playerSettingsRememberForEveryBook',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Settings like speed, loudness, etc. will be remembered for every book`
|
||||||
|
String get playerSettingsRememberForEveryBookDescription {
|
||||||
|
return Intl.message(
|
||||||
|
'Settings like speed, loudness, etc. will be remembered for every book',
|
||||||
|
name: 'playerSettingsRememberForEveryBookDescription',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Default Speed`
|
||||||
|
String get playerSettingsSpeedDefault {
|
||||||
|
return Intl.message(
|
||||||
|
'Default Speed',
|
||||||
|
name: 'playerSettingsSpeedDefault',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Speed Options`
|
||||||
|
String get playerSettingsSpeedOptions {
|
||||||
|
return Intl.message(
|
||||||
|
'Speed Options',
|
||||||
|
name: 'playerSettingsSpeedOptions',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Playback Reporting`
|
||||||
|
String get playerSettingsPlaybackReporting {
|
||||||
|
return Intl.message(
|
||||||
|
'Playback Reporting',
|
||||||
|
name: 'playerSettingsPlaybackReporting',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Minimum Position to Report`
|
||||||
|
String get playerSettingsPlaybackReportingMinimum {
|
||||||
|
return Intl.message(
|
||||||
|
'Minimum Position to Report',
|
||||||
|
name: 'playerSettingsPlaybackReportingMinimum',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Do not report playback for the first `
|
||||||
|
String get playerSettingsPlaybackReportingMinimumDescriptionHead {
|
||||||
|
return Intl.message(
|
||||||
|
'Do not report playback for the first ',
|
||||||
|
name: 'playerSettingsPlaybackReportingMinimumDescriptionHead',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `of the book`
|
||||||
|
String get playerSettingsPlaybackReportingMinimumDescriptionTail {
|
||||||
|
return Intl.message(
|
||||||
|
'of the book',
|
||||||
|
name: 'playerSettingsPlaybackReportingMinimumDescriptionTail',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Ignore Playback Position Less Than`
|
||||||
|
String get playerSettingsPlaybackReportingIgnore {
|
||||||
|
return Intl.message(
|
||||||
|
'Ignore Playback Position Less Than',
|
||||||
|
name: 'playerSettingsPlaybackReportingIgnore',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// `Auto Turn On Sleep Timer`
|
/// `Auto Turn On Sleep Timer`
|
||||||
String get autoTurnOnSleepTimer {
|
String get autoTurnOnSleepTimer {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
|
|
@ -568,6 +883,21 @@ class S {
|
||||||
args: [],
|
args: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Logs`
|
||||||
|
String get logs {
|
||||||
|
return Intl.message('Logs', name: 'logs', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Not implemented`
|
||||||
|
String get notImplemented {
|
||||||
|
return Intl.message(
|
||||||
|
'Not implemented',
|
||||||
|
name: 'notImplemented',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"refresh": "Refresh",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
|
"retry": "Retry",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"deleteDialog": "Are you sure you want to delete {item}?",
|
"deleteDialog": "Are you sure you want to delete {item}?",
|
||||||
"@deleteDialog": {
|
"@deleteDialog": {
|
||||||
|
|
@ -31,31 +33,74 @@
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"readMore": "Read More",
|
"readMore": "Read More",
|
||||||
"readLess": "Read Less",
|
"readLess": "Read Less",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"help": "Help",
|
||||||
|
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"homeListenAgain": "Listen Again",
|
"homeListenAgain": "Listen Again",
|
||||||
"homeContinueListening": "Continue Listening",
|
"homeContinueListening": "Continue Listening",
|
||||||
"homeStartListening": "Start Listening",
|
"homeStartListening": "Start Listening",
|
||||||
|
"homeBookContinueListening": "Continue Listening",
|
||||||
|
"homeBookContinueSeries": "Continue Series",
|
||||||
|
"homeBookRecentlyAdded": "Recently Added",
|
||||||
|
"homeBookRecommended": "Recommended",
|
||||||
|
"homeBookDiscover": "Discover",
|
||||||
|
"homeBookListenAgain": "Listen Again",
|
||||||
|
"homeBookNewestAuthors": "Newest Authors",
|
||||||
"bookAbout": "About the Book",
|
"bookAbout": "About the Book",
|
||||||
"bookAboutDefault": "Sorry, no description found",
|
"bookAboutDefault": "Sorry, no description found",
|
||||||
"bookMetadataAbridged": "Abridged",
|
"bookMetadataAbridged": "Abridged",
|
||||||
"bookMetadataUnabridged": "Unabridged",
|
"bookMetadataUnabridged": "Unabridged",
|
||||||
"bookMetadataLength": "Length",
|
"bookMetadataLength": "Length",
|
||||||
"bookMetadataPublished": "Published",
|
"bookMetadataPublished": "Published",
|
||||||
|
"bookShelveEmpty": "Try again",
|
||||||
|
"bookShelveEmptyText": "No shelves to display",
|
||||||
|
"bookAuthors": "Authors",
|
||||||
|
"bookGenres": "Genres",
|
||||||
|
"bookSeries": "Series",
|
||||||
|
"bookDownloads": "Downloads",
|
||||||
|
|
||||||
"library": "Library",
|
"library": "Library",
|
||||||
"libraryTooltip": "Browse your library",
|
"libraryTooltip": "Browse your library",
|
||||||
|
"librarySwitchTooltip": "Switch Library",
|
||||||
|
"libraryChange": "Change Library",
|
||||||
|
"librarySelect": "Select Library",
|
||||||
|
"libraryEmpty": "No libraries available.",
|
||||||
|
"libraryLoadError": "Error loading libraries: {error}",
|
||||||
|
"@libraryLoadError": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"explore": "explore",
|
"explore": "explore",
|
||||||
"exploreTooltip": "Search and Explore",
|
"exploreTooltip": "Search and Explore",
|
||||||
|
"exploreHint": "Seek and you shall discover...",
|
||||||
"you": "You",
|
"you": "You",
|
||||||
"youTooltip": "Your Profile and Settings",
|
"youTooltip": "Your Profile and Settings",
|
||||||
|
|
||||||
|
"settings": "Settings",
|
||||||
|
"account": "Account",
|
||||||
|
"accountSwitch": "Switch Account",
|
||||||
|
"playlistsMine": "My Playlists",
|
||||||
|
"webVersion": "Web Version",
|
||||||
|
|
||||||
"appSettings": "App Settings",
|
"appSettings": "App Settings",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"languageDescription": "Language switch",
|
"languageDescription": "Language switch",
|
||||||
"playerSettings": "Player Settings",
|
"playerSettings": "Player Settings",
|
||||||
"playerSettingsDescription": "Customize the player settings",
|
"playerSettingsDescription": "Customize the player settings",
|
||||||
|
"playerSettingsRememberForEveryBook": "Remember Player Settings for Every Book",
|
||||||
|
"playerSettingsRememberForEveryBookDescription": "Settings like speed, loudness, etc. will be remembered for every book",
|
||||||
|
"playerSettingsSpeedDefault": "Default Speed",
|
||||||
|
"playerSettingsSpeedOptions": "Speed Options",
|
||||||
|
"playerSettingsPlaybackReporting": "Playback Reporting",
|
||||||
|
"playerSettingsPlaybackReportingMinimum": "Minimum Position to Report",
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionHead": "Do not report playback for the first ",
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionTail": "of the book",
|
||||||
|
"playerSettingsPlaybackReportingIgnore": "Ignore Playback Position Less Than",
|
||||||
"autoTurnOnSleepTimer": "Auto Turn On Sleep Timer",
|
"autoTurnOnSleepTimer": "Auto Turn On Sleep Timer",
|
||||||
"automaticallyDescription": "Automatically turn on the sleep timer based on the time of day",
|
"automaticallyDescription": "Automatically turn on the sleep timer based on the time of day",
|
||||||
"shakeDetector": "Shake Detector",
|
"shakeDetector": "Shake Detector",
|
||||||
|
|
@ -82,6 +127,11 @@
|
||||||
|
|
||||||
"resetAppSettings": "Reset App Settings",
|
"resetAppSettings": "Reset App Settings",
|
||||||
"resetAppSettingsDescription": "Reset the app settings to the default values",
|
"resetAppSettingsDescription": "Reset the app settings to the default values",
|
||||||
"resetAppSettingsDialog": "Are you sure you want to reset the app settings?"
|
"resetAppSettingsDialog": "Are you sure you want to reset the app settings?",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"logs": "Logs",
|
||||||
|
"notImplemented": "Not implemented"
|
||||||
}
|
}
|
||||||
|
|
@ -5,8 +5,10 @@
|
||||||
"no": "否",
|
"no": "否",
|
||||||
"ok": "确定",
|
"ok": "确定",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
|
"refresh": "刷新",
|
||||||
"reset": "重置",
|
"reset": "重置",
|
||||||
"delete": "Delete",
|
"retry": "重试",
|
||||||
|
"delete": "删除",
|
||||||
"deleteDialog": "确定要删除 {item} 吗?",
|
"deleteDialog": "确定要删除 {item} 吗?",
|
||||||
"@deleteDialog": {
|
"@deleteDialog": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
|
@ -31,30 +33,76 @@
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"readMore": "展开",
|
"readMore": "展开",
|
||||||
"readLess": "折叠",
|
"readLess": "折叠",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"help": "Help",
|
||||||
|
|
||||||
"home": "首页",
|
"home": "首页",
|
||||||
"homeListenAgain": "再听一遍",
|
"homeListenAgain": "再听一遍",
|
||||||
"homeContinueListening": "继续收听",
|
"homeContinueListening": "继续收听",
|
||||||
"homeStartListening": "开始收听",
|
"homeStartListening": "开始收听",
|
||||||
|
"homeBookContinueListening": "继续收听",
|
||||||
|
"homeBookContinueSeries": "继续系列",
|
||||||
|
"homeBookRecentlyAdded": "最近添加",
|
||||||
|
"homeBookRecommended": "推荐",
|
||||||
|
"homeBookDiscover": "发现",
|
||||||
|
"homeBookListenAgain": "再听一遍",
|
||||||
|
"homeBookNewestAuthors": "最新作者",
|
||||||
"bookAbout": "关于本书",
|
"bookAbout": "关于本书",
|
||||||
"bookAboutDefault": "抱歉,找不到描述",
|
"bookAboutDefault": "抱歉,找不到描述",
|
||||||
|
"bookMetadataAbridged": "删节版",
|
||||||
|
"bookMetadataUnabridged": "未删节版",
|
||||||
|
"bookMetadataLength": "持续时间",
|
||||||
|
"bookMetadataPublished": "发布年份",
|
||||||
|
"bookShelveEmpty": "重试",
|
||||||
|
"bookShelveEmptyText": "未查询到书架",
|
||||||
|
"bookAuthors": "作者",
|
||||||
|
"bookGenres": "风格",
|
||||||
|
"bookSeries": "系列",
|
||||||
|
"bookDownloads": "下载",
|
||||||
|
|
||||||
"library": "媒体库",
|
"library": "媒体库",
|
||||||
"libraryTooltip": "浏览您的媒体库",
|
"libraryTooltip": "浏览您的媒体库",
|
||||||
|
"librarySwitchTooltip": "切换媒体库",
|
||||||
|
"libraryChange": "更改媒体库",
|
||||||
|
"librarySelect": "选择媒体库",
|
||||||
|
"libraryEmpty": "没有可用的库。",
|
||||||
|
"libraryLoadError": "加载库时出错:{error}",
|
||||||
|
"@libraryLoadError": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"explore": "探索",
|
"explore": "探索",
|
||||||
"exploreTooltip": "搜索和探索",
|
"exploreTooltip": "搜索和探索",
|
||||||
|
"exploreHint": "搜索与探索...",
|
||||||
"you": "我的",
|
"you": "我的",
|
||||||
"youTooltip": "您的个人资料和设置",
|
"youTooltip": "您的个人资料和设置",
|
||||||
|
|
||||||
|
"settings": "设置",
|
||||||
|
"account": "账户",
|
||||||
|
"accountSwitch": "切换账户",
|
||||||
|
"playlistsMine": "播放列表",
|
||||||
|
"webVersion": "Web版本",
|
||||||
|
|
||||||
"appSettings": "应用设置",
|
"appSettings": "应用设置",
|
||||||
"general": "通用",
|
"general": "通用",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
"languageDescription": "语言切换",
|
"languageDescription": "语言切换",
|
||||||
"playerSettings": "播放器设置",
|
"playerSettings": "播放器设置",
|
||||||
"playerSettingsDescription": "自定义播放器设置",
|
"playerSettingsDescription": "自定义播放器设置",
|
||||||
|
"playerSettingsRememberForEveryBook": "记住每本书的播放器设置",
|
||||||
|
"playerSettingsRememberForEveryBookDescription": "每本书都会记住播放速度、音量等设置",
|
||||||
|
"playerSettingsSpeedDefault": "默认播放速度",
|
||||||
|
"playerSettingsSpeedOptions": "播放速度选项",
|
||||||
|
"playerSettingsPlaybackReporting": "回放报告",
|
||||||
|
"playerSettingsPlaybackReportingMinimum": "回放报告最小位置",
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionHead": "不要报告本书前 ",
|
||||||
|
"playerSettingsPlaybackReportingMinimumDescriptionTail": " 的播放",
|
||||||
|
"playerSettingsPlaybackReportingIgnore": "忽略播放位置小于",
|
||||||
"autoTurnOnSleepTimer": "自动开启睡眠定时器",
|
"autoTurnOnSleepTimer": "自动开启睡眠定时器",
|
||||||
"automaticallyDescription": "根据一天中的时间自动打开睡眠定时器",
|
"automaticallyDescription": "根据一天中的时间自动打开睡眠定时器",
|
||||||
|
|
||||||
"shakeDetector": "抖动检测器",
|
"shakeDetector": "抖动检测器",
|
||||||
"shakeDetectorDescription": "自定义抖动检测器设置",
|
"shakeDetectorDescription": "自定义抖动检测器设置",
|
||||||
"appearance": "外观",
|
"appearance": "外观",
|
||||||
|
|
@ -79,6 +127,8 @@
|
||||||
|
|
||||||
"resetAppSettings": "重置应用程序设置",
|
"resetAppSettings": "重置应用程序设置",
|
||||||
"resetAppSettingsDescription": "将应用程序设置重置为默认值",
|
"resetAppSettingsDescription": "将应用程序设置重置为默认值",
|
||||||
"resetAppSettingsDialog": "您确定要重置应用程序设置吗?"
|
"resetAppSettingsDialog": "您确定要重置应用程序设置吗?",
|
||||||
|
|
||||||
|
"logs": "日志",
|
||||||
|
"notImplemented": "未实现"
|
||||||
}
|
}
|
||||||
|
|
@ -12,16 +12,18 @@ import 'package:vaani/features/player/core/init.dart';
|
||||||
import 'package:vaani/features/player/providers/audiobook_player.dart'
|
import 'package:vaani/features/player/providers/audiobook_player.dart'
|
||||||
show audiobookPlayerProvider, simpleAudiobookPlayerProvider;
|
show audiobookPlayerProvider, simpleAudiobookPlayerProvider;
|
||||||
import 'package:vaani/features/shake_detection/providers/shake_detector.dart';
|
import 'package:vaani/features/shake_detection/providers/shake_detector.dart';
|
||||||
|
import 'package:vaani/features/skip_start_end/skip_start_end_provider.dart';
|
||||||
import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart';
|
import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart';
|
||||||
import 'package:vaani/generated/l10n.dart';
|
import 'package:vaani/generated/l10n.dart';
|
||||||
import 'package:vaani/router/router.dart';
|
import 'package:vaani/router/router.dart';
|
||||||
import 'package:vaani/settings/api_settings_provider.dart';
|
import 'package:vaani/settings/api_settings_provider.dart';
|
||||||
import 'package:vaani/settings/app_settings_provider.dart';
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
|
import 'package:vaani/settings/settings.dart';
|
||||||
import 'package:vaani/theme/providers/system_theme_provider.dart';
|
import 'package:vaani/theme/providers/system_theme_provider.dart';
|
||||||
import 'package:vaani/theme/providers/theme_from_cover_provider.dart';
|
import 'package:vaani/theme/providers/theme_from_cover_provider.dart';
|
||||||
import 'package:vaani/theme/theme.dart';
|
import 'package:vaani/theme/theme.dart';
|
||||||
|
|
||||||
final appLogger = Logger('vaani');
|
final appLogger = Logger(AppMetadata.appName);
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
@ -171,6 +173,7 @@ class _EagerInitialization extends ConsumerWidget {
|
||||||
ref.watch(playbackReporterProvider);
|
ref.watch(playbackReporterProvider);
|
||||||
ref.watch(simpleDownloadManagerProvider);
|
ref.watch(simpleDownloadManagerProvider);
|
||||||
ref.watch(shakeDetectorProvider);
|
ref.watch(shakeDetectorProvider);
|
||||||
|
ref.watch(skipStartEndProvider);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrintStack(stackTrace: StackTrace.current, label: e.toString());
|
debugPrintStack(stackTrace: StackTrace.current, label: e.toString());
|
||||||
appLogger.severe(e.toString());
|
appLogger.severe(e.toString());
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:vaani/api/api_provider.dart';
|
import 'package:vaani/api/api_provider.dart';
|
||||||
|
import 'package:vaani/generated/l10n.dart';
|
||||||
import 'package:vaani/main.dart';
|
import 'package:vaani/main.dart';
|
||||||
import 'package:vaani/router/router.dart';
|
import 'package:vaani/router/router.dart';
|
||||||
import 'package:vaani/settings/api_settings_provider.dart';
|
import 'package:vaani/settings/api_settings_provider.dart';
|
||||||
import 'package:vaani/settings/app_settings_provider.dart'
|
import 'package:vaani/settings/app_settings_provider.dart' show appSettingsProvider;
|
||||||
show appSettingsProvider;
|
import 'package:vaani/settings/constants.dart';
|
||||||
|
|
||||||
import '../shared/widgets/shelves/home_shelf.dart';
|
import '../shared/widgets/shelves/home_shelf.dart';
|
||||||
|
|
||||||
|
|
@ -21,11 +22,12 @@ class HomePage extends HookConsumerWidget {
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
final appSettings = ref.watch(appSettingsProvider);
|
final appSettings = ref.watch(appSettingsProvider);
|
||||||
final homePageSettings = appSettings.homePageSettings;
|
final homePageSettings = appSettings.homePageSettings;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: GestureDetector(
|
title: GestureDetector(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Vaani',
|
AppMetadata.appName,
|
||||||
style: Theme.of(context).textTheme.headlineLarge,
|
style: Theme.of(context).textTheme.headlineLarge,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
@ -48,7 +50,7 @@ class HomePage extends HookConsumerWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Text('No shelves to display'),
|
Text(S.of(context).bookShelveEmptyText),
|
||||||
// try again button
|
// try again button
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
@ -57,7 +59,7 @@ class HomePage extends HookConsumerWidget {
|
||||||
);
|
);
|
||||||
ref.invalidate(personalizedViewProvider);
|
ref.invalidate(personalizedViewProvider);
|
||||||
},
|
},
|
||||||
child: const Text('Try again'),
|
child: Text(S.of(context).bookShelveEmpty),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -70,16 +72,23 @@ class HomePage extends HookConsumerWidget {
|
||||||
// check if showPlayButton is enabled for the shelf
|
// check if showPlayButton is enabled for the shelf
|
||||||
// using the id of the shelf
|
// using the id of the shelf
|
||||||
final showPlayButton = switch (shelf.id) {
|
final showPlayButton = switch (shelf.id) {
|
||||||
'continue-listening' =>
|
'continue-listening' => homePageSettings.showPlayButtonOnContinueListeningShelf,
|
||||||
homePageSettings.showPlayButtonOnContinueListeningShelf,
|
'continue-series' => homePageSettings.showPlayButtonOnContinueSeriesShelf,
|
||||||
'continue-series' =>
|
'listen-again' => homePageSettings.showPlayButtonOnListenAgainShelf,
|
||||||
homePageSettings.showPlayButtonOnContinueSeriesShelf,
|
|
||||||
'listen-again' =>
|
|
||||||
homePageSettings.showPlayButtonOnListenAgainShelf,
|
|
||||||
_ => homePageSettings.showPlayButtonOnAllRemainingShelves,
|
_ => homePageSettings.showPlayButtonOnAllRemainingShelves,
|
||||||
};
|
};
|
||||||
|
final showLabel = switch (shelf.label) {
|
||||||
|
"Continue Listening" => S.of(context).homeBookContinueListening,
|
||||||
|
"Continue Series" => S.of(context).homeBookContinueSeries,
|
||||||
|
"Recently Added" => S.of(context).homeBookRecentlyAdded,
|
||||||
|
"Recommended" => S.of(context).homeBookRecommended,
|
||||||
|
"Discover" => S.of(context).homeBookDiscover,
|
||||||
|
"Listen Again" => S.of(context).homeBookListenAgain,
|
||||||
|
"Newest Authors" => S.of(context).homeBookNewestAuthors,
|
||||||
|
_ => shelf.label
|
||||||
|
};
|
||||||
return HomeShelf(
|
return HomeShelf(
|
||||||
title: shelf.label,
|
title: showLabel,
|
||||||
shelf: shelf,
|
shelf: shelf,
|
||||||
showPlayButton: showPlayButton,
|
showPlayButton: showPlayButton,
|
||||||
);
|
);
|
||||||
|
|
@ -102,8 +111,7 @@ class HomePage extends HookConsumerWidget {
|
||||||
},
|
},
|
||||||
loading: () => const HomePageSkeleton(),
|
loading: () => const HomePageSkeleton(),
|
||||||
error: (error, stack) {
|
error: (error, stack) {
|
||||||
if (apiSettings.activeUser == null ||
|
if (apiSettings.activeUser == null || apiSettings.activeServer == null) {
|
||||||
apiSettings.activeServer == null) {
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:vaani/generated/l10n.dart';
|
||||||
import 'package:vaani/settings/app_settings_provider.dart';
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
import 'package:vaani/settings/view/buttons.dart';
|
import 'package:vaani/settings/view/buttons.dart';
|
||||||
import 'package:vaani/settings/view/simple_settings_page.dart';
|
import 'package:vaani/settings/view/simple_settings_page.dart';
|
||||||
|
|
@ -20,7 +21,7 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||||
|
|
||||||
return SimpleSettingsPage(
|
return SimpleSettingsPage(
|
||||||
title: const Text('Player Settings'),
|
title: Text(S.of(context).playerSettings),
|
||||||
sections: [
|
sections: [
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
margin: const EdgeInsetsDirectional.symmetric(
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
|
|
@ -30,10 +31,10 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
tiles: [
|
tiles: [
|
||||||
// preferred settings for every book
|
// preferred settings for every book
|
||||||
SettingsTile.switchTile(
|
SettingsTile.switchTile(
|
||||||
title: const Text('Remember Player Settings for Every Book'),
|
title: Text(S.of(context).playerSettingsRememberForEveryBook),
|
||||||
leading: const Icon(Icons.settings_applications),
|
leading: const Icon(Icons.settings_applications),
|
||||||
description: const Text(
|
description: Text(
|
||||||
'Settings like speed, loudness, etc. will be remembered for every book',
|
S.of(context).playerSettingsRememberForEveryBookDescription,
|
||||||
),
|
),
|
||||||
initialValue: playerSettings.configurePlayerForEveryBook,
|
initialValue: playerSettings.configurePlayerForEveryBook,
|
||||||
onToggle: (value) {
|
onToggle: (value) {
|
||||||
|
|
@ -47,11 +48,10 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
|
|
||||||
// preferred default speed
|
// preferred default speed
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: const Text('Default Speed'),
|
title: Text(S.of(context).playerSettingsSpeedDefault),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
'${playerSettings.preferredDefaultSpeed}x',
|
'${playerSettings.preferredDefaultSpeed}x',
|
||||||
style:
|
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
|
||||||
TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
leading: const Icon(Icons.speed),
|
leading: const Icon(Icons.speed),
|
||||||
onPressed: (context) async {
|
onPressed: (context) async {
|
||||||
|
|
@ -72,11 +72,10 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
// preferred speed options
|
// preferred speed options
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: const Text('Speed Options'),
|
title: Text(S.of(context).playerSettingsSpeedOptions),
|
||||||
description: Text(
|
description: Text(
|
||||||
playerSettings.speedOptions.map((e) => '${e}x').join(', '),
|
playerSettings.speedOptions.map((e) => '${e}x').join(', '),
|
||||||
style:
|
style: TextStyle(fontWeight: FontWeight.bold, color: primaryColor),
|
||||||
TextStyle(fontWeight: FontWeight.bold, color: primaryColor),
|
|
||||||
),
|
),
|
||||||
leading: const Icon(Icons.speed),
|
leading: const Icon(Icons.speed),
|
||||||
onPressed: (context) async {
|
onPressed: (context) async {
|
||||||
|
|
@ -100,23 +99,23 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
|
|
||||||
// Playback Reporting
|
// Playback Reporting
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: const Text('Playback Reporting'),
|
title: Text(S.of(context).playerSettingsPlaybackReporting),
|
||||||
tiles: [
|
tiles: [
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: const Text('Minimum Position to Report'),
|
title: Text(S.of(context).playerSettingsPlaybackReportingMinimum),
|
||||||
description: Text.rich(
|
description: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'Do not report playback for the first ',
|
text: S.of(context).playerSettingsPlaybackReportingMinimumDescriptionHead,
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: playerSettings
|
text: playerSettings.minimumPositionForReporting.smartBinaryFormat,
|
||||||
.minimumPositionForReporting.smartBinaryFormat,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: primaryColor,
|
color: primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const TextSpan(text: ' of the book'),
|
TextSpan(
|
||||||
|
text: S.of(context).playerSettingsPlaybackReportingMinimumDescriptionTail),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -126,7 +125,7 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return TimeDurationSelector(
|
return TimeDurationSelector(
|
||||||
title: const Text('Ignore Playback Position Less Than'),
|
title: Text(S.of(context).playerSettingsPlaybackReportingIgnore),
|
||||||
baseUnit: BaseUnit.second,
|
baseUnit: BaseUnit.second,
|
||||||
initialValue: playerSettings.minimumPositionForReporting,
|
initialValue: playerSettings.minimumPositionForReporting,
|
||||||
);
|
);
|
||||||
|
|
@ -149,8 +148,7 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
text: 'Mark complete when less than ',
|
text: 'Mark complete when less than ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: playerSettings
|
text: playerSettings.markCompleteWhenTimeLeft.smartBinaryFormat,
|
||||||
.markCompleteWhenTimeLeft.smartBinaryFormat,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: primaryColor,
|
color: primaryColor,
|
||||||
|
|
@ -189,8 +187,7 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
text: 'Report progress every ',
|
text: 'Report progress every ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: playerSettings
|
text: playerSettings.playbackReportInterval.smartBinaryFormat,
|
||||||
.playbackReportInterval.smartBinaryFormat,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: primaryColor,
|
color: primaryColor,
|
||||||
|
|
@ -234,8 +231,7 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
description: const Text(
|
description: const Text(
|
||||||
'Show the total progress of the book in the player',
|
'Show the total progress of the book in the player',
|
||||||
),
|
),
|
||||||
initialValue:
|
initialValue: playerSettings.expandedPlayerSettings.showTotalProgress,
|
||||||
playerSettings.expandedPlayerSettings.showTotalProgress,
|
|
||||||
onToggle: (value) {
|
onToggle: (value) {
|
||||||
ref.read(appSettingsProvider.notifier).update(
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
appSettings.copyWith.playerSettings
|
appSettings.copyWith.playerSettings
|
||||||
|
|
@ -250,13 +246,11 @@ class PlayerSettingsPage extends HookConsumerWidget {
|
||||||
description: const Text(
|
description: const Text(
|
||||||
'Show the progress of the current chapter in the player',
|
'Show the progress of the current chapter in the player',
|
||||||
),
|
),
|
||||||
initialValue:
|
initialValue: playerSettings.expandedPlayerSettings.showChapterProgress,
|
||||||
playerSettings.expandedPlayerSettings.showChapterProgress,
|
|
||||||
onToggle: (value) {
|
onToggle: (value) {
|
||||||
ref.read(appSettingsProvider.notifier).update(
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
appSettings.copyWith.playerSettings(
|
appSettings.copyWith.playerSettings(
|
||||||
expandedPlayerSettings: playerSettings
|
expandedPlayerSettings: playerSettings.expandedPlayerSettings
|
||||||
.expandedPlayerSettings
|
|
||||||
.copyWith(showChapterProgress: value),
|
.copyWith(showChapterProgress: value),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -315,8 +309,7 @@ class SpeedPicker extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final speedController =
|
final speedController = useTextEditingController(text: initialValue.toString());
|
||||||
useTextEditingController(text: initialValue.toString());
|
|
||||||
final speed = useState<double?>(initialValue);
|
final speed = useState<double?>(initialValue);
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Select Speed'),
|
title: const Text('Select Speed'),
|
||||||
|
|
@ -375,8 +368,7 @@ class SpeedOptionsPicker extends HookConsumerWidget {
|
||||||
onDeleted: speed == 1
|
onDeleted: speed == 1
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
speedOptions.value =
|
speedOptions.value = speedOptions.value.where((element) {
|
||||||
speedOptions.value.where((element) {
|
|
||||||
// speed option 1 can't be removed
|
// speed option 1 can't be removed
|
||||||
return element != speed;
|
return element != speed;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
|
||||||
6
lib/shared/extensions/string.dart
Normal file
6
lib/shared/extensions/string.dart
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
extension StringExtension on String {
|
||||||
|
String get capitalize {
|
||||||
|
if (isEmpty) return "";
|
||||||
|
return "${this[0].toUpperCase()}${substring(1)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:vaani/generated/l10n.dart';
|
||||||
|
|
||||||
void showNotImplementedToast(BuildContext context) {
|
void showNotImplementedToast(BuildContext context) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text("Not implemented"),
|
content: Text(S.of(context).notImplemented),
|
||||||
showCloseIcon: true,
|
showCloseIcon: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue