mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-06 11:09:28 +00:00
feat: extensive settings for media controls through notification (#28)
* feat: add notification settings customisation options * feat: add notification settings page and update routing
This commit is contained in:
parent
721b0a87fc
commit
3cf0a0b124
21 changed files with 1391 additions and 376 deletions
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 743 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -8,6 +8,9 @@ import 'package:just_audio/just_audio.dart';
|
||||||
import 'package:just_audio_background/just_audio_background.dart';
|
import 'package:just_audio_background/just_audio_background.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||||
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
|
import 'package:vaani/settings/models/app_settings.dart';
|
||||||
|
import 'package:vaani/shared/extensions/model_conversions.dart';
|
||||||
|
|
||||||
final _logger = Logger('AudiobookPlayer');
|
final _logger = Logger('AudiobookPlayer');
|
||||||
|
|
||||||
|
|
@ -81,6 +84,7 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
List<Uri>? downloadedUris,
|
List<Uri>? downloadedUris,
|
||||||
Uri? artworkUri,
|
Uri? artworkUri,
|
||||||
}) async {
|
}) async {
|
||||||
|
final appSettings = loadOrCreateAppSettings();
|
||||||
// if the book is null, stop the player
|
// if the book is null, stop the player
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
_book = null;
|
_book = null;
|
||||||
|
|
@ -128,8 +132,10 @@ 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:
|
||||||
album: book.metadata.title,
|
title: appSettings.notificationSettings.primaryTitle
|
||||||
title: book.metadata.title ?? track.title,
|
.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',
|
||||||
|
|
@ -198,7 +204,7 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Duration> get positionStream {
|
Stream<Duration> get positionStream {
|
||||||
// return the positioninbook stream
|
// return the positionInBook stream
|
||||||
return super.positionStream.map((position) {
|
return super.positionStream.map((position) {
|
||||||
if (_book == null) {
|
if (_book == null) {
|
||||||
return Duration.zero;
|
return Duration.zero;
|
||||||
|
|
@ -267,3 +273,42 @@ 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 {
|
||||||
|
String formatNotificationTitle(BookExpanded book) {
|
||||||
|
return replaceAllMapped(
|
||||||
|
RegExp(r'\$(\w+)'),
|
||||||
|
(match) {
|
||||||
|
final type = match.group(1);
|
||||||
|
return NotificationTitleType.values
|
||||||
|
.firstWhere((element) => element.stringValue == type)
|
||||||
|
.extractFrom(book) ??
|
||||||
|
match.group(0) ??
|
||||||
|
'';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationTitleUtils on NotificationTitleType {
|
||||||
|
String? extractFrom(BookExpanded book) {
|
||||||
|
var bookMetadataExpanded = book.metadata.asBookMetadataExpanded;
|
||||||
|
switch (this) {
|
||||||
|
case NotificationTitleType.bookTitle:
|
||||||
|
return bookMetadataExpanded.title;
|
||||||
|
case NotificationTitleType.chapterTitle:
|
||||||
|
// TODO: implement chapter title; depends on https://github.com/Dr-Blank/Vaani/issues/2
|
||||||
|
return bookMetadataExpanded.title;
|
||||||
|
case NotificationTitleType.author:
|
||||||
|
return bookMetadataExpanded.authorName;
|
||||||
|
case NotificationTitleType.narrator:
|
||||||
|
return bookMetadataExpanded.narratorName;
|
||||||
|
case NotificationTitleType.series:
|
||||||
|
return bookMetadataExpanded.seriesName;
|
||||||
|
case NotificationTitleType.subtitle:
|
||||||
|
return bookMetadataExpanded.subtitle;
|
||||||
|
case NotificationTitleType.year:
|
||||||
|
return bookMetadataExpanded.publishedYear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
62
lib/features/player/core/init.dart
Normal file
62
lib/features/player/core/init.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:audio_session/audio_session.dart';
|
||||||
|
import 'package:just_audio_background/just_audio_background.dart'
|
||||||
|
show JustAudioBackground, NotificationConfig;
|
||||||
|
import 'package:just_audio_media_kit/just_audio_media_kit.dart'
|
||||||
|
show JustAudioMediaKit;
|
||||||
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
|
import 'package:vaani/settings/models/app_settings.dart';
|
||||||
|
|
||||||
|
Future<void> configurePlayer() async {
|
||||||
|
// for playing audio on windows, linux
|
||||||
|
JustAudioMediaKit.ensureInitialized();
|
||||||
|
|
||||||
|
// for configuring how this app will interact with other audio apps
|
||||||
|
final session = await AudioSession.instance;
|
||||||
|
await session.configure(const AudioSessionConfiguration.speech());
|
||||||
|
|
||||||
|
final appSettings = loadOrCreateAppSettings();
|
||||||
|
|
||||||
|
// for playing audio in the background
|
||||||
|
await JustAudioBackground.init(
|
||||||
|
androidNotificationChannelId: 'com.vaani.bg_demo.channel.audio',
|
||||||
|
androidNotificationChannelName: 'Audio playback',
|
||||||
|
androidNotificationOngoing: true,
|
||||||
|
androidStopForegroundOnPause: true,
|
||||||
|
androidNotificationChannelDescription: 'Audio playback in the background',
|
||||||
|
androidNotificationIcon: 'drawable/ic_stat_notification_logo',
|
||||||
|
rewindInterval: appSettings.notificationSettings.rewindInterval,
|
||||||
|
fastForwardInterval: appSettings.notificationSettings.fastForwardInterval,
|
||||||
|
androidShowNotificationBadge: false,
|
||||||
|
notificationConfigBuilder: (state) {
|
||||||
|
final controls = [
|
||||||
|
if (appSettings.notificationSettings.mediaControls
|
||||||
|
.contains(NotificationMediaControl.skipToPreviousChapter) &&
|
||||||
|
state.hasPrevious)
|
||||||
|
MediaControl.skipToPrevious,
|
||||||
|
if (appSettings.notificationSettings.mediaControls
|
||||||
|
.contains(NotificationMediaControl.rewind))
|
||||||
|
MediaControl.rewind,
|
||||||
|
if (state.playing) MediaControl.pause else MediaControl.play,
|
||||||
|
if (appSettings.notificationSettings.mediaControls
|
||||||
|
.contains(NotificationMediaControl.fastForward))
|
||||||
|
MediaControl.fastForward,
|
||||||
|
if (appSettings.notificationSettings.mediaControls
|
||||||
|
.contains(NotificationMediaControl.skipToNextChapter) &&
|
||||||
|
state.hasNext)
|
||||||
|
MediaControl.skipToNext,
|
||||||
|
if (appSettings.notificationSettings.mediaControls
|
||||||
|
.contains(NotificationMediaControl.stop))
|
||||||
|
MediaControl.stop,
|
||||||
|
];
|
||||||
|
return NotificationConfig(
|
||||||
|
controls: controls,
|
||||||
|
systemActions: const {
|
||||||
|
MediaAction.seek,
|
||||||
|
MediaAction.seekForward,
|
||||||
|
MediaAction.seekBackward,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
import 'package:audio_session/audio_session.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:just_audio_background/just_audio_background.dart'
|
|
||||||
show JustAudioBackground;
|
|
||||||
import 'package:just_audio_media_kit/just_audio_media_kit.dart'
|
|
||||||
show JustAudioMediaKit;
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:vaani/api/server_provider.dart';
|
import 'package:vaani/api/server_provider.dart';
|
||||||
import 'package:vaani/db/storage.dart';
|
import 'package:vaani/db/storage.dart';
|
||||||
import 'package:vaani/features/downloads/providers/download_manager.dart';
|
import 'package:vaani/features/downloads/providers/download_manager.dart';
|
||||||
import 'package:vaani/features/playback_reporting/providers/playback_reporter_provider.dart';
|
import 'package:vaani/features/playback_reporting/providers/playback_reporter_provider.dart';
|
||||||
|
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';
|
||||||
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/router/router.dart';
|
import 'package:vaani/router/router.dart';
|
||||||
|
|
@ -31,23 +27,12 @@ void main() async {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// for playing audio on windows, linux
|
|
||||||
JustAudioMediaKit.ensureInitialized();
|
|
||||||
|
|
||||||
// initialize the storage
|
// initialize the storage
|
||||||
await initStorage();
|
await initStorage();
|
||||||
|
|
||||||
// for configuring how this app will interact with other audio apps
|
// initialize audio player
|
||||||
final session = await AudioSession.instance;
|
await configurePlayer();
|
||||||
await session.configure(const AudioSessionConfiguration.speech());
|
|
||||||
|
|
||||||
// for playing audio in the background
|
|
||||||
await JustAudioBackground.init(
|
|
||||||
androidNotificationChannelId: 'com.vaani.bg_demo.channel.audio',
|
|
||||||
androidNotificationChannelName: 'Audio playback',
|
|
||||||
androidNotificationOngoing: true,
|
|
||||||
androidNotificationIcon: 'mipmap/launcher_icon',
|
|
||||||
);
|
|
||||||
|
|
||||||
// run the app
|
// run the app
|
||||||
runApp(
|
runApp(
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,14 @@ class Routes {
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
);
|
);
|
||||||
static const autoSleepTimerSettings = _SimpleRoute(
|
static const autoSleepTimerSettings = _SimpleRoute(
|
||||||
pathName: 'autosleeptimer',
|
pathName: 'autoSleepTimer',
|
||||||
name: 'autoSleepTimerSettings',
|
name: 'autoSleepTimerSettings',
|
||||||
// parentRoute: settings,
|
parentRoute: settings,
|
||||||
|
);
|
||||||
|
static const notificationSettings = _SimpleRoute(
|
||||||
|
pathName: 'notifications',
|
||||||
|
name: 'notificationSettings',
|
||||||
|
parentRoute: settings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// search and explore
|
// search and explore
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import 'package:vaani/features/you/view/you_page.dart';
|
||||||
import 'package:vaani/pages/home_page.dart';
|
import 'package:vaani/pages/home_page.dart';
|
||||||
import 'package:vaani/settings/view/app_settings_page.dart';
|
import 'package:vaani/settings/view/app_settings_page.dart';
|
||||||
import 'package:vaani/settings/view/auto_sleep_timer_settings_page.dart';
|
import 'package:vaani/settings/view/auto_sleep_timer_settings_page.dart';
|
||||||
|
import 'package:vaani/settings/view/notification_settings_page.dart';
|
||||||
|
|
||||||
import 'scaffold_with_nav_bar.dart';
|
import 'scaffold_with_nav_bar.dart';
|
||||||
import 'transitions/slide.dart';
|
import 'transitions/slide.dart';
|
||||||
|
|
@ -172,16 +173,23 @@ class MyAppRouter {
|
||||||
name: Routes.settings.name,
|
name: Routes.settings.name,
|
||||||
// builder: (context, state) => const AppSettingsPage(),
|
// builder: (context, state) => const AppSettingsPage(),
|
||||||
pageBuilder: defaultPageBuilder(const AppSettingsPage()),
|
pageBuilder: defaultPageBuilder(const AppSettingsPage()),
|
||||||
),
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: Routes.autoSleepTimerSettings.localPath,
|
path: Routes.autoSleepTimerSettings.pathName,
|
||||||
name: Routes.autoSleepTimerSettings.name,
|
name: Routes.autoSleepTimerSettings.name,
|
||||||
// builder: (context, state) =>
|
|
||||||
// const AutoSleepTimerSettingsPage(),
|
|
||||||
pageBuilder: defaultPageBuilder(
|
pageBuilder: defaultPageBuilder(
|
||||||
const AutoSleepTimerSettingsPage(),
|
const AutoSleepTimerSettingsPage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.notificationSettings.pathName,
|
||||||
|
name: Routes.notificationSettings.name,
|
||||||
|
pageBuilder: defaultPageBuilder(
|
||||||
|
const NotificationSettingsPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: Routes.userManagement.localPath,
|
path: Routes.userManagement.localPath,
|
||||||
name: Routes.userManagement.name,
|
name: Routes.userManagement.name,
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,29 @@ final _box = AvailableHiveBoxes.userPrefsBox;
|
||||||
|
|
||||||
final _logger = Logger('AppSettingsProvider');
|
final _logger = Logger('AppSettingsProvider');
|
||||||
|
|
||||||
model.AppSettings readFromBoxOrCreate() {
|
model.AppSettings loadOrCreateAppSettings() {
|
||||||
// see if the settings are already in the box
|
// see if the settings are already in the box
|
||||||
|
model.AppSettings? settings;
|
||||||
if (_box.isNotEmpty) {
|
if (_box.isNotEmpty) {
|
||||||
final foundSettings = _box.getAt(0);
|
try {
|
||||||
_logger.fine('found settings in box: $foundSettings');
|
settings = _box.getAt(0);
|
||||||
return foundSettings;
|
_logger.fine('found settings in box: $settings');
|
||||||
} else {
|
} catch (e) {
|
||||||
// create a new settings object
|
_logger.warning('error reading settings from box: $e'
|
||||||
const settings = model.AppSettings();
|
'\nclearing box');
|
||||||
_logger.fine('created new settings: $settings');
|
_box.clear();
|
||||||
return settings;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
_logger.fine('no settings found in box, creating new settings');
|
||||||
|
}
|
||||||
|
return settings ?? const model.AppSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class AppSettings extends _$AppSettings {
|
class AppSettings extends _$AppSettings {
|
||||||
@override
|
@override
|
||||||
model.AppSettings build() {
|
model.AppSettings build() {
|
||||||
state = readFromBoxOrCreate();
|
state = loadOrCreateAppSettings();
|
||||||
ref.listenSelf((_, __) {
|
ref.listenSelf((_, __) {
|
||||||
writeToBox();
|
writeToBox();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'app_settings_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$appSettingsHash() => r'e0e132b782b97f11d9791d4f1e45bf4ee67dd99b';
|
String _$appSettingsHash() => r'f51d55f117692d4fb9f4b4febf02906c0953d334';
|
||||||
|
|
||||||
/// See also [AppSettings].
|
/// See also [AppSettings].
|
||||||
@ProviderFor(AppSettings)
|
@ProviderFor(AppSettings)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// a freezed class to store the settings of the app
|
// a freezed class to store the settings of the app
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'app_settings.freezed.dart';
|
part 'app_settings.freezed.dart';
|
||||||
|
|
@ -14,6 +15,7 @@ class AppSettings with _$AppSettings {
|
||||||
@Default(ThemeSettings()) ThemeSettings themeSettings,
|
@Default(ThemeSettings()) ThemeSettings themeSettings,
|
||||||
@Default(PlayerSettings()) PlayerSettings playerSettings,
|
@Default(PlayerSettings()) PlayerSettings playerSettings,
|
||||||
@Default(DownloadSettings()) DownloadSettings downloadSettings,
|
@Default(DownloadSettings()) DownloadSettings downloadSettings,
|
||||||
|
@Default(NotificationSettings()) NotificationSettings notificationSettings,
|
||||||
}) = _AppSettings;
|
}) = _AppSettings;
|
||||||
|
|
||||||
factory AppSettings.fromJson(Map<String, dynamic> json) =>
|
factory AppSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
@ -133,3 +135,53 @@ class DownloadSettings with _$DownloadSettings {
|
||||||
factory DownloadSettings.fromJson(Map<String, dynamic> json) =>
|
factory DownloadSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
_$DownloadSettingsFromJson(json);
|
_$DownloadSettingsFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class NotificationSettings with _$NotificationSettings {
|
||||||
|
const factory NotificationSettings({
|
||||||
|
@Default(Duration(seconds: 30)) Duration fastForwardInterval,
|
||||||
|
@Default(Duration(seconds: 10)) Duration rewindInterval,
|
||||||
|
@Default(true) bool progressBarIsChapterProgress,
|
||||||
|
@Default('\$bookTitle') String primaryTitle,
|
||||||
|
@Default('\$author') String secondaryTitle,
|
||||||
|
@Default(
|
||||||
|
[
|
||||||
|
NotificationMediaControl.rewind,
|
||||||
|
NotificationMediaControl.fastForward,
|
||||||
|
NotificationMediaControl.skipToPreviousChapter,
|
||||||
|
NotificationMediaControl.skipToNextChapter,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
List<NotificationMediaControl> mediaControls,
|
||||||
|
}) = _NotificationSettings;
|
||||||
|
|
||||||
|
factory NotificationSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$NotificationSettingsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationTitleType {
|
||||||
|
chapterTitle('chapterTitle'),
|
||||||
|
bookTitle('bookTitle'),
|
||||||
|
author('author'),
|
||||||
|
subtitle('subtitle'),
|
||||||
|
series('series'),
|
||||||
|
narrator('narrator'),
|
||||||
|
year('year');
|
||||||
|
|
||||||
|
const NotificationTitleType(this.stringValue);
|
||||||
|
|
||||||
|
final String stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationMediaControl {
|
||||||
|
fastForward(Icons.fast_forward),
|
||||||
|
rewind(Icons.fast_rewind),
|
||||||
|
speedToggle(Icons.speed),
|
||||||
|
stop(Icons.stop),
|
||||||
|
skipToNextChapter(Icons.skip_next),
|
||||||
|
skipToPreviousChapter(Icons.skip_previous);
|
||||||
|
|
||||||
|
const NotificationMediaControl(this.icon);
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ mixin _$AppSettings {
|
||||||
ThemeSettings get themeSettings => throw _privateConstructorUsedError;
|
ThemeSettings get themeSettings => throw _privateConstructorUsedError;
|
||||||
PlayerSettings get playerSettings => throw _privateConstructorUsedError;
|
PlayerSettings get playerSettings => throw _privateConstructorUsedError;
|
||||||
DownloadSettings get downloadSettings => throw _privateConstructorUsedError;
|
DownloadSettings get downloadSettings => throw _privateConstructorUsedError;
|
||||||
|
NotificationSettings get notificationSettings =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this AppSettings to a JSON map.
|
/// Serializes this AppSettings to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
@ -43,11 +45,13 @@ abstract class $AppSettingsCopyWith<$Res> {
|
||||||
$Res call(
|
$Res call(
|
||||||
{ThemeSettings themeSettings,
|
{ThemeSettings themeSettings,
|
||||||
PlayerSettings playerSettings,
|
PlayerSettings playerSettings,
|
||||||
DownloadSettings downloadSettings});
|
DownloadSettings downloadSettings,
|
||||||
|
NotificationSettings notificationSettings});
|
||||||
|
|
||||||
$ThemeSettingsCopyWith<$Res> get themeSettings;
|
$ThemeSettingsCopyWith<$Res> get themeSettings;
|
||||||
$PlayerSettingsCopyWith<$Res> get playerSettings;
|
$PlayerSettingsCopyWith<$Res> get playerSettings;
|
||||||
$DownloadSettingsCopyWith<$Res> get downloadSettings;
|
$DownloadSettingsCopyWith<$Res> get downloadSettings;
|
||||||
|
$NotificationSettingsCopyWith<$Res> get notificationSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -68,6 +72,7 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
||||||
Object? themeSettings = null,
|
Object? themeSettings = null,
|
||||||
Object? playerSettings = null,
|
Object? playerSettings = null,
|
||||||
Object? downloadSettings = null,
|
Object? downloadSettings = null,
|
||||||
|
Object? notificationSettings = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
themeSettings: null == themeSettings
|
themeSettings: null == themeSettings
|
||||||
|
|
@ -82,6 +87,10 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
||||||
? _value.downloadSettings
|
? _value.downloadSettings
|
||||||
: downloadSettings // ignore: cast_nullable_to_non_nullable
|
: downloadSettings // ignore: cast_nullable_to_non_nullable
|
||||||
as DownloadSettings,
|
as DownloadSettings,
|
||||||
|
notificationSettings: null == notificationSettings
|
||||||
|
? _value.notificationSettings
|
||||||
|
: notificationSettings // ignore: cast_nullable_to_non_nullable
|
||||||
|
as NotificationSettings,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,6 +123,17 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
||||||
return _then(_value.copyWith(downloadSettings: value) as $Val);
|
return _then(_value.copyWith(downloadSettings: value) as $Val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of AppSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$NotificationSettingsCopyWith<$Res> get notificationSettings {
|
||||||
|
return $NotificationSettingsCopyWith<$Res>(_value.notificationSettings,
|
||||||
|
(value) {
|
||||||
|
return _then(_value.copyWith(notificationSettings: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -127,7 +147,8 @@ abstract class _$$AppSettingsImplCopyWith<$Res>
|
||||||
$Res call(
|
$Res call(
|
||||||
{ThemeSettings themeSettings,
|
{ThemeSettings themeSettings,
|
||||||
PlayerSettings playerSettings,
|
PlayerSettings playerSettings,
|
||||||
DownloadSettings downloadSettings});
|
DownloadSettings downloadSettings,
|
||||||
|
NotificationSettings notificationSettings});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$ThemeSettingsCopyWith<$Res> get themeSettings;
|
$ThemeSettingsCopyWith<$Res> get themeSettings;
|
||||||
|
|
@ -135,6 +156,8 @@ abstract class _$$AppSettingsImplCopyWith<$Res>
|
||||||
$PlayerSettingsCopyWith<$Res> get playerSettings;
|
$PlayerSettingsCopyWith<$Res> get playerSettings;
|
||||||
@override
|
@override
|
||||||
$DownloadSettingsCopyWith<$Res> get downloadSettings;
|
$DownloadSettingsCopyWith<$Res> get downloadSettings;
|
||||||
|
@override
|
||||||
|
$NotificationSettingsCopyWith<$Res> get notificationSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -153,6 +176,7 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
|
||||||
Object? themeSettings = null,
|
Object? themeSettings = null,
|
||||||
Object? playerSettings = null,
|
Object? playerSettings = null,
|
||||||
Object? downloadSettings = null,
|
Object? downloadSettings = null,
|
||||||
|
Object? notificationSettings = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$AppSettingsImpl(
|
return _then(_$AppSettingsImpl(
|
||||||
themeSettings: null == themeSettings
|
themeSettings: null == themeSettings
|
||||||
|
|
@ -167,6 +191,10 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
|
||||||
? _value.downloadSettings
|
? _value.downloadSettings
|
||||||
: downloadSettings // ignore: cast_nullable_to_non_nullable
|
: downloadSettings // ignore: cast_nullable_to_non_nullable
|
||||||
as DownloadSettings,
|
as DownloadSettings,
|
||||||
|
notificationSettings: null == notificationSettings
|
||||||
|
? _value.notificationSettings
|
||||||
|
: notificationSettings // ignore: cast_nullable_to_non_nullable
|
||||||
|
as NotificationSettings,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +205,8 @@ class _$AppSettingsImpl implements _AppSettings {
|
||||||
const _$AppSettingsImpl(
|
const _$AppSettingsImpl(
|
||||||
{this.themeSettings = const ThemeSettings(),
|
{this.themeSettings = const ThemeSettings(),
|
||||||
this.playerSettings = const PlayerSettings(),
|
this.playerSettings = const PlayerSettings(),
|
||||||
this.downloadSettings = const DownloadSettings()});
|
this.downloadSettings = const DownloadSettings(),
|
||||||
|
this.notificationSettings = const NotificationSettings()});
|
||||||
|
|
||||||
factory _$AppSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$AppSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$AppSettingsImplFromJson(json);
|
_$$AppSettingsImplFromJson(json);
|
||||||
|
|
@ -191,10 +220,13 @@ class _$AppSettingsImpl implements _AppSettings {
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final DownloadSettings downloadSettings;
|
final DownloadSettings downloadSettings;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final NotificationSettings notificationSettings;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AppSettings(themeSettings: $themeSettings, playerSettings: $playerSettings, downloadSettings: $downloadSettings)';
|
return 'AppSettings(themeSettings: $themeSettings, playerSettings: $playerSettings, downloadSettings: $downloadSettings, notificationSettings: $notificationSettings)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -207,13 +239,15 @@ class _$AppSettingsImpl implements _AppSettings {
|
||||||
(identical(other.playerSettings, playerSettings) ||
|
(identical(other.playerSettings, playerSettings) ||
|
||||||
other.playerSettings == playerSettings) &&
|
other.playerSettings == playerSettings) &&
|
||||||
(identical(other.downloadSettings, downloadSettings) ||
|
(identical(other.downloadSettings, downloadSettings) ||
|
||||||
other.downloadSettings == downloadSettings));
|
other.downloadSettings == downloadSettings) &&
|
||||||
|
(identical(other.notificationSettings, notificationSettings) ||
|
||||||
|
other.notificationSettings == notificationSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(runtimeType, themeSettings, playerSettings,
|
||||||
Object.hash(runtimeType, themeSettings, playerSettings, downloadSettings);
|
downloadSettings, notificationSettings);
|
||||||
|
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
@ -235,7 +269,8 @@ abstract class _AppSettings implements AppSettings {
|
||||||
const factory _AppSettings(
|
const factory _AppSettings(
|
||||||
{final ThemeSettings themeSettings,
|
{final ThemeSettings themeSettings,
|
||||||
final PlayerSettings playerSettings,
|
final PlayerSettings playerSettings,
|
||||||
final DownloadSettings downloadSettings}) = _$AppSettingsImpl;
|
final DownloadSettings downloadSettings,
|
||||||
|
final NotificationSettings notificationSettings}) = _$AppSettingsImpl;
|
||||||
|
|
||||||
factory _AppSettings.fromJson(Map<String, dynamic> json) =
|
factory _AppSettings.fromJson(Map<String, dynamic> json) =
|
||||||
_$AppSettingsImpl.fromJson;
|
_$AppSettingsImpl.fromJson;
|
||||||
|
|
@ -246,6 +281,8 @@ abstract class _AppSettings implements AppSettings {
|
||||||
PlayerSettings get playerSettings;
|
PlayerSettings get playerSettings;
|
||||||
@override
|
@override
|
||||||
DownloadSettings get downloadSettings;
|
DownloadSettings get downloadSettings;
|
||||||
|
@override
|
||||||
|
NotificationSettings get notificationSettings;
|
||||||
|
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
@ -1935,3 +1972,293 @@ abstract class _DownloadSettings implements DownloadSettings {
|
||||||
_$$DownloadSettingsImplCopyWith<_$DownloadSettingsImpl> get copyWith =>
|
_$$DownloadSettingsImplCopyWith<_$DownloadSettingsImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationSettings _$NotificationSettingsFromJson(Map<String, dynamic> json) {
|
||||||
|
return _NotificationSettings.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$NotificationSettings {
|
||||||
|
Duration get fastForwardInterval => throw _privateConstructorUsedError;
|
||||||
|
Duration get rewindInterval => throw _privateConstructorUsedError;
|
||||||
|
bool get progressBarIsChapterProgress => throw _privateConstructorUsedError;
|
||||||
|
String get primaryTitle => throw _privateConstructorUsedError;
|
||||||
|
String get secondaryTitle => throw _privateConstructorUsedError;
|
||||||
|
List<NotificationMediaControl> get mediaControls =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this NotificationSettings to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of NotificationSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$NotificationSettingsCopyWith<NotificationSettings> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $NotificationSettingsCopyWith<$Res> {
|
||||||
|
factory $NotificationSettingsCopyWith(NotificationSettings value,
|
||||||
|
$Res Function(NotificationSettings) then) =
|
||||||
|
_$NotificationSettingsCopyWithImpl<$Res, NotificationSettings>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{Duration fastForwardInterval,
|
||||||
|
Duration rewindInterval,
|
||||||
|
bool progressBarIsChapterProgress,
|
||||||
|
String primaryTitle,
|
||||||
|
String secondaryTitle,
|
||||||
|
List<NotificationMediaControl> mediaControls});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$NotificationSettingsCopyWithImpl<$Res,
|
||||||
|
$Val extends NotificationSettings>
|
||||||
|
implements $NotificationSettingsCopyWith<$Res> {
|
||||||
|
_$NotificationSettingsCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of NotificationSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? fastForwardInterval = null,
|
||||||
|
Object? rewindInterval = null,
|
||||||
|
Object? progressBarIsChapterProgress = null,
|
||||||
|
Object? primaryTitle = null,
|
||||||
|
Object? secondaryTitle = null,
|
||||||
|
Object? mediaControls = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
fastForwardInterval: null == fastForwardInterval
|
||||||
|
? _value.fastForwardInterval
|
||||||
|
: fastForwardInterval // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
|
rewindInterval: null == rewindInterval
|
||||||
|
? _value.rewindInterval
|
||||||
|
: rewindInterval // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
|
progressBarIsChapterProgress: null == progressBarIsChapterProgress
|
||||||
|
? _value.progressBarIsChapterProgress
|
||||||
|
: progressBarIsChapterProgress // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
primaryTitle: null == primaryTitle
|
||||||
|
? _value.primaryTitle
|
||||||
|
: primaryTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
secondaryTitle: null == secondaryTitle
|
||||||
|
? _value.secondaryTitle
|
||||||
|
: secondaryTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
mediaControls: null == mediaControls
|
||||||
|
? _value.mediaControls
|
||||||
|
: mediaControls // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<NotificationMediaControl>,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$NotificationSettingsImplCopyWith<$Res>
|
||||||
|
implements $NotificationSettingsCopyWith<$Res> {
|
||||||
|
factory _$$NotificationSettingsImplCopyWith(_$NotificationSettingsImpl value,
|
||||||
|
$Res Function(_$NotificationSettingsImpl) then) =
|
||||||
|
__$$NotificationSettingsImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{Duration fastForwardInterval,
|
||||||
|
Duration rewindInterval,
|
||||||
|
bool progressBarIsChapterProgress,
|
||||||
|
String primaryTitle,
|
||||||
|
String secondaryTitle,
|
||||||
|
List<NotificationMediaControl> mediaControls});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$NotificationSettingsImplCopyWithImpl<$Res>
|
||||||
|
extends _$NotificationSettingsCopyWithImpl<$Res, _$NotificationSettingsImpl>
|
||||||
|
implements _$$NotificationSettingsImplCopyWith<$Res> {
|
||||||
|
__$$NotificationSettingsImplCopyWithImpl(_$NotificationSettingsImpl _value,
|
||||||
|
$Res Function(_$NotificationSettingsImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of NotificationSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? fastForwardInterval = null,
|
||||||
|
Object? rewindInterval = null,
|
||||||
|
Object? progressBarIsChapterProgress = null,
|
||||||
|
Object? primaryTitle = null,
|
||||||
|
Object? secondaryTitle = null,
|
||||||
|
Object? mediaControls = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$NotificationSettingsImpl(
|
||||||
|
fastForwardInterval: null == fastForwardInterval
|
||||||
|
? _value.fastForwardInterval
|
||||||
|
: fastForwardInterval // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
|
rewindInterval: null == rewindInterval
|
||||||
|
? _value.rewindInterval
|
||||||
|
: rewindInterval // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Duration,
|
||||||
|
progressBarIsChapterProgress: null == progressBarIsChapterProgress
|
||||||
|
? _value.progressBarIsChapterProgress
|
||||||
|
: progressBarIsChapterProgress // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
primaryTitle: null == primaryTitle
|
||||||
|
? _value.primaryTitle
|
||||||
|
: primaryTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
secondaryTitle: null == secondaryTitle
|
||||||
|
? _value.secondaryTitle
|
||||||
|
: secondaryTitle // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
mediaControls: null == mediaControls
|
||||||
|
? _value._mediaControls
|
||||||
|
: mediaControls // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<NotificationMediaControl>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$NotificationSettingsImpl implements _NotificationSettings {
|
||||||
|
const _$NotificationSettingsImpl(
|
||||||
|
{this.fastForwardInterval = const Duration(seconds: 30),
|
||||||
|
this.rewindInterval = const Duration(seconds: 10),
|
||||||
|
this.progressBarIsChapterProgress = true,
|
||||||
|
this.primaryTitle = '\$bookTitle',
|
||||||
|
this.secondaryTitle = '\$author',
|
||||||
|
final List<NotificationMediaControl> mediaControls = const [
|
||||||
|
NotificationMediaControl.rewind,
|
||||||
|
NotificationMediaControl.fastForward,
|
||||||
|
NotificationMediaControl.skipToPreviousChapter,
|
||||||
|
NotificationMediaControl.skipToNextChapter
|
||||||
|
]})
|
||||||
|
: _mediaControls = mediaControls;
|
||||||
|
|
||||||
|
factory _$NotificationSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$NotificationSettingsImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final Duration fastForwardInterval;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final Duration rewindInterval;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool progressBarIsChapterProgress;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String primaryTitle;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String secondaryTitle;
|
||||||
|
final List<NotificationMediaControl> _mediaControls;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
List<NotificationMediaControl> get mediaControls {
|
||||||
|
if (_mediaControls is EqualUnmodifiableListView) return _mediaControls;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_mediaControls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'NotificationSettings(fastForwardInterval: $fastForwardInterval, rewindInterval: $rewindInterval, progressBarIsChapterProgress: $progressBarIsChapterProgress, primaryTitle: $primaryTitle, secondaryTitle: $secondaryTitle, mediaControls: $mediaControls)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$NotificationSettingsImpl &&
|
||||||
|
(identical(other.fastForwardInterval, fastForwardInterval) ||
|
||||||
|
other.fastForwardInterval == fastForwardInterval) &&
|
||||||
|
(identical(other.rewindInterval, rewindInterval) ||
|
||||||
|
other.rewindInterval == rewindInterval) &&
|
||||||
|
(identical(other.progressBarIsChapterProgress,
|
||||||
|
progressBarIsChapterProgress) ||
|
||||||
|
other.progressBarIsChapterProgress ==
|
||||||
|
progressBarIsChapterProgress) &&
|
||||||
|
(identical(other.primaryTitle, primaryTitle) ||
|
||||||
|
other.primaryTitle == primaryTitle) &&
|
||||||
|
(identical(other.secondaryTitle, secondaryTitle) ||
|
||||||
|
other.secondaryTitle == secondaryTitle) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._mediaControls, _mediaControls));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
fastForwardInterval,
|
||||||
|
rewindInterval,
|
||||||
|
progressBarIsChapterProgress,
|
||||||
|
primaryTitle,
|
||||||
|
secondaryTitle,
|
||||||
|
const DeepCollectionEquality().hash(_mediaControls));
|
||||||
|
|
||||||
|
/// Create a copy of NotificationSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$NotificationSettingsImplCopyWith<_$NotificationSettingsImpl>
|
||||||
|
get copyWith =>
|
||||||
|
__$$NotificationSettingsImplCopyWithImpl<_$NotificationSettingsImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$NotificationSettingsImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _NotificationSettings implements NotificationSettings {
|
||||||
|
const factory _NotificationSettings(
|
||||||
|
{final Duration fastForwardInterval,
|
||||||
|
final Duration rewindInterval,
|
||||||
|
final bool progressBarIsChapterProgress,
|
||||||
|
final String primaryTitle,
|
||||||
|
final String secondaryTitle,
|
||||||
|
final List<NotificationMediaControl> mediaControls}) =
|
||||||
|
_$NotificationSettingsImpl;
|
||||||
|
|
||||||
|
factory _NotificationSettings.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$NotificationSettingsImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get fastForwardInterval;
|
||||||
|
@override
|
||||||
|
Duration get rewindInterval;
|
||||||
|
@override
|
||||||
|
bool get progressBarIsChapterProgress;
|
||||||
|
@override
|
||||||
|
String get primaryTitle;
|
||||||
|
@override
|
||||||
|
String get secondaryTitle;
|
||||||
|
@override
|
||||||
|
List<NotificationMediaControl> get mediaControls;
|
||||||
|
|
||||||
|
/// Create a copy of NotificationSettings
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$NotificationSettingsImplCopyWith<_$NotificationSettingsImpl>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ _$AppSettingsImpl _$$AppSettingsImplFromJson(Map<String, dynamic> json) =>
|
||||||
? const DownloadSettings()
|
? const DownloadSettings()
|
||||||
: DownloadSettings.fromJson(
|
: DownloadSettings.fromJson(
|
||||||
json['downloadSettings'] as Map<String, dynamic>),
|
json['downloadSettings'] as Map<String, dynamic>),
|
||||||
|
notificationSettings: json['notificationSettings'] == null
|
||||||
|
? const NotificationSettings()
|
||||||
|
: NotificationSettings.fromJson(
|
||||||
|
json['notificationSettings'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) =>
|
Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) =>
|
||||||
|
|
@ -27,6 +31,7 @@ Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) =>
|
||||||
'themeSettings': instance.themeSettings,
|
'themeSettings': instance.themeSettings,
|
||||||
'playerSettings': instance.playerSettings,
|
'playerSettings': instance.playerSettings,
|
||||||
'downloadSettings': instance.downloadSettings,
|
'downloadSettings': instance.downloadSettings,
|
||||||
|
'notificationSettings': instance.notificationSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$ThemeSettingsImpl _$$ThemeSettingsImplFromJson(Map<String, dynamic> json) =>
|
_$ThemeSettingsImpl _$$ThemeSettingsImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
|
@ -203,3 +208,50 @@ Map<String, dynamic> _$$DownloadSettingsImplToJson(
|
||||||
'maxConcurrentByHost': instance.maxConcurrentByHost,
|
'maxConcurrentByHost': instance.maxConcurrentByHost,
|
||||||
'maxConcurrentByGroup': instance.maxConcurrentByGroup,
|
'maxConcurrentByGroup': instance.maxConcurrentByGroup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_$NotificationSettingsImpl _$$NotificationSettingsImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$NotificationSettingsImpl(
|
||||||
|
fastForwardInterval: json['fastForwardInterval'] == null
|
||||||
|
? const Duration(seconds: 30)
|
||||||
|
: Duration(
|
||||||
|
microseconds: (json['fastForwardInterval'] as num).toInt()),
|
||||||
|
rewindInterval: json['rewindInterval'] == null
|
||||||
|
? const Duration(seconds: 10)
|
||||||
|
: Duration(microseconds: (json['rewindInterval'] as num).toInt()),
|
||||||
|
progressBarIsChapterProgress:
|
||||||
|
json['progressBarIsChapterProgress'] as bool? ?? true,
|
||||||
|
primaryTitle: json['primaryTitle'] as String? ?? '\$bookTitle',
|
||||||
|
secondaryTitle: json['secondaryTitle'] as String? ?? '\$author',
|
||||||
|
mediaControls: (json['mediaControls'] as List<dynamic>?)
|
||||||
|
?.map((e) => $enumDecode(_$NotificationMediaControlEnumMap, e))
|
||||||
|
.toList() ??
|
||||||
|
const [
|
||||||
|
NotificationMediaControl.rewind,
|
||||||
|
NotificationMediaControl.fastForward,
|
||||||
|
NotificationMediaControl.skipToPreviousChapter,
|
||||||
|
NotificationMediaControl.skipToNextChapter
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$NotificationSettingsImplToJson(
|
||||||
|
_$NotificationSettingsImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'fastForwardInterval': instance.fastForwardInterval.inMicroseconds,
|
||||||
|
'rewindInterval': instance.rewindInterval.inMicroseconds,
|
||||||
|
'progressBarIsChapterProgress': instance.progressBarIsChapterProgress,
|
||||||
|
'primaryTitle': instance.primaryTitle,
|
||||||
|
'secondaryTitle': instance.secondaryTitle,
|
||||||
|
'mediaControls': instance.mediaControls
|
||||||
|
.map((e) => _$NotificationMediaControlEnumMap[e]!)
|
||||||
|
.toList(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$NotificationMediaControlEnumMap = {
|
||||||
|
NotificationMediaControl.fastForward: 'fastForward',
|
||||||
|
NotificationMediaControl.rewind: 'rewind',
|
||||||
|
NotificationMediaControl.speedToggle: 'speedToggle',
|
||||||
|
NotificationMediaControl.stop: 'stop',
|
||||||
|
NotificationMediaControl.skipToNextChapter: 'skipToNextChapter',
|
||||||
|
NotificationMediaControl.skipToPreviousChapter: 'skipToPreviousChapter',
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import 'package:vaani/api/server_provider.dart';
|
||||||
import 'package:vaani/router/router.dart';
|
import 'package:vaani/router/router.dart';
|
||||||
import 'package:vaani/settings/app_settings_provider.dart';
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
import 'package:vaani/settings/models/app_settings.dart' as model;
|
import 'package:vaani/settings/models/app_settings.dart' as model;
|
||||||
|
import 'package:vaani/settings/view/simple_settings_page.dart';
|
||||||
|
|
||||||
class AppSettingsPage extends HookConsumerWidget {
|
class AppSettingsPage extends HookConsumerWidget {
|
||||||
const AppSettingsPage({
|
const AppSettingsPage({
|
||||||
|
|
@ -26,12 +27,32 @@ class AppSettingsPage extends HookConsumerWidget {
|
||||||
final serverURIController = useTextEditingController();
|
final serverURIController = useTextEditingController();
|
||||||
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
||||||
|
|
||||||
return Scaffold(
|
return SimpleSettingsPage(
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('App Settings'),
|
title: const Text('App Settings'),
|
||||||
),
|
|
||||||
body: SettingsList(
|
|
||||||
sections: [
|
sections: [
|
||||||
|
// General section
|
||||||
|
SettingsSection(
|
||||||
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'General',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
tiles: [
|
||||||
|
SettingsTile(
|
||||||
|
title: const Text('Notification Media Player'),
|
||||||
|
leading: const Icon(Icons.play_lesson),
|
||||||
|
description: const Text(
|
||||||
|
'Customize the media player in notifications',
|
||||||
|
),
|
||||||
|
onPressed: (context) {
|
||||||
|
context.pushNamed(Routes.notificationSettings.name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
// Appearance section
|
// Appearance section
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
margin: const EdgeInsetsDirectional.symmetric(
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
|
|
@ -272,7 +293,6 @@ class AppSettingsPage extends HookConsumerWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.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/settings/app_settings_provider.dart';
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
|
import 'package:vaani/settings/view/simple_settings_page.dart';
|
||||||
import 'package:vaani/shared/extensions/time_of_day.dart';
|
import 'package:vaani/shared/extensions/time_of_day.dart';
|
||||||
|
|
||||||
class AutoSleepTimerSettingsPage extends HookConsumerWidget {
|
class AutoSleepTimerSettingsPage extends HookConsumerWidget {
|
||||||
|
|
@ -14,11 +15,8 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget {
|
||||||
final appSettings = ref.watch(appSettingsProvider);
|
final appSettings = ref.watch(appSettingsProvider);
|
||||||
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
final sleepTimerSettings = appSettings.playerSettings.sleepTimerSettings;
|
||||||
|
|
||||||
return Scaffold(
|
return SimpleSettingsPage(
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Auto Sleep Timer Settings'),
|
title: const Text('Auto Sleep Timer Settings'),
|
||||||
),
|
|
||||||
body: SettingsList(
|
|
||||||
sections: [
|
sections: [
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
margin: const EdgeInsetsDirectional.symmetric(
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
|
|
@ -55,22 +53,18 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget {
|
||||||
// navigate to the time picker
|
// navigate to the time picker
|
||||||
final selected = await showTimePicker(
|
final selected = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialTime:
|
initialTime: sleepTimerSettings.autoTurnOnTime.toTimeOfDay(),
|
||||||
sleepTimerSettings.autoTurnOnTime.toTimeOfDay(),
|
|
||||||
);
|
);
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
ref.read(appSettingsProvider.notifier).update(
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
appSettings.copyWith.playerSettings
|
appSettings.copyWith.playerSettings.sleepTimerSettings(
|
||||||
.sleepTimerSettings(
|
|
||||||
autoTurnOnTime: selected.toDuration(),
|
autoTurnOnTime: selected.toDuration(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
value: Text(
|
value: Text(
|
||||||
sleepTimerSettings.autoTurnOnTime
|
sleepTimerSettings.autoTurnOnTime.toTimeOfDay().format(context),
|
||||||
.toTimeOfDay()
|
|
||||||
.format(context),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SettingsTile.navigation(
|
SettingsTile.navigation(
|
||||||
|
|
@ -83,13 +77,11 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget {
|
||||||
// navigate to the time picker
|
// navigate to the time picker
|
||||||
final selected = await showTimePicker(
|
final selected = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialTime:
|
initialTime: sleepTimerSettings.autoTurnOffTime.toTimeOfDay(),
|
||||||
sleepTimerSettings.autoTurnOffTime.toTimeOfDay(),
|
|
||||||
);
|
);
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
ref.read(appSettingsProvider.notifier).update(
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
appSettings.copyWith.playerSettings
|
appSettings.copyWith.playerSettings.sleepTimerSettings(
|
||||||
.sleepTimerSettings(
|
|
||||||
autoTurnOffTime: selected.toDuration(),
|
autoTurnOffTime: selected.toDuration(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -104,7 +96,6 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
404
lib/settings/view/notification_settings_page.dart
Normal file
404
lib/settings/view/notification_settings_page.dart
Normal file
|
|
@ -0,0 +1,404 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:vaani/settings/app_settings_provider.dart';
|
||||||
|
import 'package:vaani/settings/models/app_settings.dart';
|
||||||
|
import 'package:vaani/settings/view/simple_settings_page.dart';
|
||||||
|
|
||||||
|
class NotificationSettingsPage extends HookConsumerWidget {
|
||||||
|
const NotificationSettingsPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final appSettings = ref.watch(appSettingsProvider);
|
||||||
|
final notificationSettings = appSettings.notificationSettings;
|
||||||
|
|
||||||
|
return SimpleSettingsPage(
|
||||||
|
title: const Text('Notification Settings'),
|
||||||
|
sections: [
|
||||||
|
SettingsSection(
|
||||||
|
margin: const EdgeInsetsDirectional.only(
|
||||||
|
start: 16.0,
|
||||||
|
end: 16.0,
|
||||||
|
top: 8.0,
|
||||||
|
bottom: 8.0,
|
||||||
|
),
|
||||||
|
tiles: [
|
||||||
|
// set the primary and secondary titles
|
||||||
|
SettingsTile(
|
||||||
|
title: const Text('Primary Title'),
|
||||||
|
description: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: 'The title of the notification\n',
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: notificationSettings.primaryTitle,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.title),
|
||||||
|
onPressed: (context) async {
|
||||||
|
// show the notification title picker
|
||||||
|
final selectedTitle = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return NotificationTitlePicker(
|
||||||
|
initialValue: notificationSettings.primaryTitle,
|
||||||
|
title: 'Primary Title',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (selectedTitle != null) {
|
||||||
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
|
appSettings.copyWith.notificationSettings(
|
||||||
|
primaryTitle: selectedTitle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
SettingsTile(
|
||||||
|
title: const Text('Secondary Title'),
|
||||||
|
description: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: 'The subtitle of the notification\n',
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: notificationSettings.secondaryTitle,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.title),
|
||||||
|
onPressed: (context) async {
|
||||||
|
// show the notification title picker
|
||||||
|
final selectedTitle = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return NotificationTitlePicker(
|
||||||
|
initialValue: notificationSettings.secondaryTitle,
|
||||||
|
title: 'Secondary Title',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (selectedTitle != null) {
|
||||||
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
|
appSettings.copyWith.notificationSettings(
|
||||||
|
secondaryTitle: selectedTitle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// set forward and backward intervals
|
||||||
|
SettingsTile(
|
||||||
|
title: const Text('Forward Interval'),
|
||||||
|
description: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${notificationSettings.fastForwardInterval.inSeconds} seconds',
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TimeIntervalSlider(
|
||||||
|
defaultValue: notificationSettings.fastForwardInterval,
|
||||||
|
onChangedEnd: (interval) {
|
||||||
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
|
appSettings.copyWith.notificationSettings(
|
||||||
|
fastForwardInterval: interval,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.fast_forward),
|
||||||
|
),
|
||||||
|
SettingsTile(
|
||||||
|
title: const Text('Backward Interval'),
|
||||||
|
description: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${notificationSettings.rewindInterval.inSeconds} seconds',
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TimeIntervalSlider(
|
||||||
|
defaultValue: notificationSettings.rewindInterval,
|
||||||
|
onChangedEnd: (interval) {
|
||||||
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
|
appSettings.copyWith.notificationSettings(
|
||||||
|
rewindInterval: interval,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.fast_rewind),
|
||||||
|
),
|
||||||
|
// set the media controls
|
||||||
|
SettingsTile(
|
||||||
|
title: const Text('Media Controls'),
|
||||||
|
leading: const Icon(Icons.control_camera),
|
||||||
|
// description: const Text('Select the media controls to display'),
|
||||||
|
description: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text('Select the media controls to display'),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
children: notificationSettings.mediaControls
|
||||||
|
.map(
|
||||||
|
(control) => Icon(control.icon),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: (context) async {
|
||||||
|
final selectedControls =
|
||||||
|
await showDialog<List<NotificationMediaControl>>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return MediaControlsPicker(
|
||||||
|
selectedControls: notificationSettings.mediaControls,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (selectedControls != null) {
|
||||||
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
|
appSettings.copyWith.notificationSettings(
|
||||||
|
mediaControls: selectedControls,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// set the progress bar to show chapter progress
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
title: const Text('Show Chapter Progress'),
|
||||||
|
leading: const Icon(Icons.book),
|
||||||
|
description:
|
||||||
|
const Text('instead of the overall progress of the book'),
|
||||||
|
initialValue: notificationSettings.progressBarIsChapterProgress,
|
||||||
|
onToggle: (value) {
|
||||||
|
ref.read(appSettingsProvider.notifier).update(
|
||||||
|
appSettings.copyWith.notificationSettings(
|
||||||
|
progressBarIsChapterProgress: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MediaControlsPicker extends HookConsumerWidget {
|
||||||
|
const MediaControlsPicker({
|
||||||
|
super.key,
|
||||||
|
required this.selectedControls,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<NotificationMediaControl> selectedControls;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final selectedMediaControls = useState(selectedControls);
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Media Controls'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(selectedMediaControls.value);
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
// a list of chips to easily select the media controls to display
|
||||||
|
// with icons and labels
|
||||||
|
content: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
children: NotificationMediaControl.values
|
||||||
|
.map(
|
||||||
|
(control) => ChoiceChip(
|
||||||
|
label: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(control.icon),
|
||||||
|
const SizedBox(width: 4.0),
|
||||||
|
Text(control.name),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
selected: selectedMediaControls.value.contains(control),
|
||||||
|
onSelected: (selected) {
|
||||||
|
if (selected) {
|
||||||
|
selectedMediaControls.value = [
|
||||||
|
...selectedMediaControls.value,
|
||||||
|
control,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
selectedMediaControls.value = [
|
||||||
|
...selectedMediaControls.value.where((c) => c != control),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeIntervalSlider extends HookConsumerWidget {
|
||||||
|
const TimeIntervalSlider({
|
||||||
|
super.key,
|
||||||
|
this.title,
|
||||||
|
required this.defaultValue,
|
||||||
|
this.onChanged,
|
||||||
|
this.onChangedEnd,
|
||||||
|
this.min = const Duration(seconds: 5),
|
||||||
|
this.max = const Duration(seconds: 120),
|
||||||
|
this.step = const Duration(seconds: 5),
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget? title;
|
||||||
|
final Duration defaultValue;
|
||||||
|
final ValueChanged<Duration>? onChanged;
|
||||||
|
final ValueChanged<Duration>? onChangedEnd;
|
||||||
|
final Duration min;
|
||||||
|
final Duration max;
|
||||||
|
final Duration step;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final selectedInterval = useState(defaultValue);
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
title ?? const SizedBox.shrink(),
|
||||||
|
if (title != null) const SizedBox(height: 8.0),
|
||||||
|
Slider(
|
||||||
|
value: selectedInterval.value.inSeconds.toDouble(),
|
||||||
|
min: min.inSeconds.toDouble(),
|
||||||
|
max: max.inSeconds.toDouble(),
|
||||||
|
divisions: ((max.inSeconds - min.inSeconds) ~/ step.inSeconds),
|
||||||
|
label: '${selectedInterval.value.inSeconds} seconds',
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedInterval.value = Duration(seconds: value.toInt());
|
||||||
|
onChanged?.call(selectedInterval.value);
|
||||||
|
},
|
||||||
|
onChangeEnd: (value) {
|
||||||
|
onChangedEnd?.call(selectedInterval.value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationTitlePicker extends HookConsumerWidget {
|
||||||
|
const NotificationTitlePicker({
|
||||||
|
super.key,
|
||||||
|
required this.initialValue,
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String initialValue;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final selectedTitle = useState(initialValue);
|
||||||
|
final controller = useTextEditingController(text: initialValue);
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(selectedTitle.value);
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
// a list of chips to easily insert available fields into the text field
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: controller,
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedTitle.value = value;
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
helper: const Text('Select a field below to insert it'),
|
||||||
|
suffix: IconButton(
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
onPressed: () {
|
||||||
|
controller.clear();
|
||||||
|
selectedTitle.value = '';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8.0),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
children: NotificationTitleType.values
|
||||||
|
.map(
|
||||||
|
(type) => ActionChip(
|
||||||
|
label: Text(type.stringValue),
|
||||||
|
onPressed: () {
|
||||||
|
final text = controller.text;
|
||||||
|
final newText = '$text\$${type.stringValue}';
|
||||||
|
controller.text = newText;
|
||||||
|
selectedTitle.value = newText;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> showNotificationTitlePicker(
|
||||||
|
BuildContext context, {
|
||||||
|
required String initialValue,
|
||||||
|
required String title,
|
||||||
|
}) async {
|
||||||
|
return showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return NotificationTitlePicker(initialValue: initialValue, title: title);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
53
lib/settings/view/simple_settings_page.dart
Normal file
53
lib/settings/view/simple_settings_page.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
|
class SimpleSettingsPage extends HookConsumerWidget {
|
||||||
|
const SimpleSettingsPage({
|
||||||
|
super.key,
|
||||||
|
this.title,
|
||||||
|
this.sections,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget? title;
|
||||||
|
final List<AbstractSettingsSection>? sections;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Scaffold(
|
||||||
|
// appBar: AppBar(
|
||||||
|
// title: title,
|
||||||
|
// ),
|
||||||
|
// body: body,
|
||||||
|
// an app bar which is bigger than the default app bar but on scroll shrinks to the default app bar with the title being animated
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
expandedHeight: 200.0,
|
||||||
|
floating: false,
|
||||||
|
pinned: true,
|
||||||
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
title: title,
|
||||||
|
// background: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (sections != null)
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildListDelegate(
|
||||||
|
[
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
child: SettingsList(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
sections: sections!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
pubspec.lock
11
pubspec.lock
|
|
@ -71,7 +71,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
audio_service:
|
audio_service:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: audio_service
|
name: audio_service
|
||||||
sha256: "9dd5ba7e77567b290c35908b1950d61485b4dfdd3a0ac398e98cfeec04651b75"
|
sha256: "9dd5ba7e77567b290c35908b1950d61485b4dfdd3a0ac398e98cfeec04651b75"
|
||||||
|
|
@ -704,10 +704,11 @@ packages:
|
||||||
just_audio_background:
|
just_audio_background:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: just_audio_background
|
path: just_audio_background
|
||||||
sha256: "7547b076d5445431c780b0915707f18baa6d9588077d5d8f811e8a77b4e0bec5"
|
ref: media-notification-config
|
||||||
url: "https://pub.dev"
|
resolved-ref: "79ac48a7d322d5b8db8847b35ed0c8555fa249bc"
|
||||||
source: hosted
|
url: "https://github.com/Dr-Blank/just_audio"
|
||||||
|
source: git
|
||||||
version: "0.0.1-beta.13"
|
version: "0.0.1-beta.13"
|
||||||
just_audio_media_kit:
|
just_audio_media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ isar_version: &isar_version ^4.0.0-dev.13 # define the version to be used
|
||||||
dependencies:
|
dependencies:
|
||||||
animated_list_plus: ^0.5.2
|
animated_list_plus: ^0.5.2
|
||||||
animated_theme_switcher: ^2.0.10
|
animated_theme_switcher: ^2.0.10
|
||||||
|
audio_service: ^0.18.15
|
||||||
audio_session: ^0.1.19
|
audio_session: ^0.1.19
|
||||||
audio_video_progress_bar: ^2.0.2
|
audio_video_progress_bar: ^2.0.2
|
||||||
auto_scroll_text: ^0.0.7
|
auto_scroll_text: ^0.0.7
|
||||||
|
|
@ -59,7 +60,12 @@ dependencies:
|
||||||
isar_flutter_libs: ^4.0.0-dev.13
|
isar_flutter_libs: ^4.0.0-dev.13
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
just_audio: ^0.9.37
|
just_audio: ^0.9.37
|
||||||
just_audio_background: ^0.0.1-beta.11
|
just_audio_background:
|
||||||
|
# TODO Remove git dep when https://github.com/ryanheise/just_audio/issues/912 is closed
|
||||||
|
git:
|
||||||
|
url: https://github.com/Dr-Blank/just_audio
|
||||||
|
ref: media-notification-config
|
||||||
|
path: just_audio_background
|
||||||
just_audio_media_kit: ^2.0.4
|
just_audio_media_kit: ^2.0.4
|
||||||
list_wheel_scroll_view_nls: ^0.0.3
|
list_wheel_scroll_view_nls: ^0.0.3
|
||||||
logging: ^1.2.0
|
logging: ^1.2.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue