mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-27 11:43:47 +01:00 
			
		
		
		
	APK Service is ready
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -42,3 +42,6 @@ app.*.map.json | |||||||
| /android/app/debug | /android/app/debug | ||||||
| /android/app/profile | /android/app/profile | ||||||
| /android/app/release | /android/app/release | ||||||
|  |  | ||||||
|  | # Custom | ||||||
|  | TODO.txt | ||||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,16 +1,11 @@ | |||||||
| # obtainium | # Obtainium | ||||||
|  |  | ||||||
| A new Flutter project. | Get Android App Updates Directly From the Source. | ||||||
|  |  | ||||||
| ## Getting Started | Obtainium allows you to install and update Open-Source Apps directly from their GitHub or GitLab releases. | ||||||
|  |  | ||||||
| This project is a starting point for a Flutter application. | ***Work In Progress - Currently Unusable*** | ||||||
|  |  | ||||||
| A few resources to get you started if this is your first Flutter project: | ## Limitations | ||||||
|  | - App installs are assumed to have succeeded; failures and cancelled installs cannot be detected. | ||||||
| - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) | - Apps that are already installed are not indicated as such, since GitHub and GitLab do not provide App IDs (like `org.example.app`) to allow for comparisons. | ||||||
| - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) |  | ||||||
|  |  | ||||||
| For help getting started with Flutter development, view the |  | ||||||
| [online documentation](https://docs.flutter.dev/), which offers tutorials, |  | ||||||
| samples, guidance on mobile development, and a full API reference. |  | ||||||
							
								
								
									
										32
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | ##---------------Begin: proguard configuration for Gson  ---------- | ||||||
|  | # Gson uses generic type information stored in a class file when working with fields. Proguard | ||||||
|  | # removes such information by default, so configure it to keep all of it. | ||||||
|  | -keepattributes Signature | ||||||
|  |  | ||||||
|  | # For using GSON @Expose annotation | ||||||
|  | -keepattributes *Annotation* | ||||||
|  |  | ||||||
|  | # Gson specific classes | ||||||
|  | -dontwarn sun.misc.** | ||||||
|  | #-keep class com.google.gson.stream.** { *; } | ||||||
|  |  | ||||||
|  | # Application classes that will be serialized/deserialized over Gson | ||||||
|  | -keep class com.google.gson.examples.android.model.** { <fields>; } | ||||||
|  |  | ||||||
|  | # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, | ||||||
|  | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) | ||||||
|  | -keep class * extends com.google.gson.TypeAdapter | ||||||
|  | -keep class * implements com.google.gson.TypeAdapterFactory | ||||||
|  | -keep class * implements com.google.gson.JsonSerializer | ||||||
|  | -keep class * implements com.google.gson.JsonDeserializer | ||||||
|  |  | ||||||
|  | # Prevent R8 from leaving Data object members always null | ||||||
|  | -keepclassmembers,allowobfuscation class * { | ||||||
|  |   @com.google.gson.annotations.SerializedName <fields>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. | ||||||
|  | -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken | ||||||
|  | -keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken | ||||||
|  |  | ||||||
|  | ##---------------End: proguard configuration for Gson  ---------- | ||||||
							
								
								
									
										
											BIN
										
									
								
								android/app/src/main/res/drawable/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								android/app/src/main/res/drawable/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 544 B | 
| @@ -1,72 +1,19 @@ | |||||||
| import 'dart:io'; |  | ||||||
| import 'dart:isolate'; |  | ||||||
| import 'dart:math'; |  | ||||||
| import 'dart:ui'; |  | ||||||
|  |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:file_picker/file_picker.dart'; | import 'package:obtainium/services/apk_service.dart'; | ||||||
| import 'package:install_plugin_v2/install_plugin_v2.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:path_provider/path_provider.dart'; |  | ||||||
| import 'package:permission_handler/permission_handler.dart'; |  | ||||||
| import 'package:flutter_downloader/flutter_downloader.dart'; |  | ||||||
| import 'package:app_installer/app_installer.dart'; |  | ||||||
|  |  | ||||||
| // Port for FlutterDownloader background/foreground communication |  | ||||||
| ReceivePort _port = ReceivePort(); |  | ||||||
|  |  | ||||||
| void main() async { | void main() async { | ||||||
|   await initializeDownloader(); |   ; | ||||||
|   runApp(const MyApp()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Setup the FlutterDownloader plugin |  | ||||||
| Future<void> initializeDownloader() async { |  | ||||||
|   // Make sure FlutterDownloader can be used |  | ||||||
|   WidgetsFlutterBinding.ensureInitialized(); |   WidgetsFlutterBinding.ensureInitialized(); | ||||||
|   await FlutterDownloader.initialize(); |   runApp(MultiProvider( | ||||||
|   // Set up the status update callback for FlutterDownloader |     providers: [ | ||||||
|   FlutterDownloader.registerCallback(downloadCallbackBackground); |       Provider( | ||||||
|   // The actual callback is in the background isolate |         create: (context) => APKService(), | ||||||
|   // So setup a port to pass the data to a foreground callback |         dispose: (context, apkInstallService) => apkInstallService.dispose(), | ||||||
|   IsolateNameServer.registerPortWithName( |       ), | ||||||
|       _port.sendPort, 'downloader_send_port'); |     ], | ||||||
|   _port.listen((dynamic data) { |     child: const MyApp(), | ||||||
|     String id = data[0]; |   )); | ||||||
|     DownloadTaskStatus status = data[1]; |  | ||||||
|     int progress = data[2]; |  | ||||||
|     downloadCallbackForeground(id, status, progress); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Callback that receives FlutterDownloader status and forwards to a foreground function |  | ||||||
| @pragma('vm:entry-point') |  | ||||||
| void downloadCallbackBackground( |  | ||||||
|     String id, DownloadTaskStatus status, int progress) { |  | ||||||
|   final SendPort? send = |  | ||||||
|       IsolateNameServer.lookupPortByName('downloader_send_port'); |  | ||||||
|   send!.send([id, status, progress]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Foreground function to act on FlutterDownloader status updates (install then delete downloaded APK) |  | ||||||
| void downloadCallbackForeground( |  | ||||||
|     String id, DownloadTaskStatus status, int progress) async { |  | ||||||
|   if (status == DownloadTaskStatus.complete) { |  | ||||||
|     FlutterDownloader.open(taskId: id); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Given a URL (assumed valid), initiate an APK download (will trigger install callback when complete) |  | ||||||
| void downloadAPK(String url, String appId) async { |  | ||||||
|   var apkDir = Directory( |  | ||||||
|       "${(await getExternalStorageDirectory())?.path as String}/$appId"); |  | ||||||
|   if (apkDir.existsSync()) apkDir.deleteSync(recursive: true); |  | ||||||
|   apkDir.createSync(); |  | ||||||
|   await FlutterDownloader.enqueue( |  | ||||||
|     url: url, |  | ||||||
|     savedDir: apkDir.path, |  | ||||||
|     showNotification: true, |  | ||||||
|     openFileFromNotification: true, |  | ||||||
|   ); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Extract a GitHub project name and author account name from a GitHub URL (can be any sub-URL of the project) | // Extract a GitHub project name and author account name from a GitHub URL (can be any sub-URL of the project) | ||||||
| @@ -83,14 +30,6 @@ Map<String, String>? getAppNamesFromGitHubURL(String url) { | |||||||
|   return null; |   return null; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Future<Directory> getAPKDir() async { |  | ||||||
| //   var apkDir = Directory("${(await getExternalStorageDirectory())!.path}/apks"); |  | ||||||
| //   if (!apkDir.existsSync()) { |  | ||||||
| //     apkDir.createSync(); |  | ||||||
| //   } |  | ||||||
| //   return apkDir; |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| class MyApp extends StatelessWidget { | class MyApp extends StatelessWidget { | ||||||
|   const MyApp({super.key}); |   const MyApp({super.key}); | ||||||
|  |  | ||||||
| @@ -144,7 +83,9 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           var names = getAppNamesFromGitHubURL(urls[ind]); |           var names = getAppNamesFromGitHubURL(urls[ind]); | ||||||
|           if (names != null) { |           if (names != null) { | ||||||
|             downloadAPK(urls[ind], "${names["author"]!}_${names["appName"]!}"); |             Provider.of<APKService>(context, listen: false) | ||||||
|  |                 .downloadAndInstallAPK( | ||||||
|  |                     urls[ind], "${names["author"]!}_${names["appName"]!}"); | ||||||
|             setState(() { |             setState(() { | ||||||
|               ind = ind == (urls.length - 1) ? 0 : ind + 1; |               ind = ind == (urls.length - 1) ? 0 : ind + 1; | ||||||
|             }); |             }); | ||||||
| @@ -158,8 +99,6 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void dispose() { |   void dispose() { | ||||||
|     // Remove the FlutterDownloader communication port |  | ||||||
|     IsolateNameServer.removePortNameMapping('downloader_send_port'); |  | ||||||
|     super.dispose(); |     super.dispose(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										112
									
								
								lib/services/apk_service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								lib/services/apk_service.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  | import 'dart:io'; | ||||||
|  | import 'dart:isolate'; | ||||||
|  | import 'dart:ui'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:file_picker/file_picker.dart'; | ||||||
|  | import 'package:install_plugin_v2/install_plugin_v2.dart'; | ||||||
|  | import 'package:path_provider/path_provider.dart'; | ||||||
|  | import 'package:permission_handler/permission_handler.dart'; | ||||||
|  | import 'package:flutter_downloader/flutter_downloader.dart'; | ||||||
|  | import 'package:app_installer/app_installer.dart'; | ||||||
|  | import 'package:flutter_fgbg/flutter_fgbg.dart'; | ||||||
|  | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; | ||||||
|  |  | ||||||
|  | class APKService { | ||||||
|  |   APKService() { | ||||||
|  |     initializeDownloader(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Notifications plugin for downloads | ||||||
|  |   FlutterLocalNotificationsPlugin downloaderNotifications = | ||||||
|  |       FlutterLocalNotificationsPlugin(); | ||||||
|  |  | ||||||
|  |   // Port for FlutterDownloader background/foreground communication | ||||||
|  |   ReceivePort _port = ReceivePort(); | ||||||
|  |  | ||||||
|  |   // Variables to keep track of the app foreground status (installs can't run in the background) | ||||||
|  |   bool isForeground = true; | ||||||
|  |   StreamSubscription<FGBGType>? foregroundSubscription; | ||||||
|  |  | ||||||
|  |   // Setup the FlutterDownloader plugin (call in main()) | ||||||
|  |   Future<void> initializeDownloader() async { | ||||||
|  |     // Make sure FlutterDownloader can be used | ||||||
|  |     await FlutterDownloader.initialize(); | ||||||
|  |     // Set up the status update callback for FlutterDownloader | ||||||
|  |     FlutterDownloader.registerCallback(downloadCallbackBackground); | ||||||
|  |     // The actual callback is in the background isolate | ||||||
|  |     // So setup a port to pass the data to a foreground callback | ||||||
|  |     IsolateNameServer.registerPortWithName( | ||||||
|  |         _port.sendPort, 'downloader_send_port'); | ||||||
|  |     _port.listen((dynamic data) { | ||||||
|  |       String id = data[0]; | ||||||
|  |       DownloadTaskStatus status = data[1]; | ||||||
|  |       int progress = data[2]; | ||||||
|  |       downloadCallbackForeground(id, status, progress); | ||||||
|  |     }); | ||||||
|  |     // Initialize the notifications service | ||||||
|  |     await downloaderNotifications.initialize(const InitializationSettings( | ||||||
|  |         android: AndroidInitializationSettings('ic_launcher'))); | ||||||
|  |     // Subscribe to changes in the app foreground status | ||||||
|  |     foregroundSubscription = FGBGEvents.stream.listen((event) async { | ||||||
|  |       isForeground = event == FGBGType.foreground; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Clean up after initializeDownloader() (call in dispose()) | ||||||
|  |   void dispose() { | ||||||
|  |     IsolateNameServer.removePortNameMapping('downloader_send_port'); | ||||||
|  |     foregroundSubscription?.cancel(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Callback that receives FlutterDownloader status and forwards to a foreground function | ||||||
|  |   @pragma('vm:entry-point') | ||||||
|  |   static void downloadCallbackBackground( | ||||||
|  |       String id, DownloadTaskStatus status, int progress) { | ||||||
|  |     final SendPort? send = | ||||||
|  |         IsolateNameServer.lookupPortByName('downloader_send_port'); | ||||||
|  |     send!.send([id, status, progress]); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Foreground function to act on FlutterDownloader status updates (install downloaded APK) | ||||||
|  |   void downloadCallbackForeground( | ||||||
|  |       String id, DownloadTaskStatus status, int progress) async { | ||||||
|  |     if (status == DownloadTaskStatus.complete) { | ||||||
|  |       // Wait for app to come to the foreground if not already, and notify the user | ||||||
|  |       while (!isForeground) { | ||||||
|  |         await downloaderNotifications.show( | ||||||
|  |             1, | ||||||
|  |             'Complete App Installation', | ||||||
|  |             'Obtainium must be open to install Apps', | ||||||
|  |             const NotificationDetails( | ||||||
|  |                 android: AndroidNotificationDetails( | ||||||
|  |                     'COMPLETE_INSTALL', 'Complete App Installation', | ||||||
|  |                     channelDescription: | ||||||
|  |                         'Ask the user to return to Obtanium to finish installing an App', | ||||||
|  |                     importance: Importance.max, | ||||||
|  |                     priority: Priority.max, | ||||||
|  |                     groupKey: 'dev.imranr.obtainium.COMPLETE_INSTALL'))); | ||||||
|  |         if (await FGBGEvents.stream.first == FGBGType.foreground) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       FlutterDownloader.open(taskId: id); | ||||||
|  |       downloaderNotifications.cancel(1); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Given a URL (assumed valid), initiate an APK download (will trigger install callback when complete) | ||||||
|  |   void downloadAndInstallAPK(String url, String appId) async { | ||||||
|  |     var apkDir = Directory( | ||||||
|  |         "${(await getExternalStorageDirectory())?.path as String}/$appId"); | ||||||
|  |     if (apkDir.existsSync()) apkDir.deleteSync(recursive: true); | ||||||
|  |     apkDir.createSync(); | ||||||
|  |     await FlutterDownloader.enqueue( | ||||||
|  |       url: url, | ||||||
|  |       savedDir: apkDir.path, | ||||||
|  |       showNotification: true, | ||||||
|  |       openFileFromNotification: false, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -8,6 +8,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.0" |     version: "1.1.0" | ||||||
|  |   args: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: args | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.3.1" | ||||||
|   async: |   async: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -50,6 +57,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.5" |     version: "1.0.5" | ||||||
|  |   dbus: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: dbus | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.7.7" | ||||||
|   fake_async: |   fake_async: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -90,6 +104,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.8.1" |     version: "1.8.1" | ||||||
|  |   flutter_fgbg: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: flutter_fgbg | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.2.0" | ||||||
|   flutter_lints: |   flutter_lints: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
| @@ -97,6 +118,27 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.0.1" |     version: "2.0.1" | ||||||
|  |   flutter_local_notifications: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: flutter_local_notifications | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "9.7.0" | ||||||
|  |   flutter_local_notifications_linux: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: flutter_local_notifications_linux | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.5.1" | ||||||
|  |   flutter_local_notifications_platform_interface: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: flutter_local_notifications_platform_interface | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "5.0.0" | ||||||
|   flutter_plugin_android_lifecycle: |   flutter_plugin_android_lifecycle: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -156,6 +198,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.8.0" |     version: "1.8.0" | ||||||
|  |   nested: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: nested | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.0.0" | ||||||
|   path: |   path: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -247,6 +296,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.1.0" |     version: "0.1.0" | ||||||
|  |   petitparser: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: petitparser | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "5.0.0" | ||||||
|   platform: |   platform: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -268,6 +324,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.2.4" |     version: "4.2.4" | ||||||
|  |   provider: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: provider | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "6.0.3" | ||||||
|   sky_engine: |   sky_engine: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: flutter |     description: flutter | ||||||
| @@ -315,6 +378,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.4.12" |     version: "0.4.12" | ||||||
|  |   timezone: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: timezone | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.8.0" | ||||||
|   vector_math: |   vector_math: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -336,6 +406,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.2.0+1" |     version: "0.2.0+1" | ||||||
|  |   xml: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: xml | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "6.1.0" | ||||||
| sdks: | sdks: | ||||||
|   dart: ">=2.19.0-79.0.dev <3.0.0" |   dart: ">=2.19.0-79.0.dev <3.0.0" | ||||||
|   flutter: ">=3.0.0" |   flutter: ">=3.0.0" | ||||||
|   | |||||||
| @@ -42,6 +42,9 @@ dependencies: | |||||||
|   path_provider: ^2.0.11 |   path_provider: ^2.0.11 | ||||||
|   flutter_downloader: ^1.8.1 |   flutter_downloader: ^1.8.1 | ||||||
|   app_installer: ^1.1.0 |   app_installer: ^1.1.0 | ||||||
|  |   flutter_fgbg: ^0.2.0 | ||||||
|  |   flutter_local_notifications: ^9.7.0 | ||||||
|  |   provider: ^6.0.3 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user