feat : apk installer
This commit is contained in:
@@ -7,6 +7,14 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="28" />
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@@ -19,8 +27,8 @@
|
|||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|||||||
294
android/app/src/main/kotlin/ir/mnpc/rasadyar/ApkInstaller.kt
Normal file
294
android/app/src/main/kotlin/ir/mnpc/rasadyar/ApkInstaller.kt
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
package ir.mnpc.rasadyar
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import java.io.File
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
|
class ApkInstaller(private val context: Context) {
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val INSTALL_REQUEST_CODE = 1001
|
||||||
|
private const val TAG = "ApkInstaller"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install APK with compatibility for Android 5-15
|
||||||
|
*/
|
||||||
|
fun installApk(apkFile: File) {
|
||||||
|
when {
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
|
||||||
|
// Android 8+ (API 26+) - Use PackageInstaller API
|
||||||
|
installWithPackageInstaller(apkFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
|
||||||
|
// Android 7+ (API 24+) - Use FileProvider with Intent
|
||||||
|
installWithFileProvider(apkFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
// Android 5-6 (API 21-23) - Use file URI with Intent
|
||||||
|
installWithFileUri(apkFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android 8+ (API 26+) - PackageInstaller API
|
||||||
|
*/
|
||||||
|
private fun installWithPackageInstaller(apkFile: File) {
|
||||||
|
try {
|
||||||
|
val packageInstaller = context.packageManager.packageInstaller
|
||||||
|
val params = PackageInstaller.SessionParams(
|
||||||
|
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set installer package name for better tracking
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
params.setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sessionId = packageInstaller.createSession(params)
|
||||||
|
val session = packageInstaller.openSession(sessionId)
|
||||||
|
|
||||||
|
session.use { activeSession ->
|
||||||
|
apkFile.inputStream().use { inputStream ->
|
||||||
|
activeSession.openWrite("package", 0, apkFile.length()).use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
activeSession.fsync(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(context, InstallResultReceiver::class.java).apply {
|
||||||
|
action = "com.yourpackage.INSTALL_RESULT"
|
||||||
|
}
|
||||||
|
|
||||||
|
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, 0, intent, flags
|
||||||
|
)
|
||||||
|
|
||||||
|
activeSession.commit(pendingIntent.intentSender)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error installing with PackageInstaller", e)
|
||||||
|
// Fallback to intent method
|
||||||
|
installWithFileProvider(apkFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android 7+ (API 24+) - FileProvider with Intent
|
||||||
|
*/
|
||||||
|
private fun installWithFileProvider(apkFile: File) {
|
||||||
|
try {
|
||||||
|
val apkUri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
"${context.packageName}.fileprovider",
|
||||||
|
apkFile
|
||||||
|
)
|
||||||
|
|
||||||
|
val intent = createInstallIntent(apkUri).apply {
|
||||||
|
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
|
||||||
|
// Additional flags for better compatibility
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||||
|
putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context is Activity) {
|
||||||
|
context.startActivityForResult(intent, INSTALL_REQUEST_CODE)
|
||||||
|
} else {
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error installing with FileProvider", e)
|
||||||
|
// Final fallback for Android 7+
|
||||||
|
installWithFileUri(apkFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android 5-6 (API 21-23) - File URI with Intent
|
||||||
|
*/
|
||||||
|
private fun installWithFileUri(apkFile: File) {
|
||||||
|
try {
|
||||||
|
val apkUri = Uri.fromFile(apkFile)
|
||||||
|
val intent = createInstallIntent(apkUri).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context is Activity) {
|
||||||
|
context.startActivityForResult(intent, INSTALL_REQUEST_CODE)
|
||||||
|
} else {
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error installing with file URI", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create appropriate install intent based on Android version
|
||||||
|
*/
|
||||||
|
private fun createInstallIntent(uri: Uri): Intent {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
|
||||||
|
data = uri
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
setDataAndType(uri, "application/vnd.android.package-archive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if installation from unknown sources is allowed
|
||||||
|
*/
|
||||||
|
fun canInstallPackages(): Boolean {
|
||||||
|
return when {
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
|
||||||
|
context.packageManager.canRequestPackageInstalls()
|
||||||
|
}
|
||||||
|
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 -> {
|
||||||
|
try {
|
||||||
|
Settings.Secure.getInt(
|
||||||
|
context.contentResolver,
|
||||||
|
Settings.Secure.INSTALL_NON_MARKET_APPS
|
||||||
|
) == 1
|
||||||
|
} catch (e: Settings.SettingNotFoundException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
// For older versions, assume it's allowed
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request permission to install packages
|
||||||
|
*/
|
||||||
|
fun requestInstallPermission() {
|
||||||
|
when {
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
|
||||||
|
if (!context.packageManager.canRequestPackageInstalls()) {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
|
||||||
|
data = "package:${context.packageName}".toUri()
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 -> {
|
||||||
|
val intent = Intent(Settings.ACTION_SECURITY_SETTINGS).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if APK file is valid
|
||||||
|
*/
|
||||||
|
fun isValidApkFile(apkFile: File): Boolean {
|
||||||
|
if (!apkFile.exists() || !apkFile.canRead()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val packageInfo = context.packageManager.getPackageArchiveInfo(
|
||||||
|
apkFile.absolutePath,
|
||||||
|
PackageManager.GET_ACTIVITIES
|
||||||
|
)
|
||||||
|
packageInfo != null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error validating APK file", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstallResultReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
|
||||||
|
PackageInstaller.STATUS_SUCCESS -> {
|
||||||
|
Log.d("InstallResult", "Installation successful")
|
||||||
|
// Handle successful installation
|
||||||
|
Toast.makeText(context, "Installation successful", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstaller.STATUS_FAILURE -> {
|
||||||
|
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||||
|
Log.e("InstallResult", "Installation failed: $message")
|
||||||
|
Toast.makeText(context, "Installation failed: $message", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstaller.STATUS_FAILURE_BLOCKED -> {
|
||||||
|
Log.e("InstallResult", "Installation blocked")
|
||||||
|
Toast.makeText(context, "Installation blocked by system", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||||
|
Log.e("InstallResult", "Installation aborted")
|
||||||
|
Toast.makeText(context, "Installation was cancelled", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstaller.STATUS_FAILURE_INVALID -> {
|
||||||
|
Log.e("InstallResult", "Invalid APK")
|
||||||
|
Toast.makeText(context, "Invalid APK file", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstaller.STATUS_FAILURE_CONFLICT -> {
|
||||||
|
Log.e("InstallResult", "Installation conflict")
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Installation conflict with existing app",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstaller.STATUS_FAILURE_STORAGE -> {
|
||||||
|
Log.e("InstallResult", "Insufficient storage")
|
||||||
|
Toast.makeText(context, "Insufficient storage space", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> {
|
||||||
|
Log.e("InstallResult", "Incompatible app")
|
||||||
|
Toast.makeText(context, "App is incompatible with device", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Log.w("InstallResult", "Unknown status: $status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,16 +3,22 @@ package ir.mnpc.rasadyar
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
private val CHANNEL = "apk_installer"
|
private val CHANNEL = "apk_installer"
|
||||||
|
private val INSTALL_PACKAGES_REQUEST_CODE = 1001
|
||||||
|
val installer = ApkInstaller(this)
|
||||||
|
private val TAG = "cj"
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
@@ -21,9 +27,18 @@ class MainActivity : FlutterActivity() {
|
|||||||
flutterEngine.dartExecutor.binaryMessenger,
|
flutterEngine.dartExecutor.binaryMessenger,
|
||||||
CHANNEL
|
CHANNEL
|
||||||
).setMethodCallHandler { call, result ->
|
).setMethodCallHandler { call, result ->
|
||||||
if (call.method == "installApk") {
|
if (call.method == "apk_installer") {
|
||||||
val apkPath = call.argument<String>("appPath") ?: ""
|
val apkPath = call.argument<String>("appPath") ?: ""
|
||||||
installApk(apkPath)
|
Log.i(TAG, "configureFlutterEngine: $apkPath")
|
||||||
|
val apkFile = File(getExternalFilesDir(null), apkPath)
|
||||||
|
Log.i(TAG, "apkFile: $apkFile")
|
||||||
|
Log.i(TAG, "externalStorageDirectory: ${getExternalFilesDir(null)}")
|
||||||
|
/*
|
||||||
|
if (!installer.canInstallPackages()) {
|
||||||
|
installer.requestInstallPermission()
|
||||||
|
} else {
|
||||||
|
installer.installApk(apkFile)
|
||||||
|
}*/
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,24 +49,51 @@ class MainActivity : FlutterActivity() {
|
|||||||
|
|
||||||
private fun installApk(path: String) {
|
private fun installApk(path: String) {
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
if (!file.exists()) {
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
Log.e("jojo", "APK file does not exist: $path")
|
||||||
val apkUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
return
|
||||||
val uri = FileProvider.getUriForFile(
|
|
||||||
applicationContext,
|
|
||||||
"${applicationContext.packageName}.fileprovider",
|
|
||||||
file
|
|
||||||
)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
uri
|
|
||||||
} else {
|
|
||||||
Uri.fromFile(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
// Check if we can install unknown apps (Android 8.0+)
|
||||||
applicationContext.startActivity(intent)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
if (!packageManager.canRequestPackageInstalls()) {
|
||||||
|
requestInstallPermission()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val apkUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
"${context.packageName}.fileprovider",
|
||||||
|
file
|
||||||
|
).also {
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Uri.fromFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||||
|
context.startActivity(intent)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("jojo", "installApk error: ${e.message}", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun requestInstallPermission() {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
|
||||||
|
data = "package:$packageName".toUri()
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, INSTALL_PACKAGES_REQUEST_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<external-path
|
|
||||||
name="external_files"
|
<files-path name="app_flutter" path="app_flutter/*" />
|
||||||
path="." />
|
|
||||||
</paths>
|
</paths>
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
|
|
||||||
import 'package:rasadyar_auth/auth.dart';
|
import 'package:rasadyar_auth/auth.dart';
|
||||||
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
|
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
|
||||||
import 'package:rasadyar_core/core.dart';
|
import 'package:rasadyar_core/core.dart';
|
||||||
|
|
||||||
final di = GetIt.instance;
|
final di = GetIt.instance;
|
||||||
|
|
||||||
Future<void> setupPreInjection() async{
|
Future<void> setupPreInjection() async {
|
||||||
await setupAllCoreProvider();
|
await setupAllCoreProvider();
|
||||||
await setupAuthDI();
|
await setupAuthDI();
|
||||||
|
di.registerSingleton<DioRemote>(
|
||||||
|
DioRemote(baseUrl: 'https://everestacademy.ir/'),
|
||||||
|
instanceName: 'baseRemote',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setupInjection() async{
|
Future<void> setupInjection() async {
|
||||||
|
|
||||||
await setupChickenDI();
|
await setupChickenDI();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/animation.dart';
|
import 'dart:io';
|
||||||
import 'package:rasadyar_app/presentation/routes/app_pages.dart';
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
|
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
|
||||||
import 'package:rasadyar_core/core.dart';
|
import 'package:rasadyar_core/core.dart';
|
||||||
|
|
||||||
@@ -8,6 +10,11 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
|
|||||||
late final AnimationController rotateController;
|
late final AnimationController rotateController;
|
||||||
Rxn<Animation<double>> scaleAnimation = Rxn();
|
Rxn<Animation<double>> scaleAnimation = Rxn();
|
||||||
Rxn<Animation<double>> rotationAnimation = Rxn();
|
Rxn<Animation<double>> rotationAnimation = Rxn();
|
||||||
|
RxBool hasUpdated = false.obs;
|
||||||
|
RxBool onUpdateDownload = false.obs;
|
||||||
|
RxDouble percent = 0.0.obs;
|
||||||
|
final RxnString _updateFilePath = RxnString();
|
||||||
|
final platform = MethodChannel('apk_installer');
|
||||||
|
|
||||||
var tokenService = Get.find<TokenStorageService>();
|
var tokenService = Get.find<TokenStorageService>();
|
||||||
|
|
||||||
@@ -24,15 +31,9 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
|
|||||||
duration: const Duration(milliseconds: 8000),
|
duration: const Duration(milliseconds: 8000),
|
||||||
);
|
);
|
||||||
|
|
||||||
scaleAnimation.value = Tween<double>(
|
scaleAnimation.value = Tween<double>(begin: 0.8, end: 1.2).animate(scaleController);
|
||||||
begin: 0.8,
|
|
||||||
end: 1.2,
|
|
||||||
).animate(scaleController);
|
|
||||||
|
|
||||||
rotationAnimation.value = Tween<double>(
|
rotationAnimation.value = Tween<double>(begin: 0.0, end: 1).animate(rotateController);
|
||||||
begin: 0.0,
|
|
||||||
end: 1,
|
|
||||||
).animate(rotateController);
|
|
||||||
|
|
||||||
rotateController.forward();
|
rotateController.forward();
|
||||||
rotateController.addStatusListener((status) {
|
rotateController.addStatusListener((status) {
|
||||||
@@ -51,21 +52,171 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
|
|||||||
scaleController.forward();
|
scaleController.forward();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hasUpdated.listen((data) {
|
||||||
|
if (data) {
|
||||||
|
requiredUpdateDialog(
|
||||||
|
onConfirm: () async {
|
||||||
|
await fileDownload();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (!data && Get.isDialogOpen == true) {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onUpdateDownload.listen((data) {
|
||||||
|
hasUpdated.value = false;
|
||||||
|
if (data) {
|
||||||
|
Get.bottomSheet(
|
||||||
|
isDismissible: false,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
Container(
|
||||||
|
height: 170,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(16.0)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
const Text('در حال دانلود بروزرسانی برنامه...'),
|
||||||
|
Obx(
|
||||||
|
() => Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: percent.value,
|
||||||
|
color: AppColor.greenNormal,
|
||||||
|
minHeight: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 55.w,
|
||||||
|
child: Text(
|
||||||
|
'${(percent.value * 100).toStringAsFixed(2)}%',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ObxValue((data) {
|
||||||
|
return RElevated(
|
||||||
|
backgroundColor: AppColor.greenNormal,
|
||||||
|
height: 40.h,
|
||||||
|
onPressed: data.value != null
|
||||||
|
? () {
|
||||||
|
installApk();
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
text: 'نصب',
|
||||||
|
);
|
||||||
|
}, _updateFilePath),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ROutlinedElevated(
|
||||||
|
borderColor: AppColor.error,
|
||||||
|
text: 'خروج',
|
||||||
|
onPressed: () async {
|
||||||
|
exit(0);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onReady() {
|
void onReady() {
|
||||||
super.onReady();
|
super.onReady();
|
||||||
Future.delayed(const Duration(seconds: 1), () async {
|
|
||||||
|
hasUpdated.value = !hasUpdated.value;
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
/*Future.delayed(const Duration(seconds: 1), () async {
|
||||||
var module = tokenService.appModule.value;
|
var module = tokenService.appModule.value;
|
||||||
Get.offAndToNamed(getTargetPage(module));
|
Get.offAndToNamed(getTargetPage(module));
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
rotateController.dispose();
|
rotateController.dispose();
|
||||||
scaleController.dispose();
|
scaleController.dispose();
|
||||||
|
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> fileDownload() async {
|
||||||
|
onUpdateDownload.value = true;
|
||||||
|
Dio dio = Dio();
|
||||||
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
final filePath = "${dir.path}/app-release.apk";
|
||||||
|
final file = File(filePath);
|
||||||
|
if (await file.exists()) {
|
||||||
|
await file.delete();
|
||||||
|
}
|
||||||
|
int attempts = 0;
|
||||||
|
int retryCount = 4;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
while (attempts < retryCount && !success) {
|
||||||
|
try {
|
||||||
|
await dio.download(
|
||||||
|
'https://everestacademy.ir/app/app-release.apk',
|
||||||
|
filePath,
|
||||||
|
onReceiveProgress: (count, total) {
|
||||||
|
if (total != -1 && total > 0) {
|
||||||
|
percent.value = count / total;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
success = true;
|
||||||
|
} on DioException catch (e) {
|
||||||
|
attempts++;
|
||||||
|
percent.value = 0.0;
|
||||||
|
if (attempts >= retryCount) {
|
||||||
|
eLog("Download failed after $attempts attempts: ${e.message}");
|
||||||
|
} else {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 1200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
_updateFilePath.value = filePath;
|
||||||
|
|
||||||
|
tLog(filePath);
|
||||||
|
fLog(_updateFilePath.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateDownload.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> installApk() async {
|
||||||
|
try {
|
||||||
|
eLog(_updateFilePath.value);
|
||||||
|
|
||||||
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
await platform.invokeMethod('apk_installer', {'appPath': _updateFilePath.value});
|
||||||
|
} catch (e) {
|
||||||
|
print("خطا در نصب: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class SplashPage extends GetView<SplashLogic> {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
|
||||||
ObxValue((data) {
|
ObxValue((data) {
|
||||||
return ScaleTransition(
|
return ScaleTransition(
|
||||||
scale: data.value!,
|
scale: data.value!,
|
||||||
|
|||||||
@@ -1,56 +1,53 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
//models
|
export 'package:android_intent_plus/android_intent.dart';
|
||||||
export 'data/model/pagination_model/pagination_model.dart';
|
export 'package:android_intent_plus/flag.dart';
|
||||||
|
export 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
export 'package:dio/dio.dart';
|
||||||
//other packages
|
//other packages
|
||||||
export 'package:flutter_localizations/flutter_localizations.dart';
|
export 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
export 'package:flutter_map/flutter_map.dart';
|
export 'package:flutter_map/flutter_map.dart';
|
||||||
export 'package:flutter_map_animations/flutter_map_animations.dart';
|
export 'package:flutter_map_animations/flutter_map_animations.dart';
|
||||||
export 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
export 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||||
|
export 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
export 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
export 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
export 'package:flutter_slidable/flutter_slidable.dart';
|
export 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
export 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
export 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
export 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
export 'package:dio/dio.dart';
|
|
||||||
export 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
|
||||||
export 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
||||||
|
|
||||||
//freezed
|
//freezed
|
||||||
export 'package:freezed_annotation/freezed_annotation.dart';
|
export 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
export 'package:geolocator/geolocator.dart';
|
export 'package:geolocator/geolocator.dart';
|
||||||
export 'package:get/get.dart' hide FormData, MultipartFile, Response;
|
export 'package:get/get.dart' hide FormData, MultipartFile, Response;
|
||||||
|
|
||||||
//di
|
//di
|
||||||
export 'package:get_it/get_it.dart';
|
export 'package:get_it/get_it.dart';
|
||||||
export 'injection/di.dart';
|
|
||||||
|
|
||||||
//local storage
|
//local storage
|
||||||
export 'package:hive_ce_flutter/hive_flutter.dart';
|
export 'package:hive_ce_flutter/hive_flutter.dart';
|
||||||
export 'infrastructure/local/hive_local_storage.dart';
|
///image picker
|
||||||
|
export 'package:image_picker/image_picker.dart';
|
||||||
//encryption
|
//encryption
|
||||||
//export 'package:encrypt/encrypt.dart' show Encrypted;
|
//export 'package:encrypt/encrypt.dart' show Encrypted;
|
||||||
|
|
||||||
//Map and location
|
//Map and location
|
||||||
export 'package:latlong2/latlong.dart';
|
export 'package:latlong2/latlong.dart';
|
||||||
|
export 'package:path_provider/path_provider.dart';
|
||||||
|
export 'package:permission_handler/permission_handler.dart' hide ServiceStatus;
|
||||||
export 'package:persian_datetime_picker/persian_datetime_picker.dart';
|
export 'package:persian_datetime_picker/persian_datetime_picker.dart';
|
||||||
|
export 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
export 'package:rasadyar_core/presentation/common/common.dart';
|
export 'package:rasadyar_core/presentation/common/common.dart';
|
||||||
export 'package:rasadyar_core/presentation/utils/utils.dart';
|
export 'package:rasadyar_core/presentation/utils/utils.dart';
|
||||||
export 'package:rasadyar_core/presentation/widget/widget.dart';
|
export 'package:rasadyar_core/presentation/widget/widget.dart';
|
||||||
|
|
||||||
|
//models
|
||||||
|
export 'data/model/pagination_model/pagination_model.dart';
|
||||||
//infrastructure
|
//infrastructure
|
||||||
export 'infrastructure/infrastructure.dart';
|
export 'infrastructure/infrastructure.dart';
|
||||||
|
export 'infrastructure/local/hive_local_storage.dart';
|
||||||
///image picker
|
export 'injection/di.dart';
|
||||||
export 'package:image_picker/image_picker.dart';
|
|
||||||
|
|
||||||
//utils
|
|
||||||
export 'utils/logger_utils.dart';
|
|
||||||
export 'utils/network/network.dart';
|
|
||||||
export 'utils/extension/date_time_utils.dart';
|
export 'utils/extension/date_time_utils.dart';
|
||||||
export 'utils/extension/num_utils.dart';
|
export 'utils/extension/num_utils.dart';
|
||||||
export 'utils/map_utils.dart';
|
|
||||||
export 'utils/route_utils.dart';
|
|
||||||
export 'utils/extension/string_utils.dart';
|
export 'utils/extension/string_utils.dart';
|
||||||
|
//utils
|
||||||
|
export 'utils/logger_utils.dart';
|
||||||
|
export 'utils/map_utils.dart';
|
||||||
|
export 'utils/network/network.dart';
|
||||||
|
export 'utils/route_utils.dart';
|
||||||
export 'utils/separator_input_formatter.dart';
|
export 'utils/separator_input_formatter.dart';
|
||||||
|
|||||||
@@ -4,14 +4,17 @@ import 'package:rasadyar_core/core.dart';
|
|||||||
class DioRemote implements IHttpClient {
|
class DioRemote implements IHttpClient {
|
||||||
String? baseUrl;
|
String? baseUrl;
|
||||||
late Dio dio;
|
late Dio dio;
|
||||||
final AppInterceptor interceptors;
|
AppInterceptor? interceptors;
|
||||||
|
|
||||||
DioRemote({this.baseUrl, required this.interceptors});
|
DioRemote({this.baseUrl, this.interceptors});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
|
dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
|
||||||
dio.interceptors.add(interceptors);
|
if (interceptors != null) {
|
||||||
|
dio.interceptors.add(interceptors!);
|
||||||
|
}
|
||||||
|
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
dio.interceptors.add(
|
dio.interceptors.add(
|
||||||
PrettyDioLogger(
|
PrettyDioLogger(
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export 'delete_dialog.dart';
|
export 'delete_dialog.dart';
|
||||||
export 'warning_dialog.dart';
|
export 'warning_dialog.dart';
|
||||||
|
export 'update_dialog.dart';
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:rasadyar_core/core.dart';
|
||||||
|
|
||||||
|
Future<void> requiredUpdateDialog({required Future<void> Function() onConfirm}) async {
|
||||||
|
await Get.defaultDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
onWillPop: () async => false,
|
||||||
|
title: 'بروزرسانی',
|
||||||
|
middleText: 'برای استفاده از امکانات برنامه لطفا برنامه را بروز رسانی نمایید.',
|
||||||
|
confirm: RElevated(
|
||||||
|
height: 40.h,
|
||||||
|
width: 150.w,
|
||||||
|
text: 'خروج',
|
||||||
|
backgroundColor: AppColor.error,
|
||||||
|
onPressed: () {
|
||||||
|
exit(0);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
cancel: RElevated(
|
||||||
|
height: 40.h,
|
||||||
|
width: 150.w,
|
||||||
|
text: 'بروز رسانی',
|
||||||
|
onPressed: onConfirm,
|
||||||
|
backgroundColor: AppColor.greenNormal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> optionalUpdateDialog({required Future<void> Function() onConfirm}) async {
|
||||||
|
await Get.defaultDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
onWillPop: () async => false,
|
||||||
|
title: 'بروزرسانی',
|
||||||
|
middleText: 'برای استفاده از امکانات جدید برنامه می توانید آن را بروزرسانی نمایید.',
|
||||||
|
confirm: RElevated(
|
||||||
|
height: 40.h,
|
||||||
|
width: 150.w,
|
||||||
|
text: 'ادامه',
|
||||||
|
backgroundColor: AppColor.error,
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
),
|
||||||
|
|
||||||
|
cancel: RElevated(
|
||||||
|
height: 40.h,
|
||||||
|
width: 150.w,
|
||||||
|
text: 'بروز رسانی',
|
||||||
|
onPressed: onConfirm,
|
||||||
|
backgroundColor: AppColor.greenNormal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,6 +17,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.4.5"
|
version: "7.4.5"
|
||||||
|
android_intent_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: android_intent_plus
|
||||||
|
sha256: dfc1fd3a577205ae8f11e990fb4ece8c90cceabbee56fcf48e463ecf0bd6aae3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.3.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -926,7 +934,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
@@ -977,10 +985,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f"
|
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.0.0+1"
|
version: "12.0.1"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ dependencies:
|
|||||||
hive_ce: ^2.11.1
|
hive_ce: ^2.11.1
|
||||||
hive_ce_flutter: ^2.3.0
|
hive_ce_flutter: ^2.3.0
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
|
||||||
|
|
||||||
#SVG
|
#SVG
|
||||||
flutter_svg: ^2.0.17
|
flutter_svg: ^2.0.17
|
||||||
@@ -57,13 +56,15 @@ dependencies:
|
|||||||
get_it: ^8.0.3
|
get_it: ^8.0.3
|
||||||
|
|
||||||
#other
|
#other
|
||||||
permission_handler: ^12.0.0+1
|
permission_handler: ^12.0.1
|
||||||
persian_datetime_picker: ^3.1.0
|
persian_datetime_picker: ^3.1.0
|
||||||
encrypt: ^5.0.3
|
encrypt: ^5.0.3
|
||||||
|
|
||||||
#L10N tools
|
#L10N tools
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
|
|
||||||
|
#INITENT
|
||||||
|
android_intent_plus: ^5.3.0
|
||||||
|
|
||||||
#Map
|
#Map
|
||||||
flutter_map: ^8.1.1
|
flutter_map: ^8.1.1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:rasadyar_core/core.dart';
|
import 'package:rasadyar_core/core.dart' ;
|
||||||
import 'package:rasadyar_inspection/presentation/action/view.dart';
|
import 'package:rasadyar_inspection/presentation/action/view.dart';
|
||||||
import 'package:rasadyar_inspection/presentation/filter/view.dart';
|
import 'package:rasadyar_inspection/presentation/filter/view.dart';
|
||||||
import 'package:rasadyar_inspection/presentation/profile/view.dart';
|
import 'package:rasadyar_inspection/presentation/profile/view.dart';
|
||||||
|
|||||||
12
pubspec.lock
12
pubspec.lock
@@ -17,6 +17,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.4.5"
|
version: "7.4.5"
|
||||||
|
android_intent_plus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: android_intent_plus
|
||||||
|
sha256: dfc1fd3a577205ae8f11e990fb4ece8c90cceabbee56fcf48e463ecf0bd6aae3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.3.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1009,10 +1017,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f"
|
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.0.0+1"
|
version: "12.0.1"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user