mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-04 07:13:28 +01:00 
			
		
		
		
	Support for fixed APK URL in HTML source (#1101)
This commit is contained in:
		@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Dovršite instalaciju aplikacije",
 | 
					    "completeAppInstallationNotifChannel": "Dovršite instalaciju aplikacije",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Tražim moguće nadogradnje",
 | 
					    "checkingForUpdatesNotifChannel": "Tražim moguće nadogradnje",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Želite li ukloniti aplikaciju?",
 | 
					        "one": "Želite li ukloniti aplikaciju?",
 | 
				
			||||||
        "other": "Želite li ukloniti aplikacije?"
 | 
					        "other": "Želite li ukloniti aplikacije?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
 | 
					    "completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
 | 
					    "checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Odstranit Apku?",
 | 
					        "one": "Odstranit Apku?",
 | 
				
			||||||
        "other": "Odstranit Apky?"
 | 
					        "other": "Odstranit Apky?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "App Installation abschließen",
 | 
					    "completeAppInstallationNotifChannel": "App Installation abschließen",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Nach Aktualisierungen suchen",
 | 
					    "checkingForUpdatesNotifChannel": "Nach Aktualisierungen suchen",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps nach Aktualisierungen",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps nach Aktualisierungen",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "App entfernen?",
 | 
					        "one": "App entfernen?",
 | 
				
			||||||
        "other": "Apps entfernen?"
 | 
					        "other": "Apps entfernen?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Complete App Installation",
 | 
					    "completeAppInstallationNotifChannel": "Complete App Installation",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Checking for Updates",
 | 
					    "checkingForUpdatesNotifChannel": "Checking for Updates",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Remove App?",
 | 
					        "one": "Remove App?",
 | 
				
			||||||
        "other": "Remove Apps?"
 | 
					        "other": "Remove Apps?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Instalación Completa de la Aplicación",
 | 
					    "completeAppInstallationNotifChannel": "Instalación Completa de la Aplicación",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Buscando Actualizaciones",
 | 
					    "checkingForUpdatesNotifChannel": "Buscando Actualizaciones",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "¿Eliminar Aplicación?",
 | 
					        "one": "¿Eliminar Aplicación?",
 | 
				
			||||||
        "other": "¿Eliminar Aplicaciones?"
 | 
					        "other": "¿Eliminar Aplicaciones?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "نصب کامل برنامه",
 | 
					    "completeAppInstallationNotifChannel": "نصب کامل برنامه",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "بررسی بهروزرسانیها",
 | 
					    "checkingForUpdatesNotifChannel": "بررسی بهروزرسانیها",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "برنامه حذف شود؟",
 | 
					        "one": "برنامه حذف شود؟",
 | 
				
			||||||
        "other": "برنامه ها حذف شوند؟"
 | 
					        "other": "برنامه ها حذف شوند؟"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Installation complète de l'application",
 | 
					    "completeAppInstallationNotifChannel": "Installation complète de l'application",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Vérification des mises à jour",
 | 
					    "checkingForUpdatesNotifChannel": "Vérification des mises à jour",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Supprimer l'application ?",
 | 
					        "one": "Supprimer l'application ?",
 | 
				
			||||||
        "other": "Supprimer les applications ?"
 | 
					        "other": "Supprimer les applications ?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Teljes app telepítés",
 | 
					    "completeAppInstallationNotifChannel": "Teljes app telepítés",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Frissítések keresése",
 | 
					    "checkingForUpdatesNotifChannel": "Frissítések keresése",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Eltávolítja az alkalmazást?",
 | 
					        "one": "Eltávolítja az alkalmazást?",
 | 
				
			||||||
        "other": "Eltávolítja az alkalmazást?"
 | 
					        "other": "Eltávolítja az alkalmazást?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
 | 
					    "completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
 | 
					    "checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Rimuovere l'app?",
 | 
					        "one": "Rimuovere l'app?",
 | 
				
			||||||
        "other": "Rimuovere le app?"
 | 
					        "other": "Rimuovere le app?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "アプリのインストールを完了する",
 | 
					    "completeAppInstallationNotifChannel": "アプリのインストールを完了する",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "アップデートを確認中",
 | 
					    "checkingForUpdatesNotifChannel": "アップデートを確認中",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "インストール済みのアプリと「追跡のみ」のアプリのアップデートのみを確認する",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "インストール済みのアプリと「追跡のみ」のアプリのアップデートのみを確認する",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "アプリを削除しますか?",
 | 
					        "one": "アプリを削除しますか?",
 | 
				
			||||||
        "other": "アプリを削除しますか?"
 | 
					        "other": "アプリを削除しますか?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
 | 
					    "completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Controleren op updates",
 | 
					    "checkingForUpdatesNotifChannel": "Controleren op updates",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "App verwijderen?",
 | 
					        "one": "App verwijderen?",
 | 
				
			||||||
        "other": "Apps verwijderen?"
 | 
					        "other": "Apps verwijderen?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Ukończenie instalacji aplikacji",
 | 
					    "completeAppInstallationNotifChannel": "Ukończenie instalacji aplikacji",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Sprawdzanie dostępności aktualizacji",
 | 
					    "checkingForUpdatesNotifChannel": "Sprawdzanie dostępności aktualizacji",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Sprawdzaj tylko zainstalowane i obserwowane aplikacje pod kątem aktualizacji",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Sprawdzaj tylko zainstalowane i obserwowane aplikacje pod kątem aktualizacji",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Usunąć aplikację?",
 | 
					        "one": "Usunąć aplikację?",
 | 
				
			||||||
        "few": "Usunąć aplikacje?",
 | 
					        "few": "Usunąć aplikacje?",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Instalação completa do App",
 | 
					    "completeAppInstallationNotifChannel": "Instalação completa do App",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Checando por Atualizações",
 | 
					    "checkingForUpdatesNotifChannel": "Checando por Atualizações",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Remover App?",
 | 
					        "one": "Remover App?",
 | 
				
			||||||
        "other": "Remover Apps?"
 | 
					        "other": "Remover Apps?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Завершение установки приложения",
 | 
					    "completeAppInstallationNotifChannel": "Завершение установки приложения",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Проверка обновлений",
 | 
					    "checkingForUpdatesNotifChannel": "Проверка обновлений",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Удалить приложение?",
 | 
					        "one": "Удалить приложение?",
 | 
				
			||||||
        "other": "Удалить приложения?"
 | 
					        "other": "Удалить приложения?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Uygulama Kurulumu Tamamlandı",
 | 
					    "completeAppInstallationNotifChannel": "Uygulama Kurulumu Tamamlandı",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Güncellemeler Kontrol Ediliyor",
 | 
					    "checkingForUpdatesNotifChannel": "Güncellemeler Kontrol Ediliyor",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Yalnızca yüklü ve Yalnızca İzleme Uygulamalarını güncelleme",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Yalnızca yüklü ve Yalnızca İzleme Uygulamalarını güncelleme",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "Uygulamayı Kaldır?",
 | 
					        "one": "Uygulamayı Kaldır?",
 | 
				
			||||||
        "other": "Uygulamaları Kaldır?"
 | 
					        "other": "Uygulamaları Kaldır?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
 | 
					    "completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
 | 
					    "checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion":{
 | 
					    "removeAppQuestion":{
 | 
				
			||||||
        "one": "Gỡ ứng dụng?",
 | 
					        "one": "Gỡ ứng dụng?",
 | 
				
			||||||
        "other": "Gỡ ứng dụng?"
 | 
					        "other": "Gỡ ứng dụng?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,7 @@
 | 
				
			|||||||
    "completeAppInstallationNotifChannel": "完成应用安装",
 | 
					    "completeAppInstallationNotifChannel": "完成应用安装",
 | 
				
			||||||
    "checkingForUpdatesNotifChannel": "正在检查更新",
 | 
					    "checkingForUpdatesNotifChannel": "正在检查更新",
 | 
				
			||||||
    "onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查",
 | 
					    "onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查",
 | 
				
			||||||
 | 
					    "fixedAPKURL": "APK URL is fixed",
 | 
				
			||||||
    "removeAppQuestion": {
 | 
					    "removeAppQuestion": {
 | 
				
			||||||
        "one": "是否删除应用?",
 | 
					        "one": "是否删除应用?",
 | 
				
			||||||
        "other": "是否删除应用?"
 | 
					        "other": "是否删除应用?"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import 'package:html/parser.dart';
 | 
				
			|||||||
import 'package:http/http.dart';
 | 
					import 'package:http/http.dart';
 | 
				
			||||||
import 'package:obtainium/components/generated_form.dart';
 | 
					import 'package:obtainium/components/generated_form.dart';
 | 
				
			||||||
import 'package:obtainium/custom_errors.dart';
 | 
					import 'package:obtainium/custom_errors.dart';
 | 
				
			||||||
 | 
					import 'package:obtainium/providers/apps_provider.dart';
 | 
				
			||||||
import 'package:obtainium/providers/source_provider.dart';
 | 
					import 'package:obtainium/providers/source_provider.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
 | 
					String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
 | 
				
			||||||
@@ -94,6 +95,7 @@ class HTML extends AppSource {
 | 
				
			|||||||
            label: tr('sortByFileNamesNotLinks'))
 | 
					            label: tr('sortByFileNamesNotLinks'))
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      [GeneratedFormSwitch('reverseSort', label: tr('reverseSort'))],
 | 
					      [GeneratedFormSwitch('reverseSort', label: tr('reverseSort'))],
 | 
				
			||||||
 | 
					      [GeneratedFormSwitch('fixedAPKURL', label: tr('fixedAPKURL'))],
 | 
				
			||||||
      [
 | 
					      [
 | 
				
			||||||
        GeneratedFormTextField('customLinkFilterRegex',
 | 
					        GeneratedFormTextField('customLinkFilterRegex',
 | 
				
			||||||
            label: tr('customLinkFilterRegex'),
 | 
					            label: tr('customLinkFilterRegex'),
 | 
				
			||||||
@@ -222,7 +224,10 @@ class HTML extends AppSource {
 | 
				
			|||||||
        throw NoReleasesError();
 | 
					        throw NoReleasesError();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var rel = links.last;
 | 
					      var rel = links.last;
 | 
				
			||||||
      String? version = rel.hashCode.toString();
 | 
					      String? version;
 | 
				
			||||||
 | 
					      if (additionalSettings['fixedAPKURL'] != true) {
 | 
				
			||||||
 | 
					        version = rel.hashCode.toString();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      var versionExtractionRegEx =
 | 
					      var versionExtractionRegEx =
 | 
				
			||||||
          additionalSettings['versionExtractionRegEx'] as String?;
 | 
					          additionalSettings['versionExtractionRegEx'] as String?;
 | 
				
			||||||
      if (versionExtractionRegEx?.isNotEmpty == true) {
 | 
					      if (versionExtractionRegEx?.isNotEmpty == true) {
 | 
				
			||||||
@@ -243,9 +248,9 @@ class HTML extends AppSource {
 | 
				
			|||||||
          throw NoVersionError();
 | 
					          throw NoVersionError();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      List<String> apkUrls =
 | 
					      rel = ensureAbsoluteUrl(rel, uri);
 | 
				
			||||||
          [rel].map((e) => ensureAbsoluteUrl(e, uri)).toList();
 | 
					      version ??= (await checkDownloadHash(rel)).toString();
 | 
				
			||||||
      return APKDetails(version!, apkUrls.map((e) => MapEntry(e, e)).toList(),
 | 
					      return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
 | 
				
			||||||
          AppNames(uri.host, tr('app')));
 | 
					          AppNames(uri.host, tr('app')));
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw getObtainiumHttpError(res);
 | 
					      throw getObtainiumHttpError(res);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import 'dart:convert';
 | 
				
			|||||||
import 'dart:io';
 | 
					import 'dart:io';
 | 
				
			||||||
import 'dart:math';
 | 
					import 'dart:math';
 | 
				
			||||||
import 'package:http/http.dart' as http;
 | 
					import 'package:http/http.dart' as http;
 | 
				
			||||||
 | 
					import 'package:crypto/crypto.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
 | 
					import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
 | 
				
			||||||
import 'package:android_intent_plus/flag.dart';
 | 
					import 'package:android_intent_plus/flag.dart';
 | 
				
			||||||
@@ -139,6 +140,100 @@ List<MapEntry<String, int>> moveStrToEndMapEntryWithCount(
 | 
				
			|||||||
  return arr;
 | 
					  return arr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Future<File> downloadFileWithRetry(
 | 
				
			||||||
 | 
					    String url, String fileNameNoExt, Function? onProgress, String destDir,
 | 
				
			||||||
 | 
					    {bool useExisting = true,
 | 
				
			||||||
 | 
					    Map<String, String>? headers,
 | 
				
			||||||
 | 
					    int retries = 3}) async {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return await downloadFile(url, fileNameNoExt, onProgress, destDir,
 | 
				
			||||||
 | 
					        useExisting: useExisting, headers: headers);
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    if (retries > 0 && e is ClientException) {
 | 
				
			||||||
 | 
					      await Future.delayed(const Duration(seconds: 5));
 | 
				
			||||||
 | 
					      return await downloadFileWithRetry(
 | 
				
			||||||
 | 
					          url, fileNameNoExt, onProgress, destDir,
 | 
				
			||||||
 | 
					          useExisting: useExisting, headers: headers, retries: (retries - 1));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      rethrow;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String hashListOfLists(List<List<int>> data) {
 | 
				
			||||||
 | 
					  var bytes = utf8.encode(jsonEncode(data));
 | 
				
			||||||
 | 
					  var digest = sha256.convert(bytes);
 | 
				
			||||||
 | 
					  var hash = digest.toString();
 | 
				
			||||||
 | 
					  return hash.hashCode.toString();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Future<String> checkDownloadHash(String url,
 | 
				
			||||||
 | 
					    {int bytesToGrab = 1024, Map<String, String>? headers}) async {
 | 
				
			||||||
 | 
					  var req = Request('GET', Uri.parse(url));
 | 
				
			||||||
 | 
					  if (headers != null) {
 | 
				
			||||||
 | 
					    req.headers.addAll(headers);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  req.headers[HttpHeaders.rangeHeader] = 'bytes=0-$bytesToGrab';
 | 
				
			||||||
 | 
					  var client = http.Client();
 | 
				
			||||||
 | 
					  var response = await client.send(req);
 | 
				
			||||||
 | 
					  if (response.statusCode < 200 || response.statusCode > 299) {
 | 
				
			||||||
 | 
					    throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  List<List<int>> bytes = await response.stream.take(bytesToGrab).toList();
 | 
				
			||||||
 | 
					  return hashListOfLists(bytes);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Future<File> downloadFile(
 | 
				
			||||||
 | 
					    String url, String fileNameNoExt, Function? onProgress, String destDir,
 | 
				
			||||||
 | 
					    {bool useExisting = true, Map<String, String>? headers}) async {
 | 
				
			||||||
 | 
					  var req = Request('GET', Uri.parse(url));
 | 
				
			||||||
 | 
					  if (headers != null) {
 | 
				
			||||||
 | 
					    req.headers.addAll(headers);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  var client = http.Client();
 | 
				
			||||||
 | 
					  StreamedResponse response = await client.send(req);
 | 
				
			||||||
 | 
					  String ext =
 | 
				
			||||||
 | 
					      response.headers['content-disposition']?.split('.').last ?? 'apk';
 | 
				
			||||||
 | 
					  if (ext.endsWith('"') || ext.endsWith("other")) {
 | 
				
			||||||
 | 
					    ext = ext.substring(0, ext.length - 1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
 | 
				
			||||||
 | 
					    ext = 'apk';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
 | 
				
			||||||
 | 
					  if (!(downloadedFile.existsSync() && useExisting)) {
 | 
				
			||||||
 | 
					    File tempDownloadedFile = File('${downloadedFile.path}.part');
 | 
				
			||||||
 | 
					    if (tempDownloadedFile.existsSync()) {
 | 
				
			||||||
 | 
					      tempDownloadedFile.deleteSync(recursive: true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    var length = response.contentLength;
 | 
				
			||||||
 | 
					    var received = 0;
 | 
				
			||||||
 | 
					    double? progress;
 | 
				
			||||||
 | 
					    var sink = tempDownloadedFile.openWrite();
 | 
				
			||||||
 | 
					    await response.stream.map((s) {
 | 
				
			||||||
 | 
					      received += s.length;
 | 
				
			||||||
 | 
					      progress = (length != null ? received / length * 100 : 30);
 | 
				
			||||||
 | 
					      if (onProgress != null) {
 | 
				
			||||||
 | 
					        onProgress(progress);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return s;
 | 
				
			||||||
 | 
					    }).pipe(sink);
 | 
				
			||||||
 | 
					    await sink.close();
 | 
				
			||||||
 | 
					    progress = null;
 | 
				
			||||||
 | 
					    if (onProgress != null) {
 | 
				
			||||||
 | 
					      onProgress(progress);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (response.statusCode != 200) {
 | 
				
			||||||
 | 
					      tempDownloadedFile.deleteSync(recursive: true);
 | 
				
			||||||
 | 
					      throw response.reasonPhrase ?? tr('unexpectedError');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    tempDownloadedFile.renameSync(downloadedFile.path);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    client.close();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return downloadedFile;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AppsProvider with ChangeNotifier {
 | 
					class AppsProvider with ChangeNotifier {
 | 
				
			||||||
  // In memory App state (should always be kept in sync with local storage versions)
 | 
					  // In memory App state (should always be kept in sync with local storage versions)
 | 
				
			||||||
  Map<String, AppInMemory> apps = {};
 | 
					  Map<String, AppInMemory> apps = {};
 | 
				
			||||||
@@ -192,77 +287,6 @@ class AppsProvider with ChangeNotifier {
 | 
				
			|||||||
    }();
 | 
					    }();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<File> downloadFileWithRetry(
 | 
					 | 
				
			||||||
      String url, String fileNameNoExt, Function? onProgress,
 | 
					 | 
				
			||||||
      {bool useExisting = true,
 | 
					 | 
				
			||||||
      Map<String, String>? headers,
 | 
					 | 
				
			||||||
      int retries = 3}) async {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      return await downloadFile(url, fileNameNoExt, onProgress,
 | 
					 | 
				
			||||||
          useExisting: useExisting, headers: headers);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (retries > 0 && e is ClientException) {
 | 
					 | 
				
			||||||
        await Future.delayed(const Duration(seconds: 5));
 | 
					 | 
				
			||||||
        return await downloadFileWithRetry(url, fileNameNoExt, onProgress,
 | 
					 | 
				
			||||||
            useExisting: useExisting, headers: headers, retries: (retries - 1));
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        rethrow;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<File> downloadFile(
 | 
					 | 
				
			||||||
      String url, String fileNameNoExt, Function? onProgress,
 | 
					 | 
				
			||||||
      {bool useExisting = true, Map<String, String>? headers}) async {
 | 
					 | 
				
			||||||
    var destDir = APKDir.path;
 | 
					 | 
				
			||||||
    var req = Request('GET', Uri.parse(url));
 | 
					 | 
				
			||||||
    if (headers != null) {
 | 
					 | 
				
			||||||
      req.headers.addAll(headers);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var client = http.Client();
 | 
					 | 
				
			||||||
    StreamedResponse response = await client.send(req);
 | 
					 | 
				
			||||||
    String ext =
 | 
					 | 
				
			||||||
        response.headers['content-disposition']?.split('.').last ?? 'apk';
 | 
					 | 
				
			||||||
    if (ext.endsWith('"') || ext.endsWith("other")) {
 | 
					 | 
				
			||||||
      ext = ext.substring(0, ext.length - 1);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
 | 
					 | 
				
			||||||
      ext = 'apk';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
 | 
					 | 
				
			||||||
    if (!(downloadedFile.existsSync() && useExisting)) {
 | 
					 | 
				
			||||||
      File tempDownloadedFile = File('${downloadedFile.path}.part');
 | 
					 | 
				
			||||||
      if (tempDownloadedFile.existsSync()) {
 | 
					 | 
				
			||||||
        tempDownloadedFile.deleteSync(recursive: true);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      var length = response.contentLength;
 | 
					 | 
				
			||||||
      var received = 0;
 | 
					 | 
				
			||||||
      double? progress;
 | 
					 | 
				
			||||||
      var sink = tempDownloadedFile.openWrite();
 | 
					 | 
				
			||||||
      await response.stream.map((s) {
 | 
					 | 
				
			||||||
        received += s.length;
 | 
					 | 
				
			||||||
        progress = (length != null ? received / length * 100 : 30);
 | 
					 | 
				
			||||||
        if (onProgress != null) {
 | 
					 | 
				
			||||||
          onProgress(progress);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return s;
 | 
					 | 
				
			||||||
      }).pipe(sink);
 | 
					 | 
				
			||||||
      await sink.close();
 | 
					 | 
				
			||||||
      progress = null;
 | 
					 | 
				
			||||||
      if (onProgress != null) {
 | 
					 | 
				
			||||||
        onProgress(progress);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (response.statusCode != 200) {
 | 
					 | 
				
			||||||
        tempDownloadedFile.deleteSync(recursive: true);
 | 
					 | 
				
			||||||
        throw response.reasonPhrase ?? tr('unexpectedError');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      tempDownloadedFile.renameSync(downloadedFile.path);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      client.close();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return downloadedFile;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<File> handleAPKIDChange(App app, PackageInfo? newInfo,
 | 
					  Future<File> handleAPKIDChange(App app, PackageInfo? newInfo,
 | 
				
			||||||
      File downloadedFile, String downloadUrl) async {
 | 
					      File downloadedFile, String downloadUrl) async {
 | 
				
			||||||
    // If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
 | 
					    // If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
 | 
				
			||||||
@@ -322,7 +346,7 @@ class AppsProvider with ChangeNotifier {
 | 
				
			|||||||
          notificationsProvider?.notify(notif);
 | 
					          notificationsProvider?.notify(notif);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        prevProg = prog;
 | 
					        prevProg = prog;
 | 
				
			||||||
      });
 | 
					      }, APKDir.path);
 | 
				
			||||||
      // Set to 90 for remaining steps, will make null in 'finally'
 | 
					      // Set to 90 for remaining steps, will make null in 'finally'
 | 
				
			||||||
      if (apps[app.id] != null) {
 | 
					      if (apps[app.id] != null) {
 | 
				
			||||||
        apps[app.id]!.downloadProgress = -1;
 | 
					        apps[app.id]!.downloadProgress = -1;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -147,7 +147,7 @@ packages:
 | 
				
			|||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.3.3+6"
 | 
					    version: "0.3.3+6"
 | 
				
			||||||
  crypto:
 | 
					  crypto:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: crypto
 | 
					      name: crypto
 | 
				
			||||||
      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
 | 
					      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,6 +66,7 @@ dependencies:
 | 
				
			|||||||
  hsluv: ^1.1.3
 | 
					  hsluv: ^1.1.3
 | 
				
			||||||
  connectivity_plus: ^5.0.0
 | 
					  connectivity_plus: ^5.0.0
 | 
				
			||||||
  shared_storage: ^0.8.0
 | 
					  shared_storage: ^0.8.0
 | 
				
			||||||
 | 
					  crypto: ^3.0.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev_dependencies:
 | 
					dev_dependencies:
 | 
				
			||||||
  flutter_test:
 | 
					  flutter_test:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user