Integrated download new version feature
parent
9aea59e5a2
commit
0868ae7ee0
|
@ -51,6 +51,7 @@ android {
|
|||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.unit2">
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
|
||||
<application
|
||||
android:label="unit2"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<provider
|
||||
android:authorities = "${applicationId}.fileprovider"
|
||||
android:exported = "false"
|
||||
android:grantUriPermissions = "true"
|
||||
android:name = "androidx.core.content.FileProvider">
|
||||
<meta-data
|
||||
android:name = "android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource = "@xml/file_provider_path" />
|
||||
</provider >
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// Generated file.
|
||||
//
|
||||
// If you wish to remove Flutter's multidex support, delete this entire file.
|
||||
//
|
||||
// Modifications to this file should be done in a copy under a different name
|
||||
// as this file may be regenerated.
|
||||
|
||||
package io.flutter.app;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.multidex.MultiDex;
|
||||
|
||||
/**
|
||||
* Extension of {@link android.app.Application}, adding multidex support.
|
||||
*/
|
||||
public class FlutterMultiDexApplication extends Application {
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
MultiDex.install(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<root-path name="root" path="."/>
|
||||
|
||||
<files-path
|
||||
name="files"
|
||||
path="."/>
|
||||
|
||||
<cache-path
|
||||
name="cache"
|
||||
path="."/>
|
||||
|
||||
<external-path
|
||||
name="external"
|
||||
path="."/>
|
||||
|
||||
<external-cache-path
|
||||
name="external_cache"
|
||||
path="."/>
|
||||
|
||||
<external-files-path
|
||||
name="external_file"
|
||||
path="."/>
|
||||
</paths>
|
|
@ -27,6 +27,7 @@ class GetUuid extends UserEvent {
|
|||
GetUuid();
|
||||
}
|
||||
|
||||
|
||||
class LoadVersion extends UserEvent {}
|
||||
|
||||
class UuidLogin extends UserEvent {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:cool_alert/cool_alert.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../theme-data.dart/colors.dart';
|
||||
|
||||
showAlert(context,Function confirm) {
|
||||
CoolAlert.show(
|
||||
context: context,
|
||||
type: CoolAlertType.error,
|
||||
title: 'Download Failed!',
|
||||
text: 'Make sure you have internet connection. Please try again.',
|
||||
loopAnimation: false,
|
||||
confirmBtnText: "Try again",
|
||||
confirmBtnColor: primary,
|
||||
onConfirmBtnTap: confirm(),
|
||||
backgroundColor: Colors.black);
|
||||
}
|
|
@ -1,26 +1,51 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_app_installer/easy_app_installer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_progress_hud/flutter_progress_hud.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:unit2/bloc/bloc/user_bloc.dart';
|
||||
import 'package:unit2/screens/unit2/login/components/showAlert.dart';
|
||||
import 'package:unit2/theme-data.dart/btn-style.dart';
|
||||
|
||||
import '../../../../theme-data.dart/colors.dart';
|
||||
import '../../../../utils/cpu_architecture.dart';
|
||||
import '../../../../utils/text_container.dart';
|
||||
import '../../../../widgets/wave.dart';
|
||||
|
||||
class Update extends StatefulWidget {
|
||||
final String apkVersion;
|
||||
final String currenVersion;
|
||||
const Update({super.key,required this.apkVersion,required this.currenVersion});
|
||||
const Update(
|
||||
{super.key, required this.apkVersion, required this.currenVersion});
|
||||
|
||||
@override
|
||||
State<Update> createState() => _UpdateState();
|
||||
}
|
||||
|
||||
class _UpdateState extends State<Update> {
|
||||
String progressRating = '0';
|
||||
bool downloading = false;
|
||||
bool isDownloaded = false;
|
||||
bool asyncCall = false;
|
||||
Dio dio = Dio();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Stack(children: [
|
||||
BlocBuilder<UserBloc, UserState>(
|
||||
builder: (context, state) {
|
||||
if (state is VersionLoaded) {
|
||||
return ProgressHUD(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 25),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40, vertical: 25),
|
||||
height: MediaQuery.of(context).size.height,
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
|
@ -63,15 +88,17 @@ class _UpdateState extends State<Update> {
|
|||
TextSpan(
|
||||
text: widget.apkVersion,
|
||||
style: const TextStyle(
|
||||
color: primary, fontWeight: FontWeight.bold)),
|
||||
color: primary,
|
||||
fontWeight: FontWeight.bold)),
|
||||
const TextSpan(
|
||||
text: " did not match with the latest version ",
|
||||
text:
|
||||
" did not match with the latest version ",
|
||||
style: TextStyle(color: Colors.black)),
|
||||
TextSpan(
|
||||
text:
|
||||
widget.currenVersion,
|
||||
text: widget.currenVersion.toString(),
|
||||
style: const TextStyle(
|
||||
color: primary, fontWeight: FontWeight.bold)),
|
||||
color: primary,
|
||||
fontWeight: FontWeight.bold)),
|
||||
const TextSpan(
|
||||
text:
|
||||
". Download the app latest version to procceed using the uniT-App.",
|
||||
|
@ -80,14 +107,42 @@ class _UpdateState extends State<Update> {
|
|||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Container(
|
||||
child: downloading
|
||||
? FittedBox(
|
||||
child: Text(
|
||||
'Downloading application $progressRating%',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
SizedBox(
|
||||
height: 60,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton.icon(
|
||||
child: downloading
|
||||
? Container()
|
||||
: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.download),
|
||||
style: mainBtnStyle(primary, Colors.transparent, second),
|
||||
onPressed: () async {},
|
||||
label: const Text("Download Latest App Version.")),
|
||||
style: mainBtnStyle(
|
||||
primary, Colors.transparent, second),
|
||||
onPressed: () async {
|
||||
final progress = ProgressHUD.of(context);
|
||||
progress?.showWithText(
|
||||
'Please wait...',
|
||||
);
|
||||
setState(() {
|
||||
downloading = true;
|
||||
progressRating = '0';
|
||||
});
|
||||
await openFile();
|
||||
},
|
||||
label: const Text(
|
||||
"Download Latest App Version.")),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -95,4 +150,99 @@ class _UpdateState extends State<Update> {
|
|||
),
|
||||
);
|
||||
}
|
||||
if (state is UserError) {
|
||||
showAlert(context, () {
|
||||
setState(() {
|
||||
downloading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
const Positioned(bottom: 0, child: WaveReverse(height: 80))
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> openFile() async {
|
||||
try {
|
||||
final filePath = await downloadFile();
|
||||
await openAPK(filePath);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openAPK(String path) async {
|
||||
PermissionStatus result = await Permission.storage.request();
|
||||
if (result.isGranted) {
|
||||
await EasyAppInstaller.instance.installApk(path);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> downloadFile() async {
|
||||
final progress = ProgressHUD.of(context);
|
||||
final appStorage = await getApplicationDocumentsDirectory();
|
||||
try {
|
||||
String url = await getCPUArchitecture();
|
||||
final response = await dio.download(url, '${appStorage.path}/uniT.apk',
|
||||
deleteOnError: true,
|
||||
options: Options(
|
||||
receiveTimeout: 20000,
|
||||
sendTimeout: 20000,
|
||||
receiveDataWhenStatusError: true,
|
||||
responseType: ResponseType.bytes,
|
||||
followRedirects: false,
|
||||
validateStatus: (status) {
|
||||
return status! < 500;
|
||||
}), onReceiveProgress: (recv, total) {
|
||||
setState(() {
|
||||
progressRating = ((recv / total) * 100).toStringAsFixed(0);
|
||||
downloading = true;
|
||||
});
|
||||
if (progressRating == '100') {
|
||||
final progress = ProgressHUD.of(context);
|
||||
progress!.dismiss();
|
||||
setState(() {
|
||||
downloading = false;
|
||||
isDownloaded = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
} on TimeoutException catch (_) {
|
||||
progress!.dismiss();
|
||||
showAlert(context, () {
|
||||
setState(() {
|
||||
downloading = false;
|
||||
});
|
||||
});
|
||||
throw TimeoutException(timeoutError);
|
||||
} on SocketException catch (_) {
|
||||
progress!.dismiss();
|
||||
showAlert(context, () {
|
||||
setState(() {
|
||||
downloading = false;
|
||||
});
|
||||
});
|
||||
throw const SocketException(timeoutError);
|
||||
} on DioError catch (_) {
|
||||
progress!.dismiss();
|
||||
showAlert(context, () {
|
||||
setState(() {
|
||||
downloading = false;
|
||||
});
|
||||
});
|
||||
throw TimeoutException(timeoutError);
|
||||
} catch (_) {
|
||||
progress!.dismiss();
|
||||
showAlert(context, () {
|
||||
setState(() {
|
||||
downloading = false;
|
||||
});
|
||||
});
|
||||
throw TimeoutException(timeoutError);
|
||||
}
|
||||
return '${appStorage.path}/uniT.apk';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:unit2/model/login_data/version_info.dart';
|
||||
import 'package:system_info2/system_info2.dart';
|
||||
Future<String> getCPUArchitecture() async {
|
||||
String downloadURL = "";
|
||||
String cpuArchitecture = SysInfo.kernelArchitecture;
|
||||
VersionInfo? apkVersion;
|
||||
Dio dio = Dio();
|
||||
const String apkUrl = 'https://unitylb1.agusandelnorte.gov.ph/unit2/api/sys/apk_version/latest/';
|
||||
try {
|
||||
Response response = await dio.get(apkUrl,
|
||||
options: Options(
|
||||
receiveTimeout: 20000,
|
||||
receiveDataWhenStatusError: true,
|
||||
responseType: ResponseType.json,
|
||||
followRedirects: false,
|
||||
validateStatus: (status) {
|
||||
return status! < 500;
|
||||
}));
|
||||
if (response.statusCode == 200) {
|
||||
apkVersion = VersionInfo.fromJson(response.data['data']);
|
||||
if (cpuArchitecture.toUpperCase() == 'ARM' ||
|
||||
cpuArchitecture.toUpperCase() == 'MIPS') {
|
||||
downloadURL = apkVersion.armeabiv7aDownloadUrl!;
|
||||
} else if (cpuArchitecture.toUpperCase() == 'I686' ||
|
||||
cpuArchitecture.toUpperCase() == 'UNKNOWN' ||
|
||||
cpuArchitecture.toUpperCase() == 'X86_64') {
|
||||
downloadURL = apkVersion.x8664DownloadUrl!;
|
||||
} else if (cpuArchitecture.toUpperCase() == 'AARCH64') {
|
||||
downloadURL = apkVersion.arm64v8aDownloadUrl!;
|
||||
}
|
||||
}
|
||||
} on TimeoutException catch (_) {
|
||||
throw TimeoutException("Connection timeout");
|
||||
} on SocketException catch (_) {
|
||||
throw const SocketException("Connection timeout");
|
||||
} on DioError catch (_) {
|
||||
throw DioErrorType.connectTimeout;
|
||||
}
|
||||
return downloadURL;
|
||||
}
|
|
@ -19,7 +19,7 @@ class Request {
|
|||
Map<String, String>? param}) async {
|
||||
Response response;
|
||||
try {
|
||||
response = await get(Uri.http(host, path!, param), headers: headers)
|
||||
response = await get(Uri.https(host, path!, param), headers: headers)
|
||||
.timeout(Duration(seconds: requestTimeout));
|
||||
} on TimeoutException catch (_) {
|
||||
Fluttertoast.showToast(
|
||||
|
@ -60,39 +60,39 @@ class Request {
|
|||
Map? body,
|
||||
Map<String, String>? param}) async {
|
||||
Response response;
|
||||
// try {
|
||||
response = await post(Uri.http(host, path!, param), headers: headers,body: jsonEncode(body))
|
||||
try {
|
||||
response = await post(Uri.https(host, path!, param), headers: headers,body: jsonEncode(body))
|
||||
.timeout(Duration(seconds: requestTimeout));
|
||||
// } on TimeoutException catch (_) {
|
||||
// Fluttertoast.showToast(
|
||||
// msg: timeoutError,
|
||||
// toastLength: Toast.LENGTH_LONG,
|
||||
// gravity: ToastGravity.BOTTOM,
|
||||
// backgroundColor: Colors.black,
|
||||
// );
|
||||
// throw (timeoutError);
|
||||
// } on SocketException catch (_) {
|
||||
// Fluttertoast.showToast(
|
||||
// msg: timeoutError,
|
||||
// toastLength: Toast.LENGTH_LONG,
|
||||
// gravity: ToastGravity.BOTTOM,
|
||||
// backgroundColor: Colors.black,
|
||||
// );
|
||||
// throw (timeoutError);
|
||||
// } on FormatException catch (_) {
|
||||
// throw const FormatException(formatError);
|
||||
// } on HttpException catch (_) {
|
||||
// throw const HttpException(httpError);
|
||||
// } on Error catch (e) {
|
||||
// debugPrint("post request error: $e");
|
||||
// Fluttertoast.showToast(
|
||||
// msg: onError,
|
||||
// toastLength: Toast.LENGTH_LONG,
|
||||
// gravity: ToastGravity.BOTTOM,
|
||||
// backgroundColor: Colors.black,
|
||||
// );
|
||||
// throw (e.toString());
|
||||
// }
|
||||
} on TimeoutException catch (_) {
|
||||
Fluttertoast.showToast(
|
||||
msg: timeoutError,
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
backgroundColor: Colors.black,
|
||||
);
|
||||
throw (timeoutError);
|
||||
} on SocketException catch (_) {
|
||||
Fluttertoast.showToast(
|
||||
msg: timeoutError,
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
backgroundColor: Colors.black,
|
||||
);
|
||||
throw (timeoutError);
|
||||
} on FormatException catch (_) {
|
||||
throw const FormatException(formatError);
|
||||
} on HttpException catch (_) {
|
||||
throw const HttpException(httpError);
|
||||
} on Error catch (e) {
|
||||
debugPrint("post request error: $e");
|
||||
Fluttertoast.showToast(
|
||||
msg: onError,
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
backgroundColor: Colors.black,
|
||||
);
|
||||
throw (e.toString());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ class Url{
|
|||
static Url get instance => _instance;
|
||||
|
||||
String host(){
|
||||
return '192.168.10.219:3000';
|
||||
// return '192.168.10.219:3000';
|
||||
return 'agusandelnorte.gov.ph';
|
||||
}
|
||||
|
||||
|
||||
String authentication(){
|
||||
return '/api/account/auth/login/';
|
||||
}
|
||||
|
|
|
@ -120,6 +120,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0+1"
|
||||
cool_alert:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cool_alert
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -148,6 +155,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.6"
|
||||
easy_app_installer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: easy_app_installer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -190,6 +211,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
flare_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flare_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -242,6 +270,13 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
flutter_progress_hud:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -371,6 +406,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
lottie:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lottie
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.3"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -442,7 +484,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
|
@ -497,6 +539,41 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.0.7"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.9.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -61,6 +61,11 @@ dependencies:
|
|||
flutter_bloc: ^8.0.0
|
||||
equatable: ^2.0.5
|
||||
package_info_plus: ^3.0.2
|
||||
easy_app_installer: ^1.0.0
|
||||
path_provider: ^2.0.11
|
||||
dio: ^4.0.6
|
||||
cool_alert: ^1.1.0
|
||||
permission_handler: ^10.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
permission_handler_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
Loading…
Reference in New Issue