feat : apk updater

This commit is contained in:
2025-06-07 14:54:44 +03:30
parent 208eae5487
commit 40b8d6f913
7 changed files with 143 additions and 19 deletions

View File

@@ -26,6 +26,12 @@ android {
versionName = flutter.versionName versionName = flutter.versionName
} }
packaging {
resources {
excludes += "META-INF/DEPENDENCIES"
}
}
buildTypes { buildTypes {
release { release {
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")

View File

@@ -1,33 +1,37 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_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.MANAGE_EXTERNAL_STORAGE" />
<application <application
android:label="رصــدیـار"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"> android:icon="@mipmap/launcher_icon"
android:label="رصــدیـار">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:enableOnBackInvokedCallback="true"
android:exported="true" android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity="" android:taskAffinity=""
android:enableOnBackInvokedCallback="true"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. --> to determine the Window background behind the Flutter UI. -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme" />
/>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
@@ -35,6 +39,16 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and
@@ -43,8 +57,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.PROCESS_TEXT"/> <action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain" />
</intent> </intent>
</queries> </queries>
</manifest> </manifest>

View File

@@ -1,5 +1,57 @@
package هir.mnpc.rasadyar package ir.mnpc.rasadyar
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
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.plugin.common.MethodChannel
import java.io.File
class MainActivity : FlutterActivity() class MainActivity : FlutterActivity() {
private val CHANNEL = "apk_installer"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
if (call.method == "installApk") {
val apkPath = call.argument<String>("appPath") ?: ""
installApk(apkPath)
result.success(null)
}
}
}
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,
"${BuildConfig.APPLICATION_ID}.fileprovider",
file
)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
uri
} else {
Uri.fromFile(file)
}
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
applicationContext.startActivity(intent)
}
}

View File

@@ -0,0 +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="." />
</paths>

View File

@@ -10,18 +10,18 @@ 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:device_info_plus/device_info_plus.dart';
export 'package:dio/dio.dart' show DioException; export 'package:dio/dio.dart' ;
export 'package:pretty_dio_logger/pretty_dio_logger.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'; 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'; 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 'package:flutter_secure_storage/flutter_secure_storage.dart';
export 'infrastructure/local/hive_local_storage.dart'; export 'infrastructure/local/hive_local_storage.dart';
//encryption //encryption

View File

@@ -0,0 +1,22 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
class ApkUpdater {
static const _channel = MethodChannel('apk_installer');
static Future<void> downloadAndInstall() async {
final dio = Dio();
final apkUrl = "https://yourdomain.com/app.apk";
final dir = await getExternalStorageDirectory();
final path = "${dir!.path}/update.apk";
final file = File(path);
await dio.download(apkUrl, path);
await _channel.invokeMethod("installApk", {"path": path});
}
}

View File

@@ -233,6 +233,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
device_info_plus:
dependency: transitive
description:
name: device_info_plus
sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53"
url: "https://pub.dev"
source: hosted
version: "11.4.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
dio: dio:
dependency: transitive dependency: transitive
description: description:
@@ -1277,6 +1293,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.13.0" version: "5.13.0"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
wkt_parser: wkt_parser:
dependency: transitive dependency: transitive
description: description: