mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-30 21:13:28 +01:00 
			
		
		
		
	Compare commits
	
		
			33 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 18ccd01186 | ||
|  | c84f51b6ce | ||
|  | 48958748f6 | ||
|  | 966b1ee6e3 | ||
|  | 7432ee867f | ||
|  | 3ebbd2a4a5 | ||
|  | 82207a1b3b | ||
|  | 2ec5c7db11 | ||
|  | ab949af700 | ||
|  | 5c9a35c4f0 | ||
|  | 8651f58744 | ||
|  | d93798a8df | ||
|  | 0822f991ff | ||
|  | fde63a0f05 | ||
|  | 523e1151b2 | ||
|  | d5d6825ed9 | ||
|  | 79c4d3b9fe | ||
|  | d6b99b903f | ||
|  | 335752ee7c | ||
|  | e5afe75213 | ||
|  | 0df1c1c512 | ||
|  | bd2ac73b9f | ||
|  | 26b5a2d976 | ||
|  | c0968e8991 | ||
|  | 243b1eb2ac | ||
|  | c6c61d1c83 | ||
|  | f207967411 | ||
|  | 29bb5cc8cd | ||
|  | dbbc2ff19c | ||
|  | c012b016f9 | ||
|  | 22815b8209 | ||
|  | 619d4f1a51 | ||
|  | 50e8929763 | 
							
								
								
									
										3
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -15,6 +15,9 @@ jobs: | ||||
|        | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: subosito/flutter-action@v2 | ||||
|         with: | ||||
|           channel: stable | ||||
|           flutter-version: 3.24.5 | ||||
|       - uses: actions/setup-java@v4 | ||||
|         with: | ||||
|           distribution: 'temurin' # See 'Supported distributions' for available options | ||||
|   | ||||
| @@ -31,10 +31,7 @@ Currently supported App sources: | ||||
|   - [Tencent App Store](https://sj.qq.com/) | ||||
|   - Jenkins Jobs | ||||
|   - [APKMirror](https://apkmirror.com/) (Track-Only) | ||||
| - Open Source - App-Specific: | ||||
|   - [VLC](https://videolan.org/) | ||||
| - Other - App-Specific: | ||||
|   - [WhatsApp](https://whatsapp.com) | ||||
|   - [Telegram App](https://telegram.org) | ||||
|   - [Neutron Code](https://neutroncode.com) | ||||
| - Direct APK Link | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App Configs", | ||||
|     "allowInsecure": "Allow insecure HTTP requests", | ||||
|     "stayOneVersionBehind": "Stay one version behind latest", | ||||
|     "refreshBeforeDownload": "Refresh app details before download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Želite li ukloniti aplikaciju?", | ||||
|         "other": "Želite li ukloniti aplikacije?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Konfigurace aplikací s využitím crowdsourcingu", | ||||
|     "allowInsecure": "Povolení nezabezpečených požadavků HTTP", | ||||
|     "stayOneVersionBehind": "Zůstaňte o jednu verzi pozadu za nejnovější", | ||||
|     "refreshBeforeDownload": "Obnovení údajů o aplikaci před stažením", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Odstranit Apku?", | ||||
|         "other": "Odstranit Apky?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourcede app-konfigurationer", | ||||
|     "allowInsecure": "Tillad usikre HTTP-anmodninger", | ||||
|     "stayOneVersionBehind": "Forbliv én version bagud den seneste", | ||||
|     "refreshBeforeDownload": "Opdater app-detaljer før download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Fjern app?", | ||||
|         "other": "Fjern apps?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App-Konfigurationen", | ||||
|     "allowInsecure": "Unsichere HTTP-Anfragen zulassen", | ||||
|     "stayOneVersionBehind": "Eine Version hinter der neuesten Version bleiben", | ||||
|     "refreshBeforeDownload": "App-Details vor dem Download aktualisieren", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "App entfernen?", | ||||
|         "other": "Apps entfernen?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App Configs", | ||||
|     "allowInsecure": "Allow insecure HTTP requests", | ||||
|     "stayOneVersionBehind": "Stay one version behind latest", | ||||
|     "refreshBeforeDownload": "Refresh app details before download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Forigi la aplikaĵon?", | ||||
|         "other": "Forigi la aplikaĵojn?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App Configs", | ||||
|     "allowInsecure": "Allow insecure HTTP requests", | ||||
|     "stayOneVersionBehind": "Stay one version behind latest", | ||||
|     "refreshBeforeDownload": "Refresh app details before download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Remove App?", | ||||
|         "other": "Remove Apps?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Configuración de aplicaciones por crowdsourcing", | ||||
|     "allowInsecure": "Permitir peticiones HTTP inseguras", | ||||
|     "stayOneVersionBehind": "Mantenerse una versión por detrás de la última", | ||||
|     "refreshBeforeDownload": "Actualiza los datos de la aplicación antes de descargarla", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "¿Eliminar aplicación?", | ||||
|         "other": "¿Eliminar aplicaciones?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App Configs", | ||||
|     "allowInsecure": "درخواست های HTTP ناامن را مجاز کنید", | ||||
|     "stayOneVersionBehind": "Stay one version behind latest", | ||||
|     "refreshBeforeDownload": "Refresh app details before download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "برنامه حذف شود؟", | ||||
|         "other": "برنامه ها حذف شوند؟" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Applis communautaires", | ||||
|     "allowInsecure": "Autoriser les requêtes HTTP non sécurisées", | ||||
|     "stayOneVersionBehind": "Rester à une version de la dernière", | ||||
|     "refreshBeforeDownload": "Actualiser les détails de l'application avant de la télécharger", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Supprimer l'application ?", | ||||
|         "other": "Supprimer les applications ?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App Configs", | ||||
|     "allowInsecure": "Nem biztonságos HTTP-kérések engedélyezése", | ||||
|     "stayOneVersionBehind": "Maradjon egy verzióval a legújabb mögött", | ||||
|     "refreshBeforeDownload": "Az alkalmazás adatainak frissítése a letöltés előtt", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Eltávolítja az alkalmazást?", | ||||
|         "other": "Eltávolítja az alkalmazásokat?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Konfigurasi Aplikasi Crowdsourced", | ||||
|     "allowInsecure": "Izinkan permintaan HTTP yang tidak aman", | ||||
|     "stayOneVersionBehind": "Tetap satu versi di belakang versi terbaru", | ||||
|     "refreshBeforeDownload": "Segarkan detail aplikasi sebelum mengunduh", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Hapus aplikasi?", | ||||
|         "other": "Hapus aplikasi?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Configurazioni di app in crowdsourcing", | ||||
|     "allowInsecure": "Consentire le richieste HTTP non sicure", | ||||
|     "stayOneVersionBehind": "Rimanere una versione indietro rispetto alla più recente", | ||||
|     "refreshBeforeDownload": "Aggiornare i dettagli dell'app prima del download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Rimuovere l'app?", | ||||
|         "other": "Rimuovere le app?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "クラウドソーシングによるアプリの設定", | ||||
|     "allowInsecure": "安全でないHTTPリクエストを許可する", | ||||
|     "stayOneVersionBehind": "最新のバージョンから1つ前のものを使用する", | ||||
|     "refreshBeforeDownload": "ダウンロード前にアプリの詳細を更新する", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "アプリを削除しますか?", | ||||
|         "other": "アプリを削除しますか?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "App-configuraties door menigte", | ||||
|     "allowInsecure": "Onveilige HTTP-verzoeken toestaan", | ||||
|     "stayOneVersionBehind": "Blijf een versie achter op de nieuwste", | ||||
|     "refreshBeforeDownload": "Vernieuw app details voor download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "App verwijderen?", | ||||
|         "other": "Apps verwijderen?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Baza konfiguracji", | ||||
|     "allowInsecure": "Zezwalaj na niezabezpieczone żądania HTTP", | ||||
|     "stayOneVersionBehind": "Pozostań jedną wersję w tyle za najnowszą", | ||||
|     "refreshBeforeDownload": "Odśwież szczegóły aplikacji przed pobraniem", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Usunąć aplikację?", | ||||
|         "few": "Usunąć aplikacje?", | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Configurações de aplicações com base em crowdsourcing", | ||||
|     "allowInsecure": "Permitir pedidos HTTP inseguros", | ||||
|     "stayOneVersionBehind": "Manter-se uma versão atrás da mais recente", | ||||
|     "refreshBeforeDownload": "Atualizar os detalhes da aplicação antes da transferência", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Remover aplicativo?", | ||||
|         "other": "Remover aplicativos?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Конфиги приложений с помощью краудсорсинга", | ||||
|     "allowInsecure": "Разрешить небезопасные HTTP-запросы", | ||||
|     "stayOneVersionBehind": "Не отставайте от последней версии", | ||||
|     "refreshBeforeDownload": "Обновляйте информацию о приложении перед загрузкой", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Удалить приложение?", | ||||
|         "other": "Удалить приложения?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Appkonfigurationer med hjälp av crowdsourcing", | ||||
|     "allowInsecure": "Tillåt osäkra HTTP-förfrågningar", | ||||
|     "stayOneVersionBehind": "Håll dig en version bakom den senaste", | ||||
|     "refreshBeforeDownload": "Uppdatera appdetaljerna före nedladdning", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Ta Bort App?", | ||||
|         "other": "Ta Bort Appar?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Kitle Kaynaklı Uygulama Yapılandırmaları", | ||||
|     "allowInsecure": "Güvensiz HTTP isteklerine izin ver", | ||||
|     "stayOneVersionBehind": "En son sürümün bir sürüm gerisinde kalın", | ||||
|     "refreshBeforeDownload": "İndirmeden önce uygulama ayrıntılarını yenileyin", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Uygulamayı Kaldır?", | ||||
|         "other": "Uygulamaları Kaldır?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Налаштування краудсорсингових додатків", | ||||
|     "allowInsecure": "Дозволити незахищені HTTP-запити", | ||||
|     "stayOneVersionBehind": "Залишайтеся на одну версію актуальнішою", | ||||
|     "refreshBeforeDownload": "Оновіть інформацію про програму перед завантаженням", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Видалити застосунок?", | ||||
|         "other": "Видалити застосунки?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App Configs", | ||||
|     "allowInsecure": "Allow insecure HTTP requests", | ||||
|     "stayOneVersionBehind": "Stay one version behind latest", | ||||
|     "refreshBeforeDownload": "Refresh app details before download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Gỡ ứng dụng?", | ||||
|         "other": "Gỡ ứng dụng?" | ||||
|   | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "Crowdsourced App Configs", | ||||
|     "allowInsecure": "Allow insecure HTTP requests", | ||||
|     "stayOneVersionBehind": "Stay one version behind latest", | ||||
|     "refreshBeforeDownload": "Refresh app details before download", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "移除應用程式?", | ||||
|         "other": "移除應用程式?" | ||||
|   | ||||
| @@ -186,10 +186,10 @@ | ||||
|     "additionalOptions": "附加选项", | ||||
|     "disableVersionDetection": "禁用版本检测", | ||||
|     "noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。", | ||||
|     "downloadingX": "正在下载“{}”", | ||||
|     "downloadingX": "正在下载 {}", | ||||
|     "downloadX": "下载 {}", | ||||
|     "downloadedX": "下载 {}", | ||||
|     "releaseAsset": "发行版附件", | ||||
|     "downloadedX": "已下载 {}", | ||||
|     "releaseAsset": "发行文件", | ||||
|     "downloadNotifDescription": "提示应用的下载进度", | ||||
|     "noAPKFound": "未找到 APK 文件", | ||||
|     "noVersionDetection": "禁用版本检测", | ||||
| @@ -317,6 +317,7 @@ | ||||
|     "crowdsourcedConfigsShort": "众包应用程序配置", | ||||
|     "allowInsecure": "允许不安全的 HTTP 请求", | ||||
|     "stayOneVersionBehind": "比最新版本晚一个版本", | ||||
|     "refreshBeforeDownload": "下载前刷新应用程序详细信息", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "是否删除应用?", | ||||
|         "other": "是否删除应用?" | ||||
|   | ||||
| @@ -131,12 +131,18 @@ class APKPure extends AppSource { | ||||
|         throw NoAPKError(); | ||||
|       } | ||||
|       String version = Uri.parse(link).pathSegments.last; | ||||
|       String author = html | ||||
|               .querySelector('span.info-sdk') | ||||
|               ?.text | ||||
|               .trim() | ||||
|               .substring(version.length + 4) ?? | ||||
|           Uri.parse(standardUrl).pathSegments.reversed.last; | ||||
|       String? author; | ||||
|       try { | ||||
|         author = html | ||||
|                 .querySelector('span.info-sdk') | ||||
|                 ?.text | ||||
|                 .trim() | ||||
|                 .substring(version.length + 4) ?? | ||||
|             Uri.parse(standardUrl).pathSegments.reversed.last; | ||||
|       } catch (e) { | ||||
|         author = html.querySelector('span.info-sdk')?.text.trim() ?? | ||||
|             Uri.parse(standardUrl).pathSegments.reversed.last; | ||||
|       } | ||||
|       String appName = | ||||
|           html.querySelector('h1.info-title')?.text.trim() ?? appId; | ||||
|       String? changeLog = html | ||||
|   | ||||
| @@ -275,14 +275,15 @@ class GitHub extends AppSource { | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       List<MapEntry<String, String>> getReleaseAssetUrls(dynamic release) => | ||||
|       findReleaseAssetUrls(dynamic release) => | ||||
|           (release['assets'] as List<dynamic>?)?.map((e) { | ||||
|             var url = !e['name'].toString().toLowerCase().endsWith('.apk') | ||||
|                 ? (e['browser_download_url'] ?? e['url']) | ||||
|                 : (e['url'] ?? e['browser_download_url']); | ||||
|             return (e['name'] != null) && (url != null) | ||||
|             e['final_url'] = (e['name'] != null) && (url != null) | ||||
|                 ? MapEntry(e['name'] as String, url as String) | ||||
|                 : const MapEntry('', ''); | ||||
|             return e; | ||||
|           }).toList() ?? | ||||
|           []; | ||||
|  | ||||
| @@ -293,7 +294,9 @@ class GitHub extends AppSource { | ||||
|                   ? DateTime.parse(rel['commit']['created']) | ||||
|                   : null; | ||||
|       DateTime? getNewestAssetDateFromRelease(dynamic rel) { | ||||
|         var t = (rel['assets'] as List<dynamic>?) | ||||
|         var allAssets = rel['assets'] as List<dynamic>?; | ||||
|         var filteredAssets = rel['filteredAssets'] as List<dynamic>?; | ||||
|         var t = (filteredAssets ?? allAssets) | ||||
|             ?.map((e) { | ||||
|               return e?['updated_at'] != null | ||||
|                   ? DateTime.parse(e['updated_at']) | ||||
| @@ -387,18 +390,37 @@ class GitHub extends AppSource { | ||||
|                 .hasMatch(((releases[i]['body'] as String?) ?? '').trim())) { | ||||
|           continue; | ||||
|         } | ||||
|         var allAssetUrls = getReleaseAssetUrls(releases[i]); | ||||
|         List<MapEntry<String, String>> apkUrls = allAssetUrls | ||||
|             .where((element) => element.key.toLowerCase().endsWith('.apk')) | ||||
|         var allAssetsWithUrls = findReleaseAssetUrls(releases[i]); | ||||
|         List<MapEntry<String, String>> allAssetUrls = allAssetsWithUrls | ||||
|             .map((e) => e['final_url'] as MapEntry<String, String>) | ||||
|             .toList(); | ||||
|         var apkAssetsWithUrls = allAssetsWithUrls | ||||
|             .where((element) => | ||||
|                 (element['final_url'] as MapEntry<String, String>) | ||||
|                     .key | ||||
|                     .toLowerCase() | ||||
|                     .endsWith('.apk')) | ||||
|             .toList(); | ||||
|  | ||||
|         apkUrls = filterApks(apkUrls, additionalSettings['apkFilterRegEx'], | ||||
|         var filteredApkUrls = filterApks( | ||||
|             apkAssetsWithUrls | ||||
|                 .map((e) => e['final_url'] as MapEntry<String, String>) | ||||
|                 .toList(), | ||||
|             additionalSettings['apkFilterRegEx'], | ||||
|             additionalSettings['invertAPKFilter']); | ||||
|         if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) { | ||||
|         var filteredApks = apkAssetsWithUrls | ||||
|             .where((e) => filteredApkUrls | ||||
|                 .where((e2) => | ||||
|                     e2.key == (e['final_url'] as MapEntry<String, String>).key) | ||||
|                 .isNotEmpty) | ||||
|             .toList(); | ||||
|  | ||||
|         if (filteredApks.isEmpty && additionalSettings['trackOnly'] != true) { | ||||
|           continue; | ||||
|         } | ||||
|         targetRelease = releases[i]; | ||||
|         targetRelease['apkUrls'] = apkUrls; | ||||
|         targetRelease['apkUrls'] = filteredApkUrls; | ||||
|         targetRelease['filteredAssets'] = filteredApks; | ||||
|         targetRelease['version'] = | ||||
|             additionalSettings['releaseTitleAsVersion'] == true | ||||
|                 ? nameToFilter | ||||
| @@ -420,6 +442,7 @@ class GitHub extends AppSource { | ||||
|         throw NoReleasesError(); | ||||
|       } | ||||
|       String? version = targetRelease['version']; | ||||
|  | ||||
|       DateTime? releaseDate = getReleaseDateFromRelease( | ||||
|           targetRelease, useLatestAssetDateAsReleaseDate); | ||||
|       if (version == null) { | ||||
|   | ||||
| @@ -7,6 +7,9 @@ import 'package:obtainium/providers/apps_provider.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
|  | ||||
| String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) { | ||||
|   if (ambiguousUrl.startsWith('//')) { | ||||
|     ambiguousUrl = '${referenceAbsoluteUrl.scheme}:$ambiguousUrl'; | ||||
|   } | ||||
|   try { | ||||
|     Uri.parse(ambiguousUrl).origin; | ||||
|     return ambiguousUrl; | ||||
| @@ -353,7 +356,12 @@ class HTML extends AppSource { | ||||
|                     forAPKDownload: true), | ||||
|                 allowInsecure: additionalSettings['allowInsecure'] == true)) | ||||
|             .toString(); | ||||
|     return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(), | ||||
|     return APKDetails( | ||||
|         version, | ||||
|         [rel] | ||||
|             .map((e) => | ||||
|                 MapEntry('${e.hashCode}-${Uri.parse(e).pathSegments.last}', e)) | ||||
|             .toList(), | ||||
|         AppNames(uri.host, tr('app'))); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -33,7 +33,9 @@ class TelegramApp extends AppSource { | ||||
|         throw NoVersionError(); | ||||
|       } | ||||
|       String? apkUrl = 'https://telegram.org/dl/android/apk'; | ||||
|       return APKDetails(version, getApkUrlsFromUrls([apkUrl]), | ||||
|       return APKDetails( | ||||
|           version, | ||||
|           [MapEntry<String, String>('telegram-$version.apk', apkUrl)], | ||||
|           AppNames('Telegram', 'Telegram')); | ||||
|     } else { | ||||
|       throw getObtainiumHttpError(res); | ||||
|   | ||||
| @@ -1,110 +0,0 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:html/parser.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:obtainium/custom_errors.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
|  | ||||
| class VLC extends AppSource { | ||||
|   VLC() { | ||||
|     hosts = ['videolan.org']; | ||||
|   } | ||||
|   get dwUrlBase => 'https://get.${hosts[0]}/vlc-android/'; | ||||
|  | ||||
|   @override | ||||
|   Future<Map<String, String>?> getRequestHeaders( | ||||
|       Map<String, dynamic> additionalSettings, | ||||
|       {bool forAPKDownload = false}) async { | ||||
|     return { | ||||
|       "User-Agent": | ||||
|           "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) { | ||||
|     return 'https://${hosts[0]}'; | ||||
|   } | ||||
|  | ||||
|   Future<String?> getLatestVersion( | ||||
|       String standardUrl, Map<String, dynamic> additionalSettings) async { | ||||
|     Response res = await sourceRequest(dwUrlBase, additionalSettings); | ||||
|     if (res.statusCode == 200) { | ||||
|       var dwLinks = parse(res.body) | ||||
|           .querySelectorAll('a') | ||||
|           .where((element) => element.attributes['href'] != 'last/') | ||||
|           .map((e) => e.attributes['href']?.split('/')[0]) | ||||
|           .toList(); | ||||
|       String? version = dwLinks.isNotEmpty ? dwLinks.last : null; | ||||
|       if (version == null) { | ||||
|         throw NoVersionError(); | ||||
|       } | ||||
|       return version; | ||||
|     } else { | ||||
|       throw getObtainiumHttpError(res); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|     String standardUrl, | ||||
|     Map<String, dynamic> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get( | ||||
|         Uri.parse('https://www.videolan.org/vlc/download-android.html')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var dwUrlBase = 'get.videolan.org/vlc-android'; | ||||
|       var dwLinks = parse(res.body) | ||||
|           .querySelectorAll('a') | ||||
|           .where((element) => | ||||
|               element.attributes['href']?.contains(dwUrlBase) ?? false) | ||||
|           .toList(); | ||||
|       String? version = dwLinks.isNotEmpty | ||||
|           ? dwLinks.first.attributes['href'] | ||||
|               ?.split('/') | ||||
|               .where((s) => s.isNotEmpty) | ||||
|               .last | ||||
|           : null; | ||||
|       if (version == null) { | ||||
|         throw NoVersionError(); | ||||
|       } | ||||
|       String? targetUrl = 'https://$dwUrlBase/$version/'; | ||||
|       var apkUrls = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'] | ||||
|           .map((e) => '${targetUrl}VLC-Android-$version-$e.apk') | ||||
|           .toList(); | ||||
|       return APKDetails( | ||||
|           version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC')); | ||||
|     } else { | ||||
|       throw getObtainiumHttpError(res); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl, | ||||
|       Map<String, dynamic> additionalSettings) async { | ||||
|     Response res = await sourceRequest(apkUrl, additionalSettings); | ||||
|     if (res.statusCode == 200) { | ||||
|       String? apkUrl = | ||||
|           parse(res.body).querySelector('#alt_link')?.attributes['href']; | ||||
|       if (apkUrl == null) { | ||||
|         throw NoAPKError(); | ||||
|       } | ||||
|       return apkUrl; | ||||
|     } else if (res.statusCode == 500 && | ||||
|         res.body.toLowerCase().indexOf('mirror') > 0) { | ||||
|       var html = parse(res.body); | ||||
|       var err = ''; | ||||
|       html.body?.nodes.forEach((element) { | ||||
|         if (element.text != null) { | ||||
|           err += '${element.text}\n'; | ||||
|         } | ||||
|       }); | ||||
|       err = err.trim(); | ||||
|       if (err.isEmpty) { | ||||
|         err = tr('err'); | ||||
|       } | ||||
|       throw ObtainiumError(err); | ||||
|     } else { | ||||
|       throw getObtainiumHttpError(res); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,55 +0,0 @@ | ||||
| import 'package:html/parser.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:obtainium/custom_errors.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
|  | ||||
| class WhatsApp extends AppSource { | ||||
|   WhatsApp() { | ||||
|     hosts = ['whatsapp.com']; | ||||
|     versionDetectionDisallowed = true; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) { | ||||
|     return 'https://${hosts[0]}'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl, | ||||
|       Map<String, dynamic> additionalSettings) async { | ||||
|     Response res = | ||||
|         await sourceRequest('$standardUrl/android', additionalSettings); | ||||
|     if (res.statusCode == 200) { | ||||
|       var targetLinks = parse(res.body) | ||||
|           .querySelectorAll('a') | ||||
|           .map((e) => e.attributes['href'] ?? '') | ||||
|           .where((e) => e.isNotEmpty) | ||||
|           .where((e) => e.contains('WhatsApp.apk')) | ||||
|           .toList(); | ||||
|       if (targetLinks.isEmpty) { | ||||
|         throw NoAPKError(); | ||||
|       } | ||||
|       return targetLinks[0]; | ||||
|     } else { | ||||
|       throw getObtainiumHttpError(res); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|     String standardUrl, | ||||
|     Map<String, dynamic> additionalSettings, | ||||
|   ) async { | ||||
|     // 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, additionalSettings)); | ||||
|     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')); | ||||
|   } | ||||
| } | ||||
| @@ -838,30 +838,6 @@ class AppsPageState extends State<AppsPage> { | ||||
|       Navigator.of(context).pop(); | ||||
|     } | ||||
|  | ||||
|     resetSelectedAppsInstallStatuses() async { | ||||
|       try { | ||||
|         var values = await showDialog( | ||||
|             context: context, | ||||
|             builder: (BuildContext ctx) { | ||||
|               return GeneratedFormModal( | ||||
|                 title: tr('resetInstallStatusForSelectedAppsQuestion'), | ||||
|                 items: const [], | ||||
|                 initValid: true, | ||||
|                 message: tr('installStatusOfXWillBeResetExplanation', | ||||
|                     args: [plural('apps', selectedAppIds.length)]), | ||||
|               ); | ||||
|             }); | ||||
|         if (values != null) { | ||||
|           appsProvider.saveApps(selectedApps.map((e) { | ||||
|             e.installedVersion = null; | ||||
|             return e; | ||||
|           }).toList()); | ||||
|         } | ||||
|       } finally { | ||||
|         Navigator.of(context).pop(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     showMoreOptionsDialog() { | ||||
|       return showDialog( | ||||
|           context: context, | ||||
|   | ||||
| @@ -130,13 +130,18 @@ class _HomePageState extends State<HomePage> { | ||||
|  | ||||
|     // Check initial link if app was in cold state (terminated) | ||||
|     final appLink = await _appLinks.getInitialLink(); | ||||
|     var initLinked = false; | ||||
|     if (appLink != null) { | ||||
|       await interpretLink(appLink); | ||||
|       initLinked = true; | ||||
|     } | ||||
|  | ||||
|     // Handle link when app is in warm state (front or background) | ||||
|     _linkSubscription = _appLinks.uriLinkStream.listen((uri) async { | ||||
|       await interpretLink(uri); | ||||
|       if (!initLinked) { | ||||
|         await interpretLink(uri); | ||||
|       } else { | ||||
|         initLinked = false; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,8 @@ import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:http/io_client.dart'; | ||||
| import 'package:obtainium/app_sources/directAPKLink.dart'; | ||||
| import 'package:obtainium/app_sources/html.dart'; | ||||
| import 'package:obtainium/components/generated_form.dart'; | ||||
| import 'package:obtainium/components/generated_form_modal.dart'; | ||||
| import 'package:obtainium/custom_errors.dart'; | ||||
| @@ -888,6 +890,11 @@ class AppsProvider with ChangeNotifier { | ||||
|       } | ||||
|       MapEntry<String, String>? apkUrl; | ||||
|       var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true; | ||||
|       var refreshBeforeDownload = | ||||
|           apps[id]!.app.additionalSettings['refreshBeforeDownload'] == true; | ||||
|       if (refreshBeforeDownload) { | ||||
|         await checkUpdate(apps[id]!.app.id); | ||||
|       } | ||||
|       if (!trackOnly) { | ||||
|         // ignore: use_build_context_synchronously | ||||
|         apkUrl = await confirmAppFileUrl(apps[id]!.app, context, false); | ||||
| @@ -1074,6 +1081,11 @@ class AppsProvider with ChangeNotifier { | ||||
|         throw ObtainiumError(tr('appNotFound')); | ||||
|       } | ||||
|       MapEntry<String, String>? fileUrl; | ||||
|       var refreshBeforeDownload = | ||||
|           apps[id]!.app.additionalSettings['refreshBeforeDownload'] == true; | ||||
|       if (refreshBeforeDownload) { | ||||
|         await checkUpdate(apps[id]!.app.id); | ||||
|       } | ||||
|       if (apps[id]!.app.apkUrls.isNotEmpty || | ||||
|           apps[id]!.app.otherAssetUrls.isNotEmpty) { | ||||
|         // ignore: use_build_context_synchronously | ||||
| @@ -1149,17 +1161,25 @@ class AppsProvider with ChangeNotifier { | ||||
|     if (app?.app == null) { | ||||
|       return false; | ||||
|     } | ||||
|     var source = SourceProvider() | ||||
|         .getSource(app!.app.url, overrideSource: app.app.overrideSource); | ||||
|     var naiveStandardVersionDetection = | ||||
|         app!.app.additionalSettings['naiveStandardVersionDetection'] == true || | ||||
|             SourceProvider() | ||||
|                 .getSource(app.app.url, overrideSource: app.app.overrideSource) | ||||
|                 .naiveStandardVersionDetection; | ||||
|         app.app.additionalSettings['naiveStandardVersionDetection'] == true || | ||||
|             source.naiveStandardVersionDetection; | ||||
|     String? realInstalledVersion = | ||||
|         app.app.additionalSettings['useVersionCodeAsOSVersion'] == true | ||||
|             ? app.installedInfo?.versionCode.toString() | ||||
|             : app.installedInfo?.versionName; | ||||
|     bool isHTMLWithNoVersionDetection = | ||||
|         (source.runtimeType == HTML().runtimeType && | ||||
|             (app.app.additionalSettings['versionExtractionRegEx'] as String?) | ||||
|                     ?.isNotEmpty != | ||||
|                 true); | ||||
|     bool isDirectAPKLink = source.runtimeType == DirectAPKLink().runtimeType; | ||||
|     return app.app.additionalSettings['trackOnly'] != true && | ||||
|         app.app.additionalSettings['releaseDateAsVersion'] != true && | ||||
|         !isHTMLWithNoVersionDetection && | ||||
|         !isDirectAPKLink && | ||||
|         realInstalledVersion != null && | ||||
|         app.app.installedVersion != null && | ||||
|         (reconcileVersionDifferences( | ||||
| @@ -1230,6 +1250,7 @@ class AppsProvider with ChangeNotifier { | ||||
|         !isVersionDetectionPossible( | ||||
|             AppInMemory(app, null, installedInfo, null))) { | ||||
|       app.additionalSettings['versionDetection'] = false; | ||||
|       app.installedVersion = app.latestVersion; | ||||
|       logs.add('Could not reconcile version formats for: ${app.id}'); | ||||
|       modded = true; | ||||
|     } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter_local_notifications/flutter_local_notifications.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
|  | ||||
| class ObtainiumNotification { | ||||
| @@ -44,23 +45,19 @@ class SilentUpdateNotification extends ObtainiumNotification { | ||||
|   SilentUpdateNotification(List<App> updates, bool succeeded, {int? id}) | ||||
|       : super( | ||||
|             id ?? 3, | ||||
|             succeeded | ||||
|                 ? tr('appsUpdated') | ||||
|                 : tr('appsNotUpdated'), | ||||
|             succeeded ? tr('appsUpdated') : tr('appsNotUpdated'), | ||||
|             '', | ||||
|             'APPS_UPDATED', | ||||
|             tr('appsUpdatedNotifChannel'), | ||||
|             tr('appsUpdatedNotifDescription'), | ||||
|             Importance.defaultImportance) { | ||||
|     message = updates.length == 1 | ||||
|         ? tr(succeeded | ||||
|             ? 'xWasUpdatedToY' | ||||
|             : 'xWasNotUpdatedToY', | ||||
|                 args: [updates[0].finalName, updates[0].latestVersion]) | ||||
|         : plural(succeeded | ||||
|             ? 'xAndNMoreUpdatesInstalled' | ||||
|             : "xAndNMoreUpdatesFailed", | ||||
|                 updates.length - 1, args: [updates[0].finalName, (updates.length - 1).toString()]); | ||||
|         ? tr(succeeded ? 'xWasUpdatedToY' : 'xWasNotUpdatedToY', | ||||
|             args: [updates[0].finalName, updates[0].latestVersion]) | ||||
|         : plural( | ||||
|             succeeded ? 'xAndNMoreUpdatesInstalled' : "xAndNMoreUpdatesFailed", | ||||
|             updates.length - 1, | ||||
|             args: [updates[0].finalName, (updates.length - 1).toString()]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -214,7 +211,7 @@ class NotificationsProvider { | ||||
|                 channelDescription: channelDescription, | ||||
|                 importance: importance, | ||||
|                 priority: importanceToPriority[importance]!, | ||||
|                 groupKey: 'dev.imranr.obtainium.$channelCode', | ||||
|                 groupKey: '$obtainiumId.$channelCode', | ||||
|                 progress: progPercent ?? 0, | ||||
|                 maxProgress: 100, | ||||
|                 showProgress: progPercent != null, | ||||
|   | ||||
| @@ -28,11 +28,10 @@ import 'package:obtainium/app_sources/sourcehut.dart'; | ||||
| import 'package:obtainium/app_sources/telegramapp.dart'; | ||||
| import 'package:obtainium/app_sources/tencent.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'; | ||||
| import 'package:obtainium/providers/logs_provider.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
|  | ||||
| class AppNames { | ||||
| @@ -153,10 +152,6 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) { | ||||
|     additionalSettings['autoApkFilterByArch'] = false; | ||||
|   } | ||||
|   if (source.runtimeType == HTML().runtimeType) { | ||||
|     // HTML 'fixed URL' support should be disabled if it previously did not exist | ||||
|     if (originalAdditionalSettings['supportFixedAPKURL'] == null) { | ||||
|       additionalSettings['supportFixedAPKURL'] = false; | ||||
|     } | ||||
|     // HTML key rename | ||||
|     if (originalAdditionalSettings['sortByFileNamesNotLinks'] != null) { | ||||
|       additionalSettings['sortByLastLinkSegment'] = | ||||
| @@ -214,6 +209,56 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) { | ||||
|           '\\d+.\\d+.\\d+'; | ||||
|       additionalSettings = replacementAdditionalSettings; | ||||
|     } | ||||
|     // WhatsApp from before it was removed should be converted to HTML (#1943) | ||||
|     if (json['url'] == 'https://whatsapp.com' && | ||||
|         json['id'] == 'com.whatsapp' && | ||||
|         json['author'] == 'Meta' && | ||||
|         json['name'] == 'WhatsApp' && | ||||
|         json['overrideSource'] == null && | ||||
|         additionalSettings['trackOnly'] == false && | ||||
|         additionalSettings['versionExtractionRegEx'] == '' && | ||||
|         json['lastUpdateCheck'] != null) { | ||||
|       json['url'] = 'https://whatsapp.com/android'; | ||||
|       var replacementAdditionalSettings = getDefaultValuesFromFormItems( | ||||
|           HTML().combinedAppSpecificSettingFormItems); | ||||
|       replacementAdditionalSettings['refreshBeforeDownload'] = true; | ||||
|       additionalSettings = replacementAdditionalSettings; | ||||
|     } | ||||
|     // VLC from before it was removed should be converted to HTML (#1943) | ||||
|     if (json['url'] == 'https://videolan.org' && | ||||
|         json['id'] == 'org.videolan.vlc' && | ||||
|         json['author'] == 'VideoLAN' && | ||||
|         json['name'] == 'VLC' && | ||||
|         json['overrideSource'] == null && | ||||
|         additionalSettings['trackOnly'] == false && | ||||
|         additionalSettings['versionExtractionRegEx'] == '' && | ||||
|         json['lastUpdateCheck'] != null) { | ||||
|       json['url'] = 'https://www.videolan.org/vlc/download-android.html'; | ||||
|       var replacementAdditionalSettings = getDefaultValuesFromFormItems( | ||||
|           HTML().combinedAppSpecificSettingFormItems); | ||||
|       replacementAdditionalSettings['refreshBeforeDownload'] = true; | ||||
|       replacementAdditionalSettings['intermediateLink'] = | ||||
|           <Map<String, dynamic>>[ | ||||
|         { | ||||
|           'customLinkFilterRegex': 'APK', | ||||
|           'filterByLinkText': true, | ||||
|           'skipSort': false, | ||||
|           'reverseSort': false, | ||||
|           'sortByLastLinkSegment': false | ||||
|         }, | ||||
|         { | ||||
|           'customLinkFilterRegex': 'arm64-v8a\\.apk\$', | ||||
|           'filterByLinkText': false, | ||||
|           'skipSort': false, | ||||
|           'reverseSort': false, | ||||
|           'sortByLastLinkSegment': false | ||||
|         } | ||||
|       ]; | ||||
|       replacementAdditionalSettings['versionExtractionRegEx'] = | ||||
|           '/vlc-android/([^/]+)/'; | ||||
|       replacementAdditionalSettings['matchGroupToUse'] = "1"; | ||||
|       additionalSettings = replacementAdditionalSettings; | ||||
|     } | ||||
|   } | ||||
|   json['additionalSettings'] = jsonEncode(additionalSettings); | ||||
|   // F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately | ||||
| @@ -240,7 +285,7 @@ class App { | ||||
|   late String name; | ||||
|   String? installedVersion; | ||||
|   late String latestVersion; | ||||
|   List<MapEntry<String, String>> apkUrls = []; | ||||
|   List<MapEntry<String, String>> apkUrls = []; // Key is name, value is URL | ||||
|   List<MapEntry<String, String>> otherAssetUrls = []; | ||||
|   late int preferredApkIndex; | ||||
|   late Map<String, dynamic> additionalSettings; | ||||
| @@ -304,7 +349,14 @@ class App { | ||||
|       otherAssetUrls: otherAssetUrls); | ||||
|  | ||||
|   factory App.fromJson(Map<String, dynamic> json) { | ||||
|     json = appJSONCompatibilityModifiers(json); | ||||
|     Map<String, dynamic> originalJSON = new Map.from(json); | ||||
|     try { | ||||
|       json = appJSONCompatibilityModifiers(json); | ||||
|     } catch (e) { | ||||
|       json = originalJSON; | ||||
|       LogsProvider().add( | ||||
|           'Error running JSON compat modifiers: ${e.toString()}: ${originalJSON.toString()}'); | ||||
|     } | ||||
|     return App( | ||||
|       json['id'] as String, | ||||
|       json['url'] as String, | ||||
| @@ -586,6 +638,10 @@ abstract class AppSource { | ||||
|           label: tr('skipUpdateNotifications')) | ||||
|     ], | ||||
|     [GeneratedFormTextField('about', label: tr('about'), required: false)], | ||||
|     [ | ||||
|       GeneratedFormSwitch('refreshBeforeDownload', | ||||
|           label: tr('refreshBeforeDownload')) | ||||
|     ] | ||||
|   ]; | ||||
|  | ||||
|   // Previous 2 variables combined into one at runtime for convenient usage | ||||
| @@ -808,8 +864,6 @@ class SourceProvider { | ||||
|         Tencent(), | ||||
|         Jenkins(), | ||||
|         APKMirror(), | ||||
|         VLC(), | ||||
|         WhatsApp(), | ||||
|         TelegramApp(), | ||||
|         NeutronCode(), | ||||
|         DirectAPKLink(), | ||||
|   | ||||
							
								
								
									
										108
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -5,10 +5,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: android_intent_plus | ||||
|       sha256: "38921ec22ebb3b9a7eb678792cf6fab0b6f458b61b9d327688573449c9b47db3" | ||||
|       sha256: "53136214d506d3128c9f4e5bfce3d026abe7e8038958629811a8d3223b1757c1" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.2.0" | ||||
|     version: "5.2.1" | ||||
|   android_package_installer: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -47,10 +47,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: app_links | ||||
|       sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99 | ||||
|       sha256: "433df2e61b10519407475d7f69e470789d23d593f28224c38ba1068597be7950" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.3.2" | ||||
|     version: "6.3.3" | ||||
|   app_links_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -79,10 +79,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: archive | ||||
|       sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d | ||||
|       sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.6.1" | ||||
|     version: "4.0.2" | ||||
|   args: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -111,10 +111,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: battery_plus | ||||
|       sha256: "220c8f1961efb01d6870493b5ac5a80afaeaffc8757f7a11ed3025a8570d29e7" | ||||
|       sha256: a0409fe7d21905987eb1348ad57c634f913166f14f0c8936b73d3f5940fac551 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.2.0" | ||||
|     version: "6.2.1" | ||||
|   battery_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -175,10 +175,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: connectivity_plus | ||||
|       sha256: "876849631b0c7dc20f8b471a2a03142841b482438e3b707955464f5ffca3e4c3" | ||||
|       sha256: e0817759ec6d2d8e57eb234e6e57d2173931367a865850c7acea40d4b4f9c27d | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.1.0" | ||||
|     version: "6.1.1" | ||||
|   connectivity_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -231,18 +231,18 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: device_info_plus | ||||
|       sha256: f545ffbadee826f26f2e1a0f0cbd667ae9a6011cc0f77c0f8f00a969655e6e95 | ||||
|       sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "11.1.1" | ||||
|     version: "11.2.0" | ||||
|   device_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: device_info_plus_platform_interface | ||||
|       sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" | ||||
|       sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "7.0.1" | ||||
|     version: "7.0.2" | ||||
|   dynamic_color: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -303,10 +303,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: file_picker | ||||
|       sha256: "16dc141db5a2ccc6520ebb6a2eb5945b1b09e95085c021d9f914f8ded7f1465c" | ||||
|       sha256: c2376a6aae82358a9f9ccdd7d1f4006d08faa39a2767cce01031d9f593a8bd3b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.1.4" | ||||
|     version: "8.1.6" | ||||
|   fixnum: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -404,10 +404,10 @@ packages: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
|       name: flutter_launcher_icons | ||||
|       sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77" | ||||
|       sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.14.1" | ||||
|     version: "0.14.2" | ||||
|   flutter_lints: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
| @@ -449,18 +449,18 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_markdown | ||||
|       sha256: "999a4e3cb3e1532a971c86d6c73a480264f6a687959d4887cb4e2990821827e4" | ||||
|       sha256: "255b00afa1a7bad19727da6a7780cf3db6c3c12e68d302d85e0ff1fdf173db9e" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.7.4+2" | ||||
|     version: "0.7.4+3" | ||||
|   flutter_plugin_android_lifecycle: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_plugin_android_lifecycle | ||||
|       sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" | ||||
|       sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.23" | ||||
|     version: "2.0.24" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @@ -483,10 +483,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: fluttertoast | ||||
|       sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc" | ||||
|       sha256: "24467dc20bbe49fd63e57d8e190798c4d22cbbdac30e54209d153a15273721d1" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.2.8" | ||||
|     version: "8.2.10" | ||||
|   fraction: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -539,10 +539,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: image | ||||
|       sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d | ||||
|       sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.3.0" | ||||
|     version: "4.5.2" | ||||
|   intl: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -667,18 +667,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_android | ||||
|       sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a | ||||
|       sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.2.12" | ||||
|     version: "2.2.15" | ||||
|   path_provider_foundation: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_foundation | ||||
|       sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 | ||||
|       sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.0" | ||||
|     version: "2.4.1" | ||||
|   path_provider_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -807,6 +807,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.10.2+1" | ||||
|   posix: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: posix | ||||
|       sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.0.1" | ||||
|   provider: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -819,42 +827,42 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: share_plus | ||||
|       sha256: "9c9bafd4060728d7cdb2464c341743adbd79d327cb067ec7afb64583540b47c8" | ||||
|       sha256: "6327c3f233729374d0abaafd61f6846115b2a481b4feddd8534211dc10659400" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.1.2" | ||||
|     version: "10.1.3" | ||||
|   share_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: share_plus_platform_interface | ||||
|       sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48 | ||||
|       sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.0.1" | ||||
|     version: "5.0.2" | ||||
|   shared_preferences: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: shared_preferences | ||||
|       sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" | ||||
|       sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.3" | ||||
|     version: "2.3.4" | ||||
|   shared_preferences_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shared_preferences_android | ||||
|       sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" | ||||
|       sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.3" | ||||
|     version: "2.4.0" | ||||
|   shared_preferences_foundation: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shared_preferences_foundation | ||||
|       sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" | ||||
|       sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.5.3" | ||||
|     version: "2.5.4" | ||||
|   shared_preferences_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1057,10 +1065,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: url_launcher_ios | ||||
|       sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e | ||||
|       sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.3.1" | ||||
|     version: "6.3.2" | ||||
|   url_launcher_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1073,10 +1081,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: url_launcher_macos | ||||
|       sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" | ||||
|       sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.2.1" | ||||
|     version: "3.2.2" | ||||
|   url_launcher_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1145,10 +1153,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: webview_flutter_android | ||||
|       sha256: "285cedfd9441267f6cca8843458620b5fda1af75b04f5818d0441acda5d7df19" | ||||
|       sha256: "3d535126f7244871542b2f0b0fcf94629c9a14883250461f9abe1a6644c1c379" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.1.0" | ||||
|     version: "4.2.0" | ||||
|   webview_flutter_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1169,10 +1177,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: win32 | ||||
|       sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" | ||||
|       sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.8.0" | ||||
|     version: "5.9.0" | ||||
|   win32_registry: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1201,10 +1209,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: yaml | ||||
|       sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" | ||||
|       sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.1.2" | ||||
|     version: "3.1.3" | ||||
| sdks: | ||||
|   dart: ">=3.5.0 <4.0.0" | ||||
|   flutter: ">=3.24.0" | ||||
|   | ||||
| @@ -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: 1.1.32+2289 | ||||
| version: 1.1.37+2294 | ||||
|  | ||||
| environment: | ||||
|   sdk: '>=3.0.0 <4.0.0' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user