diff --git a/README.md b/README.md
index 852907e..1128e98 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,10 @@ This is an unofficial application. The AdGuard Home team and the development of
- [flutter svg](https://pub.dev/packages/flutter_svg)
- [bottom sheet](https://pub.dev/packages/bottom_sheet)
- [percent indicator](https://pub.dev/packages/percent_indicator)
+- [store checker](https://pub.dev/packages/store_checker)
+- [fl downloader](https://pub.dev/packages/fl_downloader)
+- [install plugin v2](https://pub.dev/packages/install_plugin_v2)
+- [permission handler](https://pub.dev/packages/permission_handler)
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 814311b..d93673c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion flutter.compileSdkVersion
+ compileSdkVersion 33
ndkVersion flutter.ndkVersion
compileOptions {
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index bf4e526..5b76055 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -5,4 +5,9 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index aada37e..6c51c3b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,9 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/lib/base.dart b/lib/base.dart
index ed649a0..4eb984e 100644
--- a/lib/base.dart
+++ b/lib/base.dart
@@ -1,25 +1,137 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
+import 'package:install_plugin_v2/install_plugin_v2.dart';
+import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
+import 'package:store_checker/store_checker.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart';
+import 'package:adguard_home_manager/widgets/update_modal.dart';
+import 'package:adguard_home_manager/widgets/download_modal.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
+import 'package:adguard_home_manager/models/github_release.dart';
+import 'package:adguard_home_manager/constants/package_name.dart';
+import 'package:adguard_home_manager/services/http_requests.dart';
+import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/models/app_screen.dart';
import 'package:adguard_home_manager/config/app_screens.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
class Base extends StatefulWidget {
- const Base({Key? key}) : super(key: key);
+ final AppConfigProvider appConfigProvider;
+
+ const Base({
+ Key? key,
+ required this.appConfigProvider,
+ }) : super(key: key);
@override
State createState() => _BaseState();
}
-class _BaseState extends State {
+class _BaseState extends State with WidgetsBindingObserver {
int selectedScreen = 0;
+ Future checkInstallationSource() async {
+ Source installationSource = await StoreChecker.getSource;
+ if (installationSource != Source.IS_INSTALLED_FROM_PLAY_STORE) {
+ final result = await checkAppUpdatesGitHub();
+ if (result['result'] == 'success') {
+ if (result['body'].tagName != widget.appConfigProvider.getAppInfo!.version) {
+ return result['body'];
+ }
+ }
+ }
+ return null;
+ }
+
+ Future managePermission() async {
+ try {
+ if (await Permission.storage.isGranted) {
+ return true;
+ }
+ else {
+ final PermissionStatus status = await Permission.storage.request();
+ if (status.isGranted == false) {
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+ } catch (e) {
+ return false;
+ }
+ }
+
+ void installApk(String path) async {
+ final granted = await managePermission();
+
+ if (granted == true) {
+ path = path.replaceFirst(r'file://', '');
+
+ final file = File(path);
+ final exists = await file.exists();
+
+ if (exists) {
+ InstallPlugin.installApk(
+ path, PackageName.packageName
+ );
+ }
+ }
+ }
+
+ @override
+ void initState() {
+ WidgetsBinding.instance.addObserver(this);
+
+ super.initState();
+
+ WidgetsBinding.instance.addPostFrameCallback((_) async {
+ void download(String link, String version) async {
+ final granted = await managePermission();
+ if (granted == true) {
+ showDialog(
+ context: context,
+ builder: (context) => DownloadModal(
+ url: link,
+ version: version,
+ onFinish: installApk,
+ ),
+ barrierDismissible: false
+ );
+ }
+ else {
+ showSnacbkar(
+ context: context,
+ appConfigProvider: widget.appConfigProvider,
+ label: AppLocalizations.of(context)!.permissionNotGranted,
+ color: Colors.red
+ );
+ }
+ }
+
+ final result = await checkInstallationSource();
+
+ if (result != null) {
+ await showDialog(
+ context: context,
+ builder: (context) => UpdateModal(
+ gitHubRelease: result,
+ onDownload: download,
+ ),
+ );
+ }
+ });
+ }
+
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of(context);
diff --git a/lib/constants/package_name.dart b/lib/constants/package_name.dart
new file mode 100644
index 0000000..e693d89
--- /dev/null
+++ b/lib/constants/package_name.dart
@@ -0,0 +1,3 @@
+class PackageName {
+ static const String packageName = "com.jgeek00.adguard_home_manager";
+}
\ No newline at end of file
diff --git a/lib/constants/urls.dart b/lib/constants/urls.dart
index 894856f..5b60855 100644
--- a/lib/constants/urls.dart
+++ b/lib/constants/urls.dart
@@ -2,4 +2,5 @@ class Urls {
static const String playStore = "https://play.google.com/store/apps/details?id=com.jgeek00.adguard_home_manager";
static const String gitHub = "https://github.com/JGeek00/adguard-home-manager";
static const String customRuleDocs = "https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters";
+ static const String checkLatestReleaseUrl = "https://api.github.com/repos/JGeek00/adguard-home-manager/releases/latest";
}
\ No newline at end of file
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index a7109d9..c0622ba 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -559,5 +559,15 @@
"nameTimeLogs": "Name and time on logs",
"nameTimeLogsDescription": "Show client name and processing time on logs list",
"hostNames": "Host names",
- "keyType": "Key type"
+ "keyType": "Key type",
+ "updateAvailable": "Update available",
+ "installedVersion": "Installed version",
+ "newVersion": "New version",
+ "source": "Source",
+ "downloadUpdate": "Download update",
+ "download": "Download",
+ "doNotRememberAgainUpdate": "Do not remember again for this version",
+ "downloadingUpdate": "Downloading",
+ "completed": "completed",
+ "permissionNotGranted": "Permission not granted"
}
\ No newline at end of file
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index d8976fe..0b558bf 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -559,5 +559,15 @@
"nameTimeLogs": "Nombre y tiempo en logs",
"nameTimeLogsDescription": "Mostrar el nombre del cliente y el tiempo de procesamiento en la lista de logs",
"hostNames": "Nombres de host",
- "keyType": "Tipo de clave"
+ "keyType": "Tipo de clave",
+ "updateAvailable": "Actualización disponible",
+ "installedVersion": "Versión instalada",
+ "newVersion": "Nueva versión",
+ "source": "Fuente",
+ "downloadUpdate": "Descargar actualización",
+ "download": "Descargar",
+ "doNotRememberAgainUpdate": "No recordar de nuevo para esta actualización",
+ "downloadingUpdate": "Descargando",
+ "completed": "completado",
+ "permissionNotGranted": "Permiso no concedido"
}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index cc10c9b..5ef8610 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -137,7 +137,7 @@ class _MainState extends State {
child: child!,
);
},
- home: const Base(),
+ home: Base(appConfigProvider: appConfigProvider),
),
);
}
diff --git a/lib/models/github_release.dart b/lib/models/github_release.dart
new file mode 100644
index 0000000..80746d5
--- /dev/null
+++ b/lib/models/github_release.dart
@@ -0,0 +1,232 @@
+class GitHubRelease {
+ final String url;
+ final String assetsUrl;
+ final String uploadUrl;
+ final String htmlUrl;
+ final int id;
+ final Author author;
+ final String nodeId;
+ final String tagName;
+ final String targetCommitish;
+ final String name;
+ final bool draft;
+ final bool prerelease;
+ final DateTime createdAt;
+ final DateTime publishedAt;
+ final List assets;
+ final String tarballUrl;
+ final String zipballUrl;
+ final String body;
+
+ GitHubRelease({
+ required this.url,
+ required this.assetsUrl,
+ required this.uploadUrl,
+ required this.htmlUrl,
+ required this.id,
+ required this.author,
+ required this.nodeId,
+ required this.tagName,
+ required this.targetCommitish,
+ required this.name,
+ required this.draft,
+ required this.prerelease,
+ required this.createdAt,
+ required this.publishedAt,
+ required this.assets,
+ required this.tarballUrl,
+ required this.zipballUrl,
+ required this.body,
+ });
+
+
+ factory GitHubRelease.fromJson(Map json) => GitHubRelease(
+ url: json["url"],
+ assetsUrl: json["assets_url"],
+ uploadUrl: json["upload_url"],
+ htmlUrl: json["html_url"],
+ id: json["id"],
+ author: Author.fromJson(json["author"]),
+ nodeId: json["node_id"],
+ tagName: json["tag_name"],
+ targetCommitish: json["target_commitish"],
+ name: json["name"],
+ draft: json["draft"],
+ prerelease: json["prerelease"],
+ createdAt: DateTime.parse(json["created_at"]),
+ publishedAt: DateTime.parse(json["published_at"]),
+ assets: List.from(json["assets"].map((x) => Asset.fromJson(x))),
+ tarballUrl: json["tarball_url"],
+ zipballUrl: json["zipball_url"],
+ body: json["body"],
+ );
+
+ Map toJson() => {
+ "url": url,
+ "assets_url": assetsUrl,
+ "upload_url": uploadUrl,
+ "html_url": htmlUrl,
+ "id": id,
+ "author": author.toJson(),
+ "node_id": nodeId,
+ "tag_name": tagName,
+ "target_commitish": targetCommitish,
+ "name": name,
+ "draft": draft,
+ "prerelease": prerelease,
+ "created_at": createdAt.toIso8601String(),
+ "published_at": publishedAt.toIso8601String(),
+ "assets": List.from(assets.map((x) => x.toJson())),
+ "tarball_url": tarballUrl,
+ "zipball_url": zipballUrl,
+ "body": body,
+ };
+}
+
+class Asset {
+ final String url;
+ final int id;
+ final String nodeId;
+ final String name;
+ final dynamic label;
+ final Author uploader;
+ final String contentType;
+ final String state;
+ final int size;
+ final int downloadCount;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+ final String browserDownloadUrl;
+
+ Asset({
+ required this.url,
+ required this.id,
+ required this.nodeId,
+ required this.name,
+ required this.label,
+ required this.uploader,
+ required this.contentType,
+ required this.state,
+ required this.size,
+ required this.downloadCount,
+ required this.createdAt,
+ required this.updatedAt,
+ required this.browserDownloadUrl,
+ });
+
+ factory Asset.fromJson(Map json) => Asset(
+ url: json["url"],
+ id: json["id"],
+ nodeId: json["node_id"],
+ name: json["name"],
+ label: json["label"],
+ uploader: Author.fromJson(json["uploader"]),
+ contentType: json["content_type"],
+ state: json["state"],
+ size: json["size"],
+ downloadCount: json["download_count"],
+ createdAt: DateTime.parse(json["created_at"]),
+ updatedAt: DateTime.parse(json["updated_at"]),
+ browserDownloadUrl: json["browser_download_url"],
+ );
+
+ Map toJson() => {
+ "url": url,
+ "id": id,
+ "node_id": nodeId,
+ "name": name,
+ "label": label,
+ "uploader": uploader.toJson(),
+ "content_type": contentType,
+ "state": state,
+ "size": size,
+ "download_count": downloadCount,
+ "created_at": createdAt.toIso8601String(),
+ "updated_at": updatedAt.toIso8601String(),
+ "browser_download_url": browserDownloadUrl,
+ };
+}
+
+class Author {
+ final String login;
+ final int id;
+ final String nodeId;
+ final String avatarUrl;
+ final String gravatarId;
+ final String url;
+ final String htmlUrl;
+ final String followersUrl;
+ final String followingUrl;
+ final String gistsUrl;
+ final String starredUrl;
+ final String subscriptionsUrl;
+ final String organizationsUrl;
+ final String reposUrl;
+ final String eventsUrl;
+ final String receivedEventsUrl;
+ final String type;
+ final bool siteAdmin;
+
+ Author({
+ required this.login,
+ required this.id,
+ required this.nodeId,
+ required this.avatarUrl,
+ required this.gravatarId,
+ required this.url,
+ required this.htmlUrl,
+ required this.followersUrl,
+ required this.followingUrl,
+ required this.gistsUrl,
+ required this.starredUrl,
+ required this.subscriptionsUrl,
+ required this.organizationsUrl,
+ required this.reposUrl,
+ required this.eventsUrl,
+ required this.receivedEventsUrl,
+ required this.type,
+ required this.siteAdmin,
+ });
+
+ factory Author.fromJson(Map json) => Author(
+ login: json["login"],
+ id: json["id"],
+ nodeId: json["node_id"],
+ avatarUrl: json["avatar_url"],
+ gravatarId: json["gravatar_id"],
+ url: json["url"],
+ htmlUrl: json["html_url"],
+ followersUrl: json["followers_url"],
+ followingUrl: json["following_url"],
+ gistsUrl: json["gists_url"],
+ starredUrl: json["starred_url"],
+ subscriptionsUrl: json["subscriptions_url"],
+ organizationsUrl: json["organizations_url"],
+ reposUrl: json["repos_url"],
+ eventsUrl: json["events_url"],
+ receivedEventsUrl: json["received_events_url"],
+ type: json["type"],
+ siteAdmin: json["site_admin"],
+ );
+
+ Map toJson() => {
+ "login": login,
+ "id": id,
+ "node_id": nodeId,
+ "avatar_url": avatarUrl,
+ "gravatar_id": gravatarId,
+ "url": url,
+ "html_url": htmlUrl,
+ "followers_url": followersUrl,
+ "following_url": followingUrl,
+ "gists_url": gistsUrl,
+ "starred_url": starredUrl,
+ "subscriptions_url": subscriptionsUrl,
+ "organizations_url": organizationsUrl,
+ "repos_url": reposUrl,
+ "events_url": eventsUrl,
+ "received_events_url": receivedEventsUrl,
+ "type": type,
+ "site_admin": siteAdmin,
+ };
+}
diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart
index 7a90887..686c989 100644
--- a/lib/services/http_requests.dart
+++ b/lib/services/http_requests.dart
@@ -8,6 +8,7 @@ import 'package:adguard_home_manager/models/dhcp.dart';
import 'package:adguard_home_manager/models/dns_info.dart';
import 'package:adguard_home_manager/models/encryption.dart';
import 'package:adguard_home_manager/models/filtering.dart';
+import 'package:adguard_home_manager/models/github_release.dart';
import 'package:adguard_home_manager/models/logs.dart';
import 'package:adguard_home_manager/models/filtering_status.dart';
import 'package:adguard_home_manager/models/app_log.dart';
@@ -17,6 +18,7 @@ import 'package:adguard_home_manager/models/server_status.dart';
import 'package:adguard_home_manager/models/clients.dart';
import 'package:adguard_home_manager/models/clients_allowed_blocked.dart';
import 'package:adguard_home_manager/models/server.dart';
+import 'package:adguard_home_manager/constants/urls.dart';
Future