feat : apk installer

This commit is contained in:
2025-07-19 15:35:07 +03:30
parent 69945a29cf
commit 3683e8a9e6
15 changed files with 641 additions and 79 deletions

View File

@@ -7,6 +7,14 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<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
@@ -19,8 +27,8 @@
android:enableOnBackInvokedCallback="true"
android:exported="true"
android:hardwareAccelerated="true"
android:requestLegacyExternalStorage="true"
android:launchMode="singleTop"
android:requestLegacyExternalStorage="true"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">

View 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")
}
}
}
}

View File

@@ -3,16 +3,22 @@ package ir.mnpc.rasadyar
import android.content.Intent
import android.net.Uri
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 io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.File
import androidx.core.net.toUri
class MainActivity : FlutterActivity() {
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) {
super.configureFlutterEngine(flutterEngine)
@@ -21,9 +27,18 @@ class MainActivity : FlutterActivity() {
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
if (call.method == "installApk") {
if (call.method == "apk_installer") {
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)
}
}
@@ -34,24 +49,51 @@ class MainActivity : FlutterActivity() {
private fun installApk(path: String) {
val file = File(path)
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val apkUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val uri = FileProvider.getUriForFile(
applicationContext,
"${applicationContext.packageName}.fileprovider",
file
)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
uri
} else {
Uri.fromFile(file)
if (!file.exists()) {
Log.e("jojo", "APK file does not exist: $path")
return
}
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
applicationContext.startActivity(intent)
// Check if we can install unknown apps (Android 8.0+)
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)
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
<files-path name="app_flutter" path="app_flutter/*" />
</paths>

View File

@@ -1,23 +1,18 @@
import 'package:rasadyar_auth/auth.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_core/core.dart';
final di = GetIt.instance;
Future<void> setupPreInjection() async{
Future<void> setupPreInjection() async {
await setupAllCoreProvider();
await setupAuthDI();
di.registerSingleton<DioRemote>(
DioRemote(baseUrl: 'https://everestacademy.ir/'),
instanceName: 'baseRemote',
);
}
Future<void> setupInjection() async{
Future<void> setupInjection() async {
await setupChickenDI();
}

View File

@@ -1,5 +1,7 @@
import 'package:flutter/animation.dart';
import 'package:rasadyar_app/presentation/routes/app_pages.dart';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
import 'package:rasadyar_core/core.dart';
@@ -8,6 +10,11 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
late final AnimationController rotateController;
Rxn<Animation<double>> scaleAnimation = 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>();
@@ -24,15 +31,9 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
duration: const Duration(milliseconds: 8000),
);
scaleAnimation.value = Tween<double>(
begin: 0.8,
end: 1.2,
).animate(scaleController);
scaleAnimation.value = Tween<double>(begin: 0.8, end: 1.2).animate(scaleController);
rotationAnimation.value = Tween<double>(
begin: 0.0,
end: 1,
).animate(rotateController);
rotationAnimation.value = Tween<double>(begin: 0.0, end: 1).animate(rotateController);
rotateController.forward();
rotateController.addStatusListener((status) {
@@ -51,21 +52,171 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
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
void 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;
Get.offAndToNamed(getTargetPage(module));
});
});*/
}
@override
void onClose() {
rotateController.dispose();
scaleController.dispose();
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");
}
}
}

View File

@@ -15,6 +15,7 @@ class SplashPage extends GetView<SplashLogic> {
child: Stack(
alignment: Alignment.center,
children: [
ObxValue((data) {
return ScaleTransition(
scale: data.value!,

View File

@@ -1,56 +1,53 @@
library;
//models
export 'data/model/pagination_model/pagination_model.dart';
export 'package:android_intent_plus/android_intent.dart';
export 'package:android_intent_plus/flag.dart';
export 'package:device_info_plus/device_info_plus.dart';
export 'package:dio/dio.dart';
//other packages
export 'package:flutter_localizations/flutter_localizations.dart';
export 'package:flutter_map/flutter_map.dart';
export 'package:flutter_map_animations/flutter_map_animations.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_slidable/flutter_slidable.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
export 'package:freezed_annotation/freezed_annotation.dart';
export 'package:geolocator/geolocator.dart';
export 'package:get/get.dart' hide FormData, MultipartFile, Response;
//di
export 'package:get_it/get_it.dart';
export 'injection/di.dart';
//local storage
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
//export 'package:encrypt/encrypt.dart' show Encrypted;
//Map and location
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:pretty_dio_logger/pretty_dio_logger.dart';
export 'package:rasadyar_core/presentation/common/common.dart';
export 'package:rasadyar_core/presentation/utils/utils.dart';
export 'package:rasadyar_core/presentation/widget/widget.dart';
//models
export 'data/model/pagination_model/pagination_model.dart';
//infrastructure
export 'infrastructure/infrastructure.dart';
///image picker
export 'package:image_picker/image_picker.dart';
//utils
export 'utils/logger_utils.dart';
export 'utils/network/network.dart';
export 'infrastructure/local/hive_local_storage.dart';
export 'injection/di.dart';
export 'utils/extension/date_time_utils.dart';
export 'utils/extension/num_utils.dart';
export 'utils/map_utils.dart';
export 'utils/route_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';

View File

@@ -4,14 +4,17 @@ import 'package:rasadyar_core/core.dart';
class DioRemote implements IHttpClient {
String? baseUrl;
late Dio dio;
final AppInterceptor interceptors;
AppInterceptor? interceptors;
DioRemote({this.baseUrl, required this.interceptors});
DioRemote({this.baseUrl, this.interceptors});
@override
Future<void> init() async {
dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
dio.interceptors.add(interceptors);
if (interceptors != null) {
dio.interceptors.add(interceptors!);
}
if (kDebugMode) {
dio.interceptors.add(
PrettyDioLogger(

View File

@@ -1,2 +1,3 @@
export 'delete_dialog.dart';
export 'warning_dialog.dart';
export 'update_dialog.dart';

View File

@@ -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,
),
);
}

View File

@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -926,7 +934,7 @@ packages:
source: hosted
version: "1.1.0"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
@@ -977,10 +985,10 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f"
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.dev"
source: hosted
version: "12.0.0+1"
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:

View File

@@ -37,8 +37,7 @@ dependencies:
hive_ce: ^2.11.1
hive_ce_flutter: ^2.3.0
flutter_secure_storage: ^9.2.4
path_provider: ^2.1.5
#SVG
flutter_svg: ^2.0.17
@@ -57,13 +56,15 @@ dependencies:
get_it: ^8.0.3
#other
permission_handler: ^12.0.0+1
permission_handler: ^12.0.1
persian_datetime_picker: ^3.1.0
encrypt: ^5.0.3
#L10N tools
intl: ^0.20.2
#INITENT
android_intent_plus: ^5.3.0
#Map
flutter_map: ^8.1.1

View File

@@ -1,5 +1,5 @@
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/filter/view.dart';
import 'package:rasadyar_inspection/presentation/profile/view.dart';

View File

@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -1009,10 +1017,10 @@ packages:
dependency: transitive
description:
name: permission_handler
sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f"
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.dev"
source: hosted
version: "12.0.0+1"
version: "12.0.1"
permission_handler_android:
dependency: transitive
description: