mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-03 23:03:29 +01:00 
			
		
		
		
	Compare commits
	
		
			63 Commits
		
	
	
		
			v0.14.22-b
			...
			v0.14.25-b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					547c2c8c0d | ||
| 
						 | 
					7453d19d27 | ||
| 
						 | 
					2a0b68d637 | ||
| 
						 | 
					02f374fdf0 | ||
| 
						 | 
					3536dcd775 | ||
| 
						 | 
					6fa887e536 | ||
| 
						 | 
					71755763b9 | ||
| 
						 | 
					5aabcccfd4 | ||
| 
						 | 
					1e009e2211 | ||
| 
						 | 
					811bc6434b | ||
| 
						 | 
					a5b2d06742 | ||
| 
						 | 
					2c00674835 | ||
| 
						 | 
					9bbff1f436 | ||
| 
						 | 
					c75416f9be | ||
| 
						 | 
					2cb750a7b0 | ||
| 
						 | 
					abfec51964 | ||
| 
						 | 
					e314717a3e | ||
| 
						 | 
					290a78acb5 | ||
| 
						 | 
					b40a4dd243 | ||
| 
						 | 
					b38f6d0581 | ||
| 
						 | 
					5c562a6bdb | ||
| 
						 | 
					238d43f916 | ||
| 
						 | 
					818dc23089 | ||
| 
						 | 
					ddb50940ce | ||
| 
						 | 
					96fb97f4ef | ||
| 
						 | 
					6c0459c2ae | ||
| 
						 | 
					30a9e1ee4b | ||
| 
						 | 
					15a1f1e6e0 | ||
| 
						 | 
					827e2b161c | ||
| 
						 | 
					e95230859e | ||
| 
						 | 
					95cb8ca493 | ||
| 
						 | 
					5735293efb | ||
| 
						 | 
					035df1f338 | ||
| 
						 | 
					36a8b04c4f | ||
| 
						 | 
					3d5411ef28 | ||
| 
						 | 
					df7b74e727 | ||
| 
						 | 
					0138599120 | ||
| 
						 | 
					66446cbffc | ||
| 
						 | 
					ef40f3900f | ||
| 
						 | 
					ce259764a8 | ||
| 
						 | 
					65187cb17b | ||
| 
						 | 
					8f25245ad1 | ||
| 
						 | 
					58b8baa019 | ||
| 
						 | 
					ba1db88c8d | ||
| 
						 | 
					c8f2f42a35 | ||
| 
						 | 
					4e5a9b2af5 | ||
| 
						 | 
					d1ab961c14 | ||
| 
						 | 
					33caf4644e | ||
| 
						 | 
					f34ffe18a8 | ||
| 
						 | 
					4b1c5e111d | ||
| 
						 | 
					dbd7c296c6 | ||
| 
						 | 
					df93cbde8f | ||
| 
						 | 
					d8cbc121a7 | ||
| 
						 | 
					8163cd5c8f | ||
| 
						 | 
					e9e9adb174 | ||
| 
						 | 
					21fdfc1eef | ||
| 
						 | 
					4304251e1e | ||
| 
						 | 
					ae0cd74b0e | ||
| 
						 | 
					6935bff244 | ||
| 
						 | 
					102b9da617 | ||
| 
						 | 
					8bd0d185ae | ||
| 
						 | 
					7bfc5ae0a8 | ||
| 
						 | 
					c72a41db9d | 
							
								
								
									
										70
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
name: Build and Release
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
    
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: subosito/flutter-action@v2
 | 
			
		||||
 | 
			
		||||
      - name: Import GPG key
 | 
			
		||||
        id: import_pgp_key
 | 
			
		||||
        uses: crazy-max/ghaction-import-gpg@v6
 | 
			
		||||
        with:
 | 
			
		||||
          gpg_private_key: ${{ secrets.PGP_KEY_BASE64 }}
 | 
			
		||||
          passphrase: ${{ secrets.PGP_PASSPHRASE }}
 | 
			
		||||
 | 
			
		||||
      - name: Build APKs
 | 
			
		||||
        run: |
 | 
			
		||||
          sed -i 's/signingConfig signingConfigs.release//g' android/app/build.gradle
 | 
			
		||||
          flutter build apk && flutter build apk --split-per-abi
 | 
			
		||||
          rm ./build/app/outputs/flutter-apk/*.sha1
 | 
			
		||||
          ls -l ./build/app/outputs/flutter-apk/
 | 
			
		||||
        
 | 
			
		||||
      - name: Sign APKs
 | 
			
		||||
        env:
 | 
			
		||||
          KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
 | 
			
		||||
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
 | 
			
		||||
          PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "${KEYSTORE_BASE64}" | base64 -d > apksign.keystore
 | 
			
		||||
          for apk in ./build/app/outputs/flutter-apk/*-release*.apk; do
 | 
			
		||||
            unsignedFn=${apk/-release/-unsigned}
 | 
			
		||||
            mv "$apk" "$unsignedFn"
 | 
			
		||||
            ${ANDROID_HOME}/build-tools/30.0.2/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
 | 
			
		||||
            sha256sum ${apk} | cut -d " " -f 1 > "$apk".sha256
 | 
			
		||||
            gpg --batch  --pinentry-mode loopback --passphrase "${PGP_PASSPHRASE}" --sign --detach-sig "$apk".sha256
 | 
			
		||||
          done
 | 
			
		||||
          rm apksign.keystore
 | 
			
		||||
          PGP_KEY_FINGERPRINT="${{ steps.import_pgp_key.outputs.fingerprint }}"
 | 
			
		||||
        
 | 
			
		||||
      - name: Extract Version
 | 
			
		||||
        id: extract_version      
 | 
			
		||||
        run: |
 | 
			
		||||
           VERSION=$(grep -oP "currentVersion = '\K[^']+" lib/main.dart)
 | 
			
		||||
           echo "version=$VERSION" >> $GITHUB_OUTPUT
 | 
			
		||||
           TAG=$(grep -oP "'.*\\\$currentVersion.*'" lib/main.dart | head -c -2 | tail -c +2 | sed "s/\$currentVersion/$VERSION/g")
 | 
			
		||||
           echo "tag=$TAG" >> $GITHUB_OUTPUT
 | 
			
		||||
           if [ -n "$(echo $TAG | grep -oP '\-beta$')" ]; then BETA=true; else BETA=false; fi
 | 
			
		||||
           echo "beta=$BETA" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Create Tag
 | 
			
		||||
        uses: mathieudutour/github-tag-action@v6.1
 | 
			
		||||
        with:
 | 
			
		||||
          github_token: ${{ secrets.GH_ACCESS_TOKEN }}
 | 
			
		||||
          custom_tag: "${{ steps.extract_version.outputs.tag }}"
 | 
			
		||||
          tag_prefix: ""
 | 
			
		||||
      
 | 
			
		||||
      - name: Create Release And Upload APKs
 | 
			
		||||
        uses: ncipollo/release-action@v1
 | 
			
		||||
        with:
 | 
			
		||||
          token: ${{ secrets.GH_ACCESS_TOKEN }}
 | 
			
		||||
          tag: "${{ steps.extract_version.outputs.tag }}"
 | 
			
		||||
          prerelease: "${{ steps.extract_version.outputs.beta }}"
 | 
			
		||||
          artifacts: ./build/app/outputs/flutter-apk/*-release*.apk*
 | 
			
		||||
          generateReleaseNotes: true
 | 
			
		||||
@@ -122,15 +122,15 @@
 | 
			
		||||
    "ascending": "Aufsteigend",
 | 
			
		||||
    "descending": "Absteigend",
 | 
			
		||||
    "bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
 | 
			
		||||
    "neverManualOnly": "Nie - nur manuell",
 | 
			
		||||
    "neverManualOnly": "Nie – nur manuell",
 | 
			
		||||
    "appearance": "Aussehen",
 | 
			
		||||
    "showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen",
 | 
			
		||||
    "pinUpdates": "Apps mit Aktualisierungen oben anheften",
 | 
			
		||||
    "updates": "Aktualisierungen",
 | 
			
		||||
    "sourceSpecific": "Quellenspezifisch",
 | 
			
		||||
    "appSource": "App-Quelle",
 | 
			
		||||
    "noLogs": "Keine Protokolle",
 | 
			
		||||
    "appLogs": "App Protokolle",
 | 
			
		||||
    "noLogs": "Keine Logs",
 | 
			
		||||
    "appLogs": "App-Logs",
 | 
			
		||||
    "close": "Schließen",
 | 
			
		||||
    "share": "Teilen",
 | 
			
		||||
    "appNotFound": "App nicht gefunden",
 | 
			
		||||
@@ -256,25 +256,25 @@
 | 
			
		||||
    "filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern",
 | 
			
		||||
    "trySelectingSuggestedVersionCode": "Versuchen, die vorgeschlagene APK-Code-Version auszuwählen",
 | 
			
		||||
    "dontSortReleasesList": "Retain release order from API",
 | 
			
		||||
    "reverseSort": "Reverse sorting",
 | 
			
		||||
    "debugMenu": "Debug Menu",
 | 
			
		||||
    "bgTaskStarted": "Background task started - check logs.",
 | 
			
		||||
    "runBgCheckNow": "Run Background Update Check Now",
 | 
			
		||||
    "reverseSort": "Umgekehrtes Sortieren",
 | 
			
		||||
    "debugMenu": "Debug Menü",
 | 
			
		||||
    "bgTaskStarted": "Hintergrundaufgabe gestartet – Logs prüfen.",
 | 
			
		||||
    "runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "App entfernen?",
 | 
			
		||||
        "other": "Apps entfernen?"
 | 
			
		||||
    },
 | 
			
		||||
    "tooManyRequestsTryAgainInMinutes": {
 | 
			
		||||
        "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
 | 
			
		||||
        "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"
 | 
			
		||||
        "one": "Zu viele Anfragen (Rate begrenzt) – versuchen Sie es in {} Minute erneut",
 | 
			
		||||
        "other": "Zu viele Anfragen (Rate begrenzt) – versuchen Sie es in {} Minuten erneut"
 | 
			
		||||
    },
 | 
			
		||||
    "bgUpdateGotErrorRetryInMinutes": {
 | 
			
		||||
        "one": "Bei der Aktualisierungsprüfung im Hintergrund wurde ein {} festgestellt, eine erneute Prüfung wird in {} Minute geplant",
 | 
			
		||||
        "other": "Bei der Aktualisierungsprüfung im Hintergrund wurde ein {} festgestellt, eine erneute Prüfung wird in {} Minuten geplant"
 | 
			
		||||
    },
 | 
			
		||||
    "bgCheckFoundUpdatesWillNotifyIfNeeded": {
 | 
			
		||||
        "one": "Hintergrundaktualisierungsprüfung fand {} Aktualisierung - benachrichtigt den Benutzer, falls erforderlich",
 | 
			
		||||
        "other": "Hintergrundaktualisierungsprüfung fand {} Aktualisierungen - benachrichtigt den Benutzer, falls erforderlich"
 | 
			
		||||
        "one": "Die Hintergrundaktualisierungsprüfung fand {} Aktualisierung – benachrichtigt den Benutzer, falls erforderlich",
 | 
			
		||||
        "other": "Die Hintergrundaktualisierungsprüfung fand {} Aktualisierungen – benachrichtigt den Benutzer, falls erforderlich"
 | 
			
		||||
    },
 | 
			
		||||
    "apps": {
 | 
			
		||||
        "one": "{} App",
 | 
			
		||||
@@ -297,8 +297,8 @@
 | 
			
		||||
        "other": "{} Tage"
 | 
			
		||||
    },
 | 
			
		||||
    "clearedNLogsBeforeXAfterY": {
 | 
			
		||||
        "one": "{n} Protokoll gelöscht (vorher = {vorher}, nachher = {nachher})",
 | 
			
		||||
        "other": "{n} Protokolle gelöscht (vorher = {vorher}, nachher = {nachher})"
 | 
			
		||||
        "one": "{n} Log gelöscht (vorher = {vorher}, nachher = {nachher})",
 | 
			
		||||
        "other": "{n} Logs gelöscht (vorher = {vorher}, nachher = {nachher})"
 | 
			
		||||
    },
 | 
			
		||||
    "xAndNMoreUpdatesAvailable": {
 | 
			
		||||
        "one": "{} und 1 weitere App haben Aktualisierungen.",
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ class FDroid extends AppSource {
 | 
			
		||||
  FDroid() {
 | 
			
		||||
    host = 'f-droid.org';
 | 
			
		||||
    name = tr('fdroid');
 | 
			
		||||
    naiveStandardVersionDetection = true;
 | 
			
		||||
    canSearch = true;
 | 
			
		||||
    additionalSourceAppSpecificSettingFormItems = [
 | 
			
		||||
      [
 | 
			
		||||
 
 | 
			
		||||
@@ -120,14 +120,14 @@ class HTML extends AppSource {
 | 
			
		||||
        GeneratedFormTextField('matchGroupToUse',
 | 
			
		||||
            label: tr('matchGroupToUse'),
 | 
			
		||||
            required: false,
 | 
			
		||||
            hint: '1',
 | 
			
		||||
            hint: '0',
 | 
			
		||||
            textInputType: const TextInputType.numberWithOptions(),
 | 
			
		||||
            additionalValidators: [
 | 
			
		||||
              (value) {
 | 
			
		||||
                if (value?.isEmpty == true) {
 | 
			
		||||
                  value = null;
 | 
			
		||||
                }
 | 
			
		||||
                value ??= '1';
 | 
			
		||||
                value ??= '0';
 | 
			
		||||
                return intValidator(value);
 | 
			
		||||
              }
 | 
			
		||||
            ])
 | 
			
		||||
@@ -216,8 +216,12 @@ class HTML extends AppSource {
 | 
			
		||||
        if (match.isEmpty) {
 | 
			
		||||
          throw NoVersionError();
 | 
			
		||||
        }
 | 
			
		||||
        version = match.last
 | 
			
		||||
            .group(int.parse(additionalSettings['matchGroupToUse'] as String));
 | 
			
		||||
        String matchGroupString =
 | 
			
		||||
            (additionalSettings['matchGroupToUse'] as String).trim();
 | 
			
		||||
        if (matchGroupString.isEmpty) {
 | 
			
		||||
          matchGroupString = "0";
 | 
			
		||||
        }
 | 
			
		||||
        version = match.last.group(int.parse(matchGroupString));
 | 
			
		||||
        if (version?.isEmpty == true) {
 | 
			
		||||
          throw NoVersionError();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -80,4 +80,20 @@ class Uptodown extends AppSource {
 | 
			
		||||
        version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName),
 | 
			
		||||
        releaseDate: relDate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<String> apkUrlPrefetchModifier(
 | 
			
		||||
      String apkUrl, String standardUrl) async {
 | 
			
		||||
    var res = await sourceRequest(apkUrl);
 | 
			
		||||
    if (res.statusCode != 200) {
 | 
			
		||||
      throw getObtainiumHttpError(res);
 | 
			
		||||
    }
 | 
			
		||||
    var html = parse(res.body);
 | 
			
		||||
    var finalUrl =
 | 
			
		||||
        (html.querySelector('.post-download')?.attributes['data-url']);
 | 
			
		||||
    if (finalUrl == null) {
 | 
			
		||||
      throw NoAPKError();
 | 
			
		||||
    }
 | 
			
		||||
    return finalUrl;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,14 +16,13 @@ class WhatsApp extends AppSource {
 | 
			
		||||
  @override
 | 
			
		||||
  Future<String> apkUrlPrefetchModifier(
 | 
			
		||||
      String apkUrl, String standardUrl) async {
 | 
			
		||||
    Response res = await sourceRequest('https://www.whatsapp.com/android');
 | 
			
		||||
    Response res = await sourceRequest('$standardUrl/android');
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
      var targetLinks = parse(res.body)
 | 
			
		||||
          .querySelectorAll('a')
 | 
			
		||||
          .map((e) => e.attributes['href'] ?? '')
 | 
			
		||||
          .where((e) => e.isNotEmpty)
 | 
			
		||||
          .where((e) =>
 | 
			
		||||
              e.contains('content.whatsapp.net') && e.contains('WhatsApp.apk'))
 | 
			
		||||
          .where((e) => e.contains('WhatsApp.apk'))
 | 
			
		||||
          .toList();
 | 
			
		||||
      if (targetLinks.isEmpty) {
 | 
			
		||||
        throw NoAPKError();
 | 
			
		||||
@@ -39,37 +38,16 @@ class WhatsApp extends AppSource {
 | 
			
		||||
    String standardUrl,
 | 
			
		||||
    Map<String, dynamic> additionalSettings,
 | 
			
		||||
  ) async {
 | 
			
		||||
    Response res = await sourceRequest('https://www.whatsapp.com/android');
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
      var targetElements = parse(res.body)
 | 
			
		||||
          .querySelectorAll('p')
 | 
			
		||||
          .where((element) => element.innerHtml.contains('Version '))
 | 
			
		||||
          .toList();
 | 
			
		||||
      if (targetElements.isEmpty) {
 | 
			
		||||
        throw NoVersionError();
 | 
			
		||||
      }
 | 
			
		||||
      var vLines = targetElements[0]
 | 
			
		||||
          .innerHtml
 | 
			
		||||
          .split('\n')
 | 
			
		||||
          .where((element) => element.contains('Version '))
 | 
			
		||||
          .toList();
 | 
			
		||||
      if (vLines.isEmpty) {
 | 
			
		||||
        throw NoVersionError();
 | 
			
		||||
      }
 | 
			
		||||
      var versionMatch = RegExp('[0-9]+(\\.[0-9]+)+').firstMatch(vLines[0]);
 | 
			
		||||
      if (versionMatch == null) {
 | 
			
		||||
        throw NoVersionError();
 | 
			
		||||
      }
 | 
			
		||||
      String version =
 | 
			
		||||
          vLines[0].substring(versionMatch.start, versionMatch.end);
 | 
			
		||||
      return APKDetails(
 | 
			
		||||
          version,
 | 
			
		||||
          getApkUrlsFromUrls([
 | 
			
		||||
            'https://www.whatsapp.com/android?v=$version&=thisIsaPlaceholder&a=realURLPrefetchedAtDownloadTime'
 | 
			
		||||
          ]),
 | 
			
		||||
          AppNames('Meta', 'WhatsApp'));
 | 
			
		||||
    } else {
 | 
			
		||||
      throw getObtainiumHttpError(res);
 | 
			
		||||
    }
 | 
			
		||||
    // This is a CDN link that is consistent per version
 | 
			
		||||
    // But it has query params that change constantly
 | 
			
		||||
    Uri apkUri =
 | 
			
		||||
        Uri.parse(await apkUrlPrefetchModifier(standardUrl, standardUrl));
 | 
			
		||||
    var unusableApkUrl = '${apkUri.origin}/${apkUri.path}';
 | 
			
		||||
    // So we use the param-less URL is a pseudo-version to add the app and check for updates
 | 
			
		||||
    // See #357 for why we can't scrape the version number directly
 | 
			
		||||
    // But we re-fetch the URL again with its latest query params at the actual download time
 | 
			
		||||
    String version = unusableApkUrl.hashCode.toString();
 | 
			
		||||
    return APKDetails(version, getApkUrlsFromUrls([unusableApkUrl]),
 | 
			
		||||
        AppNames('Meta', 'WhatsApp'));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:android_package_installer/android_package_installer.dart';
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@@ -65,25 +67,38 @@ class NotImplementedError extends ObtainiumError {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MultiAppMultiError extends ObtainiumError {
 | 
			
		||||
  Map<String, List<String>> content = {};
 | 
			
		||||
  Map<String, dynamic> rawErrors = {};
 | 
			
		||||
  Map<String, List<String>> idsByErrorString = {};
 | 
			
		||||
  Map<String, String> appIdNames = {};
 | 
			
		||||
 | 
			
		||||
  MultiAppMultiError() : super(tr('placeholder'), unexpected: true);
 | 
			
		||||
 | 
			
		||||
  add(String appId, String string) {
 | 
			
		||||
    var tempIds = content.remove(string);
 | 
			
		||||
  add(String appId, dynamic error, {String? appName}) {
 | 
			
		||||
    if (error is SocketException) {
 | 
			
		||||
      error = error.message;
 | 
			
		||||
    }
 | 
			
		||||
    rawErrors[appId] = error;
 | 
			
		||||
    var string = error.toString();
 | 
			
		||||
    var tempIds = idsByErrorString.remove(string);
 | 
			
		||||
    tempIds ??= [];
 | 
			
		||||
    tempIds.add(appId);
 | 
			
		||||
    content.putIfAbsent(string, () => tempIds!);
 | 
			
		||||
    idsByErrorString.putIfAbsent(string, () => tempIds!);
 | 
			
		||||
    if (appName != null) {
 | 
			
		||||
      appIdNames[appId] = appName;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String errorString(String appId, {bool includeIdsWithNames = false}) =>
 | 
			
		||||
      '${appIdNames.containsKey(appId) ? '${appIdNames[appId]}${includeIdsWithNames ? ' ($appId)' : ''}' : appId}: ${rawErrors[appId].toString()}';
 | 
			
		||||
 | 
			
		||||
  String errorsAppsString(String errString, List<String> appIds,
 | 
			
		||||
          {bool includeIdsWithNames = false}) =>
 | 
			
		||||
      '$errString [${list2FriendlyString(appIds.map((id) => appIdNames.containsKey(id) == true ? '${appIdNames[id]}${includeIdsWithNames ? ' ($id)' : ''}' : id).toList())}]';
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() {
 | 
			
		||||
    String finalString = '';
 | 
			
		||||
    for (var e in content.keys) {
 | 
			
		||||
      finalString += '$e: ${content[e].toString()}\n\n';
 | 
			
		||||
    }
 | 
			
		||||
    return finalString;
 | 
			
		||||
  }
 | 
			
		||||
  String toString() => idsByErrorString.entries
 | 
			
		||||
      .map((e) => errorsAppsString(e.key, e.value))
 | 
			
		||||
      .join('\n\n');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
showError(dynamic e, BuildContext context) {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
 | 
			
		||||
// ignore: implementation_imports
 | 
			
		||||
import 'package:easy_localization/src/localization.dart';
 | 
			
		||||
 | 
			
		||||
const String currentVersion = '0.14.22';
 | 
			
		||||
const String currentVersion = '0.14.25';
 | 
			
		||||
const String currentReleaseTag =
 | 
			
		||||
    'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ class AppsPageState extends State<AppsPage> {
 | 
			
		||||
        refreshingSince = DateTime.now();
 | 
			
		||||
      });
 | 
			
		||||
      return appsProvider.checkUpdates().catchError((e) {
 | 
			
		||||
        showError(e, context);
 | 
			
		||||
        showError(e is Map ? e['errors'] : e, context);
 | 
			
		||||
        return <App>[];
 | 
			
		||||
      }).whenComplete(() {
 | 
			
		||||
        setState(() {
 | 
			
		||||
@@ -833,7 +833,7 @@ class AppsPageState extends State<AppsPage> {
 | 
			
		||||
                items: const [],
 | 
			
		||||
                initValid: true,
 | 
			
		||||
                message: tr('installStatusOfXWillBeResetExplanation',
 | 
			
		||||
                    args: [plural('app', selectedAppIds.length)]),
 | 
			
		||||
                    args: [plural('apps', selectedAppIds.length)]),
 | 
			
		||||
              );
 | 
			
		||||
            });
 | 
			
		||||
        if (values != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -217,7 +217,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
 | 
			
		||||
              if (errors.isEmpty) {
 | 
			
		||||
                // ignore: use_build_context_synchronously
 | 
			
		||||
                showError(
 | 
			
		||||
                    tr('importedX', args: [plural('app', selectedUrls.length)]),
 | 
			
		||||
                    tr('importedX',
 | 
			
		||||
                        args: [plural('apps', selectedUrls.length)]),
 | 
			
		||||
                    context);
 | 
			
		||||
              } else {
 | 
			
		||||
                // ignore: use_build_context_synchronously
 | 
			
		||||
@@ -274,7 +275,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
 | 
			
		||||
            if (errors.isEmpty) {
 | 
			
		||||
              // ignore: use_build_context_synchronously
 | 
			
		||||
              showError(
 | 
			
		||||
                  tr('importedX', args: [plural('app', selectedUrls.length)]),
 | 
			
		||||
                  tr('importedX', args: [plural('apps', selectedUrls.length)]),
 | 
			
		||||
                  context);
 | 
			
		||||
            } else {
 | 
			
		||||
              // ignore: use_build_context_synchronously
 | 
			
		||||
 
 | 
			
		||||
@@ -449,7 +449,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            logs.add(
 | 
			
		||||
                'Could not install APK from XAPK \'${file.path}\': ${e.toString()}');
 | 
			
		||||
            errors.add(dir.appId, e.toString());
 | 
			
		||||
            errors.add(dir.appId, e, appName: apps[dir.appId]?.name);
 | 
			
		||||
          }
 | 
			
		||||
        } else if (file.path.toLowerCase().endsWith('.obb')) {
 | 
			
		||||
          await moveObbFile(file, dir.appId);
 | 
			
		||||
@@ -457,7 +457,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
      }
 | 
			
		||||
      if (somethingInstalled) {
 | 
			
		||||
        dir.file.delete(recursive: true);
 | 
			
		||||
      } else if (errors.content.isNotEmpty) {
 | 
			
		||||
      } else if (errors.idsByErrorString.isNotEmpty) {
 | 
			
		||||
        throw errors;
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
@@ -677,11 +677,11 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
        }
 | 
			
		||||
        installedIds.add(id);
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        errors.add(id, e.toString());
 | 
			
		||||
        errors.add(id, e, appName: apps[id]?.name);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (errors.content.isNotEmpty) {
 | 
			
		||||
    if (errors.idsByErrorString.isNotEmpty) {
 | 
			
		||||
      throw errors;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -738,7 +738,6 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
    var naiveStandardVersionDetection = SourceProvider()
 | 
			
		||||
        .getSource(app.url, overrideSource: app.overrideSource)
 | 
			
		||||
        .naiveStandardVersionDetection;
 | 
			
		||||
    ;
 | 
			
		||||
    // FIRST, COMPARE THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE ONE IS NULL
 | 
			
		||||
    if (installedInfo == null && app.installedVersion != null && !trackOnly) {
 | 
			
		||||
      // App says it's installed but isn't really (and isn't track only) - set to not installed
 | 
			
		||||
@@ -1069,7 +1068,8 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
 | 
			
		||||
  Future<List<App>> checkUpdates(
 | 
			
		||||
      {DateTime? ignoreAppsCheckedAfter,
 | 
			
		||||
      bool throwErrorsForRetry = false}) async {
 | 
			
		||||
      bool throwErrorsForRetry = false,
 | 
			
		||||
      List<String>? specificIds}) async {
 | 
			
		||||
    List<App> updates = [];
 | 
			
		||||
    MultiAppMultiError errors = MultiAppMultiError();
 | 
			
		||||
    if (!gettingUpdates) {
 | 
			
		||||
@@ -1077,27 +1077,33 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
      try {
 | 
			
		||||
        List<String> appIds = getAppsSortedByUpdateCheckTime(
 | 
			
		||||
            ignoreAppsCheckedAfter: ignoreAppsCheckedAfter);
 | 
			
		||||
        for (int i = 0; i < appIds.length; i++) {
 | 
			
		||||
        if (specificIds != null) {
 | 
			
		||||
          appIds = appIds.where((aId) => specificIds.contains(aId)).toList();
 | 
			
		||||
        }
 | 
			
		||||
        await Future.wait(appIds.map((appId) async {
 | 
			
		||||
          App? newApp;
 | 
			
		||||
          try {
 | 
			
		||||
            newApp = await checkUpdate(appIds[i]);
 | 
			
		||||
            newApp = await checkUpdate(appId);
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            if ((e is RateLimitError || e is SocketException) &&
 | 
			
		||||
                throwErrorsForRetry) {
 | 
			
		||||
              rethrow;
 | 
			
		||||
            }
 | 
			
		||||
            errors.add(appIds[i], e.toString());
 | 
			
		||||
            errors.add(appId, e, appName: apps[appId]?.name);
 | 
			
		||||
          }
 | 
			
		||||
          if (newApp != null) {
 | 
			
		||||
            updates.add(newApp);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        }), eagerError: true);
 | 
			
		||||
      } finally {
 | 
			
		||||
        gettingUpdates = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (errors.content.isNotEmpty) {
 | 
			
		||||
      throw errors;
 | 
			
		||||
    if (errors.idsByErrorString.isNotEmpty) {
 | 
			
		||||
      var res = <String, dynamic>{};
 | 
			
		||||
      res['errors'] = errors;
 | 
			
		||||
      res['updates'] = updates;
 | 
			
		||||
      throw res;
 | 
			
		||||
    }
 | 
			
		||||
    return updates;
 | 
			
		||||
  }
 | 
			
		||||
@@ -1314,18 +1320,16 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
 | 
			
		||||
 | 
			
		||||
/// Background updater function
 | 
			
		||||
///
 | 
			
		||||
/// @param List<String>? toCheck: The appIds to check for updates (default to all apps sorted by last update check time)
 | 
			
		||||
/// @param List<MapEntry<String, int>>? toCheck: The appIds to check for updates (with the number of previous attempts made per appid) (defaults to all apps)
 | 
			
		||||
///
 | 
			
		||||
/// @param List<String>? toInstall: The appIds to attempt to update (defaults to an empty array)
 | 
			
		||||
///
 | 
			
		||||
/// @param int? attemptCount: The number of times the function has failed up to this point (defaults to 0)
 | 
			
		||||
///
 | 
			
		||||
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
 | 
			
		||||
/// In update mode, all apps in toCheck are checked for updates.
 | 
			
		||||
/// If an update is available, the appId is either added to toInstall (if a background update is possible) or the user is notified.
 | 
			
		||||
/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries.
 | 
			
		||||
/// If there are errors, the task is run again for the remaining apps after a few minutes (duration depends on the errors), up to a maximum of 5 tries for any app.
 | 
			
		||||
///
 | 
			
		||||
/// Once all update checks are complete, the function is called again in install mode.
 | 
			
		||||
/// Once all update checks are complete, the task is run again in install mode.
 | 
			
		||||
/// In this mode, all apps in toInstall are downloaded and installed in the background (install result is unknown).
 | 
			
		||||
/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries.
 | 
			
		||||
///
 | 
			
		||||
@@ -1371,88 +1375,134 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
 | 
			
		||||
  logs.add(
 | 
			
		||||
      'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
 | 
			
		||||
 | 
			
		||||
  var netResult = await (Connectivity().checkConnectivity());
 | 
			
		||||
 | 
			
		||||
  if (netResult == ConnectivityResult.none) {
 | 
			
		||||
    var networkBasedRetryInterval = 15;
 | 
			
		||||
    var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
 | 
			
		||||
        .add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
 | 
			
		||||
    var potentialNetworkRetryCheck =
 | 
			
		||||
        DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
 | 
			
		||||
    var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
 | 
			
		||||
    logs.add(
 | 
			
		||||
        'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
 | 
			
		||||
    AndroidAlarmManager.oneShot(
 | 
			
		||||
        const Duration(minutes: 15), taskId + 1, bgUpdateCheck,
 | 
			
		||||
        params: {
 | 
			
		||||
          'toCheck': toCheck
 | 
			
		||||
              .map((entry) => {'key': entry.key, 'value': entry.value})
 | 
			
		||||
              .toList(),
 | 
			
		||||
          'toInstall': toInstall
 | 
			
		||||
              .map((entry) => {'key': entry.key, 'value': entry.value})
 | 
			
		||||
              .toList(),
 | 
			
		||||
        });
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!installMode) {
 | 
			
		||||
    // If in update mode...
 | 
			
		||||
    var didCompleteChecking = false;
 | 
			
		||||
    CheckingUpdatesNotification? notif;
 | 
			
		||||
    // If in update mode, we check for updates.
 | 
			
		||||
    // We divide the results into 4 groups:
 | 
			
		||||
    // - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
 | 
			
		||||
    // - toRetry - Apps with update check errors that will be retried in a while
 | 
			
		||||
    // - toThrow - Apps with update check errors that the user will be notified about (no retry)
 | 
			
		||||
    // - toInstall - Apps with updates that will be installed silently
 | 
			
		||||
    // After grouping the updates, we take care of toNotify and toThrow first
 | 
			
		||||
    // Then if toRetry is not empty, we schedule another update task to run in a while (toInstall is retained)
 | 
			
		||||
    // If toRetry is empty, we take care of toInstall
 | 
			
		||||
 | 
			
		||||
    // Init. vars.
 | 
			
		||||
    List<App> updates = [];
 | 
			
		||||
    List<App> toNotify = [];
 | 
			
		||||
    List<MapEntry<String, int>> toRetry = [];
 | 
			
		||||
    var retryAfterXSeconds = 0;
 | 
			
		||||
    MultiAppMultiError toThrow = MultiAppMultiError();
 | 
			
		||||
    var networkRestricted = false;
 | 
			
		||||
    if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
 | 
			
		||||
      var netResult = await (Connectivity().checkConnectivity());
 | 
			
		||||
      networkRestricted = (netResult != ConnectivityResult.wifi) &&
 | 
			
		||||
          (netResult != ConnectivityResult.ethernet);
 | 
			
		||||
    }
 | 
			
		||||
    // Loop through all updates and check each
 | 
			
		||||
    List<App> toNotify = [];
 | 
			
		||||
    MultiAppMultiError? errors;
 | 
			
		||||
    CheckingUpdatesNotification notif =
 | 
			
		||||
        CheckingUpdatesNotification(plural('apps', toCheck.length));
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      for (int i = 0; i < toCheck.length; i++) {
 | 
			
		||||
        var appId = toCheck[i].key;
 | 
			
		||||
        var attemptCount = toCheck[i].value + 1;
 | 
			
		||||
        AppInMemory? app = appsProvider.apps[appId];
 | 
			
		||||
        if (app?.app.installedVersion != null) {
 | 
			
		||||
          try {
 | 
			
		||||
            notificationsProvider.notify(
 | 
			
		||||
                notif = CheckingUpdatesNotification(app?.name ?? appId),
 | 
			
		||||
                cancelExisting: true);
 | 
			
		||||
            App? newApp = await appsProvider.checkUpdate(appId);
 | 
			
		||||
            if (newApp != null) {
 | 
			
		||||
              if (networkRestricted ||
 | 
			
		||||
                  !(await appsProvider.canInstallSilently(app!.app))) {
 | 
			
		||||
                toNotify.add(newApp);
 | 
			
		||||
              } else {
 | 
			
		||||
                toInstall.add(MapEntry(appId, 0));
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            if (i == (toCheck.length - 1)) {
 | 
			
		||||
              didCompleteChecking = true;
 | 
			
		||||
            }
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            // If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue checking shortly
 | 
			
		||||
            logs.add(
 | 
			
		||||
                'BG update task $taskId: Got error on checking for $appId \'${e.toString()}\'.');
 | 
			
		||||
            if (attemptCount < maxAttempts) {
 | 
			
		||||
              var remainingSeconds = e is RateLimitError
 | 
			
		||||
                  ? (i == 0 ? (e.remainingMinutes * 60) : (5 * 60))
 | 
			
		||||
                  : e is ClientException
 | 
			
		||||
                      ? (15 * 60)
 | 
			
		||||
                      : pow(attemptCount, 2).toInt();
 | 
			
		||||
              logs.add(
 | 
			
		||||
                  'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
 | 
			
		||||
              var remainingToCheck = moveStrToEndMapEntryWithCount(
 | 
			
		||||
                  toCheck.sublist(i), MapEntry(appId, attemptCount));
 | 
			
		||||
              AndroidAlarmManager.oneShot(Duration(seconds: remainingSeconds),
 | 
			
		||||
                  taskId + 1, bgUpdateCheck,
 | 
			
		||||
                  params: {
 | 
			
		||||
                    'toCheck': remainingToCheck
 | 
			
		||||
                        .map(
 | 
			
		||||
                            (entry) => {'key': entry.key, 'value': entry.value})
 | 
			
		||||
                        .toList(),
 | 
			
		||||
                    'toInstall': toInstall
 | 
			
		||||
                        .map(
 | 
			
		||||
                            (entry) => {'key': entry.key, 'value': entry.value})
 | 
			
		||||
                        .toList(),
 | 
			
		||||
                  });
 | 
			
		||||
              break;
 | 
			
		||||
            } else {
 | 
			
		||||
              // If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
 | 
			
		||||
              toCheck.removeAt(i);
 | 
			
		||||
              i--;
 | 
			
		||||
              notificationsProvider
 | 
			
		||||
                  .notify(ErrorCheckingUpdatesNotification(e.toString()));
 | 
			
		||||
            }
 | 
			
		||||
          } finally {
 | 
			
		||||
            if (notif != null) {
 | 
			
		||||
              notificationsProvider.cancel(notif.id);
 | 
			
		||||
      // Check for updates
 | 
			
		||||
      notificationsProvider.notify(notif, cancelExisting: true);
 | 
			
		||||
      updates = await appsProvider.checkUpdates(
 | 
			
		||||
          specificIds: toCheck.map((e) => e.key).toList());
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      // If there were errors, group them into toRetry and toThrow
 | 
			
		||||
      if (e is Map) {
 | 
			
		||||
        updates = e['updates'];
 | 
			
		||||
        errors = e['errors'];
 | 
			
		||||
        errors!.rawErrors.forEach((key, err) {
 | 
			
		||||
          logs.add(
 | 
			
		||||
              'BG update task $taskId: Got error on checking for $key \'${err.toString()}\'.');
 | 
			
		||||
          var toCheckApp = toCheck.where((element) => element.key == key).first;
 | 
			
		||||
          if (toCheckApp.value < maxAttempts) {
 | 
			
		||||
            toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
 | 
			
		||||
            var minRetryIntervalForThisApp = err is RateLimitError
 | 
			
		||||
                ? (err.remainingMinutes * 60)
 | 
			
		||||
                : e is ClientException
 | 
			
		||||
                    ? (15 * 60)
 | 
			
		||||
                    : pow(toCheckApp.value + 1, 2).toInt();
 | 
			
		||||
            if (minRetryIntervalForThisApp > retryAfterXSeconds) {
 | 
			
		||||
              retryAfterXSeconds = minRetryIntervalForThisApp;
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            toThrow.add(key, err, appName: errors?.appIdNames[key]);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        // We don't expect to ever get here in any situation so no need to catch
 | 
			
		||||
        logs.add('Fatal error in BG update task: ${e.toString()}');
 | 
			
		||||
        rethrow;
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      if (toNotify.isNotEmpty) {
 | 
			
		||||
        notificationsProvider.notify(UpdateNotification(toNotify));
 | 
			
		||||
      notificationsProvider.cancel(notif.id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Group the updates into toNotify and toInstall
 | 
			
		||||
    for (var i = 0; i < updates.length; i++) {
 | 
			
		||||
      if (networkRestricted ||
 | 
			
		||||
          !(await appsProvider.canInstallSilently(updates[i]))) {
 | 
			
		||||
        toNotify.add(updates[i]);
 | 
			
		||||
      } else {
 | 
			
		||||
        toInstall.add(MapEntry(updates[i].id, 0));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // If you're done checking and found some silently installable updates, schedule another task which will run in install mode
 | 
			
		||||
    if (didCompleteChecking && toInstall.isNotEmpty) {
 | 
			
		||||
 | 
			
		||||
    // Send the update notification
 | 
			
		||||
    if (toNotify.isNotEmpty) {
 | 
			
		||||
      notificationsProvider.notify(UpdateNotification(toNotify));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Send the error notifications
 | 
			
		||||
    if (toThrow.rawErrors.isNotEmpty) {
 | 
			
		||||
      for (var element in toThrow.idsByErrorString.entries) {
 | 
			
		||||
        notificationsProvider.notify(ErrorCheckingUpdatesNotification(
 | 
			
		||||
            errors!.errorsAppsString(element.key, element.value),
 | 
			
		||||
            id: Random().nextInt(10000)));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // if there are update checks to retry, schedule a retry task
 | 
			
		||||
    if (toRetry.isNotEmpty) {
 | 
			
		||||
      logs.add(
 | 
			
		||||
          'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
 | 
			
		||||
      AndroidAlarmManager.oneShot(
 | 
			
		||||
          Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck,
 | 
			
		||||
          params: {
 | 
			
		||||
            'toCheck': toRetry
 | 
			
		||||
                .map((entry) => {'key': entry.key, 'value': entry.value})
 | 
			
		||||
                .toList(),
 | 
			
		||||
            'toInstall': toInstall
 | 
			
		||||
                .map((entry) => {'key': entry.key, 'value': entry.value})
 | 
			
		||||
                .toList(),
 | 
			
		||||
          });
 | 
			
		||||
    } else if (toInstall.isNotEmpty) {
 | 
			
		||||
      // If there are no more update checks, schedule an install task
 | 
			
		||||
      logs.add(
 | 
			
		||||
          'BG update task $taskId: Done. Scheduling install task to run immediately.');
 | 
			
		||||
      AndroidAlarmManager.oneShot(
 | 
			
		||||
@@ -1463,11 +1513,14 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
 | 
			
		||||
                .map((entry) => {'key': entry.key, 'value': entry.value})
 | 
			
		||||
                .toList()
 | 
			
		||||
          });
 | 
			
		||||
    } else if (didCompleteChecking) {
 | 
			
		||||
    } else {
 | 
			
		||||
      logs.add('BG install task $taskId: Done.');
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // If in install mode...
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (installMode) {
 | 
			
		||||
    // If in install mode, we install silent updates.
 | 
			
		||||
 | 
			
		||||
    var didCompleteInstalling = false;
 | 
			
		||||
    var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
 | 
			
		||||
    if (tempObtArr.isNotEmpty) {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ import 'package:obtainium/app_sources/steammobile.dart';
 | 
			
		||||
import 'package:obtainium/app_sources/telegramapp.dart';
 | 
			
		||||
import 'package:obtainium/app_sources/uptodown.dart';
 | 
			
		||||
import 'package:obtainium/app_sources/vlc.dart';
 | 
			
		||||
import 'package:obtainium/app_sources/whatsapp.dart';
 | 
			
		||||
import 'package:obtainium/components/generated_form.dart';
 | 
			
		||||
import 'package:obtainium/custom_errors.dart';
 | 
			
		||||
import 'package:obtainium/mass_app_sources/githubstars.dart';
 | 
			
		||||
@@ -557,7 +558,7 @@ class SourceProvider {
 | 
			
		||||
        Mullvad(),
 | 
			
		||||
        Signal(),
 | 
			
		||||
        VLC(),
 | 
			
		||||
        // WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
 | 
			
		||||
        WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
 | 
			
		||||
        TelegramApp(),
 | 
			
		||||
        SteamMobile(),
 | 
			
		||||
        NeutronCode(),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							@@ -46,10 +46,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: archive
 | 
			
		||||
      sha256: d4dc11707abb32ef756ab95678c0d6df54003d98277f7c9aeda14c48e7a38c2f
 | 
			
		||||
      sha256: ca12e6c9ac022f33fd89128e7007fb5e97ab6e814d4fa05dd8d4f2db1e3c69cb
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.4.3"
 | 
			
		||||
    version: "3.4.5"
 | 
			
		||||
  args:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -291,10 +291,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: flutter_local_notifications
 | 
			
		||||
      sha256: "3002092e5b8ce2f86c3361422e52e6db6776c23ee21e0b2f71b892bf4259ef04"
 | 
			
		||||
      sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "15.1.1"
 | 
			
		||||
    version: "16.1.0"
 | 
			
		||||
  flutter_local_notifications_linux:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -320,10 +320,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: flutter_markdown
 | 
			
		||||
      sha256: a10979814c5f4ddbe2b6143fba25d927599e21e3ba65b3862995960606fae78f
 | 
			
		||||
      sha256: "8afc9a6aa6d8e8063523192ba837149dbf3d377a37c0b0fc579149a1fbd4a619"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.6.17+3"
 | 
			
		||||
    version: "0.6.18"
 | 
			
		||||
  flutter_plugin_android_lifecycle:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -538,10 +538,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: permission_handler
 | 
			
		||||
      sha256: ad65ba9af42a3d067203641de3fd9f547ded1410bad3b84400c2b4899faede70
 | 
			
		||||
      sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "11.0.0"
 | 
			
		||||
    version: "11.0.1"
 | 
			
		||||
  permission_handler_android:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -879,10 +879,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: webview_flutter
 | 
			
		||||
      sha256: "053d454c9475546b4382e9498601fb46293cdac9b3ca93f1a738375bc9a1eee4"
 | 
			
		||||
      sha256: c1ab9b81090705c6069197d9fdc1625e587b52b8d70cdde2339d177ad0dbb98e
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.3.0"
 | 
			
		||||
    version: "4.4.1"
 | 
			
		||||
  webview_flutter_android:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -903,10 +903,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: webview_flutter_wkwebview
 | 
			
		||||
      sha256: "3c7d56ca4b82654ad1f58aeefb8d593a59224f26d6b2bf8feed074361eb34c86"
 | 
			
		||||
      sha256: "30b9af6bdd457b44c08748b9190d23208b5165357cc2eb57914fee1366c42974"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.9.0"
 | 
			
		||||
    version: "3.9.1"
 | 
			
		||||
  win32:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
			
		||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
			
		||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
			
		||||
# of the product and file versions while build-number is used as the build suffix.
 | 
			
		||||
version: 0.14.22+214 # When changing this, update the tag in main() accordingly
 | 
			
		||||
version: 0.14.25+217 # When changing this, update the tag in main() accordingly
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: '>=3.0.0 <4.0.0'
 | 
			
		||||
@@ -38,7 +38,7 @@ dependencies:
 | 
			
		||||
  cupertino_icons: ^1.0.5
 | 
			
		||||
  path_provider: ^2.0.11
 | 
			
		||||
  flutter_fgbg: ^0.3.0 # Try removing reliance on this
 | 
			
		||||
  flutter_local_notifications: ^15.1.0+1
 | 
			
		||||
  flutter_local_notifications: ^16.1.0
 | 
			
		||||
  provider: ^6.0.3
 | 
			
		||||
  http: ^1.0.0
 | 
			
		||||
  webview_flutter: ^4.0.0
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user