From e23c0b6c5f1cc55f3d75ea96b6b218037e5d6fee Mon Sep 17 00:00:00 2001 From: "Dr.Blank" <64108942+Dr-Blank@users.noreply.github.com> Date: Sat, 10 Jan 2026 16:51:05 +0530 Subject: [PATCH] chore: run `dart format` --- lib/api/api_provider.dart | 48 +-- lib/api/authenticated_users_provider.dart | 20 +- lib/api/image_provider.dart | 7 +- lib/api/library_item_provider.dart | 3 +- lib/api/library_provider.dart | 5 +- lib/api/server_provider.dart | 8 +- lib/db/available_boxes.dart | 15 +- lib/db/init.dart | 7 +- .../downloads/core/download_manager.dart | 11 +- .../downloads/providers/download_manager.dart | 85 ++-- .../downloads/view/downloads_page.dart | 4 +- lib/features/explore/view/explore_page.dart | 64 ++- .../explore/view/search_result_page.dart | 39 +- .../view/library_item_actions.dart | 123 ++---- .../view/library_item_hero_section.dart | 95 ++--- .../view/library_item_metadata.dart | 41 +- .../item_viewer/view/library_item_page.dart | 93 ++--- .../view/library_item_sliver_app_bar.dart | 38 +- .../view/library_browser_page.dart | 72 ++-- .../logging/providers/logs_provider.dart | 6 +- lib/features/logging/view/logs_page.dart | 10 +- .../onboarding/providers/oauth_provider.dart | 6 +- .../onboarding/view/callback_page.dart | 34 +- .../view/onboarding_single_page.dart | 48 +-- lib/features/onboarding/view/user_login.dart | 147 ++++--- .../view/user_login_with_open_id.dart | 14 +- .../view/user_login_with_password.dart | 58 ++- .../view/user_login_with_token.dart | 12 +- .../core/playback_reporter.dart | 4 +- .../providers/playback_reporter_provider.dart | 5 +- .../player/core/audiobook_player.dart | 62 ++- lib/features/player/core/init.dart | 25 +- lib/features/player/playlist.dart | 4 +- .../player/providers/audiobook_player.dart | 5 +- .../player/providers/player_form.dart | 17 +- .../player/view/audiobook_player.dart | 69 ++-- .../player/view/player_when_expanded.dart | 29 +- .../player/view/player_when_minimized.dart | 25 +- .../widgets/audiobook_player_seek_button.dart | 5 +- .../audiobook_player_seek_chapter_button.dart | 18 +- .../widgets/chapter_selection_button.dart | 75 ++-- .../widgets/player_speed_adjust_button.dart | 22 +- .../view/widgets/playing_indicator_icon.dart | 96 ++--- .../player/view/widgets/speed_selector.dart | 74 ++-- .../shake_detection/core/shake_detector.dart | 37 +- .../providers/shake_detector.dart | 40 +- .../sleep_timer/core/sleep_timer.dart | 4 +- .../sleep_timer/view/sleep_timer_button.dart | 91 ++-- lib/features/you/view/server_manager.dart | 130 +++--- .../you/view/widgets/library_switch_chip.dart | 25 +- lib/features/you/view/you_page.dart | 22 +- lib/hacks/fix_autofill_losing_focus.dart | 11 +- lib/main.dart | 19 +- lib/pages/home_page.dart | 47 ++- lib/pages/library_page.dart | 20 +- lib/router/constants.dart | 30 +- lib/router/router.dart | 389 +++++++++--------- lib/router/transitions/slide.dart | 35 +- lib/settings/app_settings_provider.dart | 6 +- lib/settings/metadata/metadata_provider.dart | 133 +++--- lib/settings/view/app_settings_page.dart | 73 ++-- .../view/auto_sleep_timer_settings_page.dart | 29 +- lib/settings/view/buttons.dart | 15 +- .../view/home_page_settings_page.dart | 9 +- .../view/notification_settings_page.dart | 57 +-- lib/settings/view/player_settings_page.dart | 117 +++--- .../view/shake_detector_settings_page.dart | 50 ++- lib/settings/view/simple_settings_page.dart | 26 +- lib/settings/view/theme_settings_page.dart | 32 +- .../widgets/navigation_with_switch_tile.dart | 5 +- lib/shared/extensions/enum.dart | 5 +- lib/shared/extensions/model_conversions.dart | 4 +- lib/shared/extensions/obfuscation.dart | 25 +- lib/shared/extensions/time_of_day.dart | 5 +- lib/shared/hooks.dart | 22 +- lib/shared/icons/abs_icons.dart | 140 +++++-- lib/shared/widgets/add_new_server.dart | 17 +- lib/shared/widgets/drawer.dart | 9 +- .../widgets/expandable_description.dart | 11 +- lib/shared/widgets/not_implemented.dart | 5 +- lib/shared/widgets/shelves/author_shelf.dart | 15 +- lib/shared/widgets/shelves/book_shelf.dart | 114 +++-- lib/shared/widgets/shelves/home_shelf.dart | 21 +- .../providers/system_theme_provider.dart | 17 +- 84 files changed, 1565 insertions(+), 1945 deletions(-) diff --git a/lib/api/api_provider.dart b/lib/api/api_provider.dart index a991727..8994644 100644 --- a/lib/api/api_provider.dart +++ b/lib/api/api_provider.dart @@ -16,10 +16,8 @@ import 'package:vaani/shared/extensions/obfuscation.dart'; part 'api_provider.g.dart'; // TODO: workaround for https://github.com/rrousselGit/riverpod/issues/3718 -typedef ResponseErrorHandler = void Function( - Response response, [ - Object? error, -]); +typedef ResponseErrorHandler = + void Function(Response response, [Object? error]); final _logger = Logger('api_provider'); @@ -39,9 +37,7 @@ AudiobookshelfApi audiobookshelfApi(Ref ref, Uri? baseUrl) { // try to get the base url from app settings final apiSettings = ref.watch(apiSettingsProvider); baseUrl ??= apiSettings.activeServer?.serverUrl; - return AudiobookshelfApi( - baseUrl: makeBaseUrl(baseUrl.toString()), - ); + return AudiobookshelfApi(baseUrl: makeBaseUrl(baseUrl.toString())); } /// get the api instance for the authenticated user @@ -68,9 +64,9 @@ FutureOr isServerAlive(Ref ref, String address) async { } try { - return await AudiobookshelfApi(baseUrl: makeBaseUrl(address)) - .server - .ping() ?? + return await AudiobookshelfApi( + baseUrl: makeBaseUrl(address), + ).server.ping() ?? false; } catch (e) { return false; @@ -86,8 +82,9 @@ FutureOr serverStatus( ]) async { _logger.fine('fetching server status: ${baseUrl.obfuscate()}'); final api = ref.watch(audiobookshelfApiProvider(baseUrl)); - final res = - await api.server.status(responseErrorHandler: responseErrorHandler); + final res = await api.server.status( + responseErrorHandler: responseErrorHandler, + ); _logger.fine('server status: $res'); return res; } @@ -113,7 +110,9 @@ class PersonalizedView extends _$PersonalizedView { yield []; return; } - ref.read(apiSettingsProvider.notifier).updateState( + ref + .read(apiSettingsProvider.notifier) + .updateState( apiSettings.copyWith(activeLibraryId: login.userDefaultLibraryId), ); yield []; @@ -122,9 +121,8 @@ class PersonalizedView extends _$PersonalizedView { // try to find in cache // final cacheKey = 'personalizedView:${apiSettings.activeLibraryId}'; final key = 'personalizedView:${apiSettings.activeLibraryId! + user.id}'; - final cachedRes = await apiResponseCacheManager.getFileFromMemory( - key, - ) ?? + final cachedRes = + await apiResponseCacheManager.getFileFromMemory(key) ?? await apiResponseCacheManager.getFileFromCache(key); if (cachedRes != null) { _logger.fine('reading from cache: $cachedRes for key: $key'); @@ -143,8 +141,9 @@ class PersonalizedView extends _$PersonalizedView { // ! exaggerated delay // await Future.delayed(const Duration(seconds: 2)); - final res = await api.libraries - .getPersonalized(libraryId: apiSettings.activeLibraryId!); + final res = await api.libraries.getPersonalized( + libraryId: apiSettings.activeLibraryId!, + ); // debugPrint('personalizedView: ${res!.map((e) => e).toSet()}'); // save to cache if (res != null) { @@ -172,9 +171,7 @@ class PersonalizedView extends _$PersonalizedView { /// fetch continue listening audiobooks @riverpod -FutureOr fetchContinueListening( - Ref ref, -) async { +FutureOr fetchContinueListening(Ref ref) async { final api = ref.watch(authenticatedApiProvider); final res = await api.me.getSessions(); // debugPrint( @@ -184,9 +181,7 @@ FutureOr fetchContinueListening( } @riverpod -FutureOr me( - Ref ref, -) async { +FutureOr me(Ref ref) async { final api = ref.watch(authenticatedApiProvider); final errorResponseHandler = ErrorResponseHandler(); final res = await api.me.getUser( @@ -202,10 +197,7 @@ FutureOr me( } @riverpod -FutureOr login( - Ref ref, { - AuthenticatedUser? user, -}) async { +FutureOr login(Ref ref, {AuthenticatedUser? user}) async { if (user == null) { // try to get the user from settings final apiSettings = ref.watch(apiSettingsProvider); diff --git a/lib/api/authenticated_users_provider.dart b/lib/api/authenticated_users_provider.dart index 7f74ae4..6d437e0 100644 --- a/lib/api/authenticated_users_provider.dart +++ b/lib/api/authenticated_users_provider.dart @@ -35,9 +35,7 @@ class AuthenticatedUsers extends _$AuthenticatedUsers { Set readFromBoxOrCreate() { if (_box.isNotEmpty) { final foundData = _box.getRange(0, _box.length); - _logger.fine( - 'found users in box: ${foundData.obfuscate()}', - ); + _logger.fine('found users in box: ${foundData.obfuscate()}'); return foundData.toSet(); } else { _logger.fine('no settings found in box'); @@ -59,11 +57,9 @@ class AuthenticatedUsers extends _$AuthenticatedUsers { ref.invalidateSelf(); if (setActive) { final apiSettings = ref.read(apiSettingsProvider); - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeUser: user, - ), - ); + ref + .read(apiSettingsProvider.notifier) + .updateState(apiSettings.copyWith(activeUser: user)); } } @@ -86,11 +82,9 @@ class AuthenticatedUsers extends _$AuthenticatedUsers { // replace the active user with the first user in the list // or null if there are no users left final newActiveUser = state.isNotEmpty ? state.first : null; - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeUser: newActiveUser, - ), - ); + ref + .read(apiSettingsProvider.notifier) + .updateState(apiSettings.copyWith(activeUser: newActiveUser)); } } } diff --git a/lib/api/image_provider.dart b/lib/api/image_provider.dart index 9707542..79fac06 100644 --- a/lib/api/image_provider.dart +++ b/lib/api/image_provider.dart @@ -27,7 +27,8 @@ class CoverImage extends _$CoverImage { // await Future.delayed(const Duration(seconds: 2)); // try to get the image from the cache - final file = await imageCacheManager.getFileFromMemory(itemId) ?? + final file = + await imageCacheManager.getFileFromMemory(itemId) ?? await imageCacheManager.getFileFromCache(itemId); if (file != null) { @@ -44,9 +45,7 @@ class CoverImage extends _$CoverImage { ); return; } else { - _logger.fine( - 'cover image stale for $itemId, fetching from the server', - ); + _logger.fine('cover image stale for $itemId, fetching from the server'); } } else { _logger.fine('cover image not found in cache for $itemId'); diff --git a/lib/api/library_item_provider.dart b/lib/api/library_item_provider.dart index 013f62e..5a4b1a3 100644 --- a/lib/api/library_item_provider.dart +++ b/lib/api/library_item_provider.dart @@ -26,7 +26,8 @@ class LibraryItem extends _$LibraryItem { // look for the item in the cache final key = CacheKey.libraryItem(id); - final cachedFile = await apiResponseCacheManager.getFileFromMemory(key) ?? + final cachedFile = + await apiResponseCacheManager.getFileFromMemory(key) ?? await apiResponseCacheManager.getFileFromCache(key); if (cachedFile != null) { _logger.fine( diff --git a/lib/api/library_provider.dart b/lib/api/library_provider.dart index b565d26..3ba5399 100644 --- a/lib/api/library_provider.dart +++ b/lib/api/library_provider.dart @@ -33,8 +33,9 @@ Future library(Ref ref, String id) async { @riverpod Future currentLibrary(Ref ref) async { - final libraryId = - ref.watch(apiSettingsProvider.select((s) => s.activeLibraryId)); + final libraryId = ref.watch( + apiSettingsProvider.select((s) => s.activeLibraryId), + ); if (libraryId == null) { _logger.warning('No active library id found'); return null; diff --git a/lib/api/server_provider.dart b/lib/api/server_provider.dart index 690dcc4..41c82f1 100644 --- a/lib/api/server_provider.dart +++ b/lib/api/server_provider.dart @@ -80,11 +80,9 @@ class AudiobookShelfServer extends _$AudiobookShelfServer { // remove the server from the active server final apiSettings = ref.read(apiSettingsProvider); if (apiSettings.activeServer == server) { - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeServer: null, - ), - ); + ref + .read(apiSettingsProvider.notifier) + .updateState(apiSettings.copyWith(activeServer: null)); } // remove the users of this server if (removeUsers) { diff --git a/lib/db/available_boxes.dart b/lib/db/available_boxes.dart index dc2d5a1..77b0c3f 100644 --- a/lib/db/available_boxes.dart +++ b/lib/db/available_boxes.dart @@ -14,14 +14,17 @@ class AvailableHiveBoxes { static final apiSettingsBox = Hive.box(name: 'apiSettings'); /// stores the a list of [AudiobookShelfServer] - static final serverBox = - Hive.box(name: 'audiobookShelfServer'); + static final serverBox = Hive.box( + name: 'audiobookShelfServer', + ); /// stores the a list of [AuthenticatedUser] - static final authenticatedUserBox = - Hive.box(name: 'authenticatedUser'); + static final authenticatedUserBox = Hive.box( + name: 'authenticatedUser', + ); /// stores the a list of [BookSettings] - static final individualBookSettingsBox = - Hive.box(name: 'bookSettings'); + static final individualBookSettingsBox = Hive.box( + name: 'bookSettings', + ); } diff --git a/lib/db/init.dart b/lib/db/init.dart index 452648e..280144e 100644 --- a/lib/db/init.dart +++ b/lib/db/init.dart @@ -13,12 +13,7 @@ Future initStorage() async { final dir = await getApplicationDocumentsDirectory(); // use vaani as the directory for hive - final storageDir = Directory( - p.join( - dir.path, - AppMetadata.appNameLowerCase, - ), - ); + final storageDir = Directory(p.join(dir.path, AppMetadata.appNameLowerCase)); await storageDir.create(recursive: true); Hive.defaultDirectory = storageDir.path; diff --git a/lib/features/downloads/core/download_manager.dart b/lib/features/downloads/core/download_manager.dart index 0e808b1..e6db077 100644 --- a/lib/features/downloads/core/download_manager.dart +++ b/lib/features/downloads/core/download_manager.dart @@ -67,17 +67,13 @@ class AudiobookDownloadManager { late StreamSubscription _updatesSubscription; - Future queueAudioBookDownload( - LibraryItemExpanded item, - ) async { + Future queueAudioBookDownload(LibraryItemExpanded item) async { _logger.info('queuing download for item: ${item.id}'); // create a download task for each file in the item final directory = await getApplicationSupportDirectory(); for (final file in item.libraryFiles) { // check if the file is already downloaded - if (isFileDownloaded( - constructFilePath(directory, item, file), - )) { + if (isFileDownloaded(constructFilePath(directory, item, file))) { _logger.info('file already downloaded: ${file.metadata.filename}'); continue; } @@ -105,8 +101,7 @@ class AudiobookDownloadManager { Directory directory, LibraryItemExpanded item, LibraryFile file, - ) => - '${directory.path}/${item.relPath}/${file.metadata.filename}'; + ) => '${directory.path}/${item.relPath}/${file.metadata.filename}'; void dispose() { _updatesSubscription.cancel(); diff --git a/lib/features/downloads/providers/download_manager.dart b/lib/features/downloads/providers/download_manager.dart index 7f75a17..7dba395 100644 --- a/lib/features/downloads/providers/download_manager.dart +++ b/lib/features/downloads/providers/download_manager.dart @@ -52,13 +52,9 @@ class DownloadManager extends _$DownloadManager { return manager; } - Future queueAudioBookDownload( - LibraryItemExpanded item, - ) async { + Future queueAudioBookDownload(LibraryItemExpanded item) async { _logger.fine('queueing download for ${item.id}'); - await state.queueAudioBookDownload( - item, - ); + await state.queueAudioBookDownload(item); } Future deleteDownloadedItem(LibraryItemExpanded item) async { @@ -83,58 +79,57 @@ class ItemDownloadProgress extends _$ItemDownloadProgress { Future build(String id) async { final item = await ref.watch(libraryItemProvider(id).future); final manager = ref.read(downloadManagerProvider); - manager.taskUpdateStream.map((taskUpdate) { - if (taskUpdate is! TaskProgressUpdate) { - return null; - } - if (taskUpdate.task.group == id) { - return taskUpdate; - } - }).listen((task) async { - if (task != null) { - final totalSize = item.totalSize; - // if total size is 0, return 0 - if (totalSize == 0) { - state = const AsyncValue.data(0.0); - return; - } - final downloadedFiles = await manager.getDownloadedFilesMetadata(item); - // calculate total size of downloaded files and total size of item, then divide - // to get percentage - final downloadedSize = downloadedFiles.fold( - 0, - (previousValue, element) => previousValue + element.metadata.size, - ); + manager.taskUpdateStream + .map((taskUpdate) { + if (taskUpdate is! TaskProgressUpdate) { + return null; + } + if (taskUpdate.task.group == id) { + return taskUpdate; + } + }) + .listen((task) async { + if (task != null) { + final totalSize = item.totalSize; + // if total size is 0, return 0 + if (totalSize == 0) { + state = const AsyncValue.data(0.0); + return; + } + final downloadedFiles = await manager.getDownloadedFilesMetadata( + item, + ); + // calculate total size of downloaded files and total size of item, then divide + // to get percentage + final downloadedSize = downloadedFiles.fold( + 0, + (previousValue, element) => previousValue + element.metadata.size, + ); - final inProgressFileSize = task.progress * task.expectedFileSize; - final totalDownloadedSize = downloadedSize + inProgressFileSize; - final progress = totalDownloadedSize / totalSize; - // if current progress is more than calculated progress, do not update - if (progress < (state.value ?? 0.0)) { - return; - } + final inProgressFileSize = task.progress * task.expectedFileSize; + final totalDownloadedSize = downloadedSize + inProgressFileSize; + final progress = totalDownloadedSize / totalSize; + // if current progress is more than calculated progress, do not update + if (progress < (state.value ?? 0.0)) { + return; + } - state = AsyncValue.data(progress.clamp(0.0, 1.0)); - } - }); + state = AsyncValue.data(progress.clamp(0.0, 1.0)); + } + }); return null; } } @riverpod -FutureOr> downloadHistory( - Ref ref, { - String? group, -}) async { +FutureOr> downloadHistory(Ref ref, {String? group}) async { return await FileDownloader().database.allRecords(group: group); } @riverpod class IsItemDownloaded extends _$IsItemDownloaded { @override - FutureOr build( - LibraryItemExpanded item, - ) { + FutureOr build(LibraryItemExpanded item) { final manager = ref.watch(downloadManagerProvider); return manager.isItemDownloaded(item); } diff --git a/lib/features/downloads/view/downloads_page.dart b/lib/features/downloads/view/downloads_page.dart index 7c5dbfb..7d2b2cb 100644 --- a/lib/features/downloads/view/downloads_page.dart +++ b/lib/features/downloads/view/downloads_page.dart @@ -11,9 +11,7 @@ class DownloadsPage extends HookConsumerWidget { final downloadHistory = ref.watch(downloadHistoryProvider()); return Scaffold( - appBar: AppBar( - title: const Text('Downloads'), - ), + appBar: AppBar(title: const Text('Downloads')), body: Center( // history of downloads child: downloadHistory.when( diff --git a/lib/features/explore/view/explore_page.dart b/lib/features/explore/view/explore_page.dart index 2ed7b52..e747d26 100644 --- a/lib/features/explore/view/explore_page.dart +++ b/lib/features/explore/view/explore_page.dart @@ -28,18 +28,14 @@ class ExplorePage extends HookConsumerWidget { final settings = ref.watch(appSettingsProvider); final api = ref.watch(authenticatedApiProvider); return Scaffold( - appBar: AppBar( - title: const Text('Explore'), - ), + appBar: AppBar(title: const Text('Explore')), body: const MySearchBar(), ); } } class MySearchBar extends HookConsumerWidget { - const MySearchBar({ - super.key, - }); + const MySearchBar({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -61,8 +57,11 @@ class MySearchBar extends HookConsumerWidget { currentQuery = query; // In a real application, there should be some error handling here. - final options = await api.libraries - .search(libraryId: settings.activeLibraryId!, query: query, limit: 3); + final options = await api.libraries.search( + libraryId: settings.activeLibraryId!, + query: query, + limit: 3, + ); // If another search happened after this one, throw away these options. if (currentQuery != query) { @@ -97,11 +96,10 @@ class MySearchBar extends HookConsumerWidget { // opacity: 0.5 for the hint text hintStyle: WidgetStatePropertyAll( Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.5), - ), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.5), + ), ), textInputAction: TextInputAction.search, onTapOutside: (_) { @@ -120,12 +118,7 @@ class MySearchBar extends HookConsumerWidget { ); }, viewOnSubmitted: (value) { - context.pushNamed( - Routes.search.name, - queryParameters: { - 'q': value, - }, - ); + context.pushNamed(Routes.search.name, queryParameters: {'q': value}); }, suggestionsBuilder: (context, controller) async { // check if the search controller is empty @@ -191,14 +184,12 @@ List buildBookSearchResult( SearchResultMiniSection( // title: 'Books', category: SearchResultCategory.books, - options: options.book.map( - (result) { - // convert result to a book object - final book = result.libraryItem.media.asBookExpanded; - final metadata = book.metadata.asBookMetadataExpanded; - return BookSearchResultMini(book: book, metadata: metadata); - }, - ), + options: options.book.map((result) { + // convert result to a book object + final book = result.libraryItem.media.asBookExpanded; + final metadata = book.metadata.asBookMetadataExpanded; + return BookSearchResultMini(book: book, metadata: metadata); + }), ), ); } @@ -207,11 +198,9 @@ List buildBookSearchResult( SearchResultMiniSection( // title: 'Authors', category: SearchResultCategory.authors, - options: options.authors.map( - (result) { - return ListTile(title: Text(result.name)); - }, - ), + options: options.authors.map((result) { + return ListTile(title: Text(result.name)); + }), ), ); } @@ -245,10 +234,7 @@ class BookSearchResultMini extends HookConsumerWidget { child: ClipRRect( borderRadius: BorderRadius.circular(5), child: image.when( - data: (bytes) => Image.memory( - bytes, - fit: BoxFit.cover, - ), + data: (bytes) => Image.memory(bytes, fit: BoxFit.cover), loading: () => const BookCoverSkeleton(), error: (error, _) => const Icon(Icons.error), ), @@ -259,11 +245,7 @@ class BookSearchResultMini extends HookConsumerWidget { subtitle: Text( maxLines: 1, overflow: TextOverflow.ellipsis, - metadata.authors - .map( - (author) => author.name, - ) - .join(', '), + metadata.authors.map((author) => author.name).join(', '), ), onTap: () { // navigate to the book details page diff --git a/lib/features/explore/view/search_result_page.dart b/lib/features/explore/view/search_result_page.dart index 10a8a39..fef097e 100644 --- a/lib/features/explore/view/search_result_page.dart +++ b/lib/features/explore/view/search_result_page.dart @@ -5,13 +5,7 @@ import 'package:vaani/features/explore/providers/search_result_provider.dart'; import 'package:vaani/features/explore/view/explore_page.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; -enum SearchResultCategory { - books, - authors, - series, - tags, - narrators, -} +enum SearchResultCategory { books, authors, series, tags, narrators } class SearchResultPage extends HookConsumerWidget { const SearchResultPage({ @@ -41,9 +35,7 @@ class SearchResultPage extends HookConsumerWidget { body: results.when( data: (options) { if (options == null) { - return Container( - child: const Text('No data found'), - ); + return Container(child: const Text('No data found')); } if (options is BookLibrarySearchResponse) { if (category == null) { @@ -51,18 +43,15 @@ class SearchResultPage extends HookConsumerWidget { } return switch (category!) { SearchResultCategory.books => ListView.builder( - itemCount: options.book.length, - itemBuilder: (context, index) { - final book = - options.book[index].libraryItem.media.asBookExpanded; - final metadata = book.metadata.asBookMetadataExpanded; + itemCount: options.book.length, + itemBuilder: (context, index) { + final book = + options.book[index].libraryItem.media.asBookExpanded; + final metadata = book.metadata.asBookMetadataExpanded; - return BookSearchResultMini( - book: book, - metadata: metadata, - ); - }, - ), + return BookSearchResultMini(book: book, metadata: metadata); + }, + ), SearchResultCategory.authors => Container(), SearchResultCategory.series => Container(), SearchResultCategory.tags => Container(), @@ -71,12 +60,8 @@ class SearchResultPage extends HookConsumerWidget { } return null; }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (error, stackTrace) => Center( - child: Text('Error: $error'), - ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) => Center(child: Text('Error: $error')), ), ); } diff --git a/lib/features/item_viewer/view/library_item_actions.dart b/lib/features/item_viewer/view/library_item_actions.dart index 7299e5c..50ef9c2 100644 --- a/lib/features/item_viewer/view/library_item_actions.dart +++ b/lib/features/item_viewer/view/library_item_actions.dart @@ -26,10 +26,7 @@ import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/utils.dart'; class LibraryItemActions extends HookConsumerWidget { - const LibraryItemActions({ - super.key, - required this.id, - }); + const LibraryItemActions({super.key, required this.id}); final String id; @@ -68,9 +65,7 @@ class LibraryItemActions extends HookConsumerWidget { // read list button IconButton( onPressed: () {}, - icon: const Icon( - Icons.playlist_add_rounded, - ), + icon: const Icon(Icons.playlist_add_rounded), ), // share button IconButton( @@ -79,8 +74,9 @@ class LibraryItemActions extends HookConsumerWidget { var currentServerUrl = apiSettings.activeServer!.serverUrl; if (!currentServerUrl.hasScheme) { - currentServerUrl = - Uri.https(currentServerUrl.toString()); + currentServerUrl = Uri.https( + currentServerUrl.toString(), + ); } handleLaunchUrl( Uri.parse( @@ -140,7 +136,8 @@ class LibraryItemActions extends HookConsumerWidget { .database .deleteRecordWithId( record - .task.taskId, + .task + .taskId, ); Navigator.pop(context); }, @@ -161,8 +158,8 @@ class LibraryItemActions extends HookConsumerWidget { // open the file location final didOpen = await FileDownloader().openFile( - task: record.task, - ); + task: record.task, + ); if (!didOpen) { appLogger.warning( @@ -182,16 +179,13 @@ class LibraryItemActions extends HookConsumerWidget { loading: () => const Center( child: CircularProgressIndicator(), ), - error: (error, stackTrace) => Center( - child: Text('Error: $error'), - ), + error: (error, stackTrace) => + Center(child: Text('Error: $error')), ); }, ); }, - icon: const Icon( - Icons.more_vert_rounded, - ), + icon: const Icon(Icons.more_vert_rounded), ), ], ), @@ -206,10 +200,7 @@ class LibraryItemActions extends HookConsumerWidget { } class LibItemDownloadButton extends HookConsumerWidget { - const LibItemDownloadButton({ - super.key, - required this.item, - }); + const LibItemDownloadButton({super.key, required this.item}); final shelfsdk.LibraryItemExpanded item; @@ -222,9 +213,7 @@ class LibItemDownloadButton extends HookConsumerWidget { final isItemDownloading = ref.watch(isItemDownloadingProvider(item.id)); return isItemDownloading - ? ItemCurrentlyInDownloadQueue( - item: item, - ) + ? ItemCurrentlyInDownloadQueue(item: item) : IconButton( onPressed: () { appLogger.fine('Pressed download button'); @@ -233,18 +222,13 @@ class LibItemDownloadButton extends HookConsumerWidget { .read(downloadManagerProvider.notifier) .queueAudioBookDownload(item); }, - icon: const Icon( - Icons.download_rounded, - ), + icon: const Icon(Icons.download_rounded), ); } } class ItemCurrentlyInDownloadQueue extends HookConsumerWidget { - const ItemCurrentlyInDownloadQueue({ - super.key, - required this.item, - }); + const ItemCurrentlyInDownloadQueue({super.key, required this.item}); final shelfsdk.LibraryItemExpanded item; @@ -263,17 +247,12 @@ class ItemCurrentlyInDownloadQueue extends HookConsumerWidget { return Stack( alignment: Alignment.center, children: [ - CircularProgressIndicator( - value: progress, - strokeWidth: 2, - ), + CircularProgressIndicator(value: progress, strokeWidth: 2), const Icon( - Icons.download, - // color: Theme.of(context).progressIndicatorTheme.color, - ) - .animate( - onPlay: (controller) => controller.repeat(), + Icons.download, + // color: Theme.of(context).progressIndicatorTheme.color, ) + .animate(onPlay: (controller) => controller.repeat()) .fade( duration: shimmerDuration, end: 1, @@ -292,10 +271,7 @@ class ItemCurrentlyInDownloadQueue extends HookConsumerWidget { } class AlreadyItemDownloadedButton extends HookConsumerWidget { - const AlreadyItemDownloadedButton({ - super.key, - required this.item, - }); + const AlreadyItemDownloadedButton({super.key, required this.item}); final shelfsdk.LibraryItemExpanded item; @@ -317,25 +293,18 @@ class AlreadyItemDownloadedButton extends HookConsumerWidget { top: 8.0, bottom: (isBookPlaying ? playerMinHeight : 0) + 8, ), - child: DownloadSheet( - item: item, - ), + child: DownloadSheet(item: item), ); }, ); }, - icon: const Icon( - Icons.download_done_rounded, - ), + icon: const Icon(Icons.download_done_rounded), ); } } class DownloadSheet extends HookConsumerWidget { - const DownloadSheet({ - super.key, - required this.item, - }); + const DownloadSheet({super.key, required this.item}); final shelfsdk.LibraryItemExpanded item; @@ -367,9 +336,7 @@ class DownloadSheet extends HookConsumerWidget { // ), ListTile( title: const Text('Delete'), - leading: const Icon( - Icons.delete_rounded, - ), + leading: const Icon(Icons.delete_rounded), onTap: () async { // show the delete dialog final wasDeleted = await showDialog( @@ -387,9 +354,7 @@ class DownloadSheet extends HookConsumerWidget { // delete the file ref .read(downloadManagerProvider.notifier) - .deleteDownloadedItem( - item, - ); + .deleteDownloadedItem(item); GoRouter.of(context).pop(true); }, child: const Text('Yes'), @@ -409,11 +374,7 @@ class DownloadSheet extends HookConsumerWidget { appLogger.fine('Deleted ${item.media.metadata.title}'); GoRouter.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Deleted ${item.media.metadata.title}', - ), - ), + SnackBar(content: Text('Deleted ${item.media.metadata.title}')), ); } }, @@ -424,9 +385,7 @@ class DownloadSheet extends HookConsumerWidget { } class _LibraryItemPlayButton extends HookConsumerWidget { - const _LibraryItemPlayButton({ - required this.item, - }); + const _LibraryItemPlayButton({required this.item}); final shelfsdk.LibraryItemExpanded item; @@ -477,9 +436,7 @@ class _LibraryItemPlayButton extends HookConsumerWidget { ), label: Text(getPlayDisplayText()), style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), ); } @@ -502,11 +459,11 @@ class DynamicItemPlayIcon extends StatelessWidget { return Icon( isCurrentBookSetInPlayer ? isPlayingThisBook - ? Icons.pause_rounded - : Icons.play_arrow_rounded + ? Icons.pause_rounded + : Icons.play_arrow_rounded : isBookCompleted - ? Icons.replay_rounded - : Icons.play_arrow_rounded, + ? Icons.replay_rounded + : Icons.play_arrow_rounded, ); } } @@ -529,8 +486,9 @@ Future libraryItemPlayButtonOnPressed({ appLogger.info('Setting the book ${book.libraryItemId}'); appLogger.info('Initial position: ${userMediaProgress?.currentTime}'); final downloadManager = ref.watch(simpleDownloadManagerProvider); - final libItem = - await ref.read(libraryItemProvider(book.libraryItemId).future); + final libItem = await ref.read( + libraryItemProvider(book.libraryItemId).future, + ); final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem); setSourceFuture = player.setSourceAudiobook( book, @@ -546,8 +504,9 @@ Future libraryItemPlayButtonOnPressed({ } } // set the volume as this is the first time playing and dismissing causes the volume to go to 0 - var bookPlayerSettings = - ref.read(bookSettingsProvider(book.libraryItemId)).playerSettings; + var bookPlayerSettings = ref + .read(bookSettingsProvider(book.libraryItemId)) + .playerSettings; var appPlayerSettings = ref.read(appSettingsProvider).playerSettings; var configurePlayerForEveryBook = @@ -559,14 +518,14 @@ Future libraryItemPlayButtonOnPressed({ player.setVolume( configurePlayerForEveryBook ? bookPlayerSettings.preferredDefaultVolume ?? - appPlayerSettings.preferredDefaultVolume + appPlayerSettings.preferredDefaultVolume : appPlayerSettings.preferredDefaultVolume, ), // set the speed player.setSpeed( configurePlayerForEveryBook ? bookPlayerSettings.preferredDefaultSpeed ?? - appPlayerSettings.preferredDefaultSpeed + appPlayerSettings.preferredDefaultSpeed : appPlayerSettings.preferredDefaultSpeed, ), ]); diff --git a/lib/features/item_viewer/view/library_item_hero_section.dart b/lib/features/item_viewer/view/library_item_hero_section.dart index ae83420..a103b7a 100644 --- a/lib/features/item_viewer/view/library_item_hero_section.dart +++ b/lib/features/item_viewer/view/library_item_hero_section.dart @@ -42,14 +42,13 @@ class LibraryItemHeroSection extends HookConsumerWidget { child: Column( children: [ Hero( - tag: HeroTagPrefixes.bookCover + + tag: + HeroTagPrefixes.bookCover + itemId + (extraMap?.heroTagSuffix ?? ''), child: ClipRRect( borderRadius: BorderRadius.circular(16), - child: _BookCover( - itemId: itemId, - ), + child: _BookCover(itemId: itemId), ), ), // a progress bar @@ -59,9 +58,7 @@ class LibraryItemHeroSection extends HookConsumerWidget { right: 8.0, left: 8.0, ), - child: _LibraryItemProgressIndicator( - id: itemId, - ), + child: _LibraryItemProgressIndicator(id: itemId), ), ], ), @@ -77,10 +74,7 @@ class LibraryItemHeroSection extends HookConsumerWidget { } class _BookDetails extends HookConsumerWidget { - const _BookDetails({ - required this.id, - this.extraMap, - }); + const _BookDetails({required this.id, this.extraMap}); final String id; final LibraryItemExtras? extraMap; @@ -99,10 +93,7 @@ class _BookDetails extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - _BookTitle( - extraMap: extraMap, - itemBookMetadata: itemBookMetadata, - ), + _BookTitle(extraMap: extraMap, itemBookMetadata: itemBookMetadata), Container( margin: const EdgeInsets.symmetric(vertical: 16), child: Column( @@ -134,9 +125,7 @@ class _BookDetails extends HookConsumerWidget { } class _LibraryItemProgressIndicator extends HookConsumerWidget { - const _LibraryItemProgressIndicator({ - required this.id, - }); + const _LibraryItemProgressIndicator({required this.id}); final String id; @@ -157,13 +146,15 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget { Duration remainingTime; if (player.book?.libraryItemId == libraryItem.id) { // final positionStream = useStream(player.slowPositionStream); - progress = (player.positionInBook).inSeconds / + progress = + (player.positionInBook).inSeconds / libraryItem.media.asBookExpanded.duration.inSeconds; remainingTime = libraryItem.media.asBookExpanded.duration - player.positionInBook; } else { progress = mediaProgress?.progress ?? 0; - remainingTime = (libraryItem.media.asBookExpanded.duration - + remainingTime = + (libraryItem.media.asBookExpanded.duration - mediaProgress!.currentTime); } @@ -190,20 +181,17 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget { semanticsLabel: 'Book progress', semanticsValue: '${progressInPercent.toStringAsFixed(2)}%', ), - const SizedBox.square( - dimension: 4.0, - ), + const SizedBox.square(dimension: 4.0), // time remaining Text( // only show 2 decimal places '${remainingTime.smartBinaryFormat} left', style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.75), - ), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.75), + ), ), ], ), @@ -212,10 +200,7 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget { } class _HeroSectionSubLabelWithIcon extends HookConsumerWidget { - const _HeroSectionSubLabelWithIcon({ - required this.icon, - required this.text, - }); + const _HeroSectionSubLabelWithIcon({required this.icon, required this.text}); final IconData icon; final Widget text; @@ -225,8 +210,10 @@ class _HeroSectionSubLabelWithIcon extends HookConsumerWidget { final themeData = Theme.of(context); final useFontAwesome = icon.runtimeType == FontAwesomeIcons.book.runtimeType; - final useMaterialThemeOnItemPage = - ref.watch(appSettingsProvider).themeSettings.useMaterialThemeOnItemPage; + final useMaterialThemeOnItemPage = ref + .watch(appSettingsProvider) + .themeSettings + .useMaterialThemeOnItemPage; final color = useMaterialThemeOnItemPage ? themeData.colorScheme.primary : themeData.colorScheme.onSurface.withValues(alpha: 0.75); @@ -237,20 +224,10 @@ class _HeroSectionSubLabelWithIcon extends HookConsumerWidget { Container( margin: const EdgeInsets.only(right: 8, top: 2), child: useFontAwesome - ? FaIcon( - icon, - size: 16, - color: color, - ) - : Icon( - icon, - size: 16, - color: color, - ), - ), - Expanded( - child: text, + ? FaIcon(icon, size: 16, color: color) + : Icon(icon, size: 16, color: color), ), + Expanded(child: text), ], ), ); @@ -338,9 +315,7 @@ class _BookNarrators extends StatelessWidget { } class _BookCover extends HookConsumerWidget { - const _BookCover({ - required this.itemId, - }); + const _BookCover({required this.itemId}); final String itemId; @@ -358,7 +333,8 @@ class _BookCover extends HookConsumerWidget { themeOfLibraryItemProvider( itemId, brightness: Theme.of(context).brightness, - highContrast: themeSettings.highContrast || + highContrast: + themeSettings.highContrast || MediaQuery.of(context).highContrast, ), ) @@ -391,15 +367,10 @@ class _BookCover extends HookConsumerWidget { return const Icon(Icons.error); } - return Image.memory( - image, - fit: BoxFit.cover, - ); + return Image.memory(image, fit: BoxFit.cover); }, loading: () { - return const Center( - child: BookCoverSkeleton(), - ); + return const Center(child: BookCoverSkeleton()); }, error: (error, stack) { return const Center(child: Icon(Icons.error)); @@ -411,10 +382,7 @@ class _BookCover extends HookConsumerWidget { } class _BookTitle extends StatelessWidget { - const _BookTitle({ - required this.extraMap, - required this.itemBookMetadata, - }); + const _BookTitle({required this.extraMap, required this.itemBookMetadata}); final LibraryItemExtras? extraMap; final shelfsdk.BookMetadataExpanded? itemBookMetadata; @@ -426,7 +394,8 @@ class _BookTitle extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Hero( - tag: HeroTagPrefixes.bookTitle + + tag: + HeroTagPrefixes.bookTitle + // itemId + (extraMap?.heroTagSuffix ?? ''), child: Text( diff --git a/lib/features/item_viewer/view/library_item_metadata.dart b/lib/features/item_viewer/view/library_item_metadata.dart index 753a38e..e12a997 100644 --- a/lib/features/item_viewer/view/library_item_metadata.dart +++ b/lib/features/item_viewer/view/library_item_metadata.dart @@ -4,10 +4,7 @@ import 'package:vaani/api/library_item_provider.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; class LibraryItemMetadata extends HookConsumerWidget { - const LibraryItemMetadata({ - super.key, - required this.id, - }); + const LibraryItemMetadata({super.key, required this.id}); final String id; @@ -72,7 +69,8 @@ class LibraryItemMetadata extends HookConsumerWidget { ), _MetadataItem( title: 'Published', - value: itemBookMetadata?.publishedDate ?? + value: + itemBookMetadata?.publishedDate ?? itemBookMetadata?.publishedYear ?? 'Unknown', ), @@ -87,22 +85,18 @@ class LibraryItemMetadata extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, // alternate between metadata and vertical divider - children: List.generate( - children.length * 2 - 1, - (index) { - if (index.isEven) { - return children[index ~/ 2]; - } - return VerticalDivider( - indent: 6, - endIndent: 6, - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.6), - ); - }, - ), + children: List.generate(children.length * 2 - 1, (index) { + if (index.isEven) { + return children[index ~/ 2]; + } + return VerticalDivider( + indent: 6, + endIndent: 6, + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.6), + ); + }), ), ), ); @@ -111,10 +105,7 @@ class LibraryItemMetadata extends HookConsumerWidget { /// key-value pair to display as column class _MetadataItem extends StatelessWidget { - const _MetadataItem({ - required this.title, - required this.value, - }); + const _MetadataItem({required this.title, required this.value}); final String title; final String value; diff --git a/lib/features/item_viewer/view/library_item_page.dart b/lib/features/item_viewer/view/library_item_page.dart index b685a6a..6974bc6 100644 --- a/lib/features/item_viewer/view/library_item_page.dart +++ b/lib/features/item_viewer/view/library_item_page.dart @@ -16,49 +16,43 @@ import 'library_item_hero_section.dart'; import 'library_item_metadata.dart'; class LibraryItemPage extends HookConsumerWidget { - const LibraryItemPage({ - super.key, - required this.itemId, - this.extra, - }); + const LibraryItemPage({super.key, required this.itemId, this.extra}); final String itemId; final Object? extra; static const double _showFabThreshold = 300.0; @override Widget build(BuildContext context, WidgetRef ref) { - final additionalItemData = - extra is LibraryItemExtras ? extra as LibraryItemExtras : null; + final additionalItemData = extra is LibraryItemExtras + ? extra as LibraryItemExtras + : null; final scrollController = useScrollController(); final showFab = useState(false); // Effect to listen to scroll changes and update FAB visibility - useEffect( - () { - void listener() { - if (!scrollController.hasClients) { - return; // Ensure controller is attached - } - final shouldShow = scrollController.offset > _showFabThreshold; - // Update state only if it changes and widget is still mounted - if (showFab.value != shouldShow && context.mounted) { - showFab.value = shouldShow; - } + useEffect(() { + void listener() { + if (!scrollController.hasClients) { + return; // Ensure controller is attached } + final shouldShow = scrollController.offset > _showFabThreshold; + // Update state only if it changes and widget is still mounted + if (showFab.value != shouldShow && context.mounted) { + showFab.value = shouldShow; + } + } - scrollController.addListener(listener); - // Initial check in case the view starts scrolled (less likely but safe) - WidgetsBinding.instance.addPostFrameCallback((_) { - if (scrollController.hasClients && context.mounted) { - listener(); - } - }); + scrollController.addListener(listener); + // Initial check in case the view starts scrolled (less likely but safe) + WidgetsBinding.instance.addPostFrameCallback((_) { + if (scrollController.hasClients && context.mounted) { + listener(); + } + }); - // Cleanup: remove the listener when the widget is disposed - return () => scrollController.removeListener(listener); - }, - [scrollController], - ); // Re-run effect if scrollController changes + // Cleanup: remove the listener when the widget is disposed + return () => scrollController.removeListener(listener); + }, [scrollController]); // Re-run effect if scrollController changes // --- FAB Scroll-to-Top Logic --- void scrollToTop() { @@ -82,10 +76,7 @@ class LibraryItemPage extends HookConsumerWidget { transitionBuilder: (Widget child, Animation animation) { return ScaleTransition( scale: animation, - child: FadeTransition( - opacity: animation, - child: child, - ), + child: FadeTransition(opacity: animation, child: child), ); }, child: showFab.value @@ -96,9 +87,7 @@ class LibraryItemPage extends HookConsumerWidget { tooltip: 'Scroll to top', child: const Icon(Icons.arrow_upward), ) - : const SizedBox.shrink( - key: ValueKey('fab-empty'), - ), + : const SizedBox.shrink(key: ValueKey('fab-empty')), ), body: CustomScrollView( controller: scrollController, @@ -115,17 +104,11 @@ class LibraryItemPage extends HookConsumerWidget { ), ), // a horizontal display with dividers of metadata - SliverToBoxAdapter( - child: LibraryItemMetadata(id: itemId), - ), + SliverToBoxAdapter(child: LibraryItemMetadata(id: itemId)), // a row of actions like play, download, share, etc - SliverToBoxAdapter( - child: LibraryItemActions(id: itemId), - ), + SliverToBoxAdapter(child: LibraryItemActions(id: itemId)), // a expandable section for book description - SliverToBoxAdapter( - child: LibraryItemDescription(id: itemId), - ), + SliverToBoxAdapter(child: LibraryItemDescription(id: itemId)), // a padding at the bottom to make sure the last item is not hidden by mini player const SliverToBoxAdapter(child: MiniPlayerBottomPadding()), ], @@ -137,10 +120,7 @@ class LibraryItemPage extends HookConsumerWidget { } class LibraryItemDescription extends HookConsumerWidget { - const LibraryItemDescription({ - super.key, - required this.id, - }); + const LibraryItemDescription({super.key, required this.id}); final String id; @override @@ -160,16 +140,21 @@ class LibraryItemDescription extends HookConsumerWidget { double calculateWidth( BuildContext context, BoxConstraints constraints, { + /// width ratio of the cover image to the available width double widthRatio = 0.4, /// height ratio of the cover image to the available height double maxHeightToUse = 0.25, }) { - final availHeight = - min(constraints.maxHeight, MediaQuery.of(context).size.height); - final availWidth = - min(constraints.maxWidth, MediaQuery.of(context).size.width); + final availHeight = min( + constraints.maxHeight, + MediaQuery.of(context).size.height, + ); + final availWidth = min( + constraints.maxWidth, + MediaQuery.of(context).size.width, + ); // make the width widthRatio of the available width var width = availWidth * widthRatio; diff --git a/lib/features/item_viewer/view/library_item_sliver_app_bar.dart b/lib/features/item_viewer/view/library_item_sliver_app_bar.dart index 5616a96..5090160 100644 --- a/lib/features/item_viewer/view/library_item_sliver_app_bar.dart +++ b/lib/features/item_viewer/view/library_item_sliver_app_bar.dart @@ -21,28 +21,26 @@ class LibraryItemSliverAppBar extends HookConsumerWidget { final showTitle = useState(false); - useEffect( - () { - void listener() { - final shouldShow = scrollController.hasClients && - scrollController.offset > _showTitleThreshold; - if (showTitle.value != shouldShow) { - showTitle.value = shouldShow; - } + useEffect(() { + void listener() { + final shouldShow = + scrollController.hasClients && + scrollController.offset > _showTitleThreshold; + if (showTitle.value != shouldShow) { + showTitle.value = shouldShow; } + } - scrollController.addListener(listener); - // Trigger listener once initially in case the view starts scrolled - // (though unlikely for this specific use case, it's good practice) - WidgetsBinding.instance.addPostFrameCallback((_) { - if (scrollController.hasClients) { - listener(); - } - }); - return () => scrollController.removeListener(listener); - }, - [scrollController], - ); + scrollController.addListener(listener); + // Trigger listener once initially in case the view starts scrolled + // (though unlikely for this specific use case, it's good practice) + WidgetsBinding.instance.addPostFrameCallback((_) { + if (scrollController.hasClients) { + listener(); + } + }); + return () => scrollController.removeListener(listener); + }, [scrollController]); return SliverAppBar( elevation: 0, diff --git a/lib/features/library_browser/view/library_browser_page.dart b/lib/features/library_browser/view/library_browser_page.dart index 26cb2df..aa03fa8 100644 --- a/lib/features/library_browser/view/library_browser_page.dart +++ b/lib/features/library_browser/view/library_browser_page.dart @@ -41,43 +41,41 @@ class LibraryBrowserPage extends HookConsumerWidget { title: Text(appBarTitle), ), SliverList( - delegate: SliverChildListDelegate( - [ - ListTile( - title: const Text('Authors'), - leading: const Icon(Icons.person), - trailing: const Icon(Icons.chevron_right), - onTap: () { - showNotImplementedToast(context); - }, - ), - ListTile( - title: const Text('Genres'), - leading: const Icon(Icons.category), - trailing: const Icon(Icons.chevron_right), - onTap: () { - showNotImplementedToast(context); - }, - ), - ListTile( - title: const Text('Series'), - leading: const Icon(Icons.list), - trailing: const Icon(Icons.chevron_right), - onTap: () { - showNotImplementedToast(context); - }, - ), - // Downloads - ListTile( - title: const Text('Downloads'), - leading: const Icon(Icons.download), - trailing: const Icon(Icons.chevron_right), - onTap: () { - GoRouter.of(context).pushNamed(Routes.downloads.name); - }, - ), - ], - ), + delegate: SliverChildListDelegate([ + ListTile( + title: const Text('Authors'), + leading: const Icon(Icons.person), + trailing: const Icon(Icons.chevron_right), + onTap: () { + showNotImplementedToast(context); + }, + ), + ListTile( + title: const Text('Genres'), + leading: const Icon(Icons.category), + trailing: const Icon(Icons.chevron_right), + onTap: () { + showNotImplementedToast(context); + }, + ), + ListTile( + title: const Text('Series'), + leading: const Icon(Icons.list), + trailing: const Icon(Icons.chevron_right), + onTap: () { + showNotImplementedToast(context); + }, + ), + // Downloads + ListTile( + title: const Text('Downloads'), + leading: const Icon(Icons.download), + trailing: const Icon(Icons.chevron_right), + onTap: () { + GoRouter.of(context).pushNamed(Routes.downloads.name); + }, + ), + ]), ), ], ), diff --git a/lib/features/logging/providers/logs_provider.dart b/lib/features/logging/providers/logs_provider.dart index 9fd9e4a..763fa90 100644 --- a/lib/features/logging/providers/logs_provider.dart +++ b/lib/features/logging/providers/logs_provider.dart @@ -59,8 +59,10 @@ String generateZipFileName() { } Level parseLevel(String level) { - return Level.LEVELS - .firstWhere((l) => l.name == level, orElse: () => Level.ALL); + return Level.LEVELS.firstWhere( + (l) => l.name == level, + orElse: () => Level.ALL, + ); } LogRecord parseLogLine(String line) { diff --git a/lib/features/logging/view/logs_page.dart b/lib/features/logging/view/logs_page.dart index 74d1ad3..978fd19 100644 --- a/lib/features/logging/view/logs_page.dart +++ b/lib/features/logging/view/logs_page.dart @@ -54,8 +54,9 @@ class LogsPage extends HookConsumerWidget { icon: const Icon(Icons.share), onPressed: () async { appLogger.info('Preparing logs for sharing'); - final zipLogFilePath = - await ref.read(logsProvider.notifier).getZipFilePath(); + final zipLogFilePath = await ref + .read(logsProvider.notifier) + .getZipFilePath(); // submit logs final result = await Share.shareXFiles([XFile(zipLogFilePath)]); @@ -169,7 +170,6 @@ class LogsPage extends HookConsumerWidget { children: [ // a filter for log levels, loggers, and search // TODO: implement filters and search - Expanded( child: logs.when( data: (logRecords) { @@ -243,9 +243,7 @@ class LogRecordTile extends StatelessWidget { style: const TextStyle(fontStyle: FontStyle.italic), ), const TextSpan(text: '\n\n'), - TextSpan( - text: logRecord.message, - ), + TextSpan(text: logRecord.message), ], ), ), diff --git a/lib/features/onboarding/providers/oauth_provider.dart b/lib/features/onboarding/providers/oauth_provider.dart index fb7856b..38446fa 100644 --- a/lib/features/onboarding/providers/oauth_provider.dart +++ b/lib/features/onboarding/providers/oauth_provider.dart @@ -53,8 +53,10 @@ class OauthFlows extends _$OauthFlows { } state = { ...state, - oauthState: state[oauthState]! - .copyWith(isFlowComplete: true, authToken: authToken), + oauthState: state[oauthState]!.copyWith( + isFlowComplete: true, + authToken: authToken, + ), }; } } diff --git a/lib/features/onboarding/view/callback_page.dart b/lib/features/onboarding/view/callback_page.dart index f4bb098..bb4e139 100644 --- a/lib/features/onboarding/view/callback_page.dart +++ b/lib/features/onboarding/view/callback_page.dart @@ -27,9 +27,7 @@ class CallbackPage extends HookConsumerWidget { // check if the state is in the flows if (!flows.containsKey(state)) { - return const _SomethingWentWrong( - message: 'State not found', - ); + return const _SomethingWentWrong(message: 'State not found'); } // get the token @@ -45,26 +43,21 @@ class CallbackPage extends HookConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Contacting server...\nPlease wait\n\nGot:' - '\nState: $state\nCode: $code'), + Text( + 'Contacting server...\nPlease wait\n\nGot:' + '\nState: $state\nCode: $code', + ), loginAuthToken.when( data: (authenticationToken) { if (authenticationToken == null) { - handleServerError( - context, - serverErrorResponse, - ); + handleServerError(context, serverErrorResponse); return const BackToLoginButton(); } return Text('Token: $authenticationToken'); }, loading: () => const CircularProgressIndicator(), error: (error, _) { - handleServerError( - context, - serverErrorResponse, - e: error, - ); + handleServerError(context, serverErrorResponse, e: error); return Column( children: [ Text('Error with OAuth flow: $error'), @@ -81,9 +74,7 @@ class CallbackPage extends HookConsumerWidget { } class BackToLoginButton extends StatelessWidget { - const BackToLoginButton({ - super.key, - }); + const BackToLoginButton({super.key}); @override Widget build(BuildContext context) { @@ -97,9 +88,7 @@ class BackToLoginButton extends StatelessWidget { } class _SomethingWentWrong extends StatelessWidget { - const _SomethingWentWrong({ - this.message = 'Error with OAuth flow', - }); + const _SomethingWentWrong({this.message = 'Error with OAuth flow'}); final String message; @@ -109,10 +98,7 @@ class _SomethingWentWrong extends StatelessWidget { body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(message), - const BackToLoginButton(), - ], + children: [Text(message), const BackToLoginButton()], ), ), ); diff --git a/lib/features/onboarding/view/onboarding_single_page.dart b/lib/features/onboarding/view/onboarding_single_page.dart index b9a7eb5..c0e701a 100644 --- a/lib/features/onboarding/view/onboarding_single_page.dart +++ b/lib/features/onboarding/view/onboarding_single_page.dart @@ -9,9 +9,7 @@ import 'package:vaani/shared/utils.dart'; import 'package:vaani/shared/widgets/add_new_server.dart'; class OnboardingSinglePage extends HookConsumerWidget { - const OnboardingSinglePage({ - super.key, - }); + const OnboardingSinglePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -23,8 +21,9 @@ class OnboardingSinglePage extends HookConsumerWidget { child: ConstrainedBox( constraints: BoxConstraints( maxWidth: 600, - minWidth: - constraints.maxWidth < 600 ? constraints.maxWidth : 0, + minWidth: constraints.maxWidth < 600 + ? constraints.maxWidth + : 0, ), child: const Padding( padding: EdgeInsets.symmetric(vertical: 20.0), @@ -39,10 +38,7 @@ class OnboardingSinglePage extends HookConsumerWidget { } } -Widget fadeSlideTransitionBuilder( - Widget child, - Animation animation, -) { +Widget fadeSlideTransitionBuilder(Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SlideTransition( @@ -56,9 +52,7 @@ Widget fadeSlideTransitionBuilder( } class OnboardingBody extends HookConsumerWidget { - const OnboardingBody({ - super.key, - }); + const OnboardingBody({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -81,9 +75,7 @@ class OnboardingBody extends HookConsumerWidget { style: Theme.of(context).textTheme.headlineSmall, ), ), - const SizedBox.square( - dimension: 16.0, - ), + const SizedBox.square(dimension: 16.0), Padding( padding: const EdgeInsets.all(8.0), child: AnimatedSwitcher( @@ -112,21 +104,17 @@ class OnboardingBody extends HookConsumerWidget { }, ), ), - const SizedBox.square( - dimension: 16.0, - ), + const SizedBox.square(dimension: 16.0), AnimatedSwitcher( duration: 500.ms, transitionBuilder: fadeSlideTransitionBuilder, child: canUserLogin.value - ? UserLoginWidget( - server: audiobookshelfUri, - ) + ? UserLoginWidget(server: audiobookshelfUri) // ).animate().fade(duration: 600.ms).slideY(begin: 0.3, end: 0) : const RedirectToABS().animate().fadeIn().slideY( - curve: Curves.easeInOut, - duration: 500.ms, - ), + curve: Curves.easeInOut, + duration: 500.ms, + ), ), ], ); @@ -134,9 +122,7 @@ class OnboardingBody extends HookConsumerWidget { } class RedirectToABS extends StatelessWidget { - const RedirectToABS({ - super.key, - }); + const RedirectToABS({super.key}); @override Widget build(BuildContext context) { @@ -152,18 +138,14 @@ class RedirectToABS extends StatelessWidget { isSemanticButton: false, style: ButtonStyle( elevation: WidgetStateProperty.all(0), - padding: WidgetStateProperty.all( - const EdgeInsets.all(0), - ), + padding: WidgetStateProperty.all(const EdgeInsets.all(0)), ), onPressed: () async { // open the github page // ignore: avoid_print print('Opening the github page'); await handleLaunchUrl( - Uri.parse( - 'https://www.audiobookshelf.org', - ), + Uri.parse('https://www.audiobookshelf.org'), ); }, child: const Text('Click here'), diff --git a/lib/features/onboarding/view/user_login.dart b/lib/features/onboarding/view/user_login.dart index 8aeff14..7428953 100644 --- a/lib/features/onboarding/view/user_login.dart +++ b/lib/features/onboarding/view/user_login.dart @@ -22,11 +22,7 @@ import 'package:vaani/settings/api_settings_provider.dart' import 'package:vaani/settings/models/models.dart' as model; class UserLoginWidget extends HookConsumerWidget { - const UserLoginWidget({ - super.key, - required this.server, - this.onSuccess, - }); + const UserLoginWidget({super.key, required this.server, this.onSuccess}); final Uri server; final Function(model.AuthenticatedUser)? onSuccess; @@ -34,8 +30,9 @@ class UserLoginWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final serverStatusError = useMemoized(() => ErrorResponseHandler(), []); - final serverStatus = - ref.watch(serverStatusProvider(server, serverStatusError.storeError)); + final serverStatus = ref.watch( + serverStatusProvider(server, serverStatusError.storeError), + ); return serverStatus.when( data: (value) { @@ -55,9 +52,7 @@ class UserLoginWidget extends HookConsumerWidget { ); }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); + return const Center(child: CircularProgressIndicator()); }, error: (error, _) { return Center( @@ -68,10 +63,7 @@ class UserLoginWidget extends HookConsumerWidget { ElevatedButton( onPressed: () { ref.invalidate( - serverStatusProvider( - server, - serverStatusError.storeError, - ), + serverStatusProvider(server, serverStatusError.storeError), ); }, child: const Text('Try again'), @@ -84,11 +76,7 @@ class UserLoginWidget extends HookConsumerWidget { } } -enum AuthMethodChoice { - local, - openid, - authToken, -} +enum AuthMethodChoice { local, openid, authToken } class UserLoginMultipleAuth extends HookConsumerWidget { const UserLoginMultipleAuth({ @@ -117,21 +105,17 @@ class UserLoginMultipleAuth extends HookConsumerWidget { ); model.AudiobookShelfServer addServer() { - var newServer = model.AudiobookShelfServer( - serverUrl: server, - ); + var newServer = model.AudiobookShelfServer(serverUrl: server); try { // add the server to the list of servers - ref.read(audiobookShelfServerProvider.notifier).addServer( - newServer, - ); + ref.read(audiobookShelfServerProvider.notifier).addServer(newServer); } on ServerAlreadyExistsException catch (e) { newServer = e.server; } finally { - ref.read(apiSettingsProvider.notifier).updateState( - ref.read(apiSettingsProvider).copyWith( - activeServer: newServer, - ), + ref + .read(apiSettingsProvider.notifier) + .updateState( + ref.read(apiSettingsProvider).copyWith(activeServer: newServer), ); } return newServer; @@ -150,42 +134,49 @@ class UserLoginMultipleAuth extends HookConsumerWidget { runAlignment: WrapAlignment.center, runSpacing: 10, alignment: WrapAlignment.center, - children: [ - // a small label to show the user what to do - if (localAvailable) - ChoiceChip( - label: const Text('Local'), - selected: methodChoice.value == AuthMethodChoice.local, - onSelected: (selected) { - if (selected) { - methodChoice.value = AuthMethodChoice.local; - } - }, - ), - if (openIDAvailable) - ChoiceChip( - label: const Text('OpenID'), - selected: methodChoice.value == AuthMethodChoice.openid, - onSelected: (selected) { - if (selected) { - methodChoice.value = AuthMethodChoice.openid; - } - }, - ), - ChoiceChip( - label: const Text('Token'), - selected: - methodChoice.value == AuthMethodChoice.authToken, - onSelected: (selected) { - if (selected) { - methodChoice.value = AuthMethodChoice.authToken; - } - }, - ), - ].animate(interval: 100.ms).fadeIn( - duration: 150.ms, - curve: Curves.easeIn, - ), + children: + [ + // a small label to show the user what to do + if (localAvailable) + ChoiceChip( + label: const Text('Local'), + selected: + methodChoice.value == + AuthMethodChoice.local, + onSelected: (selected) { + if (selected) { + methodChoice.value = AuthMethodChoice.local; + } + }, + ), + if (openIDAvailable) + ChoiceChip( + label: const Text('OpenID'), + selected: + methodChoice.value == + AuthMethodChoice.openid, + onSelected: (selected) { + if (selected) { + methodChoice.value = + AuthMethodChoice.openid; + } + }, + ), + ChoiceChip( + label: const Text('Token'), + selected: + methodChoice.value == + AuthMethodChoice.authToken, + onSelected: (selected) { + if (selected) { + methodChoice.value = + AuthMethodChoice.authToken; + } + }, + ), + ] + .animate(interval: 100.ms) + .fadeIn(duration: 150.ms, curve: Curves.easeIn), ), ), Padding( @@ -195,21 +186,21 @@ class UserLoginMultipleAuth extends HookConsumerWidget { transitionBuilder: fadeSlideTransitionBuilder, child: switch (methodChoice.value) { AuthMethodChoice.authToken => UserLoginWithToken( - server: server, - addServer: addServer, - onSuccess: onSuccess, - ), + server: server, + addServer: addServer, + onSuccess: onSuccess, + ), AuthMethodChoice.local => UserLoginWithPassword( - server: server, - addServer: addServer, - onSuccess: onSuccess, - ), + server: server, + addServer: addServer, + onSuccess: onSuccess, + ), AuthMethodChoice.openid => UserLoginWithOpenID( - server: server, - addServer: addServer, - openIDButtonText: openIDButtonText, - onSuccess: onSuccess, - ), + server: server, + addServer: addServer, + openIDButtonText: openIDButtonText, + onSuccess: onSuccess, + ), }, ), ), diff --git a/lib/features/onboarding/view/user_login_with_open_id.dart b/lib/features/onboarding/view/user_login_with_open_id.dart index b3a1d9e..339b6c5 100644 --- a/lib/features/onboarding/view/user_login_with_open_id.dart +++ b/lib/features/onboarding/view/user_login_with_open_id.dart @@ -54,9 +54,9 @@ class UserLoginWithOpenID extends HookConsumerWidget { if (openIDLoginEndpoint == null) { if (responseErrorHandler.response.statusCode == 400 && - responseErrorHandler.response.body - .toLowerCase() - .contains(RegExp(r'invalid.*redirect.*uri'))) { + responseErrorHandler.response.body.toLowerCase().contains( + RegExp(r'invalid.*redirect.*uri'), + )) { // show error handleServerError( context, @@ -97,16 +97,16 @@ class UserLoginWithOpenID extends HookConsumerWidget { ); // add the flow to the provider - ref.read(oauthFlowsProvider.notifier).addFlow( + ref + .read(oauthFlowsProvider.notifier) + .addFlow( oauthState, verifier: verifier, serverUri: server, cookie: Cookie.fromSetCookieValue(authCookie!), ); - await handleLaunchUrl( - openIDLoginEndpoint, - ); + await handleLaunchUrl(openIDLoginEndpoint); } return Column( diff --git a/lib/features/onboarding/view/user_login_with_password.dart b/lib/features/onboarding/view/user_login_with_password.dart index 210da77..20d2613 100644 --- a/lib/features/onboarding/view/user_login_with_password.dart +++ b/lib/features/onboarding/view/user_login_with_password.dart @@ -39,17 +39,14 @@ class UserLoginWithPassword extends HookConsumerWidget { final api = ref.watch(audiobookshelfApiProvider(server)); // forward animation when the password visibility changes - useEffect( - () { - if (isPasswordVisible.value) { - isPasswordVisibleAnimationController.forward(); - } else { - isPasswordVisibleAnimationController.reverse(); - } - return null; - }, - [isPasswordVisible.value], - ); + useEffect(() { + if (isPasswordVisible.value) { + isPasswordVisibleAnimationController.forward(); + } else { + isPasswordVisibleAnimationController.reverse(); + } + return null; + }, [isPasswordVisible.value]); /// Login to the server and save the user Future loginAndSave() async { @@ -109,10 +106,9 @@ class UserLoginWithPassword extends HookConsumerWidget { decoration: InputDecoration( labelText: 'Username', labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.8), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.8), ), border: const OutlineInputBorder(), ), @@ -129,18 +125,16 @@ class UserLoginWithPassword extends HookConsumerWidget { decoration: InputDecoration( labelText: 'Password', labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.8), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.8), ), border: const OutlineInputBorder(), suffixIcon: ColorFiltered( colorFilter: ColorFilter.mode( - Theme.of(context) - .colorScheme - .primary - .withValues(alpha: 0.8), + Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.8), BlendMode.srcIn, ), child: InkWell( @@ -157,9 +151,7 @@ class UserLoginWithPassword extends HookConsumerWidget { ), ), ), - suffixIconConstraints: const BoxConstraints( - maxHeight: 45, - ), + suffixIconConstraints: const BoxConstraints(maxHeight: 45), ), ), const SizedBox(height: 30), @@ -197,10 +189,12 @@ Future handleServerError( context: context, builder: (context) => AlertDialog( title: const Text('Error'), - content: SelectableText('$title\n' - 'Got response: ${responseErrorHandler.response.body} (${responseErrorHandler.response.statusCode})\n' - 'Stacktrace: $e\n\n' - '$body\n\n'), + content: SelectableText( + '$title\n' + 'Got response: ${responseErrorHandler.response.body} (${responseErrorHandler.response.statusCode})\n' + 'Stacktrace: $e\n\n' + '$body\n\n', + ), actions: [ if (outLink != null) TextButton( @@ -214,8 +208,8 @@ Future handleServerError( // open an issue on the github page handleLaunchUrl( AppMetadata.githubRepo - // append the issue url - .replace( + // append the issue url + .replace( path: '${AppMetadata.githubRepo.path}/issues/new', ), ); diff --git a/lib/features/onboarding/view/user_login_with_token.dart b/lib/features/onboarding/view/user_login_with_token.dart index 7d2fcfb..fb5a1fa 100644 --- a/lib/features/onboarding/view/user_login_with_token.dart +++ b/lib/features/onboarding/view/user_login_with_token.dart @@ -89,10 +89,9 @@ class UserLoginWithToken extends HookConsumerWidget { decoration: InputDecoration( labelText: 'API Token', labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.8), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.8), ), border: const OutlineInputBorder(), ), @@ -107,10 +106,7 @@ class UserLoginWithToken extends HookConsumerWidget { }, ), const SizedBox(height: 10), - ElevatedButton( - onPressed: loginAndSave, - child: const Text('Login'), - ), + ElevatedButton(onPressed: loginAndSave, child: const Text('Login')), ], ), ); diff --git a/lib/features/playback_reporting/core/playback_reporter.dart b/lib/features/playback_reporting/core/playback_reporter.dart index b2be336..e140bf6 100644 --- a/lib/features/playback_reporting/core/playback_reporter.dart +++ b/lib/features/playback_reporting/core/playback_reporter.dart @@ -126,9 +126,7 @@ class PlaybackReporter { } Future tryReportPlayback(_) async { - _logger.fine( - 'callback called when elapsed ${_stopwatch.elapsed}', - ); + _logger.fine('callback called when elapsed ${_stopwatch.elapsed}'); if (player.book != null && player.positionInBook >= player.book!.duration - markCompleteWhenTimeLeft) { diff --git a/lib/features/playback_reporting/providers/playback_reporter_provider.dart b/lib/features/playback_reporting/providers/playback_reporter_provider.dart index a7ca1d5..1bee521 100644 --- a/lib/features/playback_reporting/providers/playback_reporter_provider.dart +++ b/lib/features/playback_reporting/providers/playback_reporter_provider.dart @@ -20,8 +20,9 @@ class PlaybackReporter extends _$PlaybackReporter { final deviceName = await ref.watch(deviceNameProvider.future); final deviceModel = await ref.watch(deviceModelProvider.future); final deviceSdkVersion = await ref.watch(deviceSdkVersionProvider.future); - final deviceManufacturer = - await ref.watch(deviceManufacturerProvider.future); + final deviceManufacturer = await ref.watch( + deviceManufacturerProvider.future, + ); final reporter = core.PlaybackReporter( player, diff --git a/lib/features/player/core/audiobook_player.dart b/lib/features/player/core/audiobook_player.dart index 87247fe..c3d7b46 100644 --- a/lib/features/player/core/audiobook_player.dart +++ b/lib/features/player/core/audiobook_player.dart @@ -23,7 +23,9 @@ Duration sumOfTracks(BookExpanded book, int? index) { _logger.warning('Index is null or less than 0, returning 0'); return Duration.zero; } - final total = book.tracks.sublist(0, index).fold( + final total = book.tracks + .sublist(0, index) + .fold( Duration.zero, (previousValue, element) => previousValue + element.duration, ); @@ -34,13 +36,10 @@ Duration sumOfTracks(BookExpanded book, int? index) { /// returns the [AudioTrack] to play based on the [position] in the [book] AudioTrack getTrackToPlay(BookExpanded book, Duration position) { _logger.fine('Getting track to play for position: $position'); - final track = book.tracks.firstWhere( - (element) { - return element.startOffset <= position && - (element.startOffset + element.duration) >= position; - }, - orElse: () => book.tracks.last, - ); + final track = book.tracks.firstWhere((element) { + return element.startOffset <= position && + (element.startOffset + element.duration) >= position; + }, orElse: () => book.tracks.last); _logger.fine('Track to play for position: $position is $track'); return track; } @@ -126,8 +125,12 @@ class AudiobookPlayer extends AudioPlayer { ConcatenatingAudioSource( useLazyPreparation: true, children: book.tracks.map((track) { - final retrievedUri = - _getUri(track, downloadedUris, baseUrl: baseUrl, token: token); + final retrievedUri = _getUri( + track, + downloadedUris, + baseUrl: baseUrl, + token: token, + ); _logger.fine( 'Setting source for track: ${track.title}, URI: ${retrievedUri.obfuscate()}', ); @@ -141,7 +144,8 @@ class AudiobookPlayer extends AudioPlayer { .formatNotificationTitle(book), album: appSettings.notificationSettings.secondaryTitle .formatNotificationTitle(book), - artUri: artworkUri ?? + artUri: + artworkUri ?? Uri.parse( '$baseUrl/api/items/${book.libraryItemId}/cover?token=$token&width=800', ), @@ -255,12 +259,9 @@ class AudiobookPlayer extends AudioPlayer { if (_book!.chapters.isEmpty) { return null; } - return _book!.chapters.firstWhere( - (element) { - return element.start <= positionInBook && element.end >= positionInBook; - }, - orElse: () => _book!.chapters.first, - ); + return _book!.chapters.firstWhere((element) { + return element.start <= positionInBook && element.end >= positionInBook; + }, orElse: () => _book!.chapters.first); } } @@ -271,11 +272,9 @@ Uri _getUri( required String token, }) { // check if the track is in the downloadedUris - final uri = downloadedUris?.firstWhereOrNull( - (element) { - return element.pathSegments.last == track.metadata?.filename; - }, - ); + final uri = downloadedUris?.firstWhereOrNull((element) { + return element.pathSegments.last == track.metadata?.filename; + }); return uri ?? Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token'); @@ -283,17 +282,14 @@ Uri _getUri( 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.name == type) - .extractFrom(book) ?? - match.group(0) ?? - ''; - }, - ); + return replaceAllMapped(RegExp(r'\$(\w+)'), (match) { + final type = match.group(1); + return NotificationTitleType.values + .firstWhere((element) => element.name == type) + .extractFrom(book) ?? + match.group(0) ?? + ''; + }); } } diff --git a/lib/features/player/core/init.dart b/lib/features/player/core/init.dart index abcf013..a95f3c7 100644 --- a/lib/features/player/core/init.dart +++ b/lib/features/player/core/init.dart @@ -30,23 +30,28 @@ Future configurePlayer() async { androidShowNotificationBadge: false, notificationConfigBuilder: (state) { final controls = [ - if (appSettings.notificationSettings.mediaControls - .contains(NotificationMediaControl.skipToPreviousChapter) && + if (appSettings.notificationSettings.mediaControls.contains( + NotificationMediaControl.skipToPreviousChapter, + ) && state.hasPrevious) MediaControl.skipToPrevious, - if (appSettings.notificationSettings.mediaControls - .contains(NotificationMediaControl.rewind)) + if (appSettings.notificationSettings.mediaControls.contains( + NotificationMediaControl.rewind, + )) MediaControl.rewind, if (state.playing) MediaControl.pause else MediaControl.play, - if (appSettings.notificationSettings.mediaControls - .contains(NotificationMediaControl.fastForward)) + if (appSettings.notificationSettings.mediaControls.contains( + NotificationMediaControl.fastForward, + )) MediaControl.fastForward, - if (appSettings.notificationSettings.mediaControls - .contains(NotificationMediaControl.skipToNextChapter) && + if (appSettings.notificationSettings.mediaControls.contains( + NotificationMediaControl.skipToNextChapter, + ) && state.hasNext) MediaControl.skipToNext, - if (appSettings.notificationSettings.mediaControls - .contains(NotificationMediaControl.stop)) + if (appSettings.notificationSettings.mediaControls.contains( + NotificationMediaControl.stop, + )) MediaControl.stop, ]; return NotificationConfig( diff --git a/lib/features/player/playlist.dart b/lib/features/player/playlist.dart index 61c2326..c8baedd 100644 --- a/lib/features/player/playlist.dart +++ b/lib/features/player/playlist.dart @@ -62,8 +62,8 @@ class AudiobookPlaylist { this.books = const [], currentIndex = 0, subCurrentIndex = 0, - }) : _currentIndex = currentIndex, - _subCurrentIndex = subCurrentIndex; + }) : _currentIndex = currentIndex, + _subCurrentIndex = subCurrentIndex; // most important method, gets the audio file to play // this is needed as a library item is a list of audio files diff --git a/lib/features/player/providers/audiobook_player.dart b/lib/features/player/providers/audiobook_player.dart index 91b6483..29fede9 100644 --- a/lib/features/player/providers/audiobook_player.dart +++ b/lib/features/player/providers/audiobook_player.dart @@ -16,10 +16,7 @@ class SimpleAudiobookPlayer extends _$SimpleAudiobookPlayer { @override core.AudiobookPlayer build() { final api = ref.watch(authenticatedApiProvider); - final player = core.AudiobookPlayer( - api.token!, - api.baseUrl, - ); + final player = core.AudiobookPlayer(api.token!, api.baseUrl); ref.onDispose(player.dispose); _logger.finer('created simple player'); diff --git a/lib/features/player/providers/player_form.dart b/lib/features/player/providers/player_form.dart index 73d6bdc..9652486 100644 --- a/lib/features/player/providers/player_form.dart +++ b/lib/features/player/providers/player_form.dart @@ -26,11 +26,10 @@ extension on Ref { } @Riverpod(keepAlive: true) -Raw> playerExpandProgressNotifier( - Ref ref, -) { - final ValueNotifier playerExpandProgress = - ValueNotifier(playerMinHeight); +Raw> playerExpandProgressNotifier(Ref ref) { + final ValueNotifier playerExpandProgress = ValueNotifier( + playerMinHeight, + ); return ref.disposeAndListenChangeNotifier(playerExpandProgress); } @@ -46,9 +45,7 @@ Raw> playerExpandProgressNotifier( // a provider that will listen to the playerExpandProgressNotifier and return the percentage of the player expanded @Riverpod(keepAlive: true) -double playerHeight( - Ref ref, -) { +double playerHeight(Ref ref) { final playerExpandProgress = ref.watch(playerExpandProgressProvider); // on change of the playerExpandProgress invalidate @@ -63,9 +60,7 @@ double playerHeight( final audioBookMiniplayerController = MiniplayerController(); @Riverpod(keepAlive: true) -bool isPlayerActive( - Ref ref, -) { +bool isPlayerActive(Ref ref) { try { final player = ref.watch(audiobookPlayerProvider); if (player.book != null) { diff --git a/lib/features/player/view/audiobook_player.dart b/lib/features/player/view/audiobook_player.dart index 2909a9b..e80202a 100644 --- a/lib/features/player/view/audiobook_player.dart +++ b/lib/features/player/view/audiobook_player.dart @@ -31,19 +31,15 @@ class AudiobookPlayer extends HookConsumerWidget { if (currentBook == null) { return const SizedBox.shrink(); } - final itemBeingPlayed = - ref.watch(libraryItemProvider(currentBook.libraryItemId)); + final itemBeingPlayed = ref.watch( + libraryItemProvider(currentBook.libraryItemId), + ); final player = ref.watch(audiobookPlayerProvider); final imageOfItemBeingPlayed = itemBeingPlayed.value != null - ? ref.watch( - coverImageProvider(itemBeingPlayed.value!.id), - ) + ? ref.watch(coverImageProvider(itemBeingPlayed.value!.id)) : null; final imgWidget = imageOfItemBeingPlayed?.value != null - ? Image.memory( - imageOfItemBeingPlayed!.value!, - fit: BoxFit.cover, - ) + ? Image.memory(imageOfItemBeingPlayed!.value!, fit: BoxFit.cover) : const BookCoverSkeleton(); final playPauseController = useAnimationController( @@ -65,7 +61,8 @@ class AudiobookPlayer extends HookConsumerWidget { themeOfLibraryItemProvider( itemBeingPlayed.value?.id, brightness: Theme.of(context).brightness, - highContrast: appSettings.themeSettings.highContrast || + highContrast: + appSettings.themeSettings.highContrast || MediaQuery.of(context).highContrast, ), ); @@ -88,8 +85,9 @@ class AudiobookPlayer extends HookConsumerWidget { onDragDown: (percentage) async { // preferred volume // set volume to 0 when dragging down - await player - .setVolume(preferredVolume * (1 - percentage.clamp(0, .75))); + await player.setVolume( + preferredVolume * (1 - percentage.clamp(0, .75)), + ); }, minHeight: playerMinHeight, // subtract the height of notches and other system UI @@ -109,17 +107,14 @@ class AudiobookPlayer extends HookConsumerWidget { // also at this point the image should be at its max size and in the center of the player final miniplayerPercentageDeclaration = (maxImgSize - playerMinHeight) / - (playerMaxHeight - playerMinHeight); + (playerMaxHeight - playerMinHeight); final bool isFormMiniplayer = percentage < miniplayerPercentageDeclaration; if (!isFormMiniplayer) { // this calculation needs a refactor var percentageExpandedPlayer = percentage - .inverseLerp( - miniplayerPercentageDeclaration, - 1, - ) + .inverseLerp(miniplayerPercentageDeclaration, 1) .clamp(0.0, 1.0); return PlayerWhenExpanded( @@ -164,37 +159,33 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget { return switch (player.processingState) { ProcessingState.loading || ProcessingState.buffering => const Padding( - padding: EdgeInsets.all(8.0), - child: CircularProgressIndicator(), - ), + padding: EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), ProcessingState.completed => IconButton( - onPressed: () async { - await player.seek(const Duration(seconds: 0)); - await player.play(); - }, - icon: const Icon( - Icons.replay, - ), - ), + onPressed: () async { + await player.seek(const Duration(seconds: 0)); + await player.play(); + }, + icon: const Icon(Icons.replay), + ), ProcessingState.ready => IconButton( - onPressed: () async { - await player.togglePlayPause(); - }, - iconSize: iconSize, - icon: AnimatedIcon( - icon: AnimatedIcons.play_pause, - progress: playPauseController, - ), + onPressed: () async { + await player.togglePlayPause(); + }, + iconSize: iconSize, + icon: AnimatedIcon( + icon: AnimatedIcons.play_pause, + progress: playPauseController, ), + ), ProcessingState.idle => const SizedBox.shrink(), }; } } class AudiobookChapterProgressBar extends HookConsumerWidget { - const AudiobookChapterProgressBar({ - super.key, - }); + const AudiobookChapterProgressBar({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/features/player/view/player_when_expanded.dart b/lib/features/player/view/player_when_expanded.dart index 0deda52..28e6993 100644 --- a/lib/features/player/view/player_when_expanded.dart +++ b/lib/features/player/view/player_when_expanded.dart @@ -38,10 +38,7 @@ class PlayerWhenExpanded extends HookConsumerWidget { const lateStart = 0.4; const earlyEnd = 1; final earlyPercentage = percentageExpandedPlayer - .inverseLerp( - lateStart, - earlyEnd, - ) + .inverseLerp(lateStart, earlyEnd) .clamp(0.0, 1.0); final currentChapter = ref.watch(currentPlayingChapterProvider); final currentBookMetadata = ref.watch(currentBookMetadataProvider); @@ -49,15 +46,11 @@ class PlayerWhenExpanded extends HookConsumerWidget { return Column( children: [ // sized box for system status bar; not needed as not full screen - SizedBox( - height: MediaQuery.of(context).padding.top * earlyPercentage, - ), + SizedBox(height: MediaQuery.of(context).padding.top * earlyPercentage), // a row with a down arrow to minimize the player, a pill shaped container to drag the player, and a cast button ConstrainedBox( - constraints: BoxConstraints( - maxHeight: 100 * earlyPercentage, - ), + constraints: BoxConstraints(maxHeight: 100 * earlyPercentage), child: Opacity( opacity: earlyPercentage, child: Padding( @@ -104,10 +97,9 @@ class PlayerWhenExpanded extends HookConsumerWidget { decoration: BoxDecoration( boxShadow: [ BoxShadow( - color: Theme.of(context) - .colorScheme - .primary - .withValues(alpha: 0.1), + color: Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.1), blurRadius: 32 * earlyPercentage, spreadRadius: 8 * earlyPercentage, // offset: Offset(0, 16 * earlyPercentage), @@ -170,11 +162,10 @@ class PlayerWhenExpanded extends HookConsumerWidget { currentBookMetadata?.authorName ?? '', ].join(' - '), style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.7), - ), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.7), + ), maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/lib/features/player/view/player_when_minimized.dart b/lib/features/player/view/player_when_minimized.dart index 1a5774b..9729fe2 100644 --- a/lib/features/player/view/player_when_minimized.dart +++ b/lib/features/player/view/player_when_minimized.dart @@ -32,8 +32,10 @@ class PlayerWhenMinimized extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final player = ref.watch(audiobookPlayerProvider); final vanishingPercentage = 1 - percentageMiniplayer; - final progress = - useStream(player.slowPositionStream, initialData: Duration.zero); + final progress = useStream( + player.slowPositionStream, + initialData: Duration.zero, + ); final bookMetaExpanded = ref.watch(currentBookMetadataProvider); @@ -61,9 +63,7 @@ class PlayerWhenMinimized extends HookConsumerWidget { ); }, child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: maxImgSize, - ), + constraints: BoxConstraints(maxWidth: maxImgSize), child: imgWidget, ), ), @@ -80,7 +80,8 @@ class PlayerWhenMinimized extends HookConsumerWidget { // AutoScrollText( Text( bookMetaExpanded?.title ?? '', - maxLines: 1, overflow: TextOverflow.ellipsis, + maxLines: 1, + overflow: TextOverflow.ellipsis, // velocity: // const Velocity(pixelsPerSecond: Offset(16, 0)), style: Theme.of(context).textTheme.bodyLarge, @@ -90,11 +91,10 @@ class PlayerWhenMinimized extends HookConsumerWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.7), - ), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.7), + ), ), ], ), @@ -135,7 +135,8 @@ class PlayerWhenMinimized extends HookConsumerWidget { SizedBox( height: barHeight, child: LinearProgressIndicator( - value: (progress.data ?? Duration.zero).inSeconds / + value: + (progress.data ?? Duration.zero).inSeconds / player.book!.duration.inSeconds, color: Theme.of(context).colorScheme.onPrimaryContainer, backgroundColor: Theme.of(context).colorScheme.primaryContainer, diff --git a/lib/features/player/view/widgets/audiobook_player_seek_button.dart b/lib/features/player/view/widgets/audiobook_player_seek_button.dart index ac1ec20..d1d1edc 100644 --- a/lib/features/player/view/widgets/audiobook_player_seek_button.dart +++ b/lib/features/player/view/widgets/audiobook_player_seek_button.dart @@ -4,10 +4,7 @@ import 'package:vaani/constants/sizes.dart'; import 'package:vaani/features/player/providers/audiobook_player.dart'; class AudiobookPlayerSeekButton extends HookConsumerWidget { - const AudiobookPlayerSeekButton({ - super.key, - required this.isForward, - }); + const AudiobookPlayerSeekButton({super.key, required this.isForward}); /// if true, the button seeks forward, else it seeks backwards final bool isForward; diff --git a/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart b/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart index ad47e8b..16127ad 100644 --- a/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart +++ b/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart @@ -5,10 +5,7 @@ import 'package:vaani/constants/sizes.dart'; import 'package:vaani/features/player/providers/audiobook_player.dart'; class AudiobookPlayerSeekChapterButton extends HookConsumerWidget { - const AudiobookPlayerSeekChapterButton({ - super.key, - required this.isForward, - }); + const AudiobookPlayerSeekChapterButton({super.key, required this.isForward}); /// if true, the button seeks forward, else it seeks backwards final bool isForward; @@ -27,9 +24,7 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget { void seekForward() { final index = player.book!.chapters.indexOf(player.currentChapter!); if (index < player.book!.chapters.length - 1) { - player.seek( - player.book!.chapters[index + 1].start + offset, - ); + player.seek(player.book!.chapters[index + 1].start + offset); } else { player.seek(player.currentChapter!.end); } @@ -37,8 +32,9 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget { /// seek backward to the previous chapter or the start of the current chapter void seekBackward() { - final currentPlayingChapterIndex = - player.book!.chapters.indexOf(player.currentChapter!); + final currentPlayingChapterIndex = player.book!.chapters.indexOf( + player.currentChapter!, + ); final chapterPosition = player.positionInBook - player.currentChapter!.start; BookChapter chapterToSeekTo; @@ -49,9 +45,7 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget { } else { chapterToSeekTo = player.currentChapter!; } - player.seek( - chapterToSeekTo.start + offset, - ); + player.seek(chapterToSeekTo.start + offset); } return IconButton( diff --git a/lib/features/player/view/widgets/chapter_selection_button.dart b/lib/features/player/view/widgets/chapter_selection_button.dart index 04cbd0e..ea2154e 100644 --- a/lib/features/player/view/widgets/chapter_selection_button.dart +++ b/lib/features/player/view/widgets/chapter_selection_button.dart @@ -15,9 +15,7 @@ import 'package:vaani/shared/extensions/duration_format.dart' import 'package:vaani/shared/hooks.dart' show useTimer; class ChapterSelectionButton extends HookConsumerWidget { - const ChapterSelectionButton({ - super.key, - }); + const ChapterSelectionButton({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -49,9 +47,7 @@ class ChapterSelectionButton extends HookConsumerWidget { } class ChapterSelectionModal extends HookConsumerWidget { - const ChapterSelectionModal({ - super.key, - }); + const ChapterSelectionModal({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -87,41 +83,40 @@ class ChapterSelectionModal extends HookConsumerWidget { child: currentBook?.chapters == null ? const Text('No chapters found') : Column( - children: currentBook!.chapters.map( - (chapter) { - final isCurrent = currentChapterIndex == chapter.id; - final isPlayed = currentChapterIndex != null && - chapter.id < currentChapterIndex; - return ListTile( - autofocus: isCurrent, - iconColor: isPlayed && !isCurrent - ? theme.disabledColor + children: currentBook!.chapters.map((chapter) { + final isCurrent = currentChapterIndex == chapter.id; + final isPlayed = + currentChapterIndex != null && + chapter.id < currentChapterIndex; + return ListTile( + autofocus: isCurrent, + iconColor: isPlayed && !isCurrent + ? theme.disabledColor + : null, + title: Text( + chapter.title, + style: isPlayed && !isCurrent + ? TextStyle(color: theme.disabledColor) : null, - title: Text( - chapter.title, - style: isPlayed && !isCurrent - ? TextStyle(color: theme.disabledColor) - : null, - ), - subtitle: Text( - '(${chapter.duration.smartBinaryFormat})', - style: isPlayed && !isCurrent - ? TextStyle(color: theme.disabledColor) - : null, - ), - trailing: isCurrent - ? const PlayingIndicatorIcon() - : const Icon(Icons.play_arrow), - selected: isCurrent, - key: isCurrent ? chapterKey : null, - onTap: () { - Navigator.of(context).pop(); - notifier.seek(chapter.start + 90.ms); - notifier.play(); - }, - ); - }, - ).toList(), + ), + subtitle: Text( + '(${chapter.duration.smartBinaryFormat})', + style: isPlayed && !isCurrent + ? TextStyle(color: theme.disabledColor) + : null, + ), + trailing: isCurrent + ? const PlayingIndicatorIcon() + : const Icon(Icons.play_arrow), + selected: isCurrent, + key: isCurrent ? chapterKey : null, + onTap: () { + Navigator.of(context).pop(); + notifier.seek(chapter.start + 90.ms); + notifier.play(); + }, + ); + }).toList(), ), ), ), diff --git a/lib/features/player/view/widgets/player_speed_adjust_button.dart b/lib/features/player/view/widgets/player_speed_adjust_button.dart index 36b7cd7..021f999 100644 --- a/lib/features/player/view/widgets/player_speed_adjust_button.dart +++ b/lib/features/player/view/widgets/player_speed_adjust_button.dart @@ -10,9 +10,7 @@ import 'package:vaani/settings/app_settings_provider.dart'; final _logger = Logger('PlayerSpeedAdjustButton'); class PlayerSpeedAdjustButton extends HookConsumerWidget { - const PlayerSpeedAdjustButton({ - super.key, - }); + const PlayerSpeedAdjustButton({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -35,21 +33,19 @@ class PlayerSpeedAdjustButton extends HookConsumerWidget { notifier.setSpeed(speed); if (appSettings.playerSettings.configurePlayerForEveryBook) { ref - .read( - bookSettingsProvider(bookId).notifier, - ) + .read(bookSettingsProvider(bookId).notifier) .update( - bookSettings.copyWith - .playerSettings(preferredDefaultSpeed: speed), + bookSettings.copyWith.playerSettings( + preferredDefaultSpeed: speed, + ), ); } else { ref - .read( - appSettingsProvider.notifier, - ) + .read(appSettingsProvider.notifier) .update( - appSettings.copyWith - .playerSettings(preferredDefaultSpeed: speed), + appSettings.copyWith.playerSettings( + preferredDefaultSpeed: speed, + ), ); } }, diff --git a/lib/features/player/view/widgets/playing_indicator_icon.dart b/lib/features/player/view/widgets/playing_indicator_icon.dart index d179797..35c47fc 100644 --- a/lib/features/player/view/widgets/playing_indicator_icon.dart +++ b/lib/features/player/view/widgets/playing_indicator_icon.dart @@ -59,8 +59,11 @@ class _PlayingIndicatorIconState extends State { @override void initState() { super.initState(); - _animationParams = - List.generate(widget.barCount, _createRandomParams, growable: false); + _animationParams = List.generate( + widget.barCount, + _createRandomParams, + growable: false, + ); } // Helper to generate random parameters for one bar's animation cycle @@ -72,10 +75,12 @@ class _PlayingIndicatorIconState extends State { // Note: These factors represent the scale relative to the *half-height* // if centerSymmetric is true, controlled by the alignment in scaleY. - final targetHeightFactor1 = widget.minHeightFactor + + final targetHeightFactor1 = + widget.minHeightFactor + _random.nextDouble() * (widget.maxHeightFactor - widget.minHeightFactor); - final targetHeightFactor2 = widget.minHeightFactor + + final targetHeightFactor2 = + widget.minHeightFactor + _random.nextDouble() * (widget.maxHeightFactor - widget.minHeightFactor); @@ -95,7 +100,8 @@ class _PlayingIndicatorIconState extends State { @override Widget build(BuildContext context) { - final color = widget.color ?? + final color = + widget.color ?? IconTheme.of(context).color ?? Theme.of(context).colorScheme.primary; @@ -110,8 +116,9 @@ class _PlayingIndicatorIconState extends State { final double maxHeight = widget.size; // Determine the alignment for scaling based on the symmetric flag - final Alignment scaleAlignment = - widget.centerSymmetric ? Alignment.center : Alignment.bottomCenter; + final Alignment scaleAlignment = widget.centerSymmetric + ? Alignment.center + : Alignment.bottomCenter; // Determine the cross axis alignment for the Row final CrossAxisAlignment rowAlignment = widget.centerSymmetric @@ -129,47 +136,40 @@ class _PlayingIndicatorIconState extends State { crossAxisAlignment: rowAlignment, // Use spaceEvenly for better distribution, especially with center alignment mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate( - widget.barCount, - (index) { - final params = _animationParams[index]; - // The actual bar widget that will be animated - return Container( - width: barWidth, - // Set initial height to the max potential height - // The scaleY animation will control the visible height - height: maxHeight, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(barWidth / 2), - ), - ) - .animate( - delay: params.initialDelay, - onPlay: (controller) => controller.repeat( - reverse: true, - ), - ) - // 1. Scale to targetHeightFactor1 - .scaleY( - begin: - widget.minHeightFactor, // Scale factor starts near min - end: params.targetHeightFactor1, - duration: params.duration1, - curve: Curves.easeInOutCirc, - alignment: scaleAlignment, // Apply chosen alignment - ) - // 2. Then scale to targetHeightFactor2 - .then() - .scaleY( - end: params.targetHeightFactor2, - duration: params.duration2, - curve: Curves.easeInOutCirc, - alignment: scaleAlignment, // Apply chosen alignment - ); - }, - growable: false, - ), + children: List.generate(widget.barCount, (index) { + final params = _animationParams[index]; + // The actual bar widget that will be animated + return Container( + width: barWidth, + // Set initial height to the max potential height + // The scaleY animation will control the visible height + height: maxHeight, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(barWidth / 2), + ), + ) + .animate( + delay: params.initialDelay, + onPlay: (controller) => controller.repeat(reverse: true), + ) + // 1. Scale to targetHeightFactor1 + .scaleY( + begin: widget.minHeightFactor, // Scale factor starts near min + end: params.targetHeightFactor1, + duration: params.duration1, + curve: Curves.easeInOutCirc, + alignment: scaleAlignment, // Apply chosen alignment + ) + // 2. Then scale to targetHeightFactor2 + .then() + .scaleY( + end: params.targetHeightFactor2, + duration: params.duration2, + curve: Curves.easeInOutCirc, + alignment: scaleAlignment, // Apply chosen alignment + ); + }, growable: false), ), ), ); diff --git a/lib/features/player/view/widgets/speed_selector.dart b/lib/features/player/view/widgets/speed_selector.dart index ab564b1..f14604a 100644 --- a/lib/features/player/view/widgets/speed_selector.dart +++ b/lib/features/player/view/widgets/speed_selector.dart @@ -10,10 +10,7 @@ import 'package:vaani/settings/app_settings_provider.dart'; const double itemExtent = 25; class SpeedSelector extends HookConsumerWidget { - const SpeedSelector({ - super.key, - required this.onSpeedSelected, - }); + const SpeedSelector({super.key, required this.onSpeedSelected}); final void Function(double speed) onSpeedSelected; @@ -26,34 +23,22 @@ class SpeedSelector extends HookConsumerWidget { final speedState = useState(currentSpeed); // hook the onSpeedSelected function to the state - useEffect( - () { - onSpeedSelected(speedState.value); - return null; - }, - [speedState.value], - ); + useEffect(() { + onSpeedSelected(speedState.value); + return null; + }, [speedState.value]); // the speed options - final minSpeed = min( - speeds.reduce(min), - playerSettings.minSpeed, - ); - final maxSpeed = max( - speeds.reduce(max), - playerSettings.maxSpeed, - ); + final minSpeed = min(speeds.reduce(min), playerSettings.minSpeed); + final maxSpeed = max(speeds.reduce(max), playerSettings.maxSpeed); final speedIncrement = playerSettings.speedIncrement; final availableSpeeds = ((maxSpeed - minSpeed) / speedIncrement).ceil() + 1; - final availableSpeedsList = List.generate( - availableSpeeds, - (index) { - // need to round to 2 decimal place to avoid floating point errors - return double.parse( - (minSpeed + index * speedIncrement).toStringAsFixed(2), - ); - }, - ); + final availableSpeedsList = List.generate(availableSpeeds, (index) { + // need to round to 2 decimal place to avoid floating point errors + return double.parse( + (minSpeed + index * speedIncrement).toStringAsFixed(2), + ); + }); final scrollController = useFixedExtentScrollController( initialItem: availableSpeedsList.indexOf(currentSpeed), @@ -107,18 +92,19 @@ class SpeedSelector extends HookConsumerWidget { (speed) => TextButton( style: speed == speedState.value ? TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.primaryContainer, - foregroundColor: Theme.of(context) - .colorScheme - .onPrimaryContainer, + backgroundColor: Theme.of( + context, + ).colorScheme.primaryContainer, + foregroundColor: Theme.of( + context, + ).colorScheme.onPrimaryContainer, ) // border if not selected : TextButton.styleFrom( side: BorderSide( - color: Theme.of(context) - .colorScheme - .primaryContainer, + color: Theme.of( + context, + ).colorScheme.primaryContainer, ), ), onPressed: () async { @@ -195,14 +181,13 @@ class SpeedWheel extends StatelessWidget { controller: scrollController, scrollDirection: Axis.horizontal, itemExtent: itemExtent, - diameterRatio: 1.5, squeeze: 1.2, + diameterRatio: 1.5, + squeeze: 1.2, // useMagnifier: true, // magnification: 1.5, physics: const FixedExtentScrollPhysics(), children: availableSpeedsList - .map( - (speed) => SpeedLine(speed: speed), - ) + .map((speed) => SpeedLine(speed: speed)) .toList(), onSelectedItemChanged: (index) { speedState.value = availableSpeedsList[index]; @@ -232,10 +217,7 @@ class SpeedWheel extends StatelessWidget { } class SpeedLine extends StatelessWidget { - const SpeedLine({ - super.key, - required this.speed, - }); + const SpeedLine({super.key, required this.speed}); final double speed; @@ -250,8 +232,8 @@ class SpeedLine extends StatelessWidget { width: speed % 0.5 == 0 ? 3 : speed % 0.25 == 0 - ? 2 - : 0.5, + ? 2 + : 0.5, color: Theme.of(context).colorScheme.onSurface, ), ), diff --git a/lib/features/shake_detection/core/shake_detector.dart b/lib/features/shake_detection/core/shake_detector.dart index 023a8ab..effa131 100644 --- a/lib/features/shake_detection/core/shake_detector.dart +++ b/lib/features/shake_detection/core/shake_detector.dart @@ -29,7 +29,7 @@ class ShakeDetector { DateTime _lastShakeTime = DateTime.now(); final StreamController - _detectedShakeStreamController = StreamController.broadcast(); + _detectedShakeStreamController = StreamController.broadcast(); void start() { if (_accelerometerSubscription != null) { @@ -37,26 +37,27 @@ class ShakeDetector { return; } _accelerometerSubscription = - userAccelerometerEventStream(samplingPeriod: _settings.samplingPeriod) - .listen((event) { - _logger.finest('RMS: ${event.rms}'); - if (event.rms > _settings.threshold) { - _currentShakeCount++; + userAccelerometerEventStream( + samplingPeriod: _settings.samplingPeriod, + ).listen((event) { + _logger.finest('RMS: ${event.rms}'); + if (event.rms > _settings.threshold) { + _currentShakeCount++; - if (_currentShakeCount >= _settings.shakeTriggerCount && - !isCoolDownNeeded()) { - _logger.fine('Shake detected $_currentShakeCount times'); + if (_currentShakeCount >= _settings.shakeTriggerCount && + !isCoolDownNeeded()) { + _logger.fine('Shake detected $_currentShakeCount times'); - onShakeDetected?.call(); - _detectedShakeStreamController.add(event); + onShakeDetected?.call(); + _detectedShakeStreamController.add(event); - _lastShakeTime = DateTime.now(); - _currentShakeCount = 0; - } - } else { - _currentShakeCount = 0; - } - }); + _lastShakeTime = DateTime.now(); + _currentShakeCount = 0; + } + } else { + _currentShakeCount = 0; + } + }); _logger.fine('ShakeDetector started'); } diff --git a/lib/features/shake_detection/providers/shake_detector.dart b/lib/features/shake_detection/providers/shake_detector.dart index 8892a92..fa17920 100644 --- a/lib/features/shake_detection/providers/shake_detector.dart +++ b/lib/features/shake_detection/providers/shake_detector.dart @@ -59,34 +59,29 @@ class ShakeDetector extends _$ShakeDetector { final sleepTimer = ref.watch(sleepTimerProvider); if (!shakeDetectionSettings.shakeAction.isPlaybackManagementEnabled && sleepTimer == null) { - _logger - .config('No playback management is enabled and sleep timer is off, ' - 'so shake detection is disabled'); + _logger.config( + 'No playback management is enabled and sleep timer is off, ' + 'so shake detection is disabled', + ); return null; } _logger.config('Creating shake detector'); - final detector = core.ShakeDetector( - shakeDetectionSettings, - () { - final wasActionComplete = doShakeAction( - shakeDetectionSettings.shakeAction, - ref: ref, - ); - if (wasActionComplete) { - shakeDetectionSettings.feedback.forEach(postShakeFeedback); - } - }, - ); + final detector = core.ShakeDetector(shakeDetectionSettings, () { + final wasActionComplete = doShakeAction( + shakeDetectionSettings.shakeAction, + ref: ref, + ); + if (wasActionComplete) { + shakeDetectionSettings.feedback.forEach(postShakeFeedback); + } + }); ref.onDispose(detector.dispose); return detector; } /// Perform the shake action and return whether the action was successful - bool doShakeAction( - ShakeAction shakeAction, { - required Ref ref, - }) { + bool doShakeAction(ShakeAction shakeAction, {required Ref ref}) { final player = ref.read(simpleAudiobookPlayerProvider); if (player.book == null && shakeAction.isPlaybackManagementEnabled) { _logger.warning('No book is loaded'); @@ -166,8 +161,11 @@ extension on ShakeAction { } bool get isPlaybackManagementEnabled { - return {ShakeAction.playPause, ShakeAction.fastForward, ShakeAction.rewind} - .contains(this); + return { + ShakeAction.playPause, + ShakeAction.fastForward, + ShakeAction.rewind, + }.contains(this); } bool get shouldActOnSleepTimer { diff --git a/lib/features/sleep_timer/core/sleep_timer.dart b/lib/features/sleep_timer/core/sleep_timer.dart index 2d9ea31..b774062 100644 --- a/lib/features/sleep_timer/core/sleep_timer.dart +++ b/lib/features/sleep_timer/core/sleep_timer.dart @@ -94,9 +94,7 @@ class SleepTimer { } /// starts the timer with the given duration or the default duration - void startCountDown([ - Duration? forDuration, - ]) { + void startCountDown([Duration? forDuration]) { clearCountDownTimer(); duration = forDuration ?? duration; timer = Timer(duration, () { diff --git a/lib/features/sleep_timer/view/sleep_timer_button.dart b/lib/features/sleep_timer/view/sleep_timer_button.dart index 9712813..0f33b72 100644 --- a/lib/features/sleep_timer/view/sleep_timer_button.dart +++ b/lib/features/sleep_timer/view/sleep_timer_button.dart @@ -13,9 +13,7 @@ import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/duration_format.dart'; class SleepTimerButton extends HookConsumerWidget { - const SleepTimerButton({ - super.key, - }); + const SleepTimerButton({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -47,8 +45,9 @@ class SleepTimerButton extends HookConsumerWidget { ); pendingPlayerModals--; ref.read(sleepTimerProvider.notifier).setTimer(durationState.value); - appLogger - .fine('Sleep Timer dialog closed with ${durationState.value}'); + appLogger.fine( + 'Sleep Timer dialog closed with ${durationState.value}', + ); }, child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), @@ -57,9 +56,7 @@ class SleepTimerButton extends HookConsumerWidget { Symbols.bedtime, color: Theme.of(context).colorScheme.onSurface, ) - : RemainingSleepTimeDisplay( - timer: sleepTimer, - ), + : RemainingSleepTimeDisplay(timer: sleepTimer), ), ), ); @@ -67,10 +64,7 @@ class SleepTimerButton extends HookConsumerWidget { } class SleepTimerBottomSheet extends HookConsumerWidget { - const SleepTimerBottomSheet({ - super.key, - this.onDurationSelected, - }); + const SleepTimerBottomSheet({super.key, this.onDurationSelected}); final void Function(Duration?)? onDurationSelected; @@ -91,8 +85,9 @@ class SleepTimerBottomSheet extends HookConsumerWidget { ]; final scrollController = useFixedExtentScrollController( - initialItem: - allPossibleDurations.indexOf(sleepTimer?.duration ?? minDuration), + initialItem: allPossibleDurations.indexOf( + sleepTimer?.duration ?? minDuration, + ), ); final durationState = useState( @@ -100,13 +95,10 @@ class SleepTimerBottomSheet extends HookConsumerWidget { ); // useEffect to rebuild the sleep timer when the duration changes - useEffect( - () { - onDurationSelected?.call(durationState.value); - return null; - }, - [durationState.value], - ); + useEffect(() { + onDurationSelected?.call(durationState.value); + return null; + }, [durationState.value]); return Column( mainAxisSize: MainAxisSize.min, @@ -171,18 +163,19 @@ class SleepTimerBottomSheet extends HookConsumerWidget { (timerDuration) => TextButton( style: timerDuration == durationState.value ? TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.primaryContainer, - foregroundColor: Theme.of(context) - .colorScheme - .onPrimaryContainer, + backgroundColor: Theme.of( + context, + ).colorScheme.primaryContainer, + foregroundColor: Theme.of( + context, + ).colorScheme.onPrimaryContainer, ) // border if not selected : TextButton.styleFrom( side: BorderSide( - color: Theme.of(context) - .colorScheme - .primaryContainer, + color: Theme.of( + context, + ).colorScheme.primaryContainer, ), ), onPressed: () async { @@ -215,10 +208,7 @@ class SleepTimerBottomSheet extends HookConsumerWidget { } class RemainingSleepTimeDisplay extends HookConsumerWidget { - const RemainingSleepTimeDisplay({ - super.key, - required this.timer, - }); + const RemainingSleepTimeDisplay({super.key, required this.timer}); final SleepTimer timer; @@ -230,17 +220,14 @@ class RemainingSleepTimeDisplay extends HookConsumerWidget { color: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(16), ), - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Text( timer.timer == null ? timer.duration.smartBinaryFormat : remainingTime?.smartBinaryFormat ?? '', style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onPrimary, - ), + color: Theme.of(context).colorScheme.onPrimary, + ), ), ); } @@ -272,8 +259,9 @@ class SleepTimerWheel extends StatelessWidget { icon: const Icon(Icons.remove), onPressed: () { // animate to index - 1 - final index = availableDurations - .indexOf(durationState.value ?? Duration.zero); + final index = availableDurations.indexOf( + durationState.value ?? Duration.zero, + ); if (index > 0) { scrollController.animateToItem( index - 1, @@ -289,14 +277,13 @@ class SleepTimerWheel extends StatelessWidget { controller: scrollController, scrollDirection: Axis.horizontal, itemExtent: itemExtent, - diameterRatio: 1.5, squeeze: 1.2, + diameterRatio: 1.5, + squeeze: 1.2, // useMagnifier: true, // magnification: 1.5, physics: const FixedExtentScrollPhysics(), children: availableDurations - .map( - (duration) => DurationLine(duration: duration), - ) + .map((duration) => DurationLine(duration: duration)) .toList(), onSelectedItemChanged: (index) { durationState.value = availableDurations[index]; @@ -310,8 +297,9 @@ class SleepTimerWheel extends StatelessWidget { icon: const Icon(Icons.add), onPressed: () { // animate to index + 1 - final index = availableDurations - .indexOf(durationState.value ?? Duration.zero); + final index = availableDurations.indexOf( + durationState.value ?? Duration.zero, + ); if (index < availableDurations.length - 1) { scrollController.animateToItem( index + 1, @@ -327,10 +315,7 @@ class SleepTimerWheel extends StatelessWidget { } class DurationLine extends StatelessWidget { - const DurationLine({ - super.key, - required this.duration, - }); + const DurationLine({super.key, required this.duration}); final Duration duration; @@ -345,8 +330,8 @@ class DurationLine extends StatelessWidget { width: duration.inMinutes % 5 == 0 ? 3 : duration.inMinutes % 2.5 == 0 - ? 2 - : 0.5, + ? 2 + : 0.5, color: Theme.of(context).colorScheme.onSurface, ), ), diff --git a/lib/features/you/view/server_manager.dart b/lib/features/you/view/server_manager.dart index 75e3f43..f8d13b0 100644 --- a/lib/features/you/view/server_manager.dart +++ b/lib/features/you/view/server_manager.dart @@ -20,16 +20,12 @@ import 'package:vaani/shared/extensions/obfuscation.dart' show ObfuscateSet; import 'package:vaani/shared/widgets/add_new_server.dart' show AddNewServer; class ServerManagerPage extends HookConsumerWidget { - const ServerManagerPage({ - super.key, - }); + const ServerManagerPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( - appBar: AppBar( - title: const Text('Manage Accounts'), - ), + appBar: AppBar(title: const Text('Manage Accounts')), body: Center( child: Padding( padding: const EdgeInsets.all(8.0), @@ -41,9 +37,7 @@ class ServerManagerPage extends HookConsumerWidget { } class ServerManagerBody extends HookConsumerWidget { - const ServerManagerBody({ - super.key, - }); + const ServerManagerBody({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -61,9 +55,7 @@ class ServerManagerBody extends HookConsumerWidget { // crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text( - 'Registered Servers', - ), + const Text('Registered Servers'), Expanded( child: ListView.builder( itemCount: registeredServers.length, @@ -76,21 +68,17 @@ class ServerManagerBody extends HookConsumerWidget { 'Users: ${availableUsers.where((element) => element.server == registeredServer).length}', ), // children are list of users of this server - children: availableUsers - .where( - (element) => element.server == registeredServer, - ) - .map( - (e) => AvailableUserTile(user: e), - ) - .nonNulls - .toList() - - // add buttons of delete server and add user to server at the end - ..addAll([ - AddUserTile(server: registeredServer), - DeleteServerTile(server: registeredServer), - ]), + children: + availableUsers + .where((element) => element.server == registeredServer) + .map((e) => AvailableUserTile(user: e)) + .nonNulls + .toList() + // add buttons of delete server and add user to server at the end + ..addAll([ + AddUserTile(server: registeredServer), + DeleteServerTile(server: registeredServer), + ]), ); }, ), @@ -111,28 +99,24 @@ class ServerManagerBody extends HookConsumerWidget { final newServer = model.AudiobookShelfServer( serverUrl: makeBaseUrl(serverURIController.text), ); - ref.read(audiobookShelfServerProvider.notifier).addServer( - newServer, - ); - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeServer: newServer, - ), + ref + .read(audiobookShelfServerProvider.notifier) + .addServer(newServer); + ref + .read(apiSettingsProvider.notifier) + .updateState( + apiSettings.copyWith(activeServer: newServer), ); serverURIController.clear(); } on ServerAlreadyExistsException catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(e.toString()), - ), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(e.toString()))); } } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid URL'), - ), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Invalid URL'))); } }, ), @@ -144,10 +128,7 @@ class ServerManagerBody extends HookConsumerWidget { } class DeleteServerTile extends HookConsumerWidget { - const DeleteServerTile({ - super.key, - required this.server, - }); + const DeleteServerTile({super.key, required this.server}); final model.AudiobookShelfServer server; @@ -167,9 +148,7 @@ class DeleteServerTile extends HookConsumerWidget { child: Text.rich( TextSpan( children: [ - const TextSpan( - text: 'This will remove the server ', - ), + const TextSpan(text: 'This will remove the server '), TextSpan( text: server.serverUrl.host, style: TextStyle( @@ -194,13 +173,8 @@ class DeleteServerTile extends HookConsumerWidget { TextButton( onPressed: () { ref - .read( - audiobookShelfServerProvider.notifier, - ) - .removeServer( - server, - removeUsers: true, - ); + .read(audiobookShelfServerProvider.notifier) + .removeServer(server, removeUsers: true); Navigator.of(context).pop(); }, child: const Text('Delete'), @@ -215,10 +189,7 @@ class DeleteServerTile extends HookConsumerWidget { } class AddUserTile extends HookConsumerWidget { - const AddUserTile({ - super.key, - required this.server, - }); + const AddUserTile({super.key, required this.server}); final model.AudiobookShelfServer server; @@ -252,10 +223,12 @@ class AddUserTile extends HookConsumerWidget { label: 'Switch', onPressed: () { // Switch to the new user - ref.read(apiSettingsProvider.notifier).updateState( - ref.read(apiSettingsProvider).copyWith( - activeUser: user, - ), + ref + .read(apiSettingsProvider.notifier) + .updateState( + ref + .read(apiSettingsProvider) + .copyWith(activeUser: user), ); context.goNamed(Routes.home.name); }, @@ -283,10 +256,7 @@ class AddUserTile extends HookConsumerWidget { } class AvailableUserTile extends HookConsumerWidget { - const AvailableUserTile({ - super.key, - required this.user, - }); + const AvailableUserTile({super.key, required this.user}); final model.AuthenticatedUser user; @@ -303,18 +273,14 @@ class AvailableUserTile extends HookConsumerWidget { onTap: apiSettings.activeUser == user ? null : () { - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeUser: user, - ), - ); + ref + .read(apiSettingsProvider.notifier) + .updateState(apiSettings.copyWith(activeUser: user)); // pop all routes and go to the home page // while (context.canPop()) { // context.pop(); // } - context.goNamed( - Routes.home.name, - ); + context.goNamed(Routes.home.name); }, trailing: IconButton( icon: const Icon(Icons.delete), @@ -337,9 +303,7 @@ class AvailableUserTile extends HookConsumerWidget { color: Theme.of(context).colorScheme.primary, ), ), - const TextSpan( - text: ' from this app.', - ), + const TextSpan(text: ' from this app.'), ], ), ), @@ -353,9 +317,7 @@ class AvailableUserTile extends HookConsumerWidget { TextButton( onPressed: () { ref - .read( - authenticatedUsersProvider.notifier, - ) + .read(authenticatedUsersProvider.notifier) .removeUser(user); Navigator.of(context).pop(); }, diff --git a/lib/features/you/view/widgets/library_switch_chip.dart b/lib/features/you/view/widgets/library_switch_chip.dart index a673332..2255c7a 100644 --- a/lib/features/you/view/widgets/library_switch_chip.dart +++ b/lib/features/you/view/widgets/library_switch_chip.dart @@ -11,10 +11,7 @@ import 'package:flutter/foundation.dart'; import 'package:vaani/main.dart' show appLogger; class LibrarySwitchChip extends HookConsumerWidget { - const LibrarySwitchChip({ - super.key, - required this.libraries, - }); + const LibrarySwitchChip({super.key, required this.libraries}); final List libraries; @override @@ -26,30 +23,22 @@ class LibrarySwitchChip extends HookConsumerWidget { AbsIcons.getIconByName( apiSettings.activeLibraryId != null ? libraries - .firstWhere( - (lib) => lib.id == apiSettings.activeLibraryId, - ) - .icon + .firstWhere((lib) => lib.id == apiSettings.activeLibraryId) + .icon : libraries.first.icon, ), ), // Replace with your icon label: const Text('Change Library'), // Enable only if libraries are loaded and not empty onPressed: libraries.isNotEmpty - ? () => showLibrarySwitcher( - context, - ref, - ) + ? () => showLibrarySwitcher(context, ref) : null, // Disable if no libraries ); } } // --- Helper Function to Show the Switcher --- -void showLibrarySwitcher( - BuildContext context, - WidgetRef ref, -) { +void showLibrarySwitcher(BuildContext context, WidgetRef ref) { final content = _LibrarySelectionContent(); // --- Platform-Specific UI --- @@ -209,7 +198,9 @@ class _LibrarySelectionContent extends ConsumerWidget { // Get current settings state final currentSettings = ref.read(apiSettingsProvider); // Update the active library ID - ref.read(apiSettingsProvider.notifier).updateState( + ref + .read(apiSettingsProvider.notifier) + .updateState( currentSettings.copyWith(activeLibraryId: library.id), ); // Close the dialog/bottom sheet diff --git a/lib/features/you/view/you_page.dart b/lib/features/you/view/you_page.dart index ca789db..fba9048 100644 --- a/lib/features/you/view/you_page.dart +++ b/lib/features/you/view/you_page.dart @@ -12,9 +12,7 @@ import 'package:vaani/shared/widgets/not_implemented.dart'; import 'package:vaani/shared/widgets/vaani_logo.dart'; class YouPage extends HookConsumerWidget { - const YouPage({ - super.key, - }); + const YouPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -88,8 +86,9 @@ class YouPage extends HookConsumerWidget { // Maybe show error details or allow retry ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: - Text('Failed to load libraries: $error'), + content: Text( + 'Failed to load libraries: $error', + ), ), ); }, @@ -159,9 +158,7 @@ class YouPage extends HookConsumerWidget { Theme.of(context).colorScheme.primary, BlendMode.srcIn, ), - child: const VaaniLogo( - size: 48, - ), + child: const VaaniLogo(size: 48), ), ), ], @@ -176,9 +173,7 @@ class YouPage extends HookConsumerWidget { } class UserBar extends HookConsumerWidget { - const UserBar({ - super.key, - }); + const UserBar({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -217,8 +212,9 @@ class UserBar extends HookConsumerWidget { Text( api.baseUrl.toString(), style: textTheme.bodyMedium?.copyWith( - color: - themeData.colorScheme.onSurface.withValues(alpha: 0.6), + color: themeData.colorScheme.onSurface.withValues( + alpha: 0.6, + ), ), ), ], diff --git a/lib/hacks/fix_autofill_losing_focus.dart b/lib/hacks/fix_autofill_losing_focus.dart index ffa7da6..3763654 100644 --- a/lib/hacks/fix_autofill_losing_focus.dart +++ b/lib/hacks/fix_autofill_losing_focus.dart @@ -14,10 +14,7 @@ import 'package:flutter/material.dart'; class InactiveFocusScopeObserver extends StatefulWidget { final Widget child; - const InactiveFocusScopeObserver({ - super.key, - required this.child, - }); + const InactiveFocusScopeObserver({super.key, required this.child}); @override State createState() => @@ -39,10 +36,8 @@ class _InactiveFocusScopeObserverState } @override - Widget build(BuildContext context) => FocusScope( - node: _focusScope, - child: widget.child, - ); + Widget build(BuildContext context) => + FocusScope(node: _focusScope, child: widget.child); @override void dispose() { diff --git a/lib/main.dart b/lib/main.dart index b67d8a9..0eb4cc0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -33,11 +33,7 @@ void main() async { await configurePlayer(); // run the app - runApp( - const ProviderScope( - child: _EagerInitialization(child: MyApp()), - ), - ); + runApp(const ProviderScope(child: _EagerInitialization(child: MyApp()))); } var routerConfig = const MyAppRouter().config; @@ -65,17 +61,14 @@ class MyApp extends ConsumerWidget { themeSettings.highContrast || MediaQuery.of(context).highContrast; if (shouldUseHighContrast) { - lightColorScheme = lightColorScheme.copyWith( - surface: Colors.white, - ); - darkColorScheme = darkColorScheme.copyWith( - surface: Colors.black, - ); + lightColorScheme = lightColorScheme.copyWith(surface: Colors.white); + darkColorScheme = darkColorScheme.copyWith(surface: Colors.black); } if (themeSettings.useMaterialThemeFromSystem) { - var themes = - ref.watch(systemThemeProvider(highContrast: shouldUseHighContrast)); + var themes = ref.watch( + systemThemeProvider(highContrast: shouldUseHighContrast), + ); if (themes.value != null) { lightColorScheme = themes.value!.$1; darkColorScheme = themes.value!.$2; diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 3ba1d98..202ee79 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -52,7 +52,9 @@ class HomePage extends HookConsumerWidget { // try again button ElevatedButton( onPressed: () { - ref.read(apiSettingsProvider.notifier).updateState( + ref + .read(apiSettingsProvider.notifier) + .updateState( apiSettings.copyWith(activeLibraryId: null), ); ref.invalidate(personalizedViewProvider); @@ -66,24 +68,25 @@ class HomePage extends HookConsumerWidget { final shelvesToDisplay = data // .where((element) => !element.id.contains('discover')) .map((shelf) { - appLogger.fine('building shelf ${shelf.label}'); - // check if showPlayButton is enabled for the shelf - // using the id of the shelf - final showPlayButton = switch (shelf.id) { - 'continue-listening' => - homePageSettings.showPlayButtonOnContinueListeningShelf, - 'continue-series' => - homePageSettings.showPlayButtonOnContinueSeriesShelf, - 'listen-again' => - homePageSettings.showPlayButtonOnListenAgainShelf, - _ => homePageSettings.showPlayButtonOnAllRemainingShelves, - }; - return HomeShelf( - title: shelf.label, - shelf: shelf, - showPlayButton: showPlayButton, - ); - }).toList(); + appLogger.fine('building shelf ${shelf.label}'); + // check if showPlayButton is enabled for the shelf + // using the id of the shelf + final showPlayButton = switch (shelf.id) { + 'continue-listening' => + homePageSettings.showPlayButtonOnContinueListeningShelf, + 'continue-series' => + homePageSettings.showPlayButtonOnContinueSeriesShelf, + 'listen-again' => + homePageSettings.showPlayButtonOnListenAgainShelf, + _ => homePageSettings.showPlayButtonOnAllRemainingShelves, + }; + return HomeShelf( + title: shelf.label, + shelf: shelf, + showPlayButton: showPlayButton, + ); + }) + .toList(); return RefreshIndicator( onRefresh: () async { return ref.refresh(personalizedViewProvider); @@ -132,10 +135,6 @@ class HomePageSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { - return const Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); + return const Scaffold(body: Center(child: CircularProgressIndicator())); } } diff --git a/lib/pages/library_page.dart b/lib/pages/library_page.dart index a6950a6..d203987 100644 --- a/lib/pages/library_page.dart +++ b/lib/pages/library_page.dart @@ -17,7 +17,9 @@ class LibraryPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { // set the library id as the active library if (libraryId != null) { - ref.read(apiSettingsProvider.notifier).updateState( + ref + .read(apiSettingsProvider.notifier) + .updateState( ref.watch(apiSettingsProvider).copyWith(activeLibraryId: libraryId), ); } @@ -48,12 +50,10 @@ class LibraryPage extends HookConsumerWidget { final shelvesToDisplay = data // .where((element) => !element.id.contains('discover')) .map((shelf) { - appLogger.fine('building shelf ${shelf.label}'); - return HomeShelf( - title: shelf.label, - shelf: shelf, - ); - }).toList(); + appLogger.fine('building shelf ${shelf.label}'); + return HomeShelf(title: shelf.label, shelf: shelf); + }) + .toList(); return RefreshIndicator( onRefresh: () async { return ref.refresh(personalizedViewProvider); @@ -85,10 +85,6 @@ class LibraryPageSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { - return const Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); + return const Scaffold(body: Center(child: CircularProgressIndicator())); } } diff --git a/lib/router/constants.dart b/lib/router/constants.dart index 79c4556..3a556bf 100644 --- a/lib/router/constants.dart +++ b/lib/router/constants.dart @@ -3,14 +3,8 @@ part of 'router.dart'; class Routes { - static const home = _SimpleRoute( - pathName: '', - name: 'home', - ); - static const onboarding = _SimpleRoute( - pathName: 'login', - name: 'onboarding', - ); + static const home = _SimpleRoute(pathName: '', name: 'home'); + static const onboarding = _SimpleRoute(pathName: 'login', name: 'onboarding'); static const library = _SimpleRoute( pathName: 'library', pathParamName: 'libraryId', @@ -23,10 +17,7 @@ class Routes { ); // Local settings - static const settings = _SimpleRoute( - pathName: 'config', - name: 'settings', - ); + static const settings = _SimpleRoute(pathName: 'config', name: 'settings'); static const themeSettings = _SimpleRoute( pathName: 'theme', name: 'themeSettings', @@ -64,10 +55,7 @@ class Routes { name: 'search', // parentRoute: library, ); - static const explore = _SimpleRoute( - pathName: 'explore', - name: 'explore', - ); + static const explore = _SimpleRoute(pathName: 'explore', name: 'explore'); // downloads static const downloads = _SimpleRoute( @@ -83,10 +71,7 @@ class Routes { ); // you page for the user - static const you = _SimpleRoute( - pathName: 'you', - name: 'you', - ); + static const you = _SimpleRoute(pathName: 'you', name: 'you'); // user management static const userManagement = _SimpleRoute( @@ -102,10 +87,7 @@ class Routes { ); // logs page - static const logs = _SimpleRoute( - pathName: 'logs', - name: 'logs', - ); + static const logs = _SimpleRoute(pathName: 'logs', name: 'logs'); } // a class to store path diff --git a/lib/router/router.dart b/lib/router/router.dart index eda348e..949d0ee 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -25,8 +25,9 @@ import 'transitions/slide.dart'; part 'constants.dart'; -final GlobalKey rootNavigatorKey = - GlobalKey(debugLabel: 'root'); +final GlobalKey rootNavigatorKey = GlobalKey( + debugLabel: 'root', +); final GlobalKey sectionHomeNavigatorKey = GlobalKey(debugLabel: 'HomeNavigator'); @@ -35,34 +36,35 @@ class MyAppRouter { const MyAppRouter(); GoRouter get config => GoRouter( - initialLocation: Routes.home.localPath, - debugLogDiagnostics: true, + initialLocation: Routes.home.localPath, + debugLogDiagnostics: true, + routes: [ + // sign in page + GoRoute( + path: Routes.onboarding.localPath, + name: Routes.onboarding.name, + builder: (context, state) => const OnboardingSinglePage(), routes: [ - // sign in page + // open id callback GoRoute( - path: Routes.onboarding.localPath, - name: Routes.onboarding.name, - builder: (context, state) => const OnboardingSinglePage(), - routes: [ - // open id callback - GoRoute( - path: Routes.openIDCallback.pathName, - name: Routes.openIDCallback.name, - pageBuilder: handleCallback, - ), - ], - ), - // callback for open id - // need to duplicate because of https://github.com/flutter/flutter/issues/100624 - GoRoute( - path: Routes.openIDCallback.localPath, - // name: Routes.openIDCallback.name, - // builder: handleCallback, + path: Routes.openIDCallback.pathName, + name: Routes.openIDCallback.name, pageBuilder: handleCallback, ), - // The main app shell - StatefulShellRoute.indexedStack( - builder: ( + ], + ), + // callback for open id + // need to duplicate because of https://github.com/flutter/flutter/issues/100624 + GoRoute( + path: Routes.openIDCallback.localPath, + // name: Routes.openIDCallback.name, + // builder: handleCallback, + pageBuilder: handleCallback, + ), + // The main app shell + StatefulShellRoute.indexedStack( + builder: + ( BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell, @@ -73,188 +75,187 @@ class MyAppRouter { // branches in a stateful way. return ScaffoldWithNavBar(navigationShell: navigationShell); }, - branches: [ - // The route branch for the first tab of the bottom navigation bar. - StatefulShellBranch( - navigatorKey: sectionHomeNavigatorKey, - routes: [ - GoRoute( - path: Routes.home.localPath, - name: Routes.home.name, - // builder: (context, state) => const HomePage(), - pageBuilder: defaultPageBuilder(const HomePage()), - ), - GoRoute( - path: Routes.libraryItem.localPath, - name: Routes.libraryItem.name, - // builder: (context, state) { - // final itemId = state - // .pathParameters[Routes.libraryItem.pathParamName]!; - // return LibraryItemPage( - // itemId: itemId, extra: state.extra); - // }, - pageBuilder: (context, state) { - final itemId = state - .pathParameters[Routes.libraryItem.pathParamName]!; - final child = - LibraryItemPage(itemId: itemId, extra: state.extra); - return buildPageWithDefaultTransition( - context: context, - state: state, - child: child, - ); - }, - ), - // downloads page - GoRoute( - path: Routes.downloads.localPath, - name: Routes.downloads.name, - pageBuilder: defaultPageBuilder(const DownloadsPage()), - ), - ], + branches: [ + // The route branch for the first tab of the bottom navigation bar. + StatefulShellBranch( + navigatorKey: sectionHomeNavigatorKey, + routes: [ + GoRoute( + path: Routes.home.localPath, + name: Routes.home.name, + // builder: (context, state) => const HomePage(), + pageBuilder: defaultPageBuilder(const HomePage()), ), - - // Library page - StatefulShellBranch( - routes: [ - GoRoute( - path: Routes.libraryBrowser.localPath, - name: Routes.libraryBrowser.name, - pageBuilder: defaultPageBuilder(const LibraryBrowserPage()), - ), - ], + GoRoute( + path: Routes.libraryItem.localPath, + name: Routes.libraryItem.name, + // builder: (context, state) { + // final itemId = state + // .pathParameters[Routes.libraryItem.pathParamName]!; + // return LibraryItemPage( + // itemId: itemId, extra: state.extra); + // }, + pageBuilder: (context, state) { + final itemId = + state.pathParameters[Routes.libraryItem.pathParamName]!; + final child = LibraryItemPage( + itemId: itemId, + extra: state.extra, + ); + return buildPageWithDefaultTransition( + context: context, + state: state, + child: child, + ); + }, ), - // search/explore page - StatefulShellBranch( - routes: [ - GoRoute( - path: Routes.explore.localPath, - name: Routes.explore.name, - // builder: (context, state) => const ExplorePage(), - pageBuilder: defaultPageBuilder(const ExplorePage()), - ), - // search page - GoRoute( - path: Routes.search.localPath, - name: Routes.search.name, - // builder: (context, state) { - // final libraryId = state - // .pathParameters[Routes.library.pathParamName]!; - // return LibrarySearchPage( - // libraryId: libraryId, - // extra: state.extra, - // ); - // }, - pageBuilder: (context, state) { - final queryParam = state.uri.queryParameters['q']!; - final category = state.uri.queryParameters['category']; - final child = SearchResultPage( - extra: state.extra, - query: queryParam, - category: category != null - ? SearchResultCategory.values.firstWhere( - (e) => e.toString().split('.').last == category, - ) - : null, - ); - return buildPageWithDefaultTransition( - context: context, - state: state, - child: child, - ); - }, - ), - ], - ), - // you page - StatefulShellBranch( - routes: [ - GoRoute( - path: Routes.you.localPath, - name: Routes.you.name, - pageBuilder: defaultPageBuilder(const YouPage()), - ), - GoRoute( - path: Routes.settings.localPath, - name: Routes.settings.name, - // builder: (context, state) => const AppSettingsPage(), - pageBuilder: defaultPageBuilder(const AppSettingsPage()), - routes: [ - GoRoute( - path: Routes.themeSettings.pathName, - name: Routes.themeSettings.name, - pageBuilder: defaultPageBuilder( - const ThemeSettingsPage(), - ), - ), - GoRoute( - path: Routes.autoSleepTimerSettings.pathName, - name: Routes.autoSleepTimerSettings.name, - pageBuilder: defaultPageBuilder( - const AutoSleepTimerSettingsPage(), - ), - ), - GoRoute( - path: Routes.notificationSettings.pathName, - name: Routes.notificationSettings.name, - pageBuilder: defaultPageBuilder( - const NotificationSettingsPage(), - ), - ), - GoRoute( - path: Routes.playerSettings.pathName, - name: Routes.playerSettings.name, - pageBuilder: - defaultPageBuilder(const PlayerSettingsPage()), - ), - GoRoute( - path: Routes.shakeDetectorSettings.pathName, - name: Routes.shakeDetectorSettings.name, - pageBuilder: defaultPageBuilder( - const ShakeDetectorSettingsPage(), - ), - ), - GoRoute( - path: Routes.homePageSettings.pathName, - name: Routes.homePageSettings.name, - pageBuilder: defaultPageBuilder( - const HomePageSettingsPage(), - ), - ), - ], - ), - GoRoute( - path: Routes.userManagement.localPath, - name: Routes.userManagement.name, - // builder: (context, state) => const UserManagementPage(), - pageBuilder: defaultPageBuilder(const ServerManagerPage()), - ), - ], + // downloads page + GoRoute( + path: Routes.downloads.localPath, + name: Routes.downloads.name, + pageBuilder: defaultPageBuilder(const DownloadsPage()), ), ], ), - // loggers page - GoRoute( - path: Routes.logs.localPath, - name: Routes.logs.name, - // builder: (context, state) => const LogsPage(), - pageBuilder: defaultPageBuilder(const LogsPage()), + // Library page + StatefulShellBranch( + routes: [ + GoRoute( + path: Routes.libraryBrowser.localPath, + name: Routes.libraryBrowser.name, + pageBuilder: defaultPageBuilder(const LibraryBrowserPage()), + ), + ], + ), + // search/explore page + StatefulShellBranch( + routes: [ + GoRoute( + path: Routes.explore.localPath, + name: Routes.explore.name, + // builder: (context, state) => const ExplorePage(), + pageBuilder: defaultPageBuilder(const ExplorePage()), + ), + // search page + GoRoute( + path: Routes.search.localPath, + name: Routes.search.name, + // builder: (context, state) { + // final libraryId = state + // .pathParameters[Routes.library.pathParamName]!; + // return LibrarySearchPage( + // libraryId: libraryId, + // extra: state.extra, + // ); + // }, + pageBuilder: (context, state) { + final queryParam = state.uri.queryParameters['q']!; + final category = state.uri.queryParameters['category']; + final child = SearchResultPage( + extra: state.extra, + query: queryParam, + category: category != null + ? SearchResultCategory.values.firstWhere( + (e) => e.toString().split('.').last == category, + ) + : null, + ); + return buildPageWithDefaultTransition( + context: context, + state: state, + child: child, + ); + }, + ), + ], + ), + // you page + StatefulShellBranch( + routes: [ + GoRoute( + path: Routes.you.localPath, + name: Routes.you.name, + pageBuilder: defaultPageBuilder(const YouPage()), + ), + GoRoute( + path: Routes.settings.localPath, + name: Routes.settings.name, + // builder: (context, state) => const AppSettingsPage(), + pageBuilder: defaultPageBuilder(const AppSettingsPage()), + routes: [ + GoRoute( + path: Routes.themeSettings.pathName, + name: Routes.themeSettings.name, + pageBuilder: defaultPageBuilder(const ThemeSettingsPage()), + ), + GoRoute( + path: Routes.autoSleepTimerSettings.pathName, + name: Routes.autoSleepTimerSettings.name, + pageBuilder: defaultPageBuilder( + const AutoSleepTimerSettingsPage(), + ), + ), + GoRoute( + path: Routes.notificationSettings.pathName, + name: Routes.notificationSettings.name, + pageBuilder: defaultPageBuilder( + const NotificationSettingsPage(), + ), + ), + GoRoute( + path: Routes.playerSettings.pathName, + name: Routes.playerSettings.name, + pageBuilder: defaultPageBuilder(const PlayerSettingsPage()), + ), + GoRoute( + path: Routes.shakeDetectorSettings.pathName, + name: Routes.shakeDetectorSettings.name, + pageBuilder: defaultPageBuilder( + const ShakeDetectorSettingsPage(), + ), + ), + GoRoute( + path: Routes.homePageSettings.pathName, + name: Routes.homePageSettings.name, + pageBuilder: defaultPageBuilder( + const HomePageSettingsPage(), + ), + ), + ], + ), + GoRoute( + path: Routes.userManagement.localPath, + name: Routes.userManagement.name, + // builder: (context, state) => const UserManagementPage(), + pageBuilder: defaultPageBuilder(const ServerManagerPage()), + ), + ], ), ], - ); + ), - Page handleCallback( - BuildContext context, - GoRouterState state, - ) { + // loggers page + GoRoute( + path: Routes.logs.localPath, + name: Routes.logs.name, + // builder: (context, state) => const LogsPage(), + pageBuilder: defaultPageBuilder(const LogsPage()), + ), + ], + ); + + Page handleCallback(BuildContext context, GoRouterState state) { // extract the code and state from the uri final code = state.uri.queryParameters['code']; final stateParam = state.uri.queryParameters['state']; appLogger.fine('deep linking callback: code: $code, state: $stateParam'); - var callbackPage = - CallbackPage(code: code, state: stateParam, key: ValueKey(stateParam)); + var callbackPage = CallbackPage( + code: code, + state: stateParam, + key: ValueKey(stateParam), + ); return buildPageWithDefaultTransition( context: context, state: state, diff --git a/lib/router/transitions/slide.dart b/lib/router/transitions/slide.dart index 969edea..79c6364 100644 --- a/lib/router/transitions/slide.dart +++ b/lib/router/transitions/slide.dart @@ -33,29 +33,26 @@ CustomTransitionPage buildPageWithDefaultTransition({ child: child, transitionsBuilder: (context, animation, secondaryAnimation, child) => FadeTransition( - opacity: animation, - child: SlideTransition( - position: animation.drive( - Tween( - begin: const Offset(0, 1.50), - end: Offset.zero, - ).chain( - CurveTween(curve: Curves.easeOut), + opacity: animation, + child: SlideTransition( + position: animation.drive( + Tween( + begin: const Offset(0, 1.50), + end: Offset.zero, + ).chain(CurveTween(curve: Curves.easeOut)), + ), + child: child, ), ), - child: child, - ), - ), ); } Page Function(BuildContext, GoRouterState) defaultPageBuilder( Widget child, -) => - (BuildContext context, GoRouterState state) { - return buildPageWithDefaultTransition( - context: context, - state: state, - child: child, - ); - }; +) => (BuildContext context, GoRouterState state) { + return buildPageWithDefaultTransition( + context: context, + state: state, + child: child, + ); +}; diff --git a/lib/settings/app_settings_provider.dart b/lib/settings/app_settings_provider.dart index fc10f9a..ae843c3 100644 --- a/lib/settings/app_settings_provider.dart +++ b/lib/settings/app_settings_provider.dart @@ -19,8 +19,10 @@ model.AppSettings loadOrCreateAppSettings() { settings = _box.getAt(0); _logger.fine('found settings in box: $settings'); } catch (e) { - _logger.warning('error reading settings from box: $e' - '\nclearing box'); + _logger.warning( + 'error reading settings from box: $e' + '\nclearing box', + ); _box.clear(); } } else { diff --git a/lib/settings/metadata/metadata_provider.dart b/lib/settings/metadata/metadata_provider.dart index 16802da..a1ce12f 100644 --- a/lib/settings/metadata/metadata_provider.dart +++ b/lib/settings/metadata/metadata_provider.dart @@ -12,19 +12,19 @@ Future deviceName(Ref ref) async { // try different keys to get the device name return - // android - data['product'] ?? - // ios - data['name'] ?? - // linux - data['name'] ?? - // windows - data['computerName'] ?? - // macos - data['model'] ?? - // web - data['browserName'] ?? - 'Unknown name'; + // android + data['product'] ?? + // ios + data['name'] ?? + // linux + data['name'] ?? + // windows + data['computerName'] ?? + // macos + data['model'] ?? + // web + data['browserName'] ?? + 'Unknown name'; } @Riverpod(keepAlive: true) @@ -33,19 +33,19 @@ Future deviceModel(Ref ref) async { // try different keys to get the device model return - // android, eg: Google Pixel 4 + // android, eg: Google Pixel 4 + data['model'] ?? + // ios, eg: iPhone 12 Pro + data['name'] ?? + // linux, eg: Linux Mint 20.1 + data['name'] ?? + // windows, eg: Surface Pro 7 + data['productId'] ?? + // macos, eg: MacBook Pro (13-inch, M1, 2020) data['model'] ?? - // ios, eg: iPhone 12 Pro - data['name'] ?? - // linux, eg: Linux Mint 20.1 - data['name'] ?? - // windows, eg: Surface Pro 7 - data['productId'] ?? - // macos, eg: MacBook Pro (13-inch, M1, 2020) - data['model'] ?? - // web, eg: Chrome 87.0.4280.88 - data['browserName'] ?? - 'Unknown model'; + // web, eg: Chrome 87.0.4280.88 + data['browserName'] ?? + 'Unknown model'; } @Riverpod(keepAlive: true) @@ -54,19 +54,19 @@ Future deviceSdkVersion(Ref ref) async { // try different keys to get the device sdk version return - // android, eg: 30 - data['version.sdkInt']?.toString() ?? - // ios, eg: 14.4 - data['systemVersion'] ?? - // linux, eg: 5.4.0-66-generic - data['version'] ?? - // windows, eg: 10.0.19042 - data['displayVersion'] ?? - // macos, eg: 11.2.1 - data['osRelease'] ?? - // web, eg: 87.0.4280.88 - data['appVersion'] ?? - 'Unknown sdk version'; + // android, eg: 30 + data['version.sdkInt']?.toString() ?? + // ios, eg: 14.4 + data['systemVersion'] ?? + // linux, eg: 5.4.0-66-generic + data['version'] ?? + // windows, eg: 10.0.19042 + data['displayVersion'] ?? + // macos, eg: 11.2.1 + data['osRelease'] ?? + // web, eg: 87.0.4280.88 + data['appVersion'] ?? + 'Unknown sdk version'; } @Riverpod(keepAlive: true) @@ -75,19 +75,19 @@ Future deviceManufacturer(Ref ref) async { // try different keys to get the device manufacturer return - // android, eg: Google + // android, eg: Google + data['manufacturer'] ?? + // ios, eg: Apple data['manufacturer'] ?? - // ios, eg: Apple - data['manufacturer'] ?? - // linux, eg: Linux - data['idLike'] ?? - // windows, eg: Microsoft - data['productName'] ?? - // macos, eg: Apple - data['manufacturer'] ?? - // web, eg: Google Inc. - data['vendor'] ?? - 'Unknown manufacturer'; + // linux, eg: Linux + data['idLike'] ?? + // windows, eg: Microsoft + data['productName'] ?? + // macos, eg: Apple + data['manufacturer'] ?? + // web, eg: Google Inc. + data['vendor'] ?? + 'Unknown manufacturer'; } // copied from https://pub.dev/packages/device_info_plus/example @@ -234,25 +234,28 @@ Future> _getDeviceData( deviceData = _readWebBrowserInfo(await deviceInfoPlugin.webBrowserInfo); } else { deviceData = switch (defaultTargetPlatform) { - TargetPlatform.android => - _readAndroidBuildData(await deviceInfoPlugin.androidInfo), - TargetPlatform.iOS => - _readIosDeviceInfo(await deviceInfoPlugin.iosInfo), - TargetPlatform.linux => - _readLinuxDeviceInfo(await deviceInfoPlugin.linuxInfo), - TargetPlatform.windows => - _readWindowsDeviceInfo(await deviceInfoPlugin.windowsInfo), - TargetPlatform.macOS => - _readMacOsDeviceInfo(await deviceInfoPlugin.macOsInfo), + TargetPlatform.android => _readAndroidBuildData( + await deviceInfoPlugin.androidInfo, + ), + TargetPlatform.iOS => _readIosDeviceInfo( + await deviceInfoPlugin.iosInfo, + ), + TargetPlatform.linux => _readLinuxDeviceInfo( + await deviceInfoPlugin.linuxInfo, + ), + TargetPlatform.windows => _readWindowsDeviceInfo( + await deviceInfoPlugin.windowsInfo, + ), + TargetPlatform.macOS => _readMacOsDeviceInfo( + await deviceInfoPlugin.macOsInfo, + ), TargetPlatform.fuchsia => { - errorKey: 'Fuchsia platform isn\'t supported', - }, + errorKey: 'Fuchsia platform isn\'t supported', + }, }; } } on PlatformException { - deviceData = { - errorKey: 'Failed to get platform version.', - }; + deviceData = {errorKey: 'Failed to get platform version.'}; } return deviceData; } diff --git a/lib/settings/view/app_settings_page.dart b/lib/settings/view/app_settings_page.dart index 15386fe..a193752 100644 --- a/lib/settings/view/app_settings_page.dart +++ b/lib/settings/view/app_settings_page.dart @@ -15,9 +15,7 @@ import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/settings/view/widgets/navigation_with_switch_tile.dart'; class AppSettingsPage extends HookConsumerWidget { - const AppSettingsPage({ - super.key, - }); + const AppSettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -33,17 +31,12 @@ class AppSettingsPage extends HookConsumerWidget { horizontal: 16.0, vertical: 8.0, ), - title: Text( - 'General', - style: Theme.of(context).textTheme.titleLarge, - ), + title: Text('General', style: Theme.of(context).textTheme.titleLarge), tiles: [ SettingsTile( title: const Text('Player Settings'), leading: const Icon(Icons.play_arrow), - description: const Text( - 'Customize the player settings', - ), + description: const Text('Customize the player settings'), onPressed: (context) { context.pushNamed(Routes.playerSettings.name); }, @@ -61,7 +54,9 @@ class AppSettingsPage extends HookConsumerWidget { }, value: sleepTimerSettings.autoTurnOnTimer, onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.sleepTimerSettings( autoTurnOnTimer: value, ), @@ -71,15 +66,15 @@ class AppSettingsPage extends HookConsumerWidget { NavigationWithSwitchTile( title: const Text('Shake Detector'), leading: const Icon(Icons.vibration), - description: const Text( - 'Customize the shake detector settings', - ), + description: const Text('Customize the shake detector settings'), value: appSettings.shakeDetectionSettings.isEnabled, onPressed: (context) { context.pushNamed(Routes.shakeDetectorSettings.name); }, onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.shakeDetectionSettings( isEnabled: value, ), @@ -103,9 +98,7 @@ class AppSettingsPage extends HookConsumerWidget { SettingsTile.navigation( leading: const Icon(Icons.color_lens), title: const Text('Theme Settings'), - description: const Text( - 'Customize the app theme', - ), + description: const Text('Customize the app theme'), onPressed: (context) { context.pushNamed(Routes.themeSettings.name); }, @@ -123,9 +116,7 @@ class AppSettingsPage extends HookConsumerWidget { SettingsTile.navigation( leading: const Icon(Icons.home_filled), title: const Text('Home Page Settings'), - description: const Text( - 'Customize the home page', - ), + description: const Text('Customize the home page'), onPressed: (context) { context.pushNamed(Routes.homePageSettings.name); }, @@ -147,21 +138,15 @@ class AppSettingsPage extends HookConsumerWidget { SettingsTile( title: const Text('Copy to Clipboard'), leading: const Icon(Icons.copy), - description: const Text( - 'Copy the app settings to the clipboard', - ), + description: const Text('Copy the app settings to the clipboard'), onPressed: (context) async { // copy to clipboard await Clipboard.setData( - ClipboardData( - text: jsonEncode(appSettings.toJson()), - ), + ClipboardData(text: jsonEncode(appSettings.toJson())), ); // show toast ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Settings copied to clipboard'), - ), + const SnackBar(content: Text('Settings copied to clipboard')), ); }, ), @@ -231,9 +216,7 @@ class AppSettingsPage extends HookConsumerWidget { } class RestoreDialogue extends HookConsumerWidget { - const RestoreDialogue({ - super.key, - }); + const RestoreDialogue({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -264,9 +247,7 @@ class RestoreDialogue extends HookConsumerWidget { } try { // try to decode the backup - settings.value = model.AppSettings.fromJson( - jsonDecode(value), - ); + settings.value = model.AppSettings.fromJson(jsonDecode(value)); } catch (e) { return 'Invalid backup'; } @@ -280,27 +261,21 @@ class RestoreDialogue extends HookConsumerWidget { onPressed: () { if (formKey.currentState!.validate()) { if (settings.value == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid backup'), - ), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Invalid backup'))); return; } ref.read(appSettingsProvider.notifier).update(settings.value!); settingsInputController.clear(); Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Settings restored'), - ), + const SnackBar(content: Text('Settings restored')), ); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid backup'), - ), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Invalid backup'))); } }, child: const Text('Restore'), diff --git a/lib/settings/view/auto_sleep_timer_settings_page.dart b/lib/settings/view/auto_sleep_timer_settings_page.dart index fe7d4b3..58fcf8e 100644 --- a/lib/settings/view/auto_sleep_timer_settings_page.dart +++ b/lib/settings/view/auto_sleep_timer_settings_page.dart @@ -7,16 +7,15 @@ import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/shared/extensions/time_of_day.dart'; class AutoSleepTimerSettingsPage extends HookConsumerWidget { - const AutoSleepTimerSettingsPage({ - super.key, - }); + const AutoSleepTimerSettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final appSettings = ref.watch(appSettingsProvider); final sleepTimerSettings = appSettings.sleepTimerSettings; - var enabled = sleepTimerSettings.autoTurnOnTimer && + var enabled = + sleepTimerSettings.autoTurnOnTimer && !sleepTimerSettings.alwaysAutoTurnOnTimer; final selectedValueColor = enabled ? Theme.of(context).colorScheme.primary @@ -40,7 +39,9 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { ? const Icon(Symbols.time_auto) : const Icon(Symbols.timer_off), onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.sleepTimerSettings( autoTurnOnTimer: value, ), @@ -63,7 +64,9 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { initialTime: sleepTimerSettings.autoTurnOnTime.toTimeOfDay(), ); if (selected != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.sleepTimerSettings( autoTurnOnTime: selected.toDuration(), ), @@ -89,7 +92,9 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { initialTime: sleepTimerSettings.autoTurnOffTime.toTimeOfDay(), ); if (selected != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.sleepTimerSettings( autoTurnOffTime: selected.toDuration(), ), @@ -97,9 +102,9 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { } }, trailing: Text( - sleepTimerSettings.autoTurnOffTime - .toTimeOfDay() - .format(context), + sleepTimerSettings.autoTurnOffTime.toTimeOfDay().format( + context, + ), style: TextStyle(color: selectedValueColor), ), ), @@ -112,7 +117,9 @@ class AutoSleepTimerSettingsPage extends HookConsumerWidget { 'Always turn on the sleep timer, no matter what', ), onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.sleepTimerSettings( alwaysAutoTurnOnTimer: value, ), diff --git a/lib/settings/view/buttons.dart b/lib/settings/view/buttons.dart index 4a15b41..209dc21 100644 --- a/lib/settings/view/buttons.dart +++ b/lib/settings/view/buttons.dart @@ -1,27 +1,18 @@ import 'package:flutter/material.dart'; class OkButton extends StatelessWidget { - const OkButton({ - super.key, - this.onPressed, - }); + const OkButton({super.key, this.onPressed}); final void Function()? onPressed; @override Widget build(BuildContext context) { - return TextButton( - onPressed: onPressed, - child: const Text('OK'), - ); + return TextButton(onPressed: onPressed, child: const Text('OK')); } } class CancelButton extends StatelessWidget { - const CancelButton({ - super.key, - this.onPressed, - }); + const CancelButton({super.key, this.onPressed}); final void Function()? onPressed; diff --git a/lib/settings/view/home_page_settings_page.dart b/lib/settings/view/home_page_settings_page.dart index 327044d..9b3b3fb 100644 --- a/lib/settings/view/home_page_settings_page.dart +++ b/lib/settings/view/home_page_settings_page.dart @@ -25,7 +25,8 @@ class HomePageSettingsPage extends HookConsumerWidget { tiles: [ SettingsTile.switchTile( initialValue: appSettings - .homePageSettings.showPlayButtonOnContinueListeningShelf, + .homePageSettings + .showPlayButtonOnContinueListeningShelf, title: const Text('Continue Listening'), leading: const Icon(Icons.play_arrow), description: const Text( @@ -48,7 +49,8 @@ class HomePageSettingsPage extends HookConsumerWidget { 'Show play button for books in continue series shelf', ), initialValue: appSettings - .homePageSettings.showPlayButtonOnContinueSeriesShelf, + .homePageSettings + .showPlayButtonOnContinueSeriesShelf, onToggle: (value) { appSettingsNotifier.update( appSettings.copyWith( @@ -66,7 +68,8 @@ class HomePageSettingsPage extends HookConsumerWidget { 'Show play button for all books in all remaining shelves', ), initialValue: appSettings - .homePageSettings.showPlayButtonOnAllRemainingShelves, + .homePageSettings + .showPlayButtonOnAllRemainingShelves, onToggle: (value) { appSettingsNotifier.update( appSettings.copyWith( diff --git a/lib/settings/view/notification_settings_page.dart b/lib/settings/view/notification_settings_page.dart index 140d21c..c306f87 100644 --- a/lib/settings/view/notification_settings_page.dart +++ b/lib/settings/view/notification_settings_page.dart @@ -9,9 +9,7 @@ import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/shared/extensions/enum.dart'; class NotificationSettingsPage extends HookConsumerWidget { - const NotificationSettingsPage({ - super.key, - }); + const NotificationSettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -59,7 +57,9 @@ class NotificationSettingsPage extends HookConsumerWidget { }, ); if (selectedTitle != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.notificationSettings( primaryTitle: selectedTitle, ), @@ -97,7 +97,9 @@ class NotificationSettingsPage extends HookConsumerWidget { }, ); if (selectedTitle != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.notificationSettings( secondaryTitle: selectedTitle, ), @@ -118,7 +120,9 @@ class NotificationSettingsPage extends HookConsumerWidget { child: TimeIntervalSlider( defaultValue: notificationSettings.fastForwardInterval, onChangedEnd: (interval) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.notificationSettings( fastForwardInterval: interval, ), @@ -141,7 +145,9 @@ class NotificationSettingsPage extends HookConsumerWidget { child: TimeIntervalSlider( defaultValue: notificationSettings.rewindInterval, onChangedEnd: (interval) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.notificationSettings( rewindInterval: interval, ), @@ -162,26 +168,23 @@ class NotificationSettingsPage extends HookConsumerWidget { trailing: Wrap( spacing: 8.0, children: notificationSettings.mediaControls - .map( - (control) => Icon( - control.icon, - color: primaryColor, - ), - ) + .map((control) => Icon(control.icon, color: primaryColor)) .toList(), ), onPressed: (context) async { final selectedControls = await showDialog>( - context: context, - builder: (context) { - return MediaControlsPicker( - selectedControls: notificationSettings.mediaControls, + context: context, + builder: (context) { + return MediaControlsPicker( + selectedControls: notificationSettings.mediaControls, + ); + }, ); - }, - ); if (selectedControls != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.notificationSettings( mediaControls: selectedControls, ), @@ -194,11 +197,14 @@ class NotificationSettingsPage extends HookConsumerWidget { SettingsTile.switchTile( title: const Text('Show Chapter Progress'), leading: const Icon(Icons.book), - description: - const Text('instead of the overall progress of the book'), + description: const Text( + 'instead of the overall progress of the book', + ), initialValue: notificationSettings.progressBarIsChapterProgress, onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.notificationSettings( progressBarIsChapterProgress: value, ), @@ -213,10 +219,7 @@ class NotificationSettingsPage extends HookConsumerWidget { } class MediaControlsPicker extends HookConsumerWidget { - const MediaControlsPicker({ - super.key, - required this.selectedControls, - }); + const MediaControlsPicker({super.key, required this.selectedControls}); final List selectedControls; diff --git a/lib/settings/view/player_settings_page.dart b/lib/settings/view/player_settings_page.dart index 58b050f..1d93e71 100644 --- a/lib/settings/view/player_settings_page.dart +++ b/lib/settings/view/player_settings_page.dart @@ -9,9 +9,7 @@ import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/shared/extensions/duration_format.dart'; class PlayerSettingsPage extends HookConsumerWidget { - const PlayerSettingsPage({ - super.key, - }); + const PlayerSettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -37,7 +35,9 @@ class PlayerSettingsPage extends HookConsumerWidget { ), initialValue: playerSettings.configurePlayerForEveryBook, onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings( configurePlayerForEveryBook: value, ), @@ -50,8 +50,10 @@ class PlayerSettingsPage extends HookConsumerWidget { title: const Text('Default Speed'), trailing: Text( '${playerSettings.preferredDefaultSpeed}x', - style: - TextStyle(color: primaryColor, fontWeight: FontWeight.bold), + style: TextStyle( + color: primaryColor, + fontWeight: FontWeight.bold, + ), ), leading: const Icon(Icons.speed), onPressed: (context) async { @@ -62,7 +64,9 @@ class PlayerSettingsPage extends HookConsumerWidget { ), ); if (newSpeed != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings( preferredDefaultSpeed: newSpeed, ), @@ -75,8 +79,10 @@ class PlayerSettingsPage extends HookConsumerWidget { title: const Text('Speed Options'), description: Text( playerSettings.speedOptions.map((e) => '${e}x').join(', '), - style: - TextStyle(fontWeight: FontWeight.bold, color: primaryColor), + style: TextStyle( + fontWeight: FontWeight.bold, + color: primaryColor, + ), ), leading: const Icon(Icons.speed), onPressed: (context) async { @@ -87,7 +93,9 @@ class PlayerSettingsPage extends HookConsumerWidget { ), ); if (newSpeedOptions != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings( speedOptions: newSpeedOptions..sort(), ), @@ -110,7 +118,8 @@ class PlayerSettingsPage extends HookConsumerWidget { children: [ TextSpan( text: playerSettings - .minimumPositionForReporting.smartBinaryFormat, + .minimumPositionForReporting + .smartBinaryFormat, style: TextStyle( fontWeight: FontWeight.bold, color: primaryColor, @@ -133,7 +142,9 @@ class PlayerSettingsPage extends HookConsumerWidget { }, ); if (newDuration != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings( minimumPositionForReporting: newDuration, ), @@ -150,7 +161,8 @@ class PlayerSettingsPage extends HookConsumerWidget { children: [ TextSpan( text: playerSettings - .markCompleteWhenTimeLeft.smartBinaryFormat, + .markCompleteWhenTimeLeft + .smartBinaryFormat, style: TextStyle( fontWeight: FontWeight.bold, color: primaryColor, @@ -173,7 +185,9 @@ class PlayerSettingsPage extends HookConsumerWidget { }, ); if (newDuration != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings( markCompleteWhenTimeLeft: newDuration, ), @@ -190,7 +204,8 @@ class PlayerSettingsPage extends HookConsumerWidget { children: [ TextSpan( text: playerSettings - .playbackReportInterval.smartBinaryFormat, + .playbackReportInterval + .smartBinaryFormat, style: TextStyle( fontWeight: FontWeight.bold, color: primaryColor, @@ -213,7 +228,9 @@ class PlayerSettingsPage extends HookConsumerWidget { }, ); if (newDuration != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings( playbackReportInterval: newDuration, ), @@ -237,7 +254,9 @@ class PlayerSettingsPage extends HookConsumerWidget { initialValue: playerSettings.expandedPlayerSettings.showTotalProgress, onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings .expandedPlayerSettings(showTotalProgress: value), ); @@ -253,7 +272,9 @@ class PlayerSettingsPage extends HookConsumerWidget { initialValue: playerSettings.expandedPlayerSettings.showChapterProgress, onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.playerSettings( expandedPlayerSettings: playerSettings .expandedPlayerSettings @@ -306,17 +327,15 @@ class TimeDurationSelector extends HookConsumerWidget { } class SpeedPicker extends HookConsumerWidget { - const SpeedPicker({ - super.key, - this.initialValue = 1, - }); + const SpeedPicker({super.key, this.initialValue = 1}); final double initialValue; @override Widget build(BuildContext context, WidgetRef ref) { - final speedController = - useTextEditingController(text: initialValue.toString()); + final speedController = useTextEditingController( + text: initialValue.toString(), + ); final speed = useState(initialValue); return AlertDialog( title: const Text('Select Speed'), @@ -368,30 +387,32 @@ class SpeedOptionsPicker extends HookConsumerWidget { Wrap( spacing: 8.0, runSpacing: 8.0, - children: speedOptions.value - .map( - (speed) => Chip( - label: Text('${speed}x'), - onDeleted: speed == 1 - ? null - : () { - speedOptions.value = - speedOptions.value.where((element) { - // speed option 1 can't be removed - return element != speed; - }).toList(); - }, - ), - ) - .toList() - ..sort((a, b) { - // if (a.label == const Text('1x')) { - // return -1; - // } else if (b.label == const Text('1x')) { - // return 1; - // } - return a.label.toString().compareTo(b.label.toString()); - }), + children: + speedOptions.value + .map( + (speed) => Chip( + label: Text('${speed}x'), + onDeleted: speed == 1 + ? null + : () { + speedOptions.value = speedOptions.value.where(( + element, + ) { + // speed option 1 can't be removed + return element != speed; + }).toList(); + }, + ), + ) + .toList() + ..sort((a, b) { + // if (a.label == const Text('1x')) { + // return -1; + // } else if (b.label == const Text('1x')) { + // return 1; + // } + return a.label.toString().compareTo(b.label.toString()); + }), ), TextField( focusNode: focusNode, diff --git a/lib/settings/view/shake_detector_settings_page.dart b/lib/settings/view/shake_detector_settings_page.dart index 98c5b8d..fe39b53 100644 --- a/lib/settings/view/shake_detector_settings_page.dart +++ b/lib/settings/view/shake_detector_settings_page.dart @@ -9,9 +9,7 @@ import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/shared/extensions/enum.dart'; class ShakeDetectorSettingsPage extends HookConsumerWidget { - const ShakeDetectorSettingsPage({ - super.key, - }); + const ShakeDetectorSettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -41,7 +39,9 @@ class ShakeDetectorSettingsPage extends HookConsumerWidget { ), initialValue: shakeDetectionSettings.isEnabled, onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.shakeDetectionSettings( isEnabled: value, ), @@ -77,7 +77,9 @@ class ShakeDetectorSettingsPage extends HookConsumerWidget { ); if (newThreshold != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.shakeDetectionSettings( threshold: newThreshold, ), @@ -107,7 +109,9 @@ class ShakeDetectorSettingsPage extends HookConsumerWidget { ); if (newShakeAction != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.shakeDetectionSettings( shakeAction: newShakeAction, ), @@ -131,26 +135,23 @@ class ShakeDetectorSettingsPage extends HookConsumerWidget { ) : Wrap( spacing: 8.0, - children: shakeDetectionSettings.feedback.map( - (feedback) { - return Icon( - feedback.icon, - color: selectedValueColor, - ); - }, - ).toList(), + children: shakeDetectionSettings.feedback.map((feedback) { + return Icon(feedback.icon, color: selectedValueColor); + }).toList(), ), onPressed: (context) async { final newFeedback = await showDialog>( - context: context, - builder: (context) => ShakeFeedbackSelector( - initialValue: shakeDetectionSettings.feedback, - ), - ); + context: context, + builder: (context) => ShakeFeedbackSelector( + initialValue: shakeDetectionSettings.feedback, + ), + ); if (newFeedback != null) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.shakeDetectionSettings( feedback: newFeedback, ), @@ -256,10 +257,7 @@ class ShakeActionSelector extends HookConsumerWidget { } class ShakeForceSelector extends HookConsumerWidget { - const ShakeForceSelector({ - super.key, - this.initialValue = 6, - }); + const ShakeForceSelector({super.key, this.initialValue = 6}); final double initialValue; @@ -291,9 +289,7 @@ class ShakeForceSelector extends HookConsumerWidget { shakeForce.value = 0; }, ), - helper: const Text( - 'Enter a number to set the threshold in m/s²', - ), + helper: const Text('Enter a number to set the threshold in m/s²'), ), ), Wrap( diff --git a/lib/settings/view/simple_settings_page.dart b/lib/settings/view/simple_settings_page.dart index ea9107d..e1f9fd7 100644 --- a/lib/settings/view/simple_settings_page.dart +++ b/lib/settings/view/simple_settings_page.dart @@ -4,11 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/features/player/view/mini_player_bottom_padding.dart'; class SimpleSettingsPage extends HookConsumerWidget { - const SimpleSettingsPage({ - super.key, - this.title, - this.sections, - }); + const SimpleSettingsPage({super.key, this.title, this.sections}); final Widget? title; final List? sections; @@ -34,18 +30,16 @@ class SimpleSettingsPage extends HookConsumerWidget { ), if (sections != null) SliverList( - delegate: SliverChildListDelegate( - [ - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(20)), - child: SettingsList( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - sections: sections!, - ), + delegate: SliverChildListDelegate([ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(20)), + child: SettingsList( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + sections: sections!, ), - ], - ), + ), + ]), ), // some padding at the bottom const SliverPadding(padding: EdgeInsets.only(bottom: 20)), diff --git a/lib/settings/view/theme_settings_page.dart b/lib/settings/view/theme_settings_page.dart index 315e4fa..8727be7 100644 --- a/lib/settings/view/theme_settings_page.dart +++ b/lib/settings/view/theme_settings_page.dart @@ -10,9 +10,7 @@ import 'package:vaani/settings/view/simple_settings_page.dart'; import 'package:vaani/shared/extensions/enum.dart'; class ThemeSettingsPage extends HookConsumerWidget { - const ThemeSettingsPage({ - super.key, - }); + const ThemeSettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -38,7 +36,9 @@ class ThemeSettingsPage extends HookConsumerWidget { selectedIcon: const Icon(Icons.check), selected: {themeSettings.themeMode}, onSelectionChanged: (newSelection) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.themeSettings( themeMode: newSelection.first, ), @@ -66,8 +66,8 @@ class ThemeSettingsPage extends HookConsumerWidget { themeSettings.themeMode == ThemeMode.light ? Icons.light_mode : themeSettings.themeMode == ThemeMode.dark - ? Icons.dark_mode - : Icons.auto_awesome, + ? Icons.dark_mode + : Icons.auto_awesome, ), ), @@ -82,10 +82,10 @@ class ThemeSettingsPage extends HookConsumerWidget { 'Increase the contrast between the background and the text', ), onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( - appSettings.copyWith.themeSettings( - highContrast: value, - ), + ref + .read(appSettingsProvider.notifier) + .update( + appSettings.copyWith.themeSettings(highContrast: value), ); }, ), @@ -103,7 +103,9 @@ class ThemeSettingsPage extends HookConsumerWidget { ? const Icon(Icons.auto_awesome) : const Icon(Icons.auto_fix_off), onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.themeSettings( useMaterialThemeFromSystem: value, ), @@ -164,7 +166,9 @@ class ThemeSettingsPage extends HookConsumerWidget { ? const Icon(Icons.auto_fix_high) : const Icon(Icons.auto_fix_off), onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.themeSettings( useCurrentPlayerThemeThroughoutApp: value, ), @@ -182,7 +186,9 @@ class ThemeSettingsPage extends HookConsumerWidget { ? const Icon(Icons.auto_fix_high) : const Icon(Icons.auto_fix_off), onToggle: (value) { - ref.read(appSettingsProvider.notifier).update( + ref + .read(appSettingsProvider.notifier) + .update( appSettings.copyWith.themeSettings( useMaterialThemeOnItemPage: value, ), diff --git a/lib/settings/view/widgets/navigation_with_switch_tile.dart b/lib/settings/view/widgets/navigation_with_switch_tile.dart index 8851fa1..f2d90f9 100644 --- a/lib/settings/view/widgets/navigation_with_switch_tile.dart +++ b/lib/settings/view/widgets/navigation_with_switch_tile.dart @@ -44,10 +44,7 @@ class NavigationWithSwitchTile extends AbstractSettingsTile { indent: 8.0, endIndent: 8.0, ), - Switch.adaptive( - value: value, - onChanged: onToggle, - ), + Switch.adaptive(value: value, onChanged: onToggle), ], ), ), diff --git a/lib/shared/extensions/enum.dart b/lib/shared/extensions/enum.dart index e0e6c09..0def3ea 100644 --- a/lib/shared/extensions/enum.dart +++ b/lib/shared/extensions/enum.dart @@ -13,10 +13,7 @@ extension TitleCase on Enum { String get pascalCase { // capitalize the first letter of each word return name - .replaceAllMapped( - RegExp(r'([A-Z])'), - (match) => ' ${match.group(0)}', - ) + .replaceAllMapped(RegExp(r'([A-Z])'), (match) => ' ${match.group(0)}') .trim() .split(' ') .map((word) => word[0].toUpperCase() + word.substring(1)) diff --git a/lib/shared/extensions/model_conversions.dart b/lib/shared/extensions/model_conversions.dart index 38403d8..b3ff0aa 100644 --- a/lib/shared/extensions/model_conversions.dart +++ b/lib/shared/extensions/model_conversions.dart @@ -47,8 +47,8 @@ extension ShelfConversion on Shelf { extension UserConversion on User { UserWithSessionAndMostRecentProgress - get asUserWithSessionAndMostRecentProgress => - UserWithSessionAndMostRecentProgress.fromJson(toJson()); + get asUserWithSessionAndMostRecentProgress => + UserWithSessionAndMostRecentProgress.fromJson(toJson()); User get asUser => User.fromJson(toJson()); } diff --git a/lib/shared/extensions/obfuscation.dart b/lib/shared/extensions/obfuscation.dart index 6ff85fe..2a057a4 100644 --- a/lib/shared/extensions/obfuscation.dart +++ b/lib/shared/extensions/obfuscation.dart @@ -80,9 +80,7 @@ extension ObfuscateServer on AudiobookShelfServer { if (!kReleaseMode) { return this; } - return copyWith( - serverUrl: serverUrl.obfuscate(), - ); + return copyWith(serverUrl: serverUrl.obfuscate()); } } @@ -103,10 +101,7 @@ extension ObfuscateRequest on http.BaseRequest { if (!kReleaseMode) { return this; } - return http.Request( - method, - url.obfuscate(), - ); + return http.Request(method, url.obfuscate()); } } @@ -134,9 +129,11 @@ extension ObfuscateResponse on http.Response { // token regex is `"token": "..."` return body .replaceAll( - RegExp(r'(\b\w+@\w+\.\w+\b)|' - r'(\b\d{3}-\d{3}-\d{4}\b)|' - r'(\bhttps?://\S+\b)'), + RegExp( + r'(\b\w+@\w+\.\w+\b)|' + r'(\b\d{3}-\d{3}-\d{4}\b)|' + r'(\bhttps?://\S+\b)', + ), 'obfuscated', ) .replaceAll( @@ -151,9 +148,7 @@ extension ObfuscateLoginResponse on shelfsdk.LoginResponse { if (!kReleaseMode) { return this; } - return copyWith( - user: user.obfuscate(), - ); + return copyWith(user: user.obfuscate()); } } @@ -162,8 +157,6 @@ extension ObfuscateUser on shelfsdk.User { if (!kReleaseMode) { return this; } - return shelfsdk.User.fromJson( - toJson()..['token'] = 'tokenObfuscated', - ); + return shelfsdk.User.fromJson(toJson()..['token'] = 'tokenObfuscated'); } } diff --git a/lib/shared/extensions/time_of_day.dart b/lib/shared/extensions/time_of_day.dart index 9088b46..a0b0f08 100644 --- a/lib/shared/extensions/time_of_day.dart +++ b/lib/shared/extensions/time_of_day.dart @@ -2,10 +2,7 @@ import 'package:flutter/material.dart'; extension ToTimeOfDay on Duration { TimeOfDay toTimeOfDay() { - return TimeOfDay( - hour: inHours % 24, - minute: inMinutes % 60, - ); + return TimeOfDay(hour: inHours % 24, minute: inMinutes % 60); } } diff --git a/lib/shared/hooks.dart b/lib/shared/hooks.dart index 8e27b3a..3b9fd8e 100644 --- a/lib/shared/hooks.dart +++ b/lib/shared/hooks.dart @@ -7,24 +7,18 @@ void useInterval(VoidCallback callback, Duration delay) { final savedCallback = useRef(callback); savedCallback.value = callback; - useEffect( - () { - final timer = Timer.periodic(delay, (_) => savedCallback.value()); - return timer.cancel; - }, - [delay], - ); + useEffect(() { + final timer = Timer.periodic(delay, (_) => savedCallback.value()); + return timer.cancel; + }, [delay]); } void useTimer(VoidCallback callback, Duration delay) { final savedCallback = useRef(callback); savedCallback.value = callback; - useEffect( - () { - final timer = Timer(delay, savedCallback.value); - return timer.cancel; - }, - [delay], - ); + useEffect(() { + final timer = Timer(delay, savedCallback.value); + return timer.cancel; + }, [delay]); } diff --git a/lib/shared/icons/abs_icons.dart b/lib/shared/icons/abs_icons.dart index eca3e1a..ccceb39 100644 --- a/lib/shared/icons/abs_icons.dart +++ b/lib/shared/icons/abs_icons.dart @@ -24,46 +24,106 @@ class AbsIcons { static const _kFontFam = 'AbsIcons'; static const String? _kFontPkg = null; - static const IconData audiobookshelf = - IconData(0xe900, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData microphone_2 = - IconData(0xe901, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData microphone_1 = - IconData(0xe902, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData radio = - IconData(0xe903, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData podcast = - IconData(0xe904, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData books_1 = - IconData(0xe905, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData database_2 = - IconData(0xe906, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData headphones = - IconData(0xe910, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData music = - IconData(0xe911, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData video = - IconData(0xe914, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData microphone_3 = - IconData(0xe91e, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData book = - IconData(0xe91f, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData books_2 = - IconData(0xe920, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData file_picture = - IconData(0xe927, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData database_1 = - IconData(0xe964, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData rocket = - IconData(0xe9a5, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData power = - IconData(0xe9b5, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData star = - IconData(0xe9d9, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData heart = - IconData(0xe9da, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData rss = - IconData(0xea9b, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData audiobookshelf = IconData( + 0xe900, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData microphone_2 = IconData( + 0xe901, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData microphone_1 = IconData( + 0xe902, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData radio = IconData( + 0xe903, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData podcast = IconData( + 0xe904, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData books_1 = IconData( + 0xe905, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData database_2 = IconData( + 0xe906, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData headphones = IconData( + 0xe910, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData music = IconData( + 0xe911, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData video = IconData( + 0xe914, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData microphone_3 = IconData( + 0xe91e, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData book = IconData( + 0xe91f, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData books_2 = IconData( + 0xe920, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData file_picture = IconData( + 0xe927, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData database_1 = IconData( + 0xe964, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData rocket = IconData( + 0xe9a5, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData power = IconData( + 0xe9b5, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData star = IconData( + 0xe9d9, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData heart = IconData( + 0xe9da, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData rss = IconData( + 0xea9b, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); static final Map _iconMap = { 'audiobookshelf': audiobookshelf, diff --git a/lib/shared/widgets/add_new_server.dart b/lib/shared/widgets/add_new_server.dart index 8c31fc1..450874d 100644 --- a/lib/shared/widgets/add_new_server.dart +++ b/lib/shared/widgets/add_new_server.dart @@ -52,7 +52,8 @@ class AddNewServer extends HookConsumerWidget { // do nothing appLogger.severe('Error parsing URI: $e'); } - final canSubmit = !readOnly && + final canSubmit = + !readOnly && (isServerAliveValue || (allowEmpty && newServerURI.text.isEmpty)); return TextFormField( readOnly: readOnly, @@ -71,8 +72,9 @@ class AddNewServer extends HookConsumerWidget { color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.8), ), border: const OutlineInputBorder(), - prefixText: - myController.text.startsWith(httpUrlRegExp) ? '' : 'https://', + prefixText: myController.text.startsWith(httpUrlRegExp) + ? '' + : 'https://', prefixIcon: ServerAliveIcon(server: parsedUri), // add server button @@ -101,10 +103,7 @@ class AddNewServer extends HookConsumerWidget { } class ServerAliveIcon extends HookConsumerWidget { - const ServerAliveIcon({ - super.key, - required this.server, - }); + const ServerAliveIcon({super.key, required this.server}); final Uri server; @@ -121,8 +120,8 @@ class ServerAliveIcon extends HookConsumerWidget { message: server.toString().isEmpty ? 'Server Status' : isServerAliveValue - ? 'Server connected' - : 'Cannot connect to server', + ? 'Server connected' + : 'Cannot connect to server', child: server.toString().isEmpty ? Icon( Icons.cloud_outlined, diff --git a/lib/shared/widgets/drawer.dart b/lib/shared/widgets/drawer.dart index 12aa1c2..56bec1f 100644 --- a/lib/shared/widgets/drawer.dart +++ b/lib/shared/widgets/drawer.dart @@ -4,9 +4,7 @@ import 'package:vaani/features/you/view/server_manager.dart'; import 'package:vaani/router/router.dart'; class MyDrawer extends StatelessWidget { - const MyDrawer({ - super.key, - }); + const MyDrawer({super.key}); @override Widget build(BuildContext context) { @@ -16,10 +14,7 @@ class MyDrawer extends StatelessWidget { const DrawerHeader( child: Text( 'Vaani', - style: TextStyle( - fontStyle: FontStyle.italic, - fontSize: 30, - ), + style: TextStyle(fontStyle: FontStyle.italic, fontSize: 30), ), ), ListTile( diff --git a/lib/shared/widgets/expandable_description.dart b/lib/shared/widgets/expandable_description.dart index 203c7b0..bc2cee9 100644 --- a/lib/shared/widgets/expandable_description.dart +++ b/lib/shared/widgets/expandable_description.dart @@ -55,10 +55,7 @@ class ExpandableDescription extends HookWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // header text - Text( - style: textTheme.titleMedium, - title, - ), + Text(style: textTheme.titleMedium, title), // carrot icon AnimatedRotation( turns: isDescExpanded.value ? 0.5 : 0, @@ -79,11 +76,7 @@ class ExpandableDescription extends HookWidget { child: AnimatedSwitcher( duration: duration * 3, child: isDescExpanded.value - ? Text( - style: textTheme.bodyMedium, - content, - maxLines: null, - ) + ? Text(style: textTheme.bodyMedium, content, maxLines: null) : Text( style: textTheme.bodyMedium, content, diff --git a/lib/shared/widgets/not_implemented.dart b/lib/shared/widgets/not_implemented.dart index 801f152..5c10a60 100644 --- a/lib/shared/widgets/not_implemented.dart +++ b/lib/shared/widgets/not_implemented.dart @@ -2,9 +2,6 @@ import 'package:flutter/material.dart'; void showNotImplementedToast(BuildContext context) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Not implemented"), - showCloseIcon: true, - ), + const SnackBar(content: Text("Not implemented"), showCloseIcon: true), ); } diff --git a/lib/shared/widgets/shelves/author_shelf.dart b/lib/shared/widgets/shelves/author_shelf.dart index aa0acce..36dbadf 100644 --- a/lib/shared/widgets/shelves/author_shelf.dart +++ b/lib/shared/widgets/shelves/author_shelf.dart @@ -6,11 +6,7 @@ import 'package:vaani/shared/widgets/shelves/home_shelf.dart'; /// A shelf that displays Authors on the home page class AuthorHomeShelf extends HookConsumerWidget { - const AuthorHomeShelf({ - super.key, - required this.shelf, - required this.title, - }); + const AuthorHomeShelf({super.key, required this.shelf, required this.title}); final String title; final AuthorShelf shelf; @@ -20,9 +16,7 @@ class AuthorHomeShelf extends HookConsumerWidget { return SimpleHomeShelf( title: title, children: shelf.entities - .map( - (item) => AuthorOnShelf(item: item), - ) + .map((item) => AuthorOnShelf(item: item)) .toList(), ); } @@ -30,10 +24,7 @@ class AuthorHomeShelf extends HookConsumerWidget { // a widget to display a item on the shelf class AuthorOnShelf extends HookConsumerWidget { - const AuthorOnShelf({ - super.key, - required this.item, - }); + const AuthorOnShelf({super.key, required this.item}); final Author item; diff --git a/lib/shared/widgets/shelves/book_shelf.dart b/lib/shared/widgets/shelves/book_shelf.dart index 7b21820..0919161 100644 --- a/lib/shared/widgets/shelves/book_shelf.dart +++ b/lib/shared/widgets/shelves/book_shelf.dart @@ -40,11 +40,11 @@ class BookHomeShelf extends HookConsumerWidget { .map( (item) => switch (item.mediaType) { MediaType.book => BookOnShelf( - item: item, - key: ValueKey(shelf.id + item.id), - heroTagSuffix: shelf.id, - showPlayButton: showPlayButton, - ), + item: item, + key: ValueKey(shelf.id + item.id), + heroTagSuffix: shelf.id, + showPlayButton: showPlayButton, + ), _ => Container(), }, ) @@ -83,13 +83,8 @@ class BookOnShelf extends HookConsumerWidget { // open the book context.pushNamed( Routes.libraryItem.name, - pathParameters: { - Routes.libraryItem.pathParamName!: item.id, - }, - extra: LibraryItemExtras( - book: book, - heroTagSuffix: heroTagSuffix, - ), + pathParameters: {Routes.libraryItem.pathParamName!: item.id}, + extra: LibraryItemExtras(book: book, heroTagSuffix: heroTagSuffix), ); } @@ -99,8 +94,11 @@ class BookOnShelf extends HookConsumerWidget { onTap: handleTapOnBook, borderRadius: BorderRadius.circular(10), child: Padding( - padding: - const EdgeInsets.only(bottom: 8.0, right: 4.0, left: 4.0), + padding: const EdgeInsets.only( + bottom: 8.0, + right: 4.0, + left: 4.0, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -112,7 +110,8 @@ class BookOnShelf extends HookConsumerWidget { alignment: Alignment.bottomRight, children: [ Hero( - tag: HeroTagPrefixes.bookCover + + tag: + HeroTagPrefixes.bookCover + item.id + heroTagSuffix, child: ClipRRect( @@ -128,17 +127,19 @@ class BookOnShelf extends HookConsumerWidget { var imageWidget = Image.memory( image, fit: BoxFit.fill, - cacheWidth: (height * - 1.2 * - MediaQuery.of(context) - .devicePixelRatio) - .round(), + cacheWidth: + (height * + 1.2 * + MediaQuery.of( + context, + ).devicePixelRatio) + .round(), ); return Container( decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, + color: Theme.of( + context, + ).colorScheme.onPrimaryContainer, ), child: imageWidget, ); @@ -157,9 +158,7 @@ class BookOnShelf extends HookConsumerWidget { ), // a play button on the book cover if (showPlayButton) - _BookOnShelfPlayButton( - libraryItemId: item.id, - ), + _BookOnShelfPlayButton(libraryItemId: item.id), ], ), ), @@ -202,9 +201,7 @@ class BookOnShelf extends HookConsumerWidget { } class _BookOnShelfPlayButton extends HookConsumerWidget { - const _BookOnShelfPlayButton({ - required this.libraryItemId, - }); + const _BookOnShelfPlayButton({required this.libraryItemId}); /// the id of the library item of the book final String libraryItemId; @@ -217,8 +214,9 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { player.book?.libraryItemId == libraryItemId; final isPlayingThisBook = player.playing && isCurrentBookSetInPlayer; - final userProgress = me.value?.mediaProgress - ?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId); + final userProgress = me.value?.mediaProgress?.firstWhereOrNull( + (element) => element.libraryItemId == libraryItemId, + ); final isBookCompleted = userProgress?.isFinished ?? false; const size = 40.0; @@ -226,8 +224,10 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { // if there is user progress for this book show a circular progress indicator around the play button var strokeWidth = size / 8; - final useMaterialThemeOnItemPage = - ref.watch(appSettingsProvider).themeSettings.useMaterialThemeOnItemPage; + final useMaterialThemeOnItemPage = ref + .watch(appSettingsProvider) + .themeSettings + .useMaterialThemeOnItemPage; AsyncValue coverColorScheme = const AsyncValue.loading(); if (useMaterialThemeOnItemPage && isCurrentBookSetInPlayer) { @@ -242,8 +242,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { return Theme( // if current book is set in player, get theme from the cover image data: ThemeData( - colorScheme: - coverColorScheme.value ?? Theme.of(context).colorScheme, + colorScheme: coverColorScheme.value ?? Theme.of(context).colorScheme, ), child: Padding( padding: EdgeInsets.all(strokeWidth / 2 + 2), @@ -258,10 +257,9 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { child: CircularProgressIndicator( value: userProgress.progress, strokeWidth: strokeWidth, - backgroundColor: Theme.of(context) - .colorScheme - .onPrimary - .withValues(alpha: 0.8), + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withValues(alpha: 0.8), valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.primary, ), @@ -272,22 +270,18 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { IconButton( color: Theme.of(context).colorScheme.primary, style: ButtonStyle( - padding: WidgetStateProperty.all( - EdgeInsets.zero, - ), - minimumSize: WidgetStateProperty.all( - const Size(size, size), - ), + padding: WidgetStateProperty.all(EdgeInsets.zero), + minimumSize: WidgetStateProperty.all(const Size(size, size)), backgroundColor: WidgetStateProperty.all( - Theme.of(context) - .colorScheme - .onPrimary - .withValues(alpha: 0.9), + Theme.of( + context, + ).colorScheme.onPrimary.withValues(alpha: 0.9), ), ), onPressed: () async { - final book = - await ref.watch(libraryItemProvider(libraryItemId).future); + final book = await ref.watch( + libraryItemProvider(libraryItemId).future, + ); libraryItemPlayButtonOnPressed( ref: ref, @@ -313,9 +307,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { // a skeleton for the book cover class BookCoverSkeleton extends StatelessWidget { - const BookCoverSkeleton({ - super.key, - }); + const BookCoverSkeleton({super.key}); @override Widget build(BuildContext context) { @@ -324,13 +316,13 @@ class BookCoverSkeleton extends StatelessWidget { child: SizedBox( width: 150, child: Shimmer.fromColors( - baseColor: - Theme.of(context).colorScheme.surface.withValues(alpha: 0.3), - highlightColor: - Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.1), - child: Container( - color: Theme.of(context).colorScheme.surface, - ), + baseColor: Theme.of( + context, + ).colorScheme.surface.withValues(alpha: 0.3), + highlightColor: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.1), + child: Container(color: Theme.of(context).colorScheme.surface), ), ), ); diff --git a/lib/shared/widgets/shelves/home_shelf.dart b/lib/shared/widgets/shelves/home_shelf.dart index 9a59a54..3770fe1 100644 --- a/lib/shared/widgets/shelves/home_shelf.dart +++ b/lib/shared/widgets/shelves/home_shelf.dart @@ -26,14 +26,14 @@ class HomeShelf extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return switch (shelf.type) { ShelfType.book => BookHomeShelf( - title: title, - shelf: shelf.asLibraryItemShelf, - showPlayButton: showPlayButton, - ), + title: title, + shelf: shelf.asLibraryItemShelf, + showPlayButton: showPlayButton, + ), ShelfType.authors => AuthorHomeShelf( - title: title, - shelf: shelf.asAuthorShelf, - ), + title: title, + shelf: shelf.asAuthorShelf, + ), _ => Container(), }; } @@ -75,9 +75,7 @@ class SimpleHomeShelf extends HookConsumerWidget { scrollDirection: Axis.horizontal, itemBuilder: (context, index) { if (index == 0 || index == children.length + 1) { - return const SizedBox( - width: 8, - ); + return const SizedBox(width: 8); } return children[index - 1]; }, @@ -88,7 +86,8 @@ class SimpleHomeShelf extends HookConsumerWidget { return const SizedBox(width: 4); }, - itemCount: children.length + + itemCount: + children.length + 2, // add some extra space at the start and end so that the first and last items are not at the edge ), ), diff --git a/lib/theme/providers/system_theme_provider.dart b/lib/theme/providers/system_theme_provider.dart index 529306e..cc01415 100644 --- a/lib/theme/providers/system_theme_provider.dart +++ b/lib/theme/providers/system_theme_provider.dart @@ -53,22 +53,15 @@ FutureOr<(ColorScheme light, ColorScheme dark)?> systemTheme( } if (schemeLight == null || schemeDark == null) { - _logger - .warning('dynamic_color: Dynamic color not detected on this device.'); + _logger.warning( + 'dynamic_color: Dynamic color not detected on this device.', + ); return null; } // set high contrast theme if (highContrast) { - schemeLight = schemeLight - .copyWith( - surface: Colors.white, - ) - .harmonized(); - schemeDark = schemeDark - .copyWith( - surface: Colors.black, - ) - .harmonized(); + schemeLight = schemeLight.copyWith(surface: Colors.white).harmonized(); + schemeDark = schemeDark.copyWith(surface: Colors.black).harmonized(); } return (schemeLight, schemeDark); }