diff --git a/android/settings.gradle b/android/settings.gradle index 1d6d19b..774d879 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -20,7 +20,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "org.jetbrains.kotlin.android" version "2.0.0" apply false } include ":app" diff --git a/lib/api/api_provider.dart b/lib/api/api_provider.dart index 0e5a590..748b5e0 100644 --- a/lib/api/api_provider.dart +++ b/lib/api/api_provider.dart @@ -2,7 +2,7 @@ import 'dart:convert'; -import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:whispering_pages/db/cache_manager.dart'; @@ -10,6 +10,8 @@ import 'package:whispering_pages/settings/api_settings_provider.dart'; part 'api_provider.g.dart'; +final _logger = Logger('api_provider'); + Uri makeBaseUrl(String address) { if (!address.startsWith('http') && !address.startsWith('https')) { address = 'https://$address'; @@ -103,7 +105,7 @@ class PersonalizedView extends _$PersonalizedView { for (final item in resJson) Shelf.fromJson(item as Map), ]; - debugPrint('reading from cache: $cachedRes'); + _logger.fine('reading from cache: $cachedRes'); yield res; } @@ -119,7 +121,7 @@ class PersonalizedView extends _$PersonalizedView { fileExtension: 'json', key: key, ); - debugPrint('writing to cache: $newFile'); + _logger.fine('writing to cache: $newFile'); yield res!; } diff --git a/lib/api/authenticated_user_provider.dart b/lib/api/authenticated_user_provider.dart index 157ce21..7159da8 100644 --- a/lib/api/authenticated_user_provider.dart +++ b/lib/api/authenticated_user_provider.dart @@ -1,16 +1,19 @@ -import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:whispering_pages/api/server_provider.dart' show audiobookShelfServerProvider; -import 'package:whispering_pages/settings/models/audiobookshelf_server.dart'; -import 'package:whispering_pages/settings/models/authenticated_user.dart' as model; -import 'package:whispering_pages/settings/api_settings_provider.dart'; import 'package:whispering_pages/db/storage.dart'; +import 'package:whispering_pages/settings/api_settings_provider.dart'; +import 'package:whispering_pages/settings/models/audiobookshelf_server.dart'; +import 'package:whispering_pages/settings/models/authenticated_user.dart' + as model; part 'authenticated_user_provider.g.dart'; final _box = AvailableHiveBoxes.authenticatedUserBox; +final _logger = Logger('authenticated_user_provider'); + /// provides with a set of authenticated users @riverpod class AuthenticatedUser extends _$AuthenticatedUser { @@ -32,10 +35,10 @@ class AuthenticatedUser extends _$AuthenticatedUser { Set readFromBoxOrCreate() { if (_box.isNotEmpty) { final foundData = _box.getRange(0, _box.length); - debugPrint('found users in box: $foundData'); + _logger.fine('found users in box: $foundData'); return foundData.toSet(); } else { - debugPrint('no settings found in box'); + _logger.fine('no settings found in box'); return {}; } } @@ -46,7 +49,7 @@ class AuthenticatedUser extends _$AuthenticatedUser { return; } _box.addAll(state); - debugPrint('writing state to box: $state'); + _logger.fine('writing state to box: $state'); } void addUser(model.AuthenticatedUser user) { diff --git a/lib/api/image_provider.dart b/lib/api/image_provider.dart index fdcebbb..b3a642e 100644 --- a/lib/api/image_provider.dart +++ b/lib/api/image_provider.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:whispering_pages/api/api_provider.dart'; @@ -14,6 +14,8 @@ import 'package:whispering_pages/db/cache_manager.dart'; part 'image_provider.g.dart'; +final _logger = Logger('cover_image_provider'); + @Riverpod(keepAlive: true) class CoverImage extends _$CoverImage { @override @@ -29,23 +31,23 @@ class CoverImage extends _$CoverImage { if (file != null) { // if the image is in the cache, yield it - debugPrint( + _logger.fine( 'cover image found in cache for ${libraryItem.id} at ${file.file.path}', ); yield await file.file.readAsBytes(); // return if no need to fetch from the server if (libraryItem.updatedAt.isBefore(await file.file.lastModified())) { - debugPrint( + _logger.fine( 'cover image is up to date for ${libraryItem.id}, no need to fetch from the server', ); return; } else { - debugPrint( + _logger.fine( 'cover image stale for ${libraryItem.id}, fetching from the server', ); } } else { - debugPrint('cover image not found in cache for ${libraryItem.id}'); + _logger.fine('cover image not found in cache for ${libraryItem.id}'); } // check if the image is in the cache @@ -61,7 +63,7 @@ class CoverImage extends _$CoverImage { key: libraryItem.id, fileExtension: 'jpg', ); - debugPrint( + _logger.fine( 'cover image fetched for for ${libraryItem.id}, file time: ${await newFile.lastModified()}', ); } diff --git a/lib/api/library_item_provider.dart b/lib/api/library_item_provider.dart index da693d0..94f45ea 100644 --- a/lib/api/library_item_provider.dart +++ b/lib/api/library_item_provider.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart' as shelfsdk; import 'package:whispering_pages/api/api_provider.dart'; @@ -10,6 +10,8 @@ import 'package:whispering_pages/shared/extensions/model_conversions.dart'; part 'library_item_provider.g.dart'; +final _logger = Logger('LibraryItemProvider'); + /// provides the library item for the given id @riverpod class LibraryItem extends _$LibraryItem { @@ -17,7 +19,7 @@ class LibraryItem extends _$LibraryItem { Stream build(String id) async* { final api = ref.watch(authenticatedApiProvider); - debugPrint('LibraryItemProvider fetching library item: $id'); + _logger.fine('LibraryItemProvider fetching library item: $id'); // ! this is a mock delay // await Future.delayed(const Duration(seconds: 10)); @@ -27,14 +29,15 @@ class LibraryItem extends _$LibraryItem { final cachedFile = await apiResponseCacheManager.getFileFromMemory(key) ?? await apiResponseCacheManager.getFileFromCache(key); if (cachedFile != null) { - debugPrint('LibraryItemProvider reading from cache for $id from ${cachedFile.file}'); + _logger.fine( + 'LibraryItemProvider reading from cache for $id from ${cachedFile.file}'); // read file as json final cachedItem = shelfsdk.LibraryItemExpanded.fromJson( jsonDecode(await cachedFile.file.readAsString()), ); yield cachedItem; } else { - debugPrint('LibraryItemProvider cache miss for $id'); + _logger.fine('LibraryItemProvider cache miss for $id'); } // ! this is a mock delay @@ -60,7 +63,7 @@ class LibraryItem extends _$LibraryItem { fileExtension: 'json', key: key, ); - debugPrint('writing to cache: $newFile'); + _logger.fine('writing to cache: $newFile'); yield item.asExpanded; } } diff --git a/lib/features/playback_reporting/core/playback_reporter.dart b/lib/features/playback_reporting/core/playback_reporter.dart index 2d659a1..c7e9395 100644 --- a/lib/features/playback_reporting/core/playback_reporter.dart +++ b/lib/features/playback_reporting/core/playback_reporter.dart @@ -1,9 +1,11 @@ import 'dart:async'; -import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:whispering_pages/features/player/core/audiobook_player.dart'; +final _logger = Logger('PlaybackReporter'); + /// this playback reporter will watch the player and report to the server /// /// it will by default report every 10 seconds @@ -29,7 +31,7 @@ class PlaybackReporter { _reportingInterval = value; _cancelReportTimer(); _setReportTimer(); - debugPrint('PlaybackReporter set interval: $value'); + _logger.info('set interval: $value'); } /// the minimum duration to report @@ -62,9 +64,9 @@ class PlaybackReporter { if (player.playing) { _stopwatch.start(); _setReportTimer(); - debugPrint('PlaybackReporter starting stopwatch'); + _logger.fine('starting stopwatch'); } else { - debugPrint('PlaybackReporter not starting stopwatch'); + _logger.fine('not starting stopwatch'); } _subscriptions.add( @@ -73,7 +75,7 @@ class PlaybackReporter { if (player.book != null && _reportTimer == null) { _setReportTimer(); } else if (player.book == null && _reportTimer != null) { - debugPrint('PlaybackReporter book is null, closing session'); + _logger.info('book is null, closing session'); await closeSession(); _cancelReportTimer(); } @@ -81,34 +83,34 @@ class PlaybackReporter { // start or stop the stopwatch based on the playing state if (state.playing) { _stopwatch.start(); - debugPrint( - 'PlaybackReporter player state observed, starting stopwatch at ${_stopwatch.elapsed}', + _logger.fine( + 'player state observed, starting stopwatch at ${_stopwatch.elapsed}', ); } else if (!state.playing) { _stopwatch.stop(); - debugPrint( - 'PlaybackReporter player state observed, stopping stopwatch at ${_stopwatch.elapsed}', + _logger.fine( + 'player state observed, stopping stopwatch at ${_stopwatch.elapsed}', ); await syncCurrentPosition(); } }), ); - debugPrint( - 'PlaybackReporter initialized with interval: $reportingInterval, threshold: $reportingDurationThreshold', + _logger.fine( + 'initialized with interval: $reportingInterval, threshold: $reportingDurationThreshold', ); - debugPrint( - 'PlaybackReporter initialized with deviceModel: $deviceModel, deviceSdkVersion: $deviceSdkVersion, deviceClientName: $deviceClientName, deviceClientVersion: $deviceClientVersion, deviceManufacturer: $deviceManufacturer', + _logger.fine( + 'initialized with deviceModel: $deviceModel, deviceSdkVersion: $deviceSdkVersion, deviceClientName: $deviceClientName, deviceClientVersion: $deviceClientVersion, deviceManufacturer: $deviceManufacturer', ); } void tryReportPlayback(_) async { - debugPrint( - 'PlaybackReporter callback called when elapsed ${_stopwatch.elapsed}', + _logger.fine( + 'callback called when elapsed ${_stopwatch.elapsed}', ); if (_stopwatch.elapsed > reportingDurationThreshold) { - debugPrint( - 'PlaybackReporter reporting now with elapsed ${_stopwatch.elapsed} > threshold $reportingDurationThreshold', + _logger.fine( + 'reporting now with elapsed ${_stopwatch.elapsed} > threshold $reportingDurationThreshold', ); await syncCurrentPosition(); } @@ -123,7 +125,7 @@ class PlaybackReporter { _stopwatch.stop(); _reportTimer?.cancel(); - debugPrint('PlaybackReporter disposed'); + _logger.fine('disposed'); } /// current sessionId @@ -147,7 +149,7 @@ class PlaybackReporter { ), responseErrorHandler: _responseErrorHandler, ); - debugPrint('PlaybackReporter Started session: $sessionId'); + _logger.info('Started session: $sessionId'); return _session; } @@ -159,7 +161,7 @@ class PlaybackReporter { try { _session ??= await startSession(); } on NoAudiobookPlayingError { - debugPrint('PlaybackReporter No audiobook playing to sync position'); + _logger.warning('No audiobook playing to sync position'); return; } final currentPosition = player.positionInBook; @@ -170,8 +172,8 @@ class PlaybackReporter { responseErrorHandler: _responseErrorHandler, ); - debugPrint( - 'PlaybackReporter Synced position: $currentPosition with timeListened: ${_stopwatch.elapsed} for session: $sessionId', + _logger.fine( + 'Synced position: $currentPosition with timeListened: ${_stopwatch.elapsed} for session: $sessionId', ); // reset the stopwatch @@ -180,7 +182,7 @@ class PlaybackReporter { Future closeSession() async { if (sessionId == null) { - debugPrint('PlaybackReporter No session to close'); + _logger.warning('No session to close'); return; } @@ -190,23 +192,23 @@ class PlaybackReporter { responseErrorHandler: _responseErrorHandler, ); _session = null; - debugPrint('PlaybackReporter Closed session'); + _logger.info('Closed session'); } void _setReportTimer() { _reportTimer = Timer.periodic(_reportingInterval, tryReportPlayback); - debugPrint('PlaybackReporter set timer with interval: $_reportingInterval'); + _logger.fine('set timer with interval: $_reportingInterval'); } void _cancelReportTimer() { _reportTimer?.cancel(); _reportTimer = null; - debugPrint('PlaybackReporter cancelled timer'); + _logger.fine('cancelled timer'); } void _responseErrorHandler(response, [error]) { if (response.statusCode != 200) { - debugPrint('PlaybackReporter Error with api: $response, $error'); + _logger.shout('Error with api: $response, $error'); throw PlaybackSyncError( 'Error syncing position: ${response.body}, $error', ); @@ -215,8 +217,8 @@ class PlaybackReporter { SyncSessionReqParams? _getSyncData() { if (player.book?.libraryItemId != _session?.libraryItemId) { - debugPrint( - 'PlaybackReporter Book changed, not syncing position for session: $sessionId', + _logger.info( + 'Book changed, not syncing position for session: $sessionId', ); return null; } diff --git a/lib/features/sleep_timer/core/sleep_timer.dart b/lib/features/sleep_timer/core/sleep_timer.dart index 0283d51..1561adc 100644 --- a/lib/features/sleep_timer/core/sleep_timer.dart +++ b/lib/features/sleep_timer/core/sleep_timer.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:logging/logging.dart'; import 'package:whispering_pages/features/player/core/audiobook_player.dart'; /// this timer pauses the music player after a certain duration @@ -10,6 +10,8 @@ import 'package:whispering_pages/features/player/core/audiobook_player.dart'; /// watches the state of the music player and pauses it when the timer is up /// timer is cancelled when the music player is paused or stopped class SleepTimer { + final _logger = Logger('SleepTimer'); + /// The duration after which the music player will be paused Duration _duration; @@ -54,15 +56,15 @@ class SleepTimer { }), ); - debugPrint('SleepTimer created with duration: $duration'); + _logger.fine('created with duration: $duration'); } /// resets the timer void reset() { if (timer != null) { timer!.cancel(); - debugPrint( - 'SleepTimer cancelled timer, remaining time: $remainingTime, duration: $duration', + _logger.fine( + 'cancelled timer, remaining time: $remainingTime, duration: $duration', ); timer = null; } @@ -77,10 +79,10 @@ class SleepTimer { timer = Timer(duration, () { player.pause(); reset(); - debugPrint('SleepTimer paused player after $duration'); + _logger.fine('paused player after $duration'); }); startedAt = DateTime.now(); - debugPrint('SleepTimer started for $duration at $startedAt'); + _logger.fine('started for $duration at $startedAt'); } Duration get remainingTime { @@ -105,6 +107,6 @@ class SleepTimer { for (var sub in _subscriptions) { sub.cancel(); } - debugPrint('SleepTimer disposed'); + _logger.fine('disposed'); } } diff --git a/lib/main.dart b/lib/main.dart index 9badc47..9454908 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:just_audio_background/just_audio_background.dart' show JustAudioBackground; import 'package:just_audio_media_kit/just_audio_media_kit.dart' show JustAudioMediaKit; +import 'package:logging/logging.dart'; import 'package:whispering_pages/api/server_provider.dart'; import 'package:whispering_pages/db/storage.dart'; import 'package:whispering_pages/features/playback_reporting/providers/playback_reporter_provider.dart'; @@ -13,10 +14,20 @@ import 'package:whispering_pages/features/sleep_timer/providers/sleep_timer_prov import 'package:whispering_pages/router/router.dart'; import 'package:whispering_pages/settings/api_settings_provider.dart'; import 'package:whispering_pages/settings/app_settings_provider.dart'; +import 'package:whispering_pages/shared/extensions/duration_format.dart'; import 'package:whispering_pages/theme/theme.dart'; + void main() async { WidgetsFlutterBinding.ensureInitialized(); + // Configure the root Logger + Logger.root.level = Level.ALL; // Capture all logs + Logger.root.onRecord.listen((record) { + // Print log records to the console + debugPrint( + '${record.loggerName}: ${record.level.name}: ${record.time.time}: ${record.message}', + ); + }); // for playing audio on windows, linux JustAudioMediaKit.ensureInitialized(); diff --git a/lib/settings/api_settings_provider.dart b/lib/settings/api_settings_provider.dart index 0d47f1a..f435d72 100644 --- a/lib/settings/api_settings_provider.dart +++ b/lib/settings/api_settings_provider.dart @@ -1,6 +1,6 @@ // this provider is used to provide the Api settings to the app -import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:whispering_pages/db/available_boxes.dart'; import 'package:whispering_pages/settings/models/api_settings.dart' as model; @@ -9,6 +9,8 @@ part 'api_settings_provider.g.dart'; final _box = AvailableHiveBoxes.apiSettingsBox; +final _logger = Logger('ApiSettingsProvider'); + @Riverpod(keepAlive: true) class ApiSettings extends _$ApiSettings { @override @@ -31,12 +33,12 @@ class ApiSettings extends _$ApiSettings { activeServer: foundSettings.activeUser?.server, ); } - debugPrint('found api settings in box: $foundSettings'); + _logger.fine('found api settings in box: $foundSettings'); return foundSettings; } else { // create a new settings object const settings = model.ApiSettings(); - debugPrint('created new api settings: $settings'); + _logger.fine('created new api settings: $settings'); return settings; } } @@ -45,7 +47,7 @@ class ApiSettings extends _$ApiSettings { void writeToBox() { _box.clear(); _box.add(state); - debugPrint('wrote api settings to box: $state'); + _logger.fine('wrote api settings to box: $state'); } void updateState(model.ApiSettings newSettings, {bool force = false}) { diff --git a/lib/settings/app_settings_provider.dart b/lib/settings/app_settings_provider.dart index cdcf8bd..142019a 100644 --- a/lib/settings/app_settings_provider.dart +++ b/lib/settings/app_settings_provider.dart @@ -1,6 +1,6 @@ // this provider is used to provide the app settings to the app -import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:whispering_pages/db/available_boxes.dart'; import 'package:whispering_pages/settings/models/app_settings.dart' as model; @@ -9,6 +9,8 @@ part 'app_settings_provider.g.dart'; final _box = AvailableHiveBoxes.userPrefsBox; +final _logger = Logger('AppSettingsProvider'); + @Riverpod(keepAlive: true) class AppSettings extends _$AppSettings { @override @@ -24,12 +26,12 @@ class AppSettings extends _$AppSettings { // see if the settings are already in the box if (_box.isNotEmpty) { final foundSettings = _box.getAt(0); - debugPrint('found settings in box: $foundSettings'); + _logger.fine('found settings in box: $foundSettings'); return foundSettings; } else { // create a new settings object const settings = model.AppSettings(); - debugPrint('created new settings: $settings'); + _logger.fine('created new settings: $settings'); return settings; } } @@ -38,7 +40,7 @@ class AppSettings extends _$AppSettings { void writeToBox() { _box.clear(); _box.add(state); - debugPrint('wrote settings to box: $state'); + _logger.fine('wrote settings to box: $state'); } void toggleDarkMode() { diff --git a/lib/shared/extensions/duration_format.dart b/lib/shared/extensions/duration_format.dart index 98c9dbe..db6ed19 100644 --- a/lib/shared/extensions/duration_format.dart +++ b/lib/shared/extensions/duration_format.dart @@ -9,3 +9,10 @@ extension DurationFormat on Duration { return '${hours}h ${minutes}m'; } } + +extension OnlyTime on DateTime { + // in format HH:MM:ss + // padding with 0 + String get time => + '${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}:${second.toString().padLeft(2, '0')}'; +} diff --git a/lib/theme/theme_from_cover_provider.dart b/lib/theme/theme_from_cover_provider.dart index 49310bd..ebda654 100644 --- a/lib/theme/theme_from_cover_provider.dart +++ b/lib/theme/theme_from_cover_provider.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:whispering_pages/api/image_provider.dart'; part 'theme_from_cover_provider.g.dart'; +final _logger = Logger('ThemeFromCoverProvider'); + @Riverpod(keepAlive: true) Future> themeFromCover( ThemeFromCoverRef ref, @@ -15,7 +18,7 @@ Future> themeFromCover( // ! add deliberate delay to simulate a long running task as it interferes with other animations await Future.delayed(500.ms); - debugPrint('Generating color scheme from cover image'); + _logger.fine('Generating color scheme from cover image'); return ColorScheme.fromImageProvider( provider: img, brightness: brightness, @@ -26,7 +29,7 @@ Future> themeFromCover( // RootIsolateToken? token = RootIsolateToken.instance; // final scheme = await Isolate.run( // () async { - // debugPrint('Isolate running ${Isolate.current.debugName}'); + // _logger.fine('Isolate running ${Isolate.current.debugName}'); // try { // BackgroundIsolateBinaryMessenger.ensureInitialized(token!); // WidgetsFlutterBinding.ensureInitialized(); @@ -35,7 +38,7 @@ Future> themeFromCover( // brightness: brightness, // ); // } catch (e) { - // debugPrint('Error in isolate: $e'); + // _logger.fine('Error in isolate: $e'); // return null; // } // }, @@ -60,9 +63,9 @@ FutureOr themeOfLibraryItem( return val; // coverImage.when( // data: (value) async { - // debugPrint('CoverImage: $value'); + // _logger.fine('CoverImage: $value'); // final val = ref.watch(themeFromCoverProvider(MemoryImage(value))); - // debugPrint('ColorScheme generated: $val'); + // _logger.fine('ColorScheme generated: $val'); // ref.invalidateSelf(); // return val; // }, diff --git a/pubspec.lock b/pubspec.lock index a83d68a..de3c7ef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.7" + background_downloader: + dependency: "direct main" + description: + name: background_downloader + sha256: "9504093db43da6095c44dd14fc816f3ee8961633ace12340f5d3c4fbfd346e2d" + url: "https://pub.dev" + source: hosted + version: "8.5.2" boolean_selector: dependency: transitive description: @@ -157,18 +165,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "1414d6d733a85d8ad2f1dfcb3ea7945759e35a123cb99ccfac75d0758f75edfa" + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.4.11" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.1" built_collection: dependency: transitive description: @@ -397,10 +405,10 @@ packages: dependency: transitive description: name: file_picker - sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a" + sha256: "2ca051989f69d1b2ca012b2cf3ccf78c70d40144f0861ff2c063493f7c8c3d45" url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.0.5" fixnum: dependency: transitive description: @@ -548,10 +556,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: abec47eb8c8c36ebf41d0a4c64dbbe7f956e39a012b3aafc530e951bdc12fe3f + sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554 url: "https://pub.dev" source: hosted - version: "14.1.4" + version: "14.2.0" graphs: dependency: transitive description: @@ -761,7 +769,7 @@ packages: source: hosted version: "0.0.3" logging: - dependency: transitive + dependency: "direct main" description: name: logging sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" @@ -899,10 +907,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514" + sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a url: "https://pub.dev" source: hosted - version: "2.2.5" + version: "2.2.6" path_provider_foundation: dependency: transitive description: @@ -947,10 +955,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -1263,10 +1271,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.3.0" url_launcher_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 418468f..fbcdf0e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: audio_session: ^0.1.19 audio_video_progress_bar: ^2.0.2 auto_scroll_text: ^0.0.7 + background_downloader: ^8.5.2 cached_network_image: ^3.3.1 coast: ^2.0.2 collection: ^1.18.0 @@ -61,6 +62,7 @@ dependencies: just_audio_background: ^0.0.1-beta.11 just_audio_media_kit: ^2.0.4 list_wheel_scroll_view_nls: ^0.0.3 + logging: ^1.2.0 lottie: ^3.1.0 media_kit_libs_linux: any media_kit_libs_windows_audio: any