diff --git a/ios/Podfile b/ios/Podfile index fdcc671..3e44f9c 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d7c5d8b..df72cec 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -9,6 +9,8 @@ PODS: - Flutter - audio_session (0.0.1): - Flutter + - background_downloader (0.0.1): + - Flutter - Firebase/Auth (10.15.0): - Firebase/CoreOnly - FirebaseAuth (~> 10.15.0) @@ -83,6 +85,7 @@ PODS: DEPENDENCIES: - audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`) + - background_downloader (from `.symlinks/plugins/background_downloader/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - Flutter (from `Flutter`) @@ -114,6 +117,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/audio_service/ios" audio_session: :path: ".symlinks/plugins/audio_session/ios" + background_downloader: + :path: ".symlinks/plugins/background_downloader/ios" firebase_auth: :path: ".symlinks/plugins/firebase_auth/ios" firebase_core: @@ -137,6 +142,7 @@ SPEC CHECKSUMS: AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_session: 4f3e461722055d21515cf3261b64c973c062f345 + background_downloader: 6f55e5548875be2ad4bb91b542558b5be22f339a Firebase: 66043bd4579e5b73811f96829c694c7af8d67435 firebase_auth: b62e99e6ece589afe88ebe8919eb9563b52c384c firebase_core: 28e84c2a4fcf6a50ef83f47b145ded8c1fa331e4 @@ -159,6 +165,6 @@ SPEC CHECKSUMS: RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a -PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 +PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 COCOAPODS: 1.13.0 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 45309d5..4a568d2 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,71 +1,69 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - $(BUNDLE_DISPLAY_NAME) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(BUNDLE_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - $(LAUNCH_SCREEN) - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UIViewControllerBasedStatusBarAppearance - - UIBackgroundModes - - audio - - UIStatusBarHidden - - - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - - $(GOOGLE_REVERSED_CLIENT_ID) - - - - - + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + $(BUNDLE_DISPLAY_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(BUNDLE_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + $(GOOGLE_REVERSED_CLIENT_ID) + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + audio + fetch + + UILaunchStoryboardName + $(LAUNCH_SCREEN) + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIViewControllerBasedStatusBarAppearance + + diff --git a/lib/app.dart b/lib/app.dart index 0582a8b..af5abb0 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -5,6 +5,7 @@ import 'package:varanasi_mobile_app/utils/router.dart'; import 'package:varanasi_mobile_app/widgets/responsive_sizer.dart'; import 'cubits/config/config_cubit.dart'; +import 'cubits/download/download_cubit.dart'; import 'cubits/player/player_cubit.dart'; import 'utils/theme.dart'; @@ -19,14 +20,15 @@ class Varanasi extends StatelessWidget { builder: (context, orientation, screenType) { return MultiBlocProvider( providers: [ + BlocProvider(lazy: false, create: (_) => DownloadCubit()..init()), BlocProvider( lazy: false, create: (context) => ConfigCubit()..init(), ), BlocProvider( lazy: false, - create: (context) => - MediaPlayerCubit(() => context.read())..init(), + create: (ctx) => + MediaPlayerCubit(() => ctx.read())..init(), ), ], child: Builder(builder: (context) { diff --git a/lib/cubits/download/download_cubit.dart b/lib/cubits/download/download_cubit.dart new file mode 100644 index 0000000..291b1cc --- /dev/null +++ b/lib/cubits/download/download_cubit.dart @@ -0,0 +1,157 @@ +import 'dart:async'; + +import 'package:background_downloader/background_downloader.dart'; +import 'package:equatable/equatable.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:varanasi_mobile_app/models/download.dart'; +import 'package:varanasi_mobile_app/models/media_playlist.dart'; +import 'package:varanasi_mobile_app/models/playable_item.dart'; +import 'package:varanasi_mobile_app/models/song.dart'; +import 'package:varanasi_mobile_app/utils/app_cubit.dart'; +import 'package:varanasi_mobile_app/utils/constants/strings.dart'; +import 'package:varanasi_mobile_app/utils/logger.dart'; + +part 'download_state.dart'; + +class DownloadCubit extends AppCubit { + DownloadCubit() : super(const DownloadState()); + + late final Box _downloadBox; + late final FileDownloader _downloader; + + late final Map _expando; + + Logger get _logger => Logger.instance; + + @override + FutureOr init() async { + _expando = {}; + _downloadBox = + await Hive.openBox(AppStrings.downloadBoxName); + _downloader = FileDownloader(); + _downloader.updates.listen((update) { + if (update is TaskStatusUpdate) { + _handleTaskStatusUpdate(update); + } else if (update is TaskProgressUpdate) { + _handleTaskProgressUpdate(update); + } + }); + } + + void _handleTaskProgressUpdate(TaskProgressUpdate update) { + final item = _downloadBox.get(update.task.taskId); + if (item != null) { + _downloadBox.put( + update.task.taskId, + item.copyWith(progress: update.progress), + ); + _logger.i('Dl pro for ${item.id}: ${item.progress}'); + } + } + + void _handleTaskStatusUpdate(TaskStatusUpdate update) async { + if (update.status == TaskStatus.enqueued) { + _logger.i('Download enqueued for ${update.task.taskId}'); + _downloadBox.put( + update.task.taskId, + DownloadedMedia( + id: update.task.taskId, + progress: 0, + downloadComplete: false, + media: _expando[update.task.taskId]!, + path: '', + ), + ); + return; + } + final item = _downloadBox.get(update.task.taskId); + if (update.status.isNotFinalState && item != null) { + _downloadBox.put( + item.id, + item.copyWith(downloading: update.status == TaskStatus.running), + ); + _logger.i('Download status for ${update.task.taskId}: ' + '${update.status}'); + return; + } + + if (item != null) { + if (update.status == TaskStatus.complete) { + final path = await update.task.filePath(); + _downloadBox.put( + item.id, + item.copyWith( + downloadComplete: true, + progress: 1, + path: path, + downloading: false, + ), + ); + _logger.i('Download complete for ${item.id} path: $path'); + } else if (update.status.isFinalState) { + _downloadBox.delete(item.id); + _logger.i('Download failed for ${item.id}'); + } + } + } + + String _fileNameFromSong(Song song) { + final ext = song.itemUrl.split('.').last; + return '${song.itemId}.$ext'; + } + + DownloadTask _songToTask(Song song) { + final fileName = _fileNameFromSong(song); + return DownloadTask( + taskId: song.itemId, + url: song.itemUrl, + filename: fileName, + updates: Updates.statusAndProgress, + ); + } + + Future downloadSong(PlayableMedia song) async { + assert(song is Song, 'Only songs can be downloaded'); + if (song is! Song) return; + _expando[song.itemId] = song; + final queued = await _downloader.enqueue(_songToTask(song)); + _logger.i('Queued ${song.itemId} status: $queued'); + } + + Future batchDownload(MediaPlaylist playlist) async { + final songs = playlist.mediaItems ?? []; + final filteredsong = songs.whereType(); + final tasks = filteredsong.map(_songToTask).toList(); + for (final song in filteredsong) { + _expando[song.itemId] = song; + } + await _downloader.downloadBatch( + tasks, + taskStatusCallback: _handleTaskStatusUpdate, + taskProgressCallback: _handleTaskProgressUpdate, + ); + } + + Future cancelDownload(PlayableMedia media) => + _downloader.cancelTaskWithId(media.itemId); + + /// Returns a stream of [DownloadedMedia] for the given [song]. + /// + /// The stream will emit the current download status of the song and + /// + /// will continue to emit updates until the download is complete or + Stream listen(PlayableMedia song) { + final curr = _downloadBox.get(song.itemId); + return Rx.concat([ + if (curr != null) Stream.value(curr), + _downloadBox.watch(key: song.itemId).map((e) => e.value) + ]); + } + + DownloadedMedia? getDownloadedMedia(PlayableMedia song) => + _downloadBox.get(song.itemId); + + Future deleteDownloadedMedia(PlayableMedia song) => + _downloadBox.delete(song.itemId); +} diff --git a/lib/cubits/download/download_state.dart b/lib/cubits/download/download_state.dart new file mode 100644 index 0000000..d959b6b --- /dev/null +++ b/lib/cubits/download/download_state.dart @@ -0,0 +1,8 @@ +part of 'download_cubit.dart'; + +class DownloadState extends Equatable { + const DownloadState(); + + @override + List get props => []; +} diff --git a/lib/features/home/bloc/home_bloc.dart b/lib/features/home/bloc/home_bloc.dart index 605f29d..2b75e07 100644 --- a/lib/features/home/bloc/home_bloc.dart +++ b/lib/features/home/bloc/home_bloc.dart @@ -12,7 +12,7 @@ part 'home_state.dart'; class HomeBloc extends Cubit { HomeBloc() : super(const HomeInitialState()); - FutureOr fetchModule({bool refetch = true}) async { + FutureOr fetchModule({bool refetch = false}) async { try { emit(const HomeLoadingState()); final modules = diff --git a/lib/features/library/cubit/library_state.dart b/lib/features/library/cubit/library_state.dart index d0ae2df..fddfa6b 100644 --- a/lib/features/library/cubit/library_state.dart +++ b/lib/features/library/cubit/library_state.dart @@ -112,7 +112,7 @@ class LibraryError extends LibraryState { this.error, { this.stackTrace, }) { - Logger.instance.e(error.toString(), error, stackTrace); + Logger.instance.e(error.toString(), error: error, stackTrace: stackTrace); } @override diff --git a/lib/main_development.dart b/lib/main_development.dart index 08e7a6e..818ec5d 100644 --- a/lib/main_development.dart +++ b/lib/main_development.dart @@ -1,11 +1,13 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:varanasi_mobile_app/firebase_options_dev.dart'; import 'flavors.dart'; import 'main.dart' as runner; +import 'utils/bloc_observer.dart'; Future main() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); @@ -13,5 +15,6 @@ Future main() async { F.appFlavor = Flavor.development; await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await FirebaseAuth.instance.useAuthEmulator('localhost', 9000); + Bloc.observer = AppBlocObserver(); await runner.main(); } diff --git a/lib/models/adapters.dart b/lib/models/adapters.dart index 7d6189c..b2a5a4a 100644 --- a/lib/models/adapters.dart +++ b/lib/models/adapters.dart @@ -1,4 +1,5 @@ import 'package:hive/hive.dart'; +import 'package:varanasi_mobile_app/models/download.dart'; import 'package:varanasi_mobile_app/models/sort_type.dart'; import 'album.dart'; @@ -21,6 +22,7 @@ void registerCommonTypeAdapters() { Hive.registerAdapter(PlaylistAdapter()); Hive.registerAdapter(PrimaryArtistAdapter()); Hive.registerAdapter(SongAdapter()); + Hive.registerAdapter(DownloadedMediaAdapter()); Hive.registerAdapter(DownloadUrlAdapter()); Hive.registerAdapter(MediaPlaylistAdapter()); } diff --git a/lib/models/download.dart b/lib/models/download.dart new file mode 100644 index 0000000..e07517c --- /dev/null +++ b/lib/models/download.dart @@ -0,0 +1,56 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:equatable/equatable.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:varanasi_mobile_app/models/song.dart'; + +part 'download.g.dart'; + +@HiveType(typeId: 17) +final class DownloadedMedia extends Equatable { + @HiveField(0) + final String id; + @HiveField(1) + final Song media; + @HiveField(2) + final String path; + @HiveField(3) + final double progress; + @HiveField(4) + final bool downloadComplete; + @HiveField(5) + final bool downloading; + + const DownloadedMedia({ + required this.id, + required this.media, + required this.path, + this.progress = 0, + this.downloadComplete = false, + this.downloading = false, + }); + + @override + List get props => + [media, progress, path, downloadComplete, id, downloading]; + + @override + bool get stringify => true; + + DownloadedMedia copyWith({ + String? id, + Song? media, + String? path, + double? progress, + bool? downloadComplete, + bool? downloading, + }) { + return DownloadedMedia( + id: id ?? this.id, + media: media ?? this.media, + path: path ?? this.path, + progress: progress ?? this.progress, + downloadComplete: downloadComplete ?? this.downloadComplete, + downloading: downloading ?? this.downloading, + ); + } +} diff --git a/lib/models/download.g.dart b/lib/models/download.g.dart new file mode 100644 index 0000000..478fcfc --- /dev/null +++ b/lib/models/download.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'download.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class DownloadedMediaAdapter extends TypeAdapter { + @override + final int typeId = 17; + + @override + DownloadedMedia read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return DownloadedMedia( + id: fields[0] as String, + media: fields[1] as Song, + path: fields[2] as String, + progress: fields[3] as double, + downloadComplete: fields[4] as bool, + downloading: fields[5] as bool, + ); + } + + @override + void write(BinaryWriter writer, DownloadedMedia obj) { + writer + ..writeByte(6) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.media) + ..writeByte(2) + ..write(obj.path) + ..writeByte(3) + ..write(obj.progress) + ..writeByte(4) + ..write(obj.downloadComplete) + ..writeByte(5) + ..write(obj.downloading); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DownloadedMediaAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/utils/app_snackbar.dart b/lib/utils/app_snackbar.dart new file mode 100644 index 0000000..c8d64ed --- /dev/null +++ b/lib/utils/app_snackbar.dart @@ -0,0 +1,24 @@ +import 'package:another_flushbar/flushbar.dart'; +import 'package:flutter/material.dart'; +import 'package:varanasi_mobile_app/utils/helpers/get_app_context.dart'; + +class AppSnackbar { + static Flushbar _createFlushBar(String message) => Flushbar( + message: message, + duration: const Duration(seconds: 3), + animationDuration: const Duration(milliseconds: 500), + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + borderRadius: BorderRadius.circular(8), + backgroundColor: Colors.white, + messageColor: Colors.black, + flushbarStyle: FlushbarStyle.FLOATING, + ); + + /// Shows a [Flushbar] with the given [message] and [context]. + /// + /// If [context] is not provided, [appContext] is used. + static void show(String message, [BuildContext? context]) { + context ??= appContext; + _createFlushBar(message).show(context); + } +} diff --git a/lib/utils/bloc_observer.dart b/lib/utils/bloc_observer.dart new file mode 100644 index 0000000..ebcfa6d --- /dev/null +++ b/lib/utils/bloc_observer.dart @@ -0,0 +1,12 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:varanasi_mobile_app/utils/logger.dart'; + +class AppBlocObserver extends BlocObserver { + Logger get _logger => Logger.instance; + + @override + void onCreate(BlocBase bloc) { + super.onCreate(bloc); + _logger.i('onCreate -- ${bloc.runtimeType}'); + } +} diff --git a/lib/utils/constants/strings.dart b/lib/utils/constants/strings.dart index 04a6dc4..39aa1df 100644 --- a/lib/utils/constants/strings.dart +++ b/lib/utils/constants/strings.dart @@ -2,6 +2,8 @@ class AppStrings { static const String appName = 'Project Varanasi'; static const String configBoxName = 'config'; static const String commonCacheBoxName = 'project_varanasi_cache_box'; + static const String downloadBoxName = 'download_box'; + static const String downloadRecordsBoxName = 'download_records_box'; static const String currentPlaylistCacheKey = 'current_playlist'; static const String currentPlaylistIndexCacheKey = 'current_playlist_index'; static const String currentPlaylistPositionKey = 'current_playlist_position'; diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index 81a5dc4..d320e7f 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -1,15 +1,10 @@ +import 'package:logger/logger.dart' hide Logger; import 'package:logger/logger.dart' as logger; +final printer = SimplePrinter(colors: false); + class Logger extends logger.Logger { - Logger._() - : super( - printer: logger.PrettyPrinter( - methodCount: 0, - errorMethodCount: 8, - printTime: true, - colors: true, - ), - ); + Logger._() : super(printer: printer); static Logger get instance => Logger._(); } diff --git a/lib/utils/services/http_services.dart b/lib/utils/services/http_services.dart index 15b82f6..80c0484 100644 --- a/lib/utils/services/http_services.dart +++ b/lib/utils/services/http_services.dart @@ -56,7 +56,11 @@ class HttpService { statusCode: e.response?.statusCode ?? 500, ); } on Exception catch (e, stackTrace) { - Logger.instance.e('Error while fetching data from $url', e, stackTrace); + Logger.instance.e( + 'Error while fetching data from $url', + error: e, + stackTrace: stackTrace, + ); return (null, null); } } diff --git a/lib/widgets/download_button.dart b/lib/widgets/download_button.dart new file mode 100644 index 0000000..5bc9224 --- /dev/null +++ b/lib/widgets/download_button.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:varanasi_mobile_app/cubits/download/download_cubit.dart'; +import 'package:varanasi_mobile_app/models/download.dart'; +import 'package:varanasi_mobile_app/models/playable_item.dart'; +import 'package:varanasi_mobile_app/utils/extensions/extensions.dart'; + +class DownloadButton extends StatelessWidget { + const DownloadButton(this.song, {super.key}); + + final PlayableMedia song; + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: context.select((DownloadCubit value) => value.listen(song)), + builder: (context, snapshot) { + final data = snapshot.data; + final downloaded = data?.downloadComplete ?? false; + final downloading = data?.downloading ?? false; + + return IconButton( + onPressed: () { + final cubit = context.read(); + if (downloaded) { + cubit.deleteDownloadedMedia(song); + } else if (downloading) { + cubit.cancelDownload(song); + } else { + cubit.downloadSong(song); + } + }, + icon: Icon( + downloading + ? Icons.downloading_rounded + : downloaded + ? Icons.delete_outline_rounded + : Icons.download_outlined, + ), + color: context.colorScheme.onBackground, + ); + }); + } +} diff --git a/lib/widgets/media_tile.dart b/lib/widgets/media_tile.dart index 20ea5a4..ff1c346 100644 --- a/lib/widgets/media_tile.dart +++ b/lib/widgets/media_tile.dart @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:varanasi_mobile_app/models/playable_item.dart'; import 'package:varanasi_mobile_app/utils/extensions/extensions.dart'; +import 'package:varanasi_mobile_app/widgets/download_button.dart'; import 'package:varanasi_mobile_app/widgets/music_visualizer.dart'; class MediaTile extends StatelessWidget { @@ -407,13 +408,26 @@ class MediaTile extends StatelessWidget { return VisualDensity.standard; } + Widget _buildTrailing() { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + DownloadButton(media), + // IconButton( + // onPressed: () {}, + // icon: const Icon(Icons.more_horiz), + // ), + ], + ); + } + @override Widget build(BuildContext context) { return ListTile( leading: _buildLeading(), title: _buildTitle(), subtitle: _buildSubtitle(), - trailing: trailing, + trailing: _buildTrailing(), isThreeLine: isThreeLine, dense: dense, visualDensity: _visualDensity, diff --git a/pubspec.lock b/pubspec.lock index b3fa7cd..05b0a59 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + background_downloader: + dependency: "direct main" + description: + name: background_downloader + sha256: "585b7e5cb183f2c02b64554a63703103e236f43f4ff0d730f34dbbaafd7ea076" + url: "https://pub.dev" + source: hosted + version: "7.10.1" bloc: dependency: "direct main" description: @@ -780,10 +788,10 @@ packages: dependency: "direct main" description: name: logger - sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "2.0.2+1" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f098b08..7b7cfce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: flutter_bloc: ^8.1.3 json_annotation: ^4.8.1 dio: ^5.3.0 - logger: ^1.4.0 + logger: ^2.0.2+1 hive: ^2.2.3 hive_flutter: ^1.1.0 hive_generator: ^2.0.1 @@ -72,6 +72,7 @@ dependencies: firebase_auth: ^4.10.1 google_sign_in: ^6.1.5 flutter_native_splash: ^2.3.3 + background_downloader: ^7.10.1 dev_dependencies: flutter_test: