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