From 3683e8a9e6ecba3776e0c2ca5e8e4caf1e6e3590 Mon Sep 17 00:00:00 2001 From: "mr.mojtaba" Date: Sat, 19 Jul 2025 15:35:07 +0330 Subject: [PATCH] feat : apk installer --- android/app/src/main/AndroidManifest.xml | 10 +- .../kotlin/ir/mnpc/rasadyar/ApkInstaller.kt | 294 ++++++++++++++++++ .../kotlin/ir/mnpc/rasadyar/MainActivity.kt | 76 ++++- android/app/src/main/res/xml/file_paths.xml | 6 +- lib/infrastructure/di/di.dart | 17 +- lib/presentation/pages/splash/logic.dart | 175 ++++++++++- lib/presentation/pages/splash/view.dart | 1 + packages/core/lib/core.dart | 41 ++- .../lib/infrastructure/remote/dio_remote.dart | 9 +- .../presentation/widget/dialog/dialog.dart | 3 +- .../widget/dialog/update_dialog.dart | 53 ++++ packages/core/pubspec.lock | 14 +- packages/core/pubspec.yaml | 7 +- .../lib/presentation/root/logic.dart | 2 +- pubspec.lock | 12 +- 15 files changed, 641 insertions(+), 79 deletions(-) create mode 100644 android/app/src/main/kotlin/ir/mnpc/rasadyar/ApkInstaller.kt create mode 100644 packages/core/lib/presentation/widget/dialog/update_dialog.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7c7e1d7..96504e7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,14 @@ + + + + diff --git a/android/app/src/main/kotlin/ir/mnpc/rasadyar/ApkInstaller.kt b/android/app/src/main/kotlin/ir/mnpc/rasadyar/ApkInstaller.kt new file mode 100644 index 0000000..3fb5dbe --- /dev/null +++ b/android/app/src/main/kotlin/ir/mnpc/rasadyar/ApkInstaller.kt @@ -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") + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/ir/mnpc/rasadyar/MainActivity.kt b/android/app/src/main/kotlin/ir/mnpc/rasadyar/MainActivity.kt index 5bae765..33565df 100644 --- a/android/app/src/main/kotlin/ir/mnpc/rasadyar/MainActivity.kt +++ b/android/app/src/main/kotlin/ir/mnpc/rasadyar/MainActivity.kt @@ -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("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) + } + + } diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml index 03d4219..fa0400e 100644 --- a/android/app/src/main/res/xml/file_paths.xml +++ b/android/app/src/main/res/xml/file_paths.xml @@ -1,6 +1,6 @@ - + + + diff --git a/lib/infrastructure/di/di.dart b/lib/infrastructure/di/di.dart index 8ef306c..c1aebef 100644 --- a/lib/infrastructure/di/di.dart +++ b/lib/infrastructure/di/di.dart @@ -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 setupPreInjection() async{ +Future setupPreInjection() async { await setupAllCoreProvider(); await setupAuthDI(); - - - + di.registerSingleton( + DioRemote(baseUrl: 'https://everestacademy.ir/'), + instanceName: 'baseRemote', + ); } -Future setupInjection() async{ - +Future setupInjection() async { await setupChickenDI(); - - } - - diff --git a/lib/presentation/pages/splash/logic.dart b/lib/presentation/pages/splash/logic.dart index 8f26018..305ccac 100644 --- a/lib/presentation/pages/splash/logic.dart +++ b/lib/presentation/pages/splash/logic.dart @@ -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> scaleAnimation = Rxn(); Rxn> 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(); @@ -24,15 +31,9 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin { duration: const Duration(milliseconds: 8000), ); - scaleAnimation.value = Tween( - begin: 0.8, - end: 1.2, - ).animate(scaleController); + scaleAnimation.value = Tween(begin: 0.8, end: 1.2).animate(scaleController); - rotationAnimation.value = Tween( - begin: 0.0, - end: 1, - ).animate(rotateController); + rotationAnimation.value = Tween(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 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 installApk() async { + try { + eLog(_updateFilePath.value); + + final dir = await getApplicationDocumentsDirectory(); + await platform.invokeMethod('apk_installer', {'appPath': _updateFilePath.value}); + } catch (e) { + print("خطا در نصب: $e"); + } + } } diff --git a/lib/presentation/pages/splash/view.dart b/lib/presentation/pages/splash/view.dart index 4cd5ce8..140ed32 100644 --- a/lib/presentation/pages/splash/view.dart +++ b/lib/presentation/pages/splash/view.dart @@ -15,6 +15,7 @@ class SplashPage extends GetView { child: Stack( alignment: Alignment.center, children: [ + ObxValue((data) { return ScaleTransition( scale: data.value!, diff --git a/packages/core/lib/core.dart b/packages/core/lib/core.dart index 6d9a392..eb3c911 100644 --- a/packages/core/lib/core.dart +++ b/packages/core/lib/core.dart @@ -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'; diff --git a/packages/core/lib/infrastructure/remote/dio_remote.dart b/packages/core/lib/infrastructure/remote/dio_remote.dart index e1d9d73..b5b179e 100644 --- a/packages/core/lib/infrastructure/remote/dio_remote.dart +++ b/packages/core/lib/infrastructure/remote/dio_remote.dart @@ -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 init() async { dio = Dio(BaseOptions(baseUrl: baseUrl ?? '')); - dio.interceptors.add(interceptors); + if (interceptors != null) { + dio.interceptors.add(interceptors!); + } + if (kDebugMode) { dio.interceptors.add( PrettyDioLogger( diff --git a/packages/core/lib/presentation/widget/dialog/dialog.dart b/packages/core/lib/presentation/widget/dialog/dialog.dart index 788ecce..9f489da 100644 --- a/packages/core/lib/presentation/widget/dialog/dialog.dart +++ b/packages/core/lib/presentation/widget/dialog/dialog.dart @@ -1,2 +1,3 @@ export 'delete_dialog.dart'; -export 'warning_dialog.dart'; \ No newline at end of file +export 'warning_dialog.dart'; +export 'update_dialog.dart'; \ No newline at end of file diff --git a/packages/core/lib/presentation/widget/dialog/update_dialog.dart b/packages/core/lib/presentation/widget/dialog/update_dialog.dart new file mode 100644 index 0000000..0b4e748 --- /dev/null +++ b/packages/core/lib/presentation/widget/dialog/update_dialog.dart @@ -0,0 +1,53 @@ +import 'dart:io'; + +import 'package:rasadyar_core/core.dart'; + +Future requiredUpdateDialog({required Future 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 optionalUpdateDialog({required Future 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, + ), + ); +} diff --git a/packages/core/pubspec.lock b/packages/core/pubspec.lock index 1965d8f..9cb141c 100644 --- a/packages/core/pubspec.lock +++ b/packages/core/pubspec.lock @@ -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: diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml index 263fceb..852db42 100644 --- a/packages/core/pubspec.yaml +++ b/packages/core/pubspec.yaml @@ -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 diff --git a/packages/inspection/lib/presentation/root/logic.dart b/packages/inspection/lib/presentation/root/logic.dart index e95a9b3..31bd149 100644 --- a/packages/inspection/lib/presentation/root/logic.dart +++ b/packages/inspection/lib/presentation/root/logic.dart @@ -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'; diff --git a/pubspec.lock b/pubspec.lock index 1bba7de..2e46f88 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: