mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-30 12:30:16 +02:00
Compare commits
28 Commits
v0.14.34-b
...
v0.14.36-b
Author | SHA1 | Date | |
---|---|---|---|
|
3eca704f4a | ||
|
9c95129311 | ||
|
bf34c1bcdb | ||
|
284c687d77 | ||
|
09afb5a3f5 | ||
|
0138721451 | ||
|
2d5f610941 | ||
|
864fa7762b | ||
|
4fde38ee6a | ||
|
6cdf0f10d4 | ||
|
b66592c25f | ||
|
62f1dc17a0 | ||
|
0e9a8a937a | ||
|
9a86b245ce | ||
|
64533f7a3f | ||
|
0b7de8d387 | ||
|
8eba4860fe | ||
|
b53e2f57e6 | ||
|
e1e834297b | ||
|
e37dc6e341 | ||
|
c91c896854 | ||
|
7e5dfa03d6 | ||
|
1a4ec3f049 | ||
|
756763fcbe | ||
|
93036c4e67 | ||
|
15bf972ef6 | ||
|
bcb4567382 | ||
|
3890c4ffb9 |
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -21,6 +21,7 @@ jobs:
|
||||
|
||||
- name: Build APKs
|
||||
run: |
|
||||
sed -i 's/signingConfig signingConfigs.release//g' android/app/build.gradle
|
||||
flutter build apk --flavor normal && flutter build apk --split-per-abi --flavor normal
|
||||
for file in build/app/outputs/flutter-apk/app-*normal*.apk*; do mv "$file" "${file//-normal/}"; done
|
||||
rm ./build/app/outputs/flutter-apk/*.sha1
|
||||
|
@@ -71,9 +71,17 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Nije instalirano",
|
||||
"estimateInBrackets": "(Procjena)",
|
||||
"selectAll": "Označi sve",
|
||||
"deselectN": "Poništi odabir {}",
|
||||
"deselectX": "Poništi odabir {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
|
||||
"removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
|
||||
"removeSelectedApps": "Ukloni odabrane aplikacije",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
|
||||
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
|
||||
"about": "O nama",
|
||||
"requiresCredentialsInSettings": "Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
||||
"requiresCredentialsInSettings": "{}: Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
||||
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
|
||||
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
|
||||
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Dovršite instalaciju aplikacije",
|
||||
"checkingForUpdatesNotifChannel": "Tražim moguće nadogradnje",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Želite li ukloniti aplikaciju?",
|
||||
"other": "Želite li ukloniti aplikacije?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Není nainstalováno",
|
||||
"estimateInBrackets": "(přibližně)",
|
||||
"selectAll": "Vybrat Vše",
|
||||
"deselectN": "{} deselected",
|
||||
"deselectX": "{} deselected",
|
||||
"xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.",
|
||||
"removeSelectedAppsQuestion": "Odebrat vybrané aplikace?",
|
||||
"removeSelectedApps": "Odebrat vybrané aplikace",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "Vyžaduje další pověření (v nastavení)",
|
||||
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
|
||||
"checkOnStart": "Zkontrolovat jednou při spuštění",
|
||||
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
|
||||
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
|
||||
"checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Odstranit Apku?",
|
||||
"other": "Odstranit Apky?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Nicht installiert",
|
||||
"estimateInBrackets": "(Ungefähr)",
|
||||
"selectAll": "Alle auswählen",
|
||||
"deselectN": "{} abgewählt",
|
||||
"deselectX": "{} abgewählt",
|
||||
"xWillBeRemovedButRemainInstalled": "{} wird aus Obtainium entfernt, bleibt aber auf dem Gerät installiert.",
|
||||
"removeSelectedAppsQuestion": "Ausgewählte Apps entfernen?",
|
||||
"removeSelectedApps": "Ausgewählte Apps entfernen",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
|
||||
"about": "Über",
|
||||
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||
"requiresCredentialsInSettings": "{}: Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||
"checkOnStart": "Überprüfe einmalig beim Start",
|
||||
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
|
||||
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "App Installation abschließen",
|
||||
"checkingForUpdatesNotifChannel": "Nach Aktualisierungen suchen",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps auf Aktualisierungen",
|
||||
"supportFixedAPKURL": "neuere Version anhand der ersten dreißig Zahlen der Checksumme der APK URL erraten, wenn anderweitig nicht unterstützt",
|
||||
"selectX": "Wähle {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "App entfernen?",
|
||||
"other": "Apps entfernen?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Not Installed",
|
||||
"estimateInBrackets": "(Estimate)",
|
||||
"selectAll": "Select All",
|
||||
"deselectN": "Deselect {}",
|
||||
"deselectX": "Deselect {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} will be removed from Obtainium but remain installed on device.",
|
||||
"removeSelectedAppsQuestion": "Remove Selected Apps?",
|
||||
"removeSelectedApps": "Remove Selected Apps",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{} needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check for updates on startup",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Complete App Installation",
|
||||
"checkingForUpdatesNotifChannel": "Checking for Updates",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remove App?",
|
||||
"other": "Remove Apps?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "No Instalado",
|
||||
"estimateInBrackets": "(Aproximado)",
|
||||
"selectAll": "Seleccionar Todo",
|
||||
"deselectN": "Deseleccionar {}",
|
||||
"deselectX": "Deseleccionar {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} será borrada de Obtainium pero continuará instalada en el dispositivo.",
|
||||
"removeSelectedAppsQuestion": "¿Borrar aplicaciones seleccionadas?",
|
||||
"removeSelectedApps": "Borrar Aplicaciones Seleccionadas",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check for updates on startup",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Instalación Completa de la Aplicación",
|
||||
"checkingForUpdatesNotifChannel": "Buscando Actualizaciones",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "¿Eliminar Aplicación?",
|
||||
"other": "¿Eliminar Aplicaciones?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "نصب نشده",
|
||||
"estimateInBrackets": "(تخمین زدن)",
|
||||
"selectAll": "انتخاب همه",
|
||||
"deselectN": "لغو انتخاب {}",
|
||||
"deselectX": "لغو انتخاب {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف میشود اما روی دستگاه نصب میماند.",
|
||||
"removeSelectedAppsQuestion": "برنامه های انتخابی حذف شود؟",
|
||||
"removeSelectedApps": "حذف برنامه های انتخاب شده",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
|
||||
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
|
||||
"about": "درباره",
|
||||
"requiresCredentialsInSettings": "این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
||||
"requiresCredentialsInSettings": "{}: این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
||||
"checkOnStart": "بررسی در شروع",
|
||||
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
|
||||
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",
|
||||
@@ -239,42 +239,44 @@
|
||||
"sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل",
|
||||
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
|
||||
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیشفرض '.apk$')",
|
||||
"appsPossiblyUpdated": "App Updates Attempted",
|
||||
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
|
||||
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
|
||||
"enableBackgroundUpdates": "Enable background updates",
|
||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||
"verifyLatestTag": "Verify the 'latest' tag",
|
||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
||||
"intermediateLinkNotFound": "Intermediate link not found",
|
||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||
"versionExtractionRegEx": "Version Extraction RegEx",
|
||||
"matchGroupToUse": "Match Group to Use",
|
||||
"highlightTouchTargets": "Highlight less obvious touch targets",
|
||||
"pickExportDir": "Pick Export Directory",
|
||||
"autoExportOnChanges": "Auto-export on changes",
|
||||
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
||||
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
||||
"dontSortReleasesList": "Retain release order from API",
|
||||
"reverseSort": "Reverse sorting",
|
||||
"debugMenu": "Debug Menu",
|
||||
"bgTaskStarted": "Background task started - check logs.",
|
||||
"runBgCheckNow": "Run Background Update Check Now",
|
||||
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
||||
"installing": "Installing",
|
||||
"skipUpdateNotifications": "Skip update notifications",
|
||||
"appsPossiblyUpdated": "بهروزرسانی برنامه انجام شد",
|
||||
"appsPossiblyUpdatedNotifDescription": "به کاربر اطلاع میدهد که بهروزرسانیهای یک یا چند برنامه به طور بالقوه در پسزمینه اعمال شده است",
|
||||
"xWasPossiblyUpdatedToY": "ممکن است {} به {} به روز شده باشد.",
|
||||
"enableBackgroundUpdates": "به روز رسانی پس زمینه را فعال کنید",
|
||||
"backgroundUpdateReqsExplanation": "به روز رسانی پس زمینه ممکن است برای همه برنامه ها امکان پذیر نباشد.",
|
||||
"backgroundUpdateLimitsExplanation": "موفقیت نصب پسزمینه تنها زمانی مشخص میشود که Obtainium باز شود.",
|
||||
"verifyLatestTag": "برچسب "آخرین" را تأیید کنید",
|
||||
"intermediateLinkRegex": "برای اولین بار بازدید از لینک "متوسط" را فیلتر کنید",
|
||||
"intermediateLinkNotFound": "لینک میانی پیدا نشد",
|
||||
"exemptFromBackgroundUpdates": "معاف از بهروزرسانیهای پسزمینه (در صورت فعال بودن)",
|
||||
"bgUpdatesOnWiFiOnly": "بهروزرسانیهای پسزمینه را در صورت عدم اتصال به WiFi غیرفعال کنید",
|
||||
"autoSelectHighestVersionCode": "انتخاب خودکار بالاترین نسخه کد APK",
|
||||
"versionExtractionRegEx": "نسخه استخراج RegEx",
|
||||
"matchGroupToUse": "گروه مورد استفاده را مطابقت دهید",
|
||||
"highlightTouchTargets": "اهداف لمسی کمتر واضح را برجسته کنید",
|
||||
"pickExportDir": "فهرست صادرات را انتخاب کنید",
|
||||
"autoExportOnChanges": "صادرات خودکار تغییرات",
|
||||
"filterVersionsByRegEx": "فیلتر کردن نسخه ها با RegEx",
|
||||
"trySelectingSuggestedVersionCode": "نسخه پیشنهادی APK نسخه کد را انتخاب کنید",
|
||||
"dontSortReleasesList": "حفظ سفارش انتشار از API",
|
||||
"reverseSort": "مرتب سازی معکوس",
|
||||
"debugMenu": "منوی اشکال زدایی",
|
||||
"bgTaskStarted": "کار پس زمینه شروع شد - لاگ های مربوط را بررسی کنید.",
|
||||
"runBgCheckNow": "اکنون بهروزرسانی پسزمینه را بررسی کنید",
|
||||
"versionExtractWholePage": "نسخه Extraction Regex را در کل صفحه اعمال کنید",
|
||||
"installing": "در حال نصب",
|
||||
"skipUpdateNotifications": "رد شدن از اعلان های به روز رسانی",
|
||||
"updatesAvailableNotifChannel": "بروزرسانی در دسترس ",
|
||||
"appsUpdatedNotifChannel": "برنامه ها به روز شدند",
|
||||
"appsPossiblyUpdatedNotifChannel": "App Updates Attempted",
|
||||
"appsPossiblyUpdatedNotifChannel": "بهروزرسانی برنامه انجام شد",
|
||||
"errorCheckingUpdatesNotifChannel": "خطا در بررسی بهروزرسانیها",
|
||||
"appsRemovedNotifChannel": "برنامه ها حذف شدند",
|
||||
"downloadingXNotifChannel": "در حال دانلود {}",
|
||||
"completeAppInstallationNotifChannel": "نصب کامل برنامه",
|
||||
"checkingForUpdatesNotifChannel": "بررسی بهروزرسانیها",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "فقط برنامه های نصب شده و فقط ردیابی را برای به روز رسانی بررسی کنید",
|
||||
"supportFixedAPKURL": "پشتیبانی از URL های APK ثابت",
|
||||
"selectX": "انتخاب کنید {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "برنامه حذف شود؟",
|
||||
"other": "برنامه ها حذف شوند؟"
|
||||
@@ -324,7 +326,7 @@
|
||||
"other": "{} و {} برنامه دیگر به روز شدند."
|
||||
},
|
||||
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||
"one": "{} and 1 more app may have been updated.",
|
||||
"other": "{} and {} more apps may have been updated."
|
||||
"one": "{} و 1 برنامه دیگر ممکن است به روز شده باشند.",
|
||||
"other": "ممکن است {} و {} برنامه های دیگر به روز شده باشند."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Pas installé",
|
||||
"estimateInBrackets": "(Estimation)",
|
||||
"selectAll": "Tout sélectionner",
|
||||
"deselectN": "Déselectionner {}",
|
||||
"deselectX": "Déselectionner {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} sera supprimé d'Obtainium mais restera installé sur l'appareil.",
|
||||
"removeSelectedAppsQuestion": "Supprimer les applications sélectionnées ?",
|
||||
"removeSelectedApps": "Supprimer les applications sélectionnées",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check for updates on startup",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Installation complète de l'application",
|
||||
"checkingForUpdatesNotifChannel": "Vérification des mises à jour",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Supprimer l'application ?",
|
||||
"other": "Supprimer les applications ?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Nem telepített",
|
||||
"estimateInBrackets": "(Becslés)",
|
||||
"selectAll": "Mindet kiválaszt",
|
||||
"deselectN": "Törölje {} kijelölését",
|
||||
"deselectX": "Törölje {} kijelölését",
|
||||
"xWillBeRemovedButRemainInstalled": "A(z) {} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.",
|
||||
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
|
||||
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
|
||||
"about": "Rólunk",
|
||||
"requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
||||
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
||||
"checkOnStart": "Egyszer az alkalmazás indításakor is",
|
||||
"tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból",
|
||||
"removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Teljes app telepítés",
|
||||
"checkingForUpdatesNotifChannel": "Frissítések keresése",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Eltávolítja az alkalmazást?",
|
||||
"other": "Eltávolítja az alkalmazást?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Non installato",
|
||||
"estimateInBrackets": "(stimato)",
|
||||
"selectAll": "Seleziona tutto",
|
||||
"deselectN": "Deseleziona {}",
|
||||
"deselectX": "Deseleziona {}",
|
||||
"xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.",
|
||||
"removeSelectedAppsQuestion": "Rimuovere le app selezionate?",
|
||||
"removeSelectedApps": "Rimuovi le app selezionate",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca e migliora la rilevazione di apk)",
|
||||
"about": "Informazioni",
|
||||
"requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)",
|
||||
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
|
||||
"checkOnStart": "Controlla una volta all'avvio",
|
||||
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
|
||||
"removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
|
||||
"checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Rimuovere l'app?",
|
||||
"other": "Rimuovere le app?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "未インストール",
|
||||
"estimateInBrackets": "(推定)",
|
||||
"selectAll": "すべて選択",
|
||||
"deselectN": "{}件の選択を解除",
|
||||
"deselectX": "{}件の選択を解除",
|
||||
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
|
||||
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
|
||||
"removeSelectedApps": "選択したアプリを削除する",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
|
||||
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
|
||||
"about": "概要",
|
||||
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
|
||||
"requiresCredentialsInSettings": "{}: これには追加の認証が必要です (設定にて)",
|
||||
"checkOnStart": "起動時にアップデートを確認する",
|
||||
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
|
||||
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "アプリのインストールを完了する",
|
||||
"checkingForUpdatesNotifChannel": "アップデートを確認中",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "インストール済みのアプリと「追跡のみ」のアプリのアップデートのみを確認する",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "アプリを削除しますか?",
|
||||
"other": "アプリを削除しますか?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Niet geinstalleerd",
|
||||
"estimateInBrackets": "(Ongeveer)",
|
||||
"selectAll": "Selecteer alles",
|
||||
"deselectN": "Deselecteer {}",
|
||||
"deselectX": "Deselecteer {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} zal worden verwijderd uit Obtainium, maar blijft geïnstalleerd op het apparaat.",
|
||||
"removeSelectedAppsQuestion": "Geselecteerde apps verwijderen??",
|
||||
"removeSelectedApps": "Geselecteerde apps verwijderen",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Maakt het mogelijk beter te zoeken naar APK's)",
|
||||
"about": "Over",
|
||||
"requiresCredentialsInSettings": "Dit vereist aanvullende referenties (in Instellingen)",
|
||||
"requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
|
||||
"checkOnStart": "Controleren op updates bij opstarten",
|
||||
"tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode",
|
||||
"removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
|
||||
"checkingForUpdatesNotifChannel": "Controleren op updates",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "App verwijderen?",
|
||||
"other": "Apps verwijderen?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Nie zainstalowano",
|
||||
"estimateInBrackets": "(Szacunkowo)",
|
||||
"selectAll": "Zaznacz wszystkie",
|
||||
"deselectN": "Odznacz {}",
|
||||
"deselectX": "Odznacz {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
|
||||
"removeSelectedAppsQuestion": "Usunąć wybrane aplikacje?",
|
||||
"removeSelectedApps": "Usuń wybrane aplikacje",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
|
||||
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
|
||||
"about": "Więcej informacji",
|
||||
"requiresCredentialsInSettings": "Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
||||
"requiresCredentialsInSettings": "{}: Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
||||
"checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
|
||||
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
|
||||
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Ukończenie instalacji aplikacji",
|
||||
"checkingForUpdatesNotifChannel": "Sprawdzanie dostępności aktualizacji",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Sprawdzaj tylko zainstalowane i obserwowane aplikacje pod kątem aktualizacji",
|
||||
"supportFixedAPKURL": "Obsługuj stałe adresy URL APK",
|
||||
"selectX": "Wybierz {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Usunąć aplikację?",
|
||||
"few": "Usunąć aplikacje?",
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Não Instalado",
|
||||
"estimateInBrackets": "(Aproximado)",
|
||||
"selectAll": "Selecionar All",
|
||||
"deselectN": "Deselecionar {}",
|
||||
"deselectX": "Deselecionar {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} sera removido do Obtainium mais permanecerá instalado no dispositivo.",
|
||||
"removeSelectedAppsQuestion": "Remover Apps Selecionados?",
|
||||
"removeSelectedApps": "Remover Apps Selecionados",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
|
||||
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
|
||||
"about": "Sobre",
|
||||
"requiresCredentialsInSettings": "Isso requer credenciais adicionais (em Configurações)",
|
||||
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
|
||||
"checkOnStart": "Checar por atualizações ao iniciar ",
|
||||
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
|
||||
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Instalação completa do App",
|
||||
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remover App?",
|
||||
"other": "Remover Apps?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Не установлено",
|
||||
"estimateInBrackets": "(Оценка)",
|
||||
"selectAll": "Выбрать всё",
|
||||
"deselectN": "Отменить выбор {}",
|
||||
"deselectX": "Отменить выбор {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется на устройстве",
|
||||
"removeSelectedAppsQuestion": "Удалить выбранные приложения?",
|
||||
"removeSelectedApps": "Удалить выбранные приложения",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
|
||||
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
|
||||
"about": "Описание",
|
||||
"requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)",
|
||||
"requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
|
||||
"checkOnStart": "Проверять наличие обновлений при запуске",
|
||||
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
|
||||
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Завершение установки приложения",
|
||||
"checkingForUpdatesNotifChannel": "Проверка обновлений",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Удалить приложение?",
|
||||
"other": "Удалить приложения?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Inte Installerad",
|
||||
"estimateInBrackets": "(Uppskattning)",
|
||||
"selectAll": "Välj Alla",
|
||||
"deselectN": "Avmarkera {}",
|
||||
"deselectX": "Avmarkera {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} kommer tas bort från Obtainium men kommer vara fortsatt installerad på enheten.",
|
||||
"removeSelectedAppsQuestion": "Ta bort markerade Appar?",
|
||||
"removeSelectedApps": "Ta bort markerade Appar",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "Om",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Kolla efter uppdateringar vid start",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Yüklenmedi",
|
||||
"estimateInBrackets": "(Tahmini)",
|
||||
"selectAll": "Hepsini Seç",
|
||||
"deselectN": "{}'yi Seçimden Kaldır",
|
||||
"deselectX": "{}'yi Seçimden Kaldır",
|
||||
"xWillBeRemovedButRemainInstalled": "{} Obtainium'dan kaldırılacak ancak cihazınızda yüklü kalacaktır.",
|
||||
"removeSelectedAppsQuestion": "Seçilen Uygulamaları Kaldırmak İstiyor musunuz?",
|
||||
"removeSelectedApps": "Seçilen Uygulamaları Kaldır",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Yüklenmemiş Uygulamaları Uygulamalar Görünümünün Altına Taşı",
|
||||
"gitlabPATLabel": "GitLab Kişisel Erişim Belirteci\n(Arama ve Daha İyi APK Keşfi İçin)",
|
||||
"about": "Hakkında",
|
||||
"requiresCredentialsInSettings": "Bu, ek kimlik bilgilerine ihtiyaç duyar (Ayarlar'da)",
|
||||
"requiresCredentialsInSettings": "{}: Bu, ek kimlik bilgilerine ihtiyaç duyar (Ayarlar'da)",
|
||||
"checkOnStart": "Başlangıçta güncellemeleri kontrol et",
|
||||
"tryInferAppIdFromCode": "Uygulama kimliğini kaynak kodundan çıkarma girişimi",
|
||||
"removeOnExternalUninstall": "Harici kaldırmada otomatik olarak kaldırılan uygulamalar",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Uygulama Kurulumu Tamamlandı",
|
||||
"checkingForUpdatesNotifChannel": "Güncellemeler Kontrol Ediliyor",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Yalnızca yüklü ve Yalnızca İzleme Uygulamalarını güncelleme",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Uygulamayı Kaldır?",
|
||||
"other": "Uygulamaları Kaldır?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "Chưa cài đặt",
|
||||
"estimateInBrackets": "(Ước lượng)",
|
||||
"selectAll": "Chọn tất cả",
|
||||
"deselectN": "Bỏ chọn {}",
|
||||
"deselectX": "Bỏ chọn {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} sẽ bị xóa khỏi Obtainium nhưng vẫn còn cài đặt trên thiết bị.",
|
||||
"removeSelectedAppsQuestion": "Xóa ứng dụng đã chọn?",
|
||||
"removeSelectedApps": "Xóa ứng dụng đã chọn",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Di chuyển Ứng dụng chưa được cài đặt xuống cuối chế độ xem Ứng dụng",
|
||||
"gitlabPATLabel": "Mã thông báo truy cập cá nhân GitLab\n(Cho phép tìm kiếm và khám phá APK tốt hơn)",
|
||||
"about": "Giới thiệu",
|
||||
"requiresCredentialsInSettings": "Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
||||
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
||||
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
|
||||
"tryInferAppIdFromCode": "Thử suy ra ID ứng dụng từ mã nguồn",
|
||||
"removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
|
||||
"checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion":{
|
||||
"one": "Gỡ ứng dụng?",
|
||||
"other": "Gỡ ứng dụng?"
|
||||
|
@@ -55,7 +55,7 @@
|
||||
"notInstalled": "未安装",
|
||||
"estimateInBrackets": "(推测)",
|
||||
"selectAll": "全选",
|
||||
"deselectN": "取消选择 {}",
|
||||
"deselectX": "取消选择 {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
||||
"removeSelectedAppsQuestion": "是否删除选中的应用?",
|
||||
"removeSelectedApps": "删除选中的应用",
|
||||
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
||||
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
|
||||
"about": "相关文档",
|
||||
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
|
||||
"requiresCredentialsInSettings": "{}: 此功能需要额外的凭据(在“设置”中添加)",
|
||||
"checkOnStart": "启动时进行一次检查",
|
||||
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
|
||||
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
|
||||
@@ -275,6 +275,8 @@
|
||||
"completeAppInstallationNotifChannel": "完成应用安装",
|
||||
"checkingForUpdatesNotifChannel": "正在检查更新",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "是否删除应用?",
|
||||
"other": "是否删除应用?"
|
||||
|
@@ -32,7 +32,8 @@ class APKMirror extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
|
||||
RegExp standardUrlRegEx =
|
||||
RegExp('^https?://(www\\.)?$host/apk/[^/]+/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@@ -139,11 +139,11 @@ APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||
}
|
||||
}
|
||||
// Apply the release filter if any
|
||||
if (filterVersionsByRegEx != null) {
|
||||
if (filterVersionsByRegEx?.isNotEmpty == true) {
|
||||
version = null;
|
||||
releaseChoices = [];
|
||||
for (var i = 0; i < releases.length; i++) {
|
||||
if (RegExp(filterVersionsByRegEx)
|
||||
if (RegExp(filterVersionsByRegEx!)
|
||||
.hasMatch(releases[i]['versionName'])) {
|
||||
version = releases[i]['versionName'];
|
||||
}
|
||||
|
@@ -54,17 +54,25 @@ class FDroidRepo extends AppSource {
|
||||
@override
|
||||
Future<Map<String, List<String>>> search(String query,
|
||||
{Map<String, dynamic> querySettings = const {}}) async {
|
||||
query = removeQueryParamsFromUrl(standardizeUrl(query));
|
||||
var res = await sourceRequest('$query/index.xml');
|
||||
String? url = querySettings['url'];
|
||||
if (url == null) {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
url = removeQueryParamsFromUrl(standardizeUrl(url));
|
||||
var res = await sourceRequest('$url/index.xml');
|
||||
if (res.statusCode == 200) {
|
||||
var body = parse(res.body);
|
||||
Map<String, List<String>> results = {};
|
||||
body.querySelectorAll('application').toList().forEach((app) {
|
||||
String appId = app.attributes['id']!;
|
||||
results['$query?appId=$appId'] = [
|
||||
app.querySelector('name')?.innerHtml ?? appId,
|
||||
app.querySelector('desc')?.innerHtml ?? ''
|
||||
];
|
||||
String appName = app.querySelector('name')?.innerHtml ?? appId;
|
||||
String appDesc = app.querySelector('desc')?.innerHtml ?? '';
|
||||
if (query.isEmpty ||
|
||||
appId.contains(query) ||
|
||||
appName.contains(query) ||
|
||||
appDesc.contains(query)) {
|
||||
results['$url?appId=$appId'] = [appName, appDesc];
|
||||
}
|
||||
});
|
||||
return results;
|
||||
} else {
|
||||
|
@@ -117,22 +117,23 @@ class GitHub extends AppSource {
|
||||
.decode(body['content'].toString().split('\n').join('')))
|
||||
.split('\n')
|
||||
.map((e) => e.trim());
|
||||
var appId = trimmedLines
|
||||
.where((l) =>
|
||||
l.startsWith('applicationId "') ||
|
||||
l.startsWith('applicationId \''))
|
||||
.first;
|
||||
appId = appId
|
||||
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1];
|
||||
if (appId.startsWith('\${') && appId.endsWith('}')) {
|
||||
appId = trimmedLines
|
||||
.where((l) => l.startsWith(
|
||||
'def ${appId.substring(2, appId.length - 1)}'))
|
||||
.first;
|
||||
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
|
||||
}
|
||||
if (appId.isNotEmpty) {
|
||||
var appIds = trimmedLines.where((l) =>
|
||||
l.startsWith('applicationId "') ||
|
||||
l.startsWith('applicationId \''));
|
||||
appIds = appIds.map((appId) => appId
|
||||
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1]);
|
||||
appIds = appIds.map((appId) {
|
||||
if (appId.startsWith('\${') && appId.endsWith('}')) {
|
||||
appId = trimmedLines
|
||||
.where((l) => l.startsWith(
|
||||
'def ${appId.substring(2, appId.length - 1)}'))
|
||||
.first;
|
||||
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
|
||||
}
|
||||
return appId;
|
||||
}).where((appId) => appId.isNotEmpty);
|
||||
if (appIds.length == 1) {
|
||||
return appIds.first;
|
||||
}
|
||||
} catch (err) {
|
||||
LogsProvider().add(
|
||||
|
@@ -48,6 +48,12 @@ class GitLab extends AppSource {
|
||||
label: tr('fallbackToOlderReleases'), defaultValue: true)
|
||||
]
|
||||
];
|
||||
searchQuerySettingFormItems = [
|
||||
GeneratedFormTextField('PAT',
|
||||
label: tr('gitlabPATLabel').split('(')[0],
|
||||
password: true,
|
||||
required: false)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -80,12 +86,18 @@ class GitLab extends AppSource {
|
||||
@override
|
||||
Future<Map<String, List<String>>> search(String query,
|
||||
{Map<String, dynamic> querySettings = const {}}) async {
|
||||
String? PAT = await getPATIfAny({});
|
||||
if (PAT == null) {
|
||||
throw CredsNeededError(name);
|
||||
String? PAT;
|
||||
if (!hostChanged) {
|
||||
PAT = await getPATIfAny({});
|
||||
if (PAT == null) {
|
||||
throw CredsNeededError(name);
|
||||
}
|
||||
}
|
||||
if ((querySettings['PAT'] as String?)?.isNotEmpty == true) {
|
||||
PAT = querySettings['PAT'];
|
||||
}
|
||||
var url =
|
||||
'https://$host/api/v4/search?private_token=$PAT&scope=projects&search=${Uri.encodeQueryComponent(query)}';
|
||||
'https://$host/api/v4/search?${PAT?.isNotEmpty == true ? 'private_token=$PAT&' : ''}scope=projects&search=${Uri.encodeQueryComponent(query)}';
|
||||
var res = await sourceRequest(url);
|
||||
if (res.statusCode != 200) {
|
||||
throw getObtainiumHttpError(res);
|
||||
@@ -174,7 +186,6 @@ class GitLab extends AppSource {
|
||||
...getLinksFromParsedHTML(entryContent,
|
||||
RegExp('/[^/]+\\.apk\$', caseSensitive: false), '')
|
||||
.where((element) => Uri.parse(element).host != '')
|
||||
|
||||
];
|
||||
var entryId = entry.querySelector('id')?.innerHtml;
|
||||
var version =
|
||||
@@ -192,7 +203,7 @@ class GitLab extends AppSource {
|
||||
});
|
||||
}
|
||||
if (apkDetailsList.isEmpty) {
|
||||
throw NoReleasesError();
|
||||
throw NoReleasesError(note: tr('gitlabSourceNote'));
|
||||
}
|
||||
if (fallbackToOlderReleases) {
|
||||
if (additionalSettings['trackOnly'] != true) {
|
||||
@@ -200,7 +211,7 @@ class GitLab extends AppSource {
|
||||
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
|
||||
}
|
||||
if (apkDetailsList.isEmpty) {
|
||||
throw NoReleasesError();
|
||||
throw NoReleasesError(note: tr('gitlabSourceNote'));
|
||||
}
|
||||
}
|
||||
return apkDetailsList.first;
|
||||
|
@@ -4,6 +4,7 @@ import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
||||
@@ -94,6 +95,10 @@ class HTML extends AppSource {
|
||||
label: tr('sortByFileNamesNotLinks'))
|
||||
],
|
||||
[GeneratedFormSwitch('reverseSort', label: tr('reverseSort'))],
|
||||
[
|
||||
GeneratedFormSwitch('supportFixedAPKURL',
|
||||
defaultValue: true, label: tr('supportFixedAPKURL')),
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('customLinkFilterRegex',
|
||||
label: tr('customLinkFilterRegex'),
|
||||
@@ -222,7 +227,10 @@ class HTML extends AppSource {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
var rel = links.last;
|
||||
String? version = rel.hashCode.toString();
|
||||
String? version;
|
||||
if (additionalSettings['supportFixedAPKURL'] != true) {
|
||||
version = rel.hashCode.toString();
|
||||
}
|
||||
var versionExtractionRegEx =
|
||||
additionalSettings['versionExtractionRegEx'] as String?;
|
||||
if (versionExtractionRegEx?.isNotEmpty == true) {
|
||||
@@ -243,9 +251,9 @@ class HTML extends AppSource {
|
||||
throw NoVersionError();
|
||||
}
|
||||
}
|
||||
List<String> apkUrls =
|
||||
[rel].map((e) => ensureAbsoluteUrl(e, uri)).toList();
|
||||
return APKDetails(version!, apkUrls.map((e) => MapEntry(e, e)).toList(),
|
||||
rel = ensureAbsoluteUrl(rel, uri);
|
||||
version ??= (await checkDownloadHash(rel)).toString();
|
||||
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||
AppNames(uri.host, tr('app')));
|
||||
} else {
|
||||
throw getObtainiumHttpError(res);
|
||||
|
@@ -34,7 +34,9 @@ class CredsNeededError extends ObtainiumError {
|
||||
}
|
||||
|
||||
class NoReleasesError extends ObtainiumError {
|
||||
NoReleasesError() : super(tr('noReleaseFound'));
|
||||
NoReleasesError({String? note})
|
||||
: super(
|
||||
'${tr('noReleaseFound')}${note?.isNotEmpty == true ? '\n\n$note' : ''}');
|
||||
}
|
||||
|
||||
class NoAPKError extends ObtainiumError {
|
||||
|
@@ -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.34';
|
||||
const String currentVersion = '0.14.36';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
|
@@ -254,57 +254,79 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
],
|
||||
);
|
||||
|
||||
runSearch() async {
|
||||
runSearch({bool filtered = true}) async {
|
||||
setState(() {
|
||||
searching = true;
|
||||
});
|
||||
var sourceStrings = <String, List<String>>{};
|
||||
sourceProvider.sources
|
||||
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
||||
.forEach((s) {
|
||||
sourceStrings[s.name] = [s.name];
|
||||
});
|
||||
try {
|
||||
var results = await Future.wait(sourceProvider.sources
|
||||
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
||||
.map((e) async {
|
||||
try {
|
||||
return await e.search(searchQuery);
|
||||
} catch (err) {
|
||||
if (err is! CredsNeededError) {
|
||||
rethrow;
|
||||
} else {
|
||||
return <String, List<String>>{};
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// .then((results) async {
|
||||
// Interleave results instead of simple reduce
|
||||
Map<String, List<String>> res = {};
|
||||
var si = 0;
|
||||
var done = false;
|
||||
while (!done) {
|
||||
done = true;
|
||||
for (var r in results) {
|
||||
if (r.length > si) {
|
||||
done = false;
|
||||
res.addEntries([r.entries.elementAt(si)]);
|
||||
}
|
||||
}
|
||||
si++;
|
||||
}
|
||||
if (res.isEmpty) {
|
||||
throw ObtainiumError(tr('noResults'));
|
||||
}
|
||||
List<String>? selectedUrls = res.isEmpty
|
||||
? []
|
||||
// ignore: use_build_context_synchronously
|
||||
: await showDialog<List<String>?>(
|
||||
var searchSources = await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return UrlSelectionModal(
|
||||
urlsWithDescriptions: res,
|
||||
selectedByDefault: false,
|
||||
onlyOneSelectionAllowed: true,
|
||||
return SelectionModal(
|
||||
title: tr('selectX', args: [plural('source', 2)]),
|
||||
entries: sourceStrings,
|
||||
selectedByDefault: true,
|
||||
onlyOneSelectionAllowed: false,
|
||||
titlesAreLinks: false,
|
||||
);
|
||||
});
|
||||
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
||||
changeUserInput(selectedUrls[0], true, false, isSearch: true);
|
||||
}) ??
|
||||
[];
|
||||
if (searchSources.isNotEmpty) {
|
||||
var results = await Future.wait(sourceProvider.sources
|
||||
.where((e) => searchSources.contains(e.name))
|
||||
.map((e) async {
|
||||
try {
|
||||
return await e.search(searchQuery);
|
||||
} catch (err) {
|
||||
if (err is! CredsNeededError) {
|
||||
rethrow;
|
||||
} else {
|
||||
err.unexpected = true;
|
||||
showError(err, context);
|
||||
return <String, List<String>>{};
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// .then((results) async {
|
||||
// Interleave results instead of simple reduce
|
||||
Map<String, List<String>> res = {};
|
||||
var si = 0;
|
||||
var done = false;
|
||||
while (!done) {
|
||||
done = true;
|
||||
for (var r in results) {
|
||||
if (r.length > si) {
|
||||
done = false;
|
||||
res.addEntries([r.entries.elementAt(si)]);
|
||||
}
|
||||
}
|
||||
si++;
|
||||
}
|
||||
if (res.isEmpty) {
|
||||
throw ObtainiumError(tr('noResults'));
|
||||
}
|
||||
List<String>? selectedUrls = res.isEmpty
|
||||
? []
|
||||
// ignore: use_build_context_synchronously
|
||||
: await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return SelectionModal(
|
||||
entries: res,
|
||||
selectedByDefault: false,
|
||||
onlyOneSelectionAllowed: true,
|
||||
);
|
||||
});
|
||||
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
||||
changeUserInput(selectedUrls[0], true, false, isSearch: true);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
showError(e, context);
|
||||
@@ -470,23 +492,21 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
...sourceProvider.sources
|
||||
.map((e) => GestureDetector(
|
||||
onTap: e.host != null
|
||||
? () {
|
||||
launchUrlString('https://${e.host}',
|
||||
mode: LaunchMode.externalApplication);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
||||
style: TextStyle(
|
||||
decoration: e.host != null
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
fontStyle: FontStyle.italic),
|
||||
)))
|
||||
|
||||
...sourceProvider.sources.map((e) => GestureDetector(
|
||||
onTap: e.host != null
|
||||
? () {
|
||||
launchUrlString('https://${e.host}',
|
||||
mode: LaunchMode.externalApplication);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
||||
style: TextStyle(
|
||||
decoration: e.host != null
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
fontStyle: FontStyle.italic),
|
||||
)))
|
||||
]);
|
||||
|
||||
return Scaffold(
|
||||
|
@@ -145,6 +145,29 @@ class _AppPageState extends State<AppPage> {
|
||||
appsProvider.saveApps([app.app]);
|
||||
}
|
||||
}),
|
||||
if (app?.app.additionalSettings['about'] is String &&
|
||||
app?.app.additionalSettings['about'].isNotEmpty)
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
GestureDetector(
|
||||
onLongPress: () {
|
||||
Clipboard.setData(ClipboardData(
|
||||
text: app?.app.additionalSettings['about'] ?? ''));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(tr('copiedToClipboard')),
|
||||
));
|
||||
},
|
||||
child: Text(
|
||||
app?.app.additionalSettings['about'],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:obtainium/app_sources/fdroidrepo.dart';
|
||||
import 'package:obtainium/components/custom_app_bar.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
@@ -189,17 +190,29 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
items: [
|
||||
[
|
||||
GeneratedFormTextField('searchQuery',
|
||||
label: tr('searchQuery'))
|
||||
label: tr('searchQuery'),
|
||||
required: source.name != FDroidRepo().name)
|
||||
],
|
||||
...source.searchQuerySettingFormItems.map((e) => [e]),
|
||||
[
|
||||
GeneratedFormTextField('url',
|
||||
label: source.host != null
|
||||
? tr('overrideSource')
|
||||
: plural('url', 1).substring(2),
|
||||
defaultValue: source.host ?? '',
|
||||
required: true)
|
||||
],
|
||||
...source.searchQuerySettingFormItems.map((e) => [e])
|
||||
],
|
||||
);
|
||||
});
|
||||
if (values != null &&
|
||||
(values['searchQuery'] as String?)?.isNotEmpty == true) {
|
||||
if (values != null) {
|
||||
setState(() {
|
||||
importInProgress = true;
|
||||
});
|
||||
if (values['url'] != source.host) {
|
||||
source = sourceProvider.getSource(values['url'],
|
||||
overrideSource: source.runtimeType.toString());
|
||||
}
|
||||
var urlsWithDescriptions = await source
|
||||
.search(values['searchQuery'] as String, querySettings: values);
|
||||
if (urlsWithDescriptions.isNotEmpty) {
|
||||
@@ -208,8 +221,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return UrlSelectionModal(
|
||||
urlsWithDescriptions: urlsWithDescriptions,
|
||||
return SelectionModal(
|
||||
entries: urlsWithDescriptions,
|
||||
selectedByDefault: false,
|
||||
);
|
||||
});
|
||||
@@ -269,8 +282,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return UrlSelectionModal(
|
||||
urlsWithDescriptions: urlsWithDescriptions);
|
||||
return SelectionModal(entries: urlsWithDescriptions);
|
||||
});
|
||||
if (selectedUrls != null) {
|
||||
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
||||
@@ -300,6 +312,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
});
|
||||
}
|
||||
|
||||
var sourceStrings = <String, List<String>>{};
|
||||
sourceProvider.sources.where((e) => e.canSearch).forEach((s) {
|
||||
sourceStrings[s.name] = [s.name];
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: CustomScrollView(slivers: <Widget>[
|
||||
@@ -409,6 +426,54 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
const Divider(
|
||||
height: 32,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () async {
|
||||
var searchSourceName =
|
||||
await showDialog<
|
||||
List<String>?>(
|
||||
context: context,
|
||||
builder:
|
||||
(BuildContext
|
||||
ctx) {
|
||||
return SelectionModal(
|
||||
title: tr(
|
||||
'selectX',
|
||||
args: [
|
||||
tr('source')
|
||||
]),
|
||||
entries:
|
||||
sourceStrings,
|
||||
selectedByDefault:
|
||||
false,
|
||||
onlyOneSelectionAllowed:
|
||||
true,
|
||||
titlesAreLinks:
|
||||
false,
|
||||
);
|
||||
}) ??
|
||||
[];
|
||||
var searchSource =
|
||||
sourceProvider.sources
|
||||
.where((e) =>
|
||||
searchSourceName
|
||||
.contains(
|
||||
e.name))
|
||||
.toList();
|
||||
if (searchSource.isNotEmpty) {
|
||||
runSourceSearch(
|
||||
searchSource[0]);
|
||||
}
|
||||
},
|
||||
child: Text(tr('searchX',
|
||||
args: [tr('source')])))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed:
|
||||
importInProgress ? null : urlListImport,
|
||||
@@ -424,39 +489,19 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
)),
|
||||
],
|
||||
),
|
||||
...sourceProvider.sources
|
||||
.where((element) => element.canSearch)
|
||||
.map((source) => Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () {
|
||||
runSourceSearch(source);
|
||||
},
|
||||
child: Text(
|
||||
tr('searchX', args: [source.name])))
|
||||
]))
|
||||
,
|
||||
...sourceProvider.massUrlSources
|
||||
.map((source) => Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () {
|
||||
runMassSourceImport(source);
|
||||
},
|
||||
child: Text(
|
||||
tr('importX', args: [source.name])))
|
||||
]))
|
||||
,
|
||||
...sourceProvider.massUrlSources.map((source) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () {
|
||||
runMassSourceImport(source);
|
||||
},
|
||||
child: Text(
|
||||
tr('importX', args: [source.name])))
|
||||
])),
|
||||
const Spacer(),
|
||||
const Divider(
|
||||
height: 32,
|
||||
@@ -532,112 +577,171 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UrlSelectionModal extends StatefulWidget {
|
||||
UrlSelectionModal(
|
||||
class SelectionModal extends StatefulWidget {
|
||||
SelectionModal(
|
||||
{super.key,
|
||||
required this.urlsWithDescriptions,
|
||||
required this.entries,
|
||||
this.selectedByDefault = true,
|
||||
this.onlyOneSelectionAllowed = false});
|
||||
this.onlyOneSelectionAllowed = false,
|
||||
this.titlesAreLinks = true,
|
||||
this.title});
|
||||
|
||||
Map<String, List<String>> urlsWithDescriptions;
|
||||
String? title;
|
||||
Map<String, List<String>> entries;
|
||||
bool selectedByDefault;
|
||||
bool onlyOneSelectionAllowed;
|
||||
bool titlesAreLinks;
|
||||
|
||||
@override
|
||||
State<UrlSelectionModal> createState() => _UrlSelectionModalState();
|
||||
State<SelectionModal> createState() => _SelectionModalState();
|
||||
}
|
||||
|
||||
class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
Map<MapEntry<String, List<String>>, bool> urlWithDescriptionSelections = {};
|
||||
class _SelectionModalState extends State<SelectionModal> {
|
||||
Map<MapEntry<String, List<String>>, bool> entrySelections = {};
|
||||
String filterRegex = '';
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
for (var url in widget.urlsWithDescriptions.entries) {
|
||||
urlWithDescriptionSelections.putIfAbsent(url,
|
||||
for (var url in widget.entries.entries) {
|
||||
entrySelections.putIfAbsent(url,
|
||||
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
|
||||
}
|
||||
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
|
||||
selectOnlyOne(widget.urlsWithDescriptions.entries.first.key);
|
||||
selectOnlyOne(widget.entries.entries.first.key);
|
||||
}
|
||||
}
|
||||
|
||||
selectOnlyOne(String url) {
|
||||
for (var uwd in urlWithDescriptionSelections.keys) {
|
||||
urlWithDescriptionSelections[uwd] = uwd.key == url;
|
||||
for (var e in entrySelections.keys) {
|
||||
entrySelections[e] = e.key == url;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Map<MapEntry<String, List<String>>, bool> filteredEntrySelections = {};
|
||||
entrySelections.forEach((key, value) {
|
||||
var searchableText = key.value.isEmpty ? key.key : key.value[0];
|
||||
if (filterRegex.isEmpty || RegExp(filterRegex).hasMatch(searchableText)) {
|
||||
filteredEntrySelections.putIfAbsent(key, () => value);
|
||||
}
|
||||
});
|
||||
if (filterRegex.isNotEmpty && filteredEntrySelections.isEmpty) {
|
||||
entrySelections.forEach((key, value) {
|
||||
var searchableText = key.value.isEmpty ? key.key : key.value[0];
|
||||
if (filterRegex.isEmpty ||
|
||||
RegExp(filterRegex, caseSensitive: false)
|
||||
.hasMatch(searchableText)) {
|
||||
filteredEntrySelections.putIfAbsent(key, () => value);
|
||||
}
|
||||
});
|
||||
}
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
title: Text(
|
||||
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
|
||||
title: Text(widget.title ?? tr('pick')),
|
||||
content: Column(children: [
|
||||
...urlWithDescriptionSelections.keys.map((urlWithD) {
|
||||
GeneratedForm(
|
||||
items: [
|
||||
[
|
||||
GeneratedFormTextField('filter',
|
||||
label: tr('filter'),
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
return regExValidator(value);
|
||||
}
|
||||
])
|
||||
]
|
||||
],
|
||||
onValueChanges: (value, valid, isBuilding) {
|
||||
if (valid && !isBuilding) {
|
||||
if (value['filter'] != null) {
|
||||
setState(() {
|
||||
filterRegex = value['filter'];
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
...filteredEntrySelections.keys.map((entry) {
|
||||
selectThis(bool? value) {
|
||||
setState(() {
|
||||
value ??= false;
|
||||
if (value! && widget.onlyOneSelectionAllowed) {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
selectOnlyOne(entry.key);
|
||||
} else {
|
||||
urlWithDescriptionSelections[urlWithD] = value!;
|
||||
entrySelections[entry] = value!;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var urlLink = GestureDetector(
|
||||
onTap: () {
|
||||
launchUrlString(urlWithD.key,
|
||||
mode: LaunchMode.externalApplication);
|
||||
},
|
||||
onTap: !widget.titlesAreLinks
|
||||
? null
|
||||
: () {
|
||||
launchUrlString(entry.key,
|
||||
mode: LaunchMode.externalApplication);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
urlWithD.value[0],
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
entry.value.isEmpty ? entry.key : entry.value[0],
|
||||
style: TextStyle(
|
||||
decoration: widget.titlesAreLinks
|
||||
? TextDecoration.underline
|
||||
: null,
|
||||
fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
Text(
|
||||
Uri.parse(urlWithD.key).host,
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline, fontSize: 12),
|
||||
)
|
||||
if (widget.titlesAreLinks)
|
||||
Text(
|
||||
Uri.parse(entry.key).host,
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline, fontSize: 12),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
||||
var descriptionText = Text(
|
||||
urlWithD.value[1].length > 128
|
||||
? '${urlWithD.value[1].substring(0, 128)}...'
|
||||
: urlWithD.value[1],
|
||||
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
|
||||
);
|
||||
var descriptionText = entry.value.length <= 1
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
entry.value[1].length > 128
|
||||
? '${entry.value[1].substring(0, 128)}...'
|
||||
: entry.value[1],
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic, fontSize: 12),
|
||||
);
|
||||
|
||||
var selectedUrlsWithDs = urlWithDescriptionSelections.entries
|
||||
.where((e) => e.value)
|
||||
.toList();
|
||||
var selectedEntries =
|
||||
entrySelections.entries.where((e) => e.value).toList();
|
||||
|
||||
var singleSelectTile = ListTile(
|
||||
title: urlLink,
|
||||
subtitle: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
});
|
||||
},
|
||||
child: descriptionText,
|
||||
),
|
||||
leading: Radio<String>(
|
||||
value: urlWithD.key,
|
||||
groupValue: selectedUrlsWithDs.isEmpty
|
||||
title: GestureDetector(
|
||||
onTap: widget.titlesAreLinks
|
||||
? null
|
||||
: selectedUrlsWithDs.first.key.key,
|
||||
: () {
|
||||
selectThis(!(entrySelections[entry] ?? false));
|
||||
},
|
||||
child: urlLink,
|
||||
),
|
||||
subtitle: entry.value.length <= 1
|
||||
? null
|
||||
: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectOnlyOne(entry.key);
|
||||
});
|
||||
},
|
||||
child: descriptionText,
|
||||
),
|
||||
leading: Radio<String>(
|
||||
value: entry.key,
|
||||
groupValue: selectedEntries.isEmpty
|
||||
? null
|
||||
: selectedEntries.first.key.key,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
selectOnlyOne(entry.key);
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -645,7 +749,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
|
||||
var multiSelectTile = Row(children: [
|
||||
Checkbox(
|
||||
value: urlWithDescriptionSelections[urlWithD],
|
||||
value: entrySelections[entry],
|
||||
onChanged: (value) {
|
||||
selectThis(value);
|
||||
}),
|
||||
@@ -660,14 +764,22 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
urlLink,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
selectThis(
|
||||
!(urlWithDescriptionSelections[urlWithD] ?? false));
|
||||
},
|
||||
child: descriptionText,
|
||||
onTap: widget.titlesAreLinks
|
||||
? null
|
||||
: () {
|
||||
selectThis(!(entrySelections[entry] ?? false));
|
||||
},
|
||||
child: urlLink,
|
||||
),
|
||||
entry.value.length <= 1
|
||||
? const SizedBox.shrink()
|
||||
: GestureDetector(
|
||||
onTap: () {
|
||||
selectThis(!(entrySelections[entry] ?? false));
|
||||
},
|
||||
child: descriptionText,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
)
|
||||
@@ -687,24 +799,18 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
},
|
||||
child: Text(tr('cancel'))),
|
||||
TextButton(
|
||||
onPressed:
|
||||
urlWithDescriptionSelections.values.where((b) => b).isEmpty
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).pop(urlWithDescriptionSelections
|
||||
.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((e) => e.key.key)
|
||||
.toList());
|
||||
},
|
||||
onPressed: entrySelections.values.where((b) => b).isEmpty
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).pop(entrySelections.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((e) => e.key.key)
|
||||
.toList());
|
||||
},
|
||||
child: Text(widget.onlyOneSelectionAllowed
|
||||
? tr('pick')
|
||||
: tr('importX', args: [
|
||||
plural(
|
||||
'url',
|
||||
urlWithDescriptionSelections.values
|
||||
.where((b) => b)
|
||||
.length)
|
||||
: tr('selectX', args: [
|
||||
entrySelections.values.where((b) => b).length.toString()
|
||||
])))
|
||||
],
|
||||
);
|
||||
|
@@ -6,6 +6,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
||||
import 'package:android_intent_plus/flag.dart';
|
||||
@@ -139,6 +140,100 @@ List<MapEntry<String, int>> moveStrToEndMapEntryWithCount(
|
||||
return arr;
|
||||
}
|
||||
|
||||
Future<File> downloadFileWithRetry(
|
||||
String url, String fileNameNoExt, Function? onProgress, String destDir,
|
||||
{bool useExisting = true,
|
||||
Map<String, String>? headers,
|
||||
int retries = 3}) async {
|
||||
try {
|
||||
return await downloadFile(url, fileNameNoExt, onProgress, destDir,
|
||||
useExisting: useExisting, headers: headers);
|
||||
} catch (e) {
|
||||
if (retries > 0 && e is ClientException) {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
return await downloadFileWithRetry(
|
||||
url, fileNameNoExt, onProgress, destDir,
|
||||
useExisting: useExisting, headers: headers, retries: (retries - 1));
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String hashListOfLists(List<List<int>> data) {
|
||||
var bytes = utf8.encode(jsonEncode(data));
|
||||
var digest = sha256.convert(bytes);
|
||||
var hash = digest.toString();
|
||||
return hash.hashCode.toString();
|
||||
}
|
||||
|
||||
Future<String> checkDownloadHash(String url,
|
||||
{int bytesToGrab = 1024, Map<String, String>? headers}) async {
|
||||
var req = Request('GET', Uri.parse(url));
|
||||
if (headers != null) {
|
||||
req.headers.addAll(headers);
|
||||
}
|
||||
req.headers[HttpHeaders.rangeHeader] = 'bytes=0-$bytesToGrab';
|
||||
var client = http.Client();
|
||||
var response = await client.send(req);
|
||||
if (response.statusCode < 200 || response.statusCode > 299) {
|
||||
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
|
||||
}
|
||||
List<List<int>> bytes = await response.stream.take(bytesToGrab).toList();
|
||||
return hashListOfLists(bytes);
|
||||
}
|
||||
|
||||
Future<File> downloadFile(
|
||||
String url, String fileNameNoExt, Function? onProgress, String destDir,
|
||||
{bool useExisting = true, Map<String, String>? headers}) async {
|
||||
var req = Request('GET', Uri.parse(url));
|
||||
if (headers != null) {
|
||||
req.headers.addAll(headers);
|
||||
}
|
||||
var client = http.Client();
|
||||
StreamedResponse response = await client.send(req);
|
||||
String ext =
|
||||
response.headers['content-disposition']?.split('.').last ?? 'apk';
|
||||
if (ext.endsWith('"') || ext.endsWith("other")) {
|
||||
ext = ext.substring(0, ext.length - 1);
|
||||
}
|
||||
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
|
||||
ext = 'apk';
|
||||
}
|
||||
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
|
||||
if (!(downloadedFile.existsSync() && useExisting)) {
|
||||
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
||||
if (tempDownloadedFile.existsSync()) {
|
||||
tempDownloadedFile.deleteSync(recursive: true);
|
||||
}
|
||||
var length = response.contentLength;
|
||||
var received = 0;
|
||||
double? progress;
|
||||
var sink = tempDownloadedFile.openWrite();
|
||||
await response.stream.map((s) {
|
||||
received += s.length;
|
||||
progress = (length != null ? received / length * 100 : 30);
|
||||
if (onProgress != null) {
|
||||
onProgress(progress);
|
||||
}
|
||||
return s;
|
||||
}).pipe(sink);
|
||||
await sink.close();
|
||||
progress = null;
|
||||
if (onProgress != null) {
|
||||
onProgress(progress);
|
||||
}
|
||||
if (response.statusCode != 200) {
|
||||
tempDownloadedFile.deleteSync(recursive: true);
|
||||
throw response.reasonPhrase ?? tr('unexpectedError');
|
||||
}
|
||||
tempDownloadedFile.renameSync(downloadedFile.path);
|
||||
} else {
|
||||
client.close();
|
||||
}
|
||||
return downloadedFile;
|
||||
}
|
||||
|
||||
class AppsProvider with ChangeNotifier {
|
||||
// In memory App state (should always be kept in sync with local storage versions)
|
||||
Map<String, AppInMemory> apps = {};
|
||||
@@ -192,77 +287,6 @@ class AppsProvider with ChangeNotifier {
|
||||
}();
|
||||
}
|
||||
|
||||
Future<File> downloadFileWithRetry(
|
||||
String url, String fileNameNoExt, Function? onProgress,
|
||||
{bool useExisting = true,
|
||||
Map<String, String>? headers,
|
||||
int retries = 3}) async {
|
||||
try {
|
||||
return await downloadFile(url, fileNameNoExt, onProgress,
|
||||
useExisting: useExisting, headers: headers);
|
||||
} catch (e) {
|
||||
if (retries > 0 && e is ClientException) {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
return await downloadFileWithRetry(url, fileNameNoExt, onProgress,
|
||||
useExisting: useExisting, headers: headers, retries: (retries - 1));
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> downloadFile(
|
||||
String url, String fileNameNoExt, Function? onProgress,
|
||||
{bool useExisting = true, Map<String, String>? headers}) async {
|
||||
var destDir = APKDir.path;
|
||||
var req = Request('GET', Uri.parse(url));
|
||||
if (headers != null) {
|
||||
req.headers.addAll(headers);
|
||||
}
|
||||
var client = http.Client();
|
||||
StreamedResponse response = await client.send(req);
|
||||
String ext =
|
||||
response.headers['content-disposition']?.split('.').last ?? 'apk';
|
||||
if (ext.endsWith('"') || ext.endsWith("other")) {
|
||||
ext = ext.substring(0, ext.length - 1);
|
||||
}
|
||||
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
|
||||
ext = 'apk';
|
||||
}
|
||||
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
|
||||
if (!(downloadedFile.existsSync() && useExisting)) {
|
||||
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
||||
if (tempDownloadedFile.existsSync()) {
|
||||
tempDownloadedFile.deleteSync(recursive: true);
|
||||
}
|
||||
var length = response.contentLength;
|
||||
var received = 0;
|
||||
double? progress;
|
||||
var sink = tempDownloadedFile.openWrite();
|
||||
await response.stream.map((s) {
|
||||
received += s.length;
|
||||
progress = (length != null ? received / length * 100 : 30);
|
||||
if (onProgress != null) {
|
||||
onProgress(progress);
|
||||
}
|
||||
return s;
|
||||
}).pipe(sink);
|
||||
await sink.close();
|
||||
progress = null;
|
||||
if (onProgress != null) {
|
||||
onProgress(progress);
|
||||
}
|
||||
if (response.statusCode != 200) {
|
||||
tempDownloadedFile.deleteSync(recursive: true);
|
||||
throw response.reasonPhrase ?? tr('unexpectedError');
|
||||
}
|
||||
tempDownloadedFile.renameSync(downloadedFile.path);
|
||||
} else {
|
||||
client.close();
|
||||
}
|
||||
return downloadedFile;
|
||||
}
|
||||
|
||||
Future<File> handleAPKIDChange(App app, PackageInfo? newInfo,
|
||||
File downloadedFile, String downloadUrl) async {
|
||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
||||
@@ -322,7 +346,7 @@ class AppsProvider with ChangeNotifier {
|
||||
notificationsProvider?.notify(notif);
|
||||
}
|
||||
prevProg = prog;
|
||||
});
|
||||
}, APKDir.path);
|
||||
// Set to 90 for remaining steps, will make null in 'finally'
|
||||
if (apps[app.id] != null) {
|
||||
apps[app.id]!.downloadProgress = -1;
|
||||
|
@@ -67,10 +67,11 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
||||
.reduce((value, element) => [...value, ...element]);
|
||||
Map<String, dynamic> additionalSettings =
|
||||
getDefaultValuesFromFormItems([formItems]);
|
||||
Map<String, dynamic> originalAdditionalSettings = {};
|
||||
if (json['additionalSettings'] != null) {
|
||||
additionalSettings.addEntries(
|
||||
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']))
|
||||
.entries);
|
||||
originalAdditionalSettings =
|
||||
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']));
|
||||
additionalSettings.addEntries(originalAdditionalSettings.entries);
|
||||
}
|
||||
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
|
||||
if (json['additionalData'] != null) {
|
||||
@@ -134,6 +135,11 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
||||
if (additionalSettings['autoApkFilterByArch'] == null) {
|
||||
additionalSettings['autoApkFilterByArch'] = false;
|
||||
}
|
||||
// HTML 'fixed URL' support should be disabled if it previously did not exist
|
||||
if (source.runtimeType == HTML().runtimeType &&
|
||||
originalAdditionalSettings['supportFixedAPKURL'] == null) {
|
||||
additionalSettings['supportFixedAPKURL'] = false;
|
||||
}
|
||||
json['additionalSettings'] = jsonEncode(additionalSettings);
|
||||
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
||||
// This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid)
|
||||
@@ -448,7 +454,8 @@ abstract class AppSource {
|
||||
[
|
||||
GeneratedFormSwitch('skipUpdateNotifications',
|
||||
label: tr('skipUpdateNotifications'))
|
||||
]
|
||||
],
|
||||
[GeneratedFormTextField('about', label: tr('about'), required: false)]
|
||||
];
|
||||
|
||||
// Previous 2 variables combined into one at runtime for convenient usage
|
||||
@@ -596,7 +603,7 @@ class SourceProvider {
|
||||
AppSource? source;
|
||||
for (var s in sources.where((element) => element.host != null)) {
|
||||
if (RegExp(
|
||||
'://(${s.allowSubDomains ? '([^\\.]+\\.)*' : ''}|www\\.)${s.host}(/|\\z)?')
|
||||
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}${s.host}(/|\\z)?')
|
||||
.hasMatch(url)) {
|
||||
source = s;
|
||||
break;
|
||||
|
78
pubspec.lock
78
pubspec.lock
@@ -5,10 +5,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: android_alarm_manager_plus
|
||||
sha256: "82fb28c867c4b3dd7e9157728e46426b8916362f977dbba46b949210f00099f4"
|
||||
sha256: "84720c8ad2758aabfbeafd24a8c355d8c8dd3aa52b01eaf3bb827c7210f61a91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.4"
|
||||
android_intent_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -38,10 +38,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: animations
|
||||
sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70
|
||||
sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
version: "2.0.10"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -118,10 +118,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: b502a681ba415272ecc41400bd04fe543ed1a62632137dc84d25a91e7746f55f
|
||||
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
version: "5.0.2"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -142,12 +142,12 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c"
|
||||
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3+6"
|
||||
version: "0.3.3+8"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
@@ -182,10 +182,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419"
|
||||
sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
version: "9.1.1"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -291,10 +291,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3"
|
||||
sha256: bb5cd63ff7c91d6efe452e41d0d0ae6348925c82eafd10ce170ef585ea04776e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.1.0"
|
||||
version: "16.2.0"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -370,10 +370,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -538,50 +538,58 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8"
|
||||
sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
version: "11.1.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e
|
||||
sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.1.0"
|
||||
version: "12.0.1"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
version: "9.2.0"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0+2"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
||||
sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.12.0"
|
||||
version: "4.0.2"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
version: "0.2.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -847,10 +855,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2"
|
||||
sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -903,10 +911,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f"
|
||||
sha256: "68e86162aa8fc646ae859e1585995c096c95fc2476881fa0c4a8d10f56013a5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
version: "2.8.0"
|
||||
webview_flutter_wkwebview:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -919,10 +927,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f"
|
||||
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.0"
|
||||
version: "5.1.1"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -943,10 +951,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.2"
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
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.34+228 # When changing this, update the tag in main() accordingly
|
||||
version: 0.14.36+230 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@@ -66,6 +66,7 @@ dependencies:
|
||||
hsluv: ^1.1.3
|
||||
connectivity_plus: ^5.0.0
|
||||
shared_storage: ^0.8.0
|
||||
crypto: ^3.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Reference in New Issue
Block a user