mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Compare commits
14 Commits
v0.14.41-b
...
v0.15.0-be
Author | SHA1 | Date | |
---|---|---|---|
bfe09791d5 | |||
85d103f3f6 | |||
9e6dbe2465 | |||
355e5ccda6 | |||
a5f6f05e10 | |||
db0d35d80b | |||
6fca2a3931 | |||
0305a42b02 | |||
77d81716ed | |||
3e54e80eb6 | |||
3c9bb63d32 | |||
617ab9efab | |||
bc574097e2 | |||
4cc64dc233 |
@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 34
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -54,7 +54,7 @@ android {
|
|||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
targetSdkVersion 34
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="dev.imranr.obtainium">
|
package="dev.imranr.obtainium">
|
||||||
<application
|
<application
|
||||||
android:label="Obtainium"
|
android:label="Obtainium"
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
ext.kotlin_version = '1.7.10'
|
||||||
|
ext {
|
||||||
|
compileSdkVersion = 34 // or latest
|
||||||
|
targetSdkVersion = 34 // or latest
|
||||||
|
appCompatVersion = "1.4.2" // or latest
|
||||||
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -16,6 +21,10 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
// [required] background_fetch
|
||||||
|
url "${project(':background_fetch').projectDir}/libs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Dodajte ove informacije u Postavkama.",
|
"addInfoInSettings": "Dodajte ove informacije u Postavkama.",
|
||||||
"githubSourceNote": "GitHub ograničavanje se može izbjeći korišćenjem tokena za lični pristup.",
|
"githubSourceNote": "GitHub ograničavanje se može izbjeći korišćenjem tokena za lični pristup.",
|
||||||
"gitlabSourceNote": "GitLab APK preuzimanje možda neće raditi bez tokena za lični pristup.",
|
"gitlabSourceNote": "GitLab APK preuzimanje možda neće raditi bez tokena za lični pristup.",
|
||||||
"sortByFileNamesNotLinks": "Sortirajte po imenima datoteka umjesto po punim linkovima",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtirajte promjene u izdanju po regularnom izrazu",
|
"filterReleaseNotesByRegEx": "Filtirajte promjene u izdanju po regularnom izrazu",
|
||||||
"customLinkFilterRegex": "Prilagođeni APK link filtrira se po regularnom izrazu (Zadano '.apk$')",
|
"customLinkFilterRegex": "Prilagođeni APK link filtrira se po regularnom izrazu (Zadano '.apk$')",
|
||||||
"appsPossiblyUpdated": "Pokušano ažuriranje aplikacija",
|
"appsPossiblyUpdated": "Pokušano ažuriranje aplikacija",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Ažuriranja u pozadini možda neće raditi za sve aplikacije.",
|
"backgroundUpdateReqsExplanation": "Ažuriranja u pozadini možda neće raditi za sve aplikacije.",
|
||||||
"backgroundUpdateLimitsExplanation": "Uspjeh ažuriranja u pozadini se može provjeriti tek kada otvorite Obtainium.",
|
"backgroundUpdateLimitsExplanation": "Uspjeh ažuriranja u pozadini se može provjeriti tek kada otvorite Obtainium.",
|
||||||
"verifyLatestTag": "Provjerite 'posljednu' ('latest') oznaku",
|
"verifyLatestTag": "Provjerite 'posljednu' ('latest') oznaku",
|
||||||
"intermediateLinkRegex": "Filtrirajte da prvo posjetite 'Intemediate' link",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link nije nađen",
|
"intermediateLinkNotFound": "Intermediate link nije nađen",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Izuzmi iz ažuriranja u pozadini (ako su uključeni)",
|
"exemptFromBackgroundUpdates": "Izuzmi iz ažuriranja u pozadini (ako su uključeni)",
|
||||||
"bgUpdatesOnWiFiOnly": "Isključite ažuriranje u pozadini kada niste na WiFi-ju",
|
"bgUpdatesOnWiFiOnly": "Isključite ažuriranje u pozadini kada niste na WiFi-ju",
|
||||||
"autoSelectHighestVersionCode": "Automatski izaberite najveću (verziju) versionCode APK-a",
|
"autoSelectHighestVersionCode": "Automatski izaberite najveću (verziju) versionCode APK-a",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Přidat tuto informaci do nastavení.",
|
"addInfoInSettings": "Přidat tuto informaci do nastavení.",
|
||||||
"githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.",
|
"githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.",
|
||||||
"gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API",
|
"gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API",
|
||||||
"sortByFileNamesNotLinks": "Řadit podle názvů souborů místo celých odkazů",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu",
|
"filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu",
|
||||||
"customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')",
|
"customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')",
|
||||||
"appsPossiblyUpdated": "Byly provedeny pokusy o aktualizaci aplikací",
|
"appsPossiblyUpdated": "Byly provedeny pokusy o aktualizaci aplikací",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možné pro všechny aplikace.",
|
"backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možné pro všechny aplikace.",
|
||||||
"backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřen Obtainium.",
|
"backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřen Obtainium.",
|
||||||
"verifyLatestTag": "Ověřit značku 'latest'",
|
"verifyLatestTag": "Ověřit značku 'latest'",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Vyloučit aktualizace na pozadí (pokud jsou povoleny)",
|
"exemptFromBackgroundUpdates": "Vyloučit aktualizace na pozadí (pokud jsou povoleny)",
|
||||||
"bgUpdatesOnWiFiOnly": "Zakázat aktualizace na pozadí, pokud není přítomna Wi-Fi",
|
"bgUpdatesOnWiFiOnly": "Zakázat aktualizace na pozadí, pokud není přítomna Wi-Fi",
|
||||||
"autoSelectHighestVersionCode": "Automatický výběr nejvyššího kódu verze APK",
|
"autoSelectHighestVersionCode": "Automatický výběr nejvyššího kódu verze APK",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
|
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
|
||||||
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
|
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
|
||||||
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
|
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
|
||||||
"sortByFileNamesNotLinks": "Sortiere nach Dateinamen, anstelle von ganzen Links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
|
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
|
||||||
"customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')",
|
"customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Aktualisierungen wurden versucht",
|
"appsPossiblyUpdated": "App Aktualisierungen wurden versucht",
|
||||||
@ -247,7 +247,9 @@
|
|||||||
"backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.",
|
"backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.",
|
||||||
"verifyLatestTag": "Überprüfe das „latest“ Tag",
|
"verifyLatestTag": "Überprüfe das „latest“ Tag",
|
||||||
"intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll",
|
"intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll",
|
||||||
"intermediateLinkNotFound": "„Zwischen“link nicht gefunden",
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "„Zwischen“-Link nicht gefunden",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)",
|
"exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)",
|
||||||
"bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist",
|
"bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist",
|
||||||
"autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen",
|
"autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen",
|
||||||
@ -256,13 +258,13 @@
|
|||||||
"highlightTouchTargets": "Weniger offensichtliche Touch-Ziele hervorheben",
|
"highlightTouchTargets": "Weniger offensichtliche Touch-Ziele hervorheben",
|
||||||
"pickExportDir": "Export-Verzeichnis wählen",
|
"pickExportDir": "Export-Verzeichnis wählen",
|
||||||
"autoExportOnChanges": "Automatischer Export bei Änderung(en)",
|
"autoExportOnChanges": "Automatischer Export bei Änderung(en)",
|
||||||
"includeSettings": "Include settings",
|
"includeSettings": "Einstellungen einbeziehen",
|
||||||
"filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern",
|
"filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern",
|
||||||
"trySelectingSuggestedVersionCode": "Versuchen, den vorgeschlagenen APK-Versionscode auszuwählen",
|
"trySelectingSuggestedVersionCode": "Versuchen, den vorgeschlagenen APK-Versionscode auszuwählen",
|
||||||
"dontSortReleasesList": "Freigaberelease von der API ordern",
|
"dontSortReleasesList": "Freigaberelease von der API ordern",
|
||||||
"reverseSort": "Umgekehrtes Sortieren",
|
"reverseSort": "Umgekehrtes Sortieren",
|
||||||
"takeFirstLink": "Take first link",
|
"takeFirstLink": "Verwende den ersten Link",
|
||||||
"skipSort": "Skip sorting",
|
"skipSort": "Überspringe Sortieren",
|
||||||
"debugMenu": "Debug-Menü",
|
"debugMenu": "Debug-Menü",
|
||||||
"bgTaskStarted": "Hintergrundaufgabe gestartet – Logs prüfen.",
|
"bgTaskStarted": "Hintergrundaufgabe gestartet – Logs prüfen.",
|
||||||
"runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen",
|
"runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen",
|
||||||
@ -280,7 +282,7 @@
|
|||||||
"onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps auf Aktualisierungen",
|
"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",
|
"supportFixedAPKURL": "neuere Version anhand der ersten dreißig Zahlen der Checksumme der APK URL erraten, wenn anderweitig nicht unterstützt",
|
||||||
"selectX": "Wähle {}",
|
"selectX": "Wähle {}",
|
||||||
"parallelDownloads": "Allow parallel downloads",
|
"parallelDownloads": "Erlaube parallele Downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App entfernen?",
|
"one": "App entfernen?",
|
||||||
"other": "Apps entfernen?"
|
"other": "Apps entfernen?"
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Add this info in the Settings.",
|
"addInfoInSettings": "Add this info in the Settings.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
"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.",
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
"verifyLatestTag": "Verify the 'latest' tag",
|
"verifyLatestTag": "Verify the 'latest' tag",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
||||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
|
@ -9,12 +9,12 @@
|
|||||||
"placeholder": "Espacio reservado",
|
"placeholder": "Espacio reservado",
|
||||||
"someErrors": "Han ocurrido algunos errores",
|
"someErrors": "Han ocurrido algunos errores",
|
||||||
"unexpectedError": "Error Inesperado",
|
"unexpectedError": "Error Inesperado",
|
||||||
"ok": "Correcto",
|
"ok": "OK",
|
||||||
"and": "y",
|
"and": "y",
|
||||||
"githubPATLabel": "Token Github de Acceso Personal\n(Reduce tiempos de espera)",
|
"githubPATLabel": "Token Github de Acceso Personal\n(Reduce tiempos de espera)",
|
||||||
"includePrereleases": "Incluir versiones preliminares",
|
"includePrereleases": "Incluir versiones preliminares",
|
||||||
"fallbackToOlderReleases": "Retroceder a versiones previas",
|
"fallbackToOlderReleases": "Retroceder a versiones previas",
|
||||||
"filterReleaseTitlesByRegEx": "Filtrar Títulos de Versiones",
|
"filterReleaseTitlesByRegEx": "Filtrar por título de version",
|
||||||
"invalidRegEx": "Expresión inválida",
|
"invalidRegEx": "Expresión inválida",
|
||||||
"noDescription": "Sin descripción",
|
"noDescription": "Sin descripción",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"source": "Origen",
|
"source": "Origen",
|
||||||
"app": "Aplicación",
|
"app": "Aplicación",
|
||||||
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
|
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
|
||||||
"youPickedTrackOnly": "Debes seleccionar la opción de 'Solo Seguimiento'.",
|
"youPickedTrackOnly": "Debe seleccionar la opción de 'Solo Seguimiento'.",
|
||||||
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargarla o actalizarla.",
|
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargarla o actalizarla.",
|
||||||
"cancelled": "Cancelado",
|
"cancelled": "Cancelado",
|
||||||
"appAlreadyAdded": "Aplicación ya añadida",
|
"appAlreadyAdded": "Aplicación ya añadida",
|
||||||
@ -46,8 +46,8 @@
|
|||||||
"searchableInBrackets": "(soporta búsqueda)",
|
"searchableInBrackets": "(soporta búsqueda)",
|
||||||
"appsString": "Aplicaciones",
|
"appsString": "Aplicaciones",
|
||||||
"noApps": "Sin Aplicaciones",
|
"noApps": "Sin Aplicaciones",
|
||||||
"noAppsForFilter": "Sin Aplicaciones para Filtrar",
|
"noAppsForFilter": "Sin aplicaciones para filtrar",
|
||||||
"byX": "Por {}",
|
"byX": "por: {}",
|
||||||
"percentProgress": "Progreso: {}%",
|
"percentProgress": "Progreso: {}%",
|
||||||
"pleaseWait": "Por favor, espere",
|
"pleaseWait": "Por favor, espere",
|
||||||
"updateAvailable": "Actualización Disponible",
|
"updateAvailable": "Actualización Disponible",
|
||||||
@ -102,8 +102,8 @@
|
|||||||
"importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como \"No Instalada\".\nPara solucionarlo, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
|
"importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como \"No Instalada\".\nPara solucionarlo, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
|
||||||
"importErrors": "Errores de Importación",
|
"importErrors": "Errores de Importación",
|
||||||
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
|
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
|
||||||
"followingURLsHadErrors": "Las siguientes URLs tuvieron problemas:",
|
"followingURLsHadErrors": "Las siguientes URLs han tenido problemas:",
|
||||||
"okay": "Correcto",
|
"okay": "Aceptar",
|
||||||
"selectURL": "Seleccionar URL",
|
"selectURL": "Seleccionar URL",
|
||||||
"selectURLs": "Seleccionar URLs",
|
"selectURLs": "Seleccionar URLs",
|
||||||
"pick": "Escoger",
|
"pick": "Escoger",
|
||||||
@ -113,7 +113,7 @@
|
|||||||
"followSystem": "Seguir al Sistema",
|
"followSystem": "Seguir al Sistema",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Usar negros puros en tema oscuro",
|
"useBlackTheme": "Negro puro en tema Oscuro",
|
||||||
"appSortBy": "Ordenar Apps Por",
|
"appSortBy": "Ordenar Apps Por",
|
||||||
"authorName": "Autor/Nombre",
|
"authorName": "Autor/Nombre",
|
||||||
"nameAuthor": "Nombre/Autor",
|
"nameAuthor": "Nombre/Autor",
|
||||||
@ -135,10 +135,10 @@
|
|||||||
"share": "Compartir",
|
"share": "Compartir",
|
||||||
"appNotFound": "Aplicación no encontrada",
|
"appNotFound": "Aplicación no encontrada",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "Selecciona una APK",
|
"pickAnAPK": "Seleccione una APK",
|
||||||
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
|
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
|
||||||
"deviceSupportsXArch": "Tu dispositivo soporta las siguientes arquitecturas de procesador: {}.",
|
"deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.",
|
||||||
"deviceSupportsFollowingArchs": "Tu dispositivo soporta las siguientes arquitecturas de procesador:",
|
"deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:",
|
||||||
"warning": "Aviso",
|
"warning": "Aviso",
|
||||||
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
|
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
|
||||||
"updatesAvailable": "Actualizaciones Disponibles",
|
"updatesAvailable": "Actualizaciones Disponibles",
|
||||||
@ -158,7 +158,7 @@
|
|||||||
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación",
|
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación",
|
||||||
"checkingForUpdates": "Buscando Actualizaciones",
|
"checkingForUpdates": "Buscando Actualizaciones",
|
||||||
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
|
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
|
||||||
"pleaseAllowInstallPerm": "Por favor, permite a Obtainium instalar aplicaciones",
|
"pleaseAllowInstallPerm": "Por favor, permita que Obtainium instale aplicaciones",
|
||||||
"trackOnly": "Solo Seguimiento",
|
"trackOnly": "Solo Seguimiento",
|
||||||
"errorWithHttpStatusCode": "Error {}",
|
"errorWithHttpStatusCode": "Error {}",
|
||||||
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
|
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
|
||||||
@ -207,15 +207,15 @@
|
|||||||
"removeFromObtainium": "Eliminar de Obtainium",
|
"removeFromObtainium": "Eliminar de Obtainium",
|
||||||
"uninstallFromDevice": "Desinstalar del Dispositivo",
|
"uninstallFromDevice": "Desinstalar del Dispositivo",
|
||||||
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
|
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
|
||||||
"releaseDateAsVersion": "Usar Fecha de Publicación como Versión",
|
"releaseDateAsVersion": "Por fecha de publicación",
|
||||||
"releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.",
|
"releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.",
|
||||||
"changes": "Cambios",
|
"changes": "Cambios",
|
||||||
"releaseDate": "Fecha de Publicación",
|
"releaseDate": "Fecha de Publicación",
|
||||||
"importFromURLsInFile": "Importar URLs desde archivo (como OPML)",
|
"importFromURLsInFile": "Importar URLs desde archivo (como OPML)",
|
||||||
"versionDetection": "Detección de Versiones",
|
"versionDetection": "Detección de Versiones",
|
||||||
"standardVersionDetection": "Detección de versiones estándar",
|
"standardVersionDetection": "Por versión",
|
||||||
"groupByCategory": "Agrupar por Categoría",
|
"groupByCategory": "Agrupar por Categoría",
|
||||||
"autoApkFilterByArch": "Filtrar las APKs mediante arquitecturas de procesador, si es posible",
|
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador, si es posible",
|
||||||
"overrideSource": "Sobrescribir Fuente",
|
"overrideSource": "Sobrescribir Fuente",
|
||||||
"dontShowAgain": "No mostrar de nuevo",
|
"dontShowAgain": "No mostrar de nuevo",
|
||||||
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
|
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
|
||||||
@ -233,11 +233,11 @@
|
|||||||
"reversePageTransitions": "Invertir animaciones de transición de la página",
|
"reversePageTransitions": "Invertir animaciones de transición de la página",
|
||||||
"minStarCount": "Número Mínimo de Estrellas",
|
"minStarCount": "Número Mínimo de Estrellas",
|
||||||
"addInfoBelow": "Añadir esta información debajo.",
|
"addInfoBelow": "Añadir esta información debajo.",
|
||||||
"addInfoInSettings": "Añadir esta información en Ajustes.",
|
"addInfoInSettings": "Puede añadir esta información en Ajustes.",
|
||||||
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con una clave API.",
|
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con una clave API.",
|
||||||
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin una clave API.",
|
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin una clave API.",
|
||||||
"sortByFileNamesNotLinks": "Ordenar por nombres de fichero en vez de por enlaces completos",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtrar por Notas de Versión (Release Notes)",
|
"filterReleaseNotesByRegEx": "Filtrar por notas de nersión (release notes)",
|
||||||
"customLinkFilterRegex": "Filtro personalizado de Enlace APK (por defecto '.apk$')",
|
"customLinkFilterRegex": "Filtro personalizado de Enlace APK (por defecto '.apk$')",
|
||||||
"appsPossiblyUpdated": "Actualización de Apps intentada",
|
"appsPossiblyUpdated": "Actualización de Apps intentada",
|
||||||
"appsPossiblyUpdatedNotifDescription": "Notifica al usuario que las actualizaciones en segundo plano podrían haberse realizado para una o más aplicaciones",
|
"appsPossiblyUpdatedNotifDescription": "Notifica al usuario que las actualizaciones en segundo plano podrían haberse realizado para una o más aplicaciones",
|
||||||
@ -245,10 +245,12 @@
|
|||||||
"enableBackgroundUpdates": "Habilitar actualizaciones en segundo plano",
|
"enableBackgroundUpdates": "Habilitar actualizaciones en segundo plano",
|
||||||
"backgroundUpdateReqsExplanation": "Las actualizaciones en segundo plano pueden no estar disponibles para todas las aplicaciones.",
|
"backgroundUpdateReqsExplanation": "Las actualizaciones en segundo plano pueden no estar disponibles para todas las aplicaciones.",
|
||||||
"backgroundUpdateLimitsExplanation": "El éxito de las instalaciones en segundo plano solo se puede comprobar con Obtainium abierto.",
|
"backgroundUpdateLimitsExplanation": "El éxito de las instalaciones en segundo plano solo se puede comprobar con Obtainium abierto.",
|
||||||
"verifyLatestTag": "Comprueba la etiqueta 'latest'",
|
"verifyLatestTag": "Comprueba la etiqueta 'Latest'",
|
||||||
"intermediateLinkRegex": "Filtrar por Enlace 'Intermedio' para Visitar Primero",
|
"intermediateLinkRegex": "Filtrar por enlace 'intermedio' para visitar primero",
|
||||||
"intermediateLinkNotFound": "Enlace Intermedio no encontrado",
|
"filterByLinkText": "Filter links by link text",
|
||||||
"exemptFromBackgroundUpdates": "Exento de actualizciones en segundo plano (si están habilitadas)",
|
"intermediateLinkNotFound": "Enlace intermedio no encontrado",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Exenta de actualizciones en segundo plano (si están habilitadas)",
|
||||||
"bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi",
|
"bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto Selección de la versionCode APK superior",
|
"autoSelectHighestVersionCode": "Auto Selección de la versionCode APK superior",
|
||||||
"versionExtractionRegEx": "Versión de Extracción de RegEx",
|
"versionExtractionRegEx": "Versión de Extracción de RegEx",
|
||||||
@ -258,7 +260,7 @@
|
|||||||
"autoExportOnChanges": "Auto Exportar cuando haya cambios",
|
"autoExportOnChanges": "Auto Exportar cuando haya cambios",
|
||||||
"includeSettings": "Incluir ajustes",
|
"includeSettings": "Incluir ajustes",
|
||||||
"filterVersionsByRegEx": "Filtrar por Versiones",
|
"filterVersionsByRegEx": "Filtrar por Versiones",
|
||||||
"trySelectingSuggestedVersionCode": "Prueba seleccionando la versionCode APK sugerida",
|
"trySelectingSuggestedVersionCode": "Pruebe seleccionando la versionCode APK sugerida",
|
||||||
"dontSortReleasesList": "Mantener el order de publicación desde API",
|
"dontSortReleasesList": "Mantener el order de publicación desde API",
|
||||||
"reverseSort": "Orden inverso",
|
"reverseSort": "Orden inverso",
|
||||||
"takeFirstLink": "Usar primer enlace",
|
"takeFirstLink": "Usar primer enlace",
|
||||||
@ -268,7 +270,7 @@
|
|||||||
"runBgCheckNow": "Ejecutar verficiación de actualizaciones en segundo plano",
|
"runBgCheckNow": "Ejecutar verficiación de actualizaciones en segundo plano",
|
||||||
"versionExtractWholePage": "Aplicar la Versión de Extracción Regex a la Página Entera",
|
"versionExtractWholePage": "Aplicar la Versión de Extracción Regex a la Página Entera",
|
||||||
"installing": "Instalando",
|
"installing": "Instalando",
|
||||||
"skipUpdateNotifications": "Omitir notificaciones sobre actualizaciones",
|
"skipUpdateNotifications": "Omitir de notificaciones sobre actualizaciones",
|
||||||
"updatesAvailableNotifChannel": "Actualizaciones Disponibles",
|
"updatesAvailableNotifChannel": "Actualizaciones Disponibles",
|
||||||
"appsUpdatedNotifChannel": "Aplicaciones Actualizadas",
|
"appsUpdatedNotifChannel": "Aplicaciones Actualizadas",
|
||||||
"appsPossiblyUpdatedNotifChannel": "Se ha Intentado Actualizar la Aplicación",
|
"appsPossiblyUpdatedNotifChannel": "Se ha Intentado Actualizar la Aplicación",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
|
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
|
||||||
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
|
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
|
||||||
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
|
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
|
||||||
"sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
|
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
|
||||||
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیشفرض '.apk$')",
|
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیشفرض '.apk$')",
|
||||||
"appsPossiblyUpdated": "بهروزرسانی برنامه انجام شد",
|
"appsPossiblyUpdated": "بهروزرسانی برنامه انجام شد",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "به روز رسانی پس زمینه ممکن است برای همه برنامه ها امکان پذیر نباشد.",
|
"backgroundUpdateReqsExplanation": "به روز رسانی پس زمینه ممکن است برای همه برنامه ها امکان پذیر نباشد.",
|
||||||
"backgroundUpdateLimitsExplanation": "موفقیت نصب پسزمینه تنها زمانی مشخص میشود که Obtainium باز شود.",
|
"backgroundUpdateLimitsExplanation": "موفقیت نصب پسزمینه تنها زمانی مشخص میشود که Obtainium باز شود.",
|
||||||
"verifyLatestTag": "برچسب \"آخرین\" را تأیید کنید",
|
"verifyLatestTag": "برچسب \"آخرین\" را تأیید کنید",
|
||||||
"intermediateLinkRegex": "برای اولین بار بازدید از لینک \"متوسط\" را فیلتر کنید",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "لینک میانی پیدا نشد",
|
"intermediateLinkNotFound": "لینک میانی پیدا نشد",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "معاف از بهروزرسانیهای پسزمینه (در صورت فعال بودن)",
|
"exemptFromBackgroundUpdates": "معاف از بهروزرسانیهای پسزمینه (در صورت فعال بودن)",
|
||||||
"bgUpdatesOnWiFiOnly": "بهروزرسانیهای پسزمینه را در صورت عدم اتصال به WiFi غیرفعال کنید",
|
"bgUpdatesOnWiFiOnly": "بهروزرسانیهای پسزمینه را در صورت عدم اتصال به WiFi غیرفعال کنید",
|
||||||
"autoSelectHighestVersionCode": "انتخاب خودکار بالاترین نسخه کد APK",
|
"autoSelectHighestVersionCode": "انتخاب خودکار بالاترین نسخه کد APK",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Add this info in the Settings.",
|
"addInfoInSettings": "Add this info in the Settings.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
"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.",
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
"verifyLatestTag": "Verify the 'latest' tag",
|
"verifyLatestTag": "Verify the 'latest' tag",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
||||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.",
|
"addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.",
|
||||||
"githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.",
|
"githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.",
|
||||||
"gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.",
|
"gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.",
|
||||||
"sortByFileNamesNotLinks": "Fájlnevek szerinti elrendezés teljes linkek helyett",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
|
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
|
||||||
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
|
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
|
||||||
"appsPossiblyUpdated": "App frissítési kísérlet",
|
"appsPossiblyUpdated": "App frissítési kísérlet",
|
||||||
@ -245,8 +245,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Előfordulhat, hogy nem minden appnál lehetséges a háttérbeli frissítés.",
|
"backgroundUpdateReqsExplanation": "Előfordulhat, hogy nem minden appnál lehetséges a háttérbeli frissítés.",
|
||||||
"backgroundUpdateLimitsExplanation": "A háttérben történő telepítés sikeressége csak az Obtainium megnyitásakor állapítható meg.",
|
"backgroundUpdateLimitsExplanation": "A háttérben történő telepítés sikeressége csak az Obtainium megnyitásakor állapítható meg.",
|
||||||
"verifyLatestTag": "Ellenőrizze a „legújabb” címkét",
|
"verifyLatestTag": "Ellenőrizze a „legújabb” címkét",
|
||||||
"intermediateLinkRegex": "Szűrés egy 'közvetítő' linkre, amelyet először meg kell látogatni",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Közvetítő link nem található",
|
"intermediateLinkNotFound": "Közvetítő link nem található",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)",
|
"exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)",
|
||||||
"bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n",
|
"bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n",
|
||||||
"autoSelectHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
|
"autoSelectHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Aggiungi questa info nelle impostazioni.",
|
"addInfoInSettings": "Aggiungi questa info nelle impostazioni.",
|
||||||
"githubSourceNote": "Il limite di ricerca GitHub può essere evitato usando una chiave API.",
|
"githubSourceNote": "Il limite di ricerca GitHub può essere evitato usando una chiave API.",
|
||||||
"gitlabSourceNote": "L'estrazione di APK da GitLab potrebbe non funzionare senza chiave API.",
|
"gitlabSourceNote": "L'estrazione di APK da GitLab potrebbe non funzionare senza chiave API.",
|
||||||
"sortByFileNamesNotLinks": "Ordina per nome del file invece dei link completi",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtra le note di rilascio con espressione regolare",
|
"filterReleaseNotesByRegEx": "Filtra le note di rilascio con espressione regolare",
|
||||||
"customLinkFilterRegex": "Filtra link APK personalizzato con espressione regolare (predefinito '.apk$')",
|
"customLinkFilterRegex": "Filtra link APK personalizzato con espressione regolare (predefinito '.apk$')",
|
||||||
"appsPossiblyUpdated": "Aggiornamenti app tentati",
|
"appsPossiblyUpdated": "Aggiornamenti app tentati",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Gli aggiornamenti in secondo piano potrebbero non essere possibili per tutte le app.",
|
"backgroundUpdateReqsExplanation": "Gli aggiornamenti in secondo piano potrebbero non essere possibili per tutte le app.",
|
||||||
"backgroundUpdateLimitsExplanation": "La riuscita di un'installazione in secondo piano può essere determinata solo quando viene aperto Obtainium.",
|
"backgroundUpdateLimitsExplanation": "La riuscita di un'installazione in secondo piano può essere determinata solo quando viene aperto Obtainium.",
|
||||||
"verifyLatestTag": "Verifica l'etichetta 'Latest'",
|
"verifyLatestTag": "Verifica l'etichetta 'Latest'",
|
||||||
"intermediateLinkRegex": "Filtra un link 'Intermedio' da visitare prima",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Link intermedio non trovato",
|
"intermediateLinkNotFound": "Link intermedio non trovato",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Esente da aggiornamenti in secondo piano (se attivo)",
|
"exemptFromBackgroundUpdates": "Esente da aggiornamenti in secondo piano (se attivo)",
|
||||||
"bgUpdatesOnWiFiOnly": "Disattiva aggiornamenti in secondo piano quando non si usa il WiFi",
|
"bgUpdatesOnWiFiOnly": "Disattiva aggiornamenti in secondo piano quando non si usa il WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-seleziona APK con versionCode più alto",
|
"autoSelectHighestVersionCode": "Auto-seleziona APK con versionCode più alto",
|
||||||
@ -256,13 +258,13 @@
|
|||||||
"highlightTouchTargets": "Evidenzia elementi toccabili meno ovvi",
|
"highlightTouchTargets": "Evidenzia elementi toccabili meno ovvi",
|
||||||
"pickExportDir": "Scegli cartella esp.",
|
"pickExportDir": "Scegli cartella esp.",
|
||||||
"autoExportOnChanges": "Auto-esporta dopo modifiche",
|
"autoExportOnChanges": "Auto-esporta dopo modifiche",
|
||||||
"includeSettings": "Include settings",
|
"includeSettings": "Includi impostazioni",
|
||||||
"filterVersionsByRegEx": "Filtra versioni con espressione regolare",
|
"filterVersionsByRegEx": "Filtra versioni con espressione regolare",
|
||||||
"trySelectingSuggestedVersionCode": "Prova a selezionare APK con versionCode suggerito",
|
"trySelectingSuggestedVersionCode": "Prova a selezionare APK con versionCode suggerito",
|
||||||
"dontSortReleasesList": "Conserva l'ordine di release da API",
|
"dontSortReleasesList": "Conserva l'ordine di release da API",
|
||||||
"reverseSort": "Ordine inverso",
|
"reverseSort": "Ordine inverso",
|
||||||
"takeFirstLink": "Take first link",
|
"takeFirstLink": "Prendi il primo link",
|
||||||
"skipSort": "Skip sorting",
|
"skipSort": "Salta ordinamento",
|
||||||
"debugMenu": "Menu di debug",
|
"debugMenu": "Menu di debug",
|
||||||
"bgTaskStarted": "Attività in secondo piano iniziata - controllo log.",
|
"bgTaskStarted": "Attività in secondo piano iniziata - controllo log.",
|
||||||
"runBgCheckNow": "Inizia aggiornamento in secondo piano ora",
|
"runBgCheckNow": "Inizia aggiornamento in secondo piano ora",
|
||||||
@ -278,9 +280,14 @@
|
|||||||
"completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
|
"completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
|
||||||
"checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
|
"checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
|
"onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
|
||||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
"supportFixedAPKURL": "Supporta URL fissi di APK",
|
||||||
"selectX": "Select {}",
|
"selectX": "Seleziona {}",
|
||||||
"parallelDownloads": "Allow parallel downloads",
|
"parallelDownloads": "Permetti download paralleli",
|
||||||
|
"installMethod": "Metodo d'installazione",
|
||||||
|
"normal": "Normale",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Rimuovere l'app?",
|
"one": "Rimuovere l'app?",
|
||||||
"other": "Rimuovere le app?"
|
"other": "Rimuovere le app?"
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "設定でこの情報を追加してください。",
|
"addInfoInSettings": "設定でこの情報を追加してください。",
|
||||||
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
|
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
|
||||||
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
|
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
|
||||||
"sortByFileNamesNotLinks": "フルのリンクではなくファイル名でソートする",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
|
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
|
||||||
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
|
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
|
||||||
"appsPossiblyUpdated": "アプリのアップデートを試行",
|
"appsPossiblyUpdated": "アプリのアップデートを試行",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
|
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
|
||||||
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
|
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
|
||||||
"verifyLatestTag": "'latest'タグを確認する",
|
"verifyLatestTag": "'latest'タグを確認する",
|
||||||
"intermediateLinkRegex": "最初にアクセスする「中間」リンクをフィルタリングする",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "中間リンクが見つかりませんでした",
|
"intermediateLinkNotFound": "中間リンクが見つかりませんでした",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
|
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
|
||||||
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする",
|
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする",
|
||||||
"autoSelectHighestVersionCode": "最も高いバージョンコードのAPKを自動で選択する",
|
"autoSelectHighestVersionCode": "最も高いバージョンコードのAPKを自動で選択する",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
|
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
|
||||||
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
|
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
|
||||||
"gitlabSourceNote": "GitLab APK-extractie werkt mogelijk niet zonder een API-sleutel.",
|
"gitlabSourceNote": "GitLab APK-extractie werkt mogelijk niet zonder een API-sleutel.",
|
||||||
"sortByFileNamesNotLinks": "Sorteren op bestandsnamen in plaats van volledige links.",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
|
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
|
||||||
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",
|
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",
|
||||||
"appsPossiblyUpdated": "Poging tot app-updates",
|
"appsPossiblyUpdated": "Poging tot app-updates",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Achtergrondupdates zijn mogelijk niet voor alle apps mogelijk.",
|
"backgroundUpdateReqsExplanation": "Achtergrondupdates zijn mogelijk niet voor alle apps mogelijk.",
|
||||||
"backgroundUpdateLimitsExplanation": "Het succes van een installatie in de achtergrond kan alleen worden bepaald wanneer Obtainium is geopend.",
|
"backgroundUpdateLimitsExplanation": "Het succes van een installatie in de achtergrond kan alleen worden bepaald wanneer Obtainium is geopend.",
|
||||||
"verifyLatestTag": "Verifieer de 'Laatste'-tag",
|
"verifyLatestTag": "Verifieer de 'Laatste'-tag",
|
||||||
"intermediateLinkRegex": "Filter voor een 'tussenliggende' link om eerst te bezoeken",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
|
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrondupdates (indien ingeschakeld)",
|
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrondupdates (indien ingeschakeld)",
|
||||||
"bgUpdatesOnWiFiOnly": "Achtergrondupdates uitschakelen wanneer niet verbonden met WiFi",
|
"bgUpdatesOnWiFiOnly": "Achtergrondupdates uitschakelen wanneer niet verbonden met WiFi",
|
||||||
"autoSelectHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
|
"autoSelectHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
|
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
|
||||||
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
|
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
|
||||||
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
|
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
|
||||||
"sortByFileNamesNotLinks": "Sortuj wg nazw plików zamiast pełnych linków",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
|
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
|
||||||
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",
|
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",
|
||||||
"appsPossiblyUpdated": "Aplikacje mogły zostać zaktualizowane",
|
"appsPossiblyUpdated": "Aplikacje mogły zostać zaktualizowane",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Aktualizacje w tle mogą nie być możliwe dla wszystkich aplikacji.",
|
"backgroundUpdateReqsExplanation": "Aktualizacje w tle mogą nie być możliwe dla wszystkich aplikacji.",
|
||||||
"backgroundUpdateLimitsExplanation": "Powodzenie instalacji w tle można określić dopiero po otwarciu Obtainium.",
|
"backgroundUpdateLimitsExplanation": "Powodzenie instalacji w tle można określić dopiero po otwarciu Obtainium.",
|
||||||
"verifyLatestTag": "Zweryfikuj najnowszy tag",
|
"verifyLatestTag": "Zweryfikuj najnowszy tag",
|
||||||
"intermediateLinkRegex": "Filtr linków \"pośrednich\" do odwiedzenia w pierwszej kolejności",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego",
|
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)",
|
"exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)",
|
||||||
"bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi",
|
"bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi",
|
||||||
"autoSelectHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
|
"autoSelectHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Adicionar essa informação nas configurações.",
|
"addInfoInSettings": "Adicionar essa informação nas configurações.",
|
||||||
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
|
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
|
||||||
"gitlabSourceNote": "A extração de APK do GitLab pode não funcionar sem uma chave de API.",
|
"gitlabSourceNote": "A extração de APK do GitLab pode não funcionar sem uma chave de API.",
|
||||||
"sortByFileNamesNotLinks": "Classifique por nomes de arquivos em vez de links completos",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
|
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
|
||||||
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
|
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
|
||||||
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
|
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Atualizações em segundo plano podem não ser possíveis para todos os Apps.",
|
"backgroundUpdateReqsExplanation": "Atualizações em segundo plano podem não ser possíveis para todos os Apps.",
|
||||||
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
|
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
|
||||||
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
|
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
|
||||||
"intermediateLinkRegex": "Filtre por um Link 'Intermediário' para Visitar Primeiro",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Link intermediário não encontrado",
|
"intermediateLinkNotFound": "Link intermediário não encontrado",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
|
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
|
||||||
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
|
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
|
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
|
||||||
@ -277,10 +279,15 @@
|
|||||||
"downloadingXNotifChannel": "Baixando {}",
|
"downloadingXNotifChannel": "Baixando {}",
|
||||||
"completeAppInstallationNotifChannel": "Instalação completa do App",
|
"completeAppInstallationNotifChannel": "Instalação completa do App",
|
||||||
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
|
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Apenas checar apps instalados e 'Apenas Seguir' por updates",
|
||||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
"supportFixedAPKURL": "Suporte APK com URLs fixas",
|
||||||
"selectX": "Select {}",
|
"selectX": "Selecionar {}",
|
||||||
"parallelDownloads": "Allow parallel downloads",
|
"parallelDownloads": "Permitir downloads paralelos",
|
||||||
|
"installMethod": "Método de instalação",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku não esta rodando",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Remover App?",
|
"one": "Remover App?",
|
||||||
"other": "Remover Apps?"
|
"other": "Remover Apps?"
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Добавьте эту информацию в Настройки",
|
"addInfoInSettings": "Добавьте эту информацию в Настройки",
|
||||||
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
|
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
|
||||||
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
|
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
|
||||||
"sortByFileNamesNotLinks": "Сортировать по именам файлов, а не ссылкам целиком",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
|
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
|
||||||
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",
|
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",
|
||||||
"appsPossiblyUpdated": "Попытки обновления приложений",
|
"appsPossiblyUpdated": "Попытки обновления приложений",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Фоновые обновления могут быть возможны не для всех приложений",
|
"backgroundUpdateReqsExplanation": "Фоновые обновления могут быть возможны не для всех приложений",
|
||||||
"backgroundUpdateLimitsExplanation": "Успешность фоновой установки можно определить только после открытия Obtainium",
|
"backgroundUpdateLimitsExplanation": "Успешность фоновой установки можно определить только после открытия Obtainium",
|
||||||
"verifyLatestTag": "Проверять тег 'latest'",
|
"verifyLatestTag": "Проверять тег 'latest'",
|
||||||
"intermediateLinkRegex": "Фильтр промежуточных ссылок для первоочередного посещения\n(регулярное выражение)",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
|
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
|
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
|
||||||
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
|
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
|
||||||
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
|
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Lägg till denna information i Inställningar.",
|
"addInfoInSettings": "Lägg till denna information i Inställningar.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Bakgrundsuppdateringar är inte möjligt för alla appar.",
|
"backgroundUpdateReqsExplanation": "Bakgrundsuppdateringar är inte möjligt för alla appar.",
|
||||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
"verifyLatestTag": "Verifiera 'senaste'-taggen",
|
"verifyLatestTag": "Verifiera 'senaste'-taggen",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Undta från bakgrundsuppdateringar (om aktiverad)",
|
"exemptFromBackgroundUpdates": "Undta från bakgrundsuppdateringar (om aktiverad)",
|
||||||
"bgUpdatesOnWiFiOnly": "Inaktivera Bakgrundsuppdateringar utan WiFi",
|
"bgUpdatesOnWiFiOnly": "Inaktivera Bakgrundsuppdateringar utan WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Bu bilgiyi Ayarlar'da ekleyin.",
|
"addInfoInSettings": "Bu bilgiyi Ayarlar'da ekleyin.",
|
||||||
"githubSourceNote": "GitHub hız sınırlaması bir API anahtarı kullanılarak atlanabilir.",
|
"githubSourceNote": "GitHub hız sınırlaması bir API anahtarı kullanılarak atlanabilir.",
|
||||||
"gitlabSourceNote": "GitLab APK çıkarma işlemi bir API anahtarı olmadan çalışmayabilir.",
|
"gitlabSourceNote": "GitLab APK çıkarma işlemi bir API anahtarı olmadan çalışmayabilir.",
|
||||||
"sortByFileNamesNotLinks": "Bağlantılar yerine dosya adlarına göre sırala",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Sürüm Notlarını Düzenli İfade ile Filtrele",
|
"filterReleaseNotesByRegEx": "Sürüm Notlarını Düzenli İfade ile Filtrele",
|
||||||
"customLinkFilterRegex": "Özel APK Bağlantı Filtresi Düzenli İfade ile (Varsayılan '.apk$')",
|
"customLinkFilterRegex": "Özel APK Bağlantı Filtresi Düzenli İfade ile (Varsayılan '.apk$')",
|
||||||
"appsPossiblyUpdated": "Uygulama Güncellemeleri Denendi",
|
"appsPossiblyUpdated": "Uygulama Güncellemeleri Denendi",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Arka plan güncellemeleri tüm uygulamalar için mümkün olmayabilir.",
|
"backgroundUpdateReqsExplanation": "Arka plan güncellemeleri tüm uygulamalar için mümkün olmayabilir.",
|
||||||
"backgroundUpdateLimitsExplanation": "Arka plan kurulumunun başarısı, Obtainium'un açıldığında ancak belirlenebilir.",
|
"backgroundUpdateLimitsExplanation": "Arka plan kurulumunun başarısı, Obtainium'un açıldığında ancak belirlenebilir.",
|
||||||
"verifyLatestTag": "'latest' etiketini doğrula",
|
"verifyLatestTag": "'latest' etiketini doğrula",
|
||||||
"intermediateLinkRegex": "İlk Ziyaret Edilecek 'Ara' Bağlantısını Filtrele",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Ara bağlantı bulunamadı",
|
"intermediateLinkNotFound": "Ara bağlantı bulunamadı",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Arka plan güncellemelerinden muaf tut (etkinse)",
|
"exemptFromBackgroundUpdates": "Arka plan güncellemelerinden muaf tut (etkinse)",
|
||||||
"bgUpdatesOnWiFiOnly": "WiFi olmadığında arka plan güncellemelerini devre dışı bırak",
|
"bgUpdatesOnWiFiOnly": "WiFi olmadığında arka plan güncellemelerini devre dışı bırak",
|
||||||
"autoSelectHighestVersionCode": "Otomatik olarak en yüksek sürüm kodunu seç",
|
"autoSelectHighestVersionCode": "Otomatik olarak en yüksek sürüm kodunu seç",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Thêm thông tin này vào Cài đặt.",
|
"addInfoInSettings": "Thêm thông tin này vào Cài đặt.",
|
||||||
"githubSourceNote": "Có thể tránh được việc giới hạn tốc độ GitHub bằng cách sử dụng khóa API.",
|
"githubSourceNote": "Có thể tránh được việc giới hạn tốc độ GitHub bằng cách sử dụng khóa API.",
|
||||||
"gitlabSourceNote": "Trích xuất APK GitLab có thể không hoạt động nếu không có khóa API.",
|
"gitlabSourceNote": "Trích xuất APK GitLab có thể không hoạt động nếu không có khóa API.",
|
||||||
"sortByFileNamesNotLinks": "Sắp xếp theo tên tệp thay vì liên kết đầy đủ",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Lọc ghi chú phát hành theo biểu thức chính quy",
|
"filterReleaseNotesByRegEx": "Lọc ghi chú phát hành theo biểu thức chính quy",
|
||||||
"customLinkFilterRegex": "Bộ lọc liên kết APK tùy chỉnh theo biểu thức chính quy (Mặc định '.apk$')",
|
"customLinkFilterRegex": "Bộ lọc liên kết APK tùy chỉnh theo biểu thức chính quy (Mặc định '.apk$')",
|
||||||
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
|
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật nền cho tất cả ứng dụng.",
|
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật nền cho tất cả ứng dụng.",
|
||||||
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt nền chỉ có thể được xác định khi mở Obtainium.",
|
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt nền chỉ có thể được xác định khi mở Obtainium.",
|
||||||
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
|
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
|
||||||
"intermediateLinkRegex": "Lọc tìm liên kết 'Trung gian' để truy cập trước",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Không tìm thấy liên kết trung gian",
|
"intermediateLinkNotFound": "Không tìm thấy liên kết trung gian",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Miễn cập nhật nền (nếu được bật)",
|
"exemptFromBackgroundUpdates": "Miễn cập nhật nền (nếu được bật)",
|
||||||
"bgUpdatesOnWiFiOnly": "Tắt cập nhật nền khi không có WiFi",
|
"bgUpdatesOnWiFiOnly": "Tắt cập nhật nền khi không có WiFi",
|
||||||
"autoSelectHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
|
"autoSelectHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
|
||||||
|
@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "在“设置”中添加此凭据。",
|
"addInfoInSettings": "在“设置”中添加此凭据。",
|
||||||
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
|
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
|
||||||
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
|
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
|
||||||
"sortByFileNamesNotLinks": "使用文件名代替链接进行排序",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)",
|
"filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)",
|
||||||
"customLinkFilterRegex": "筛选自定义来源 APK 文件链接\n(正则表达式,默认匹配模式为“.apk$”)",
|
"customLinkFilterRegex": "筛选自定义来源 APK 文件链接\n(正则表达式,默认匹配模式为“.apk$”)",
|
||||||
"appsPossiblyUpdated": "已尝试更新应用",
|
"appsPossiblyUpdated": "已尝试更新应用",
|
||||||
@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
|
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
|
||||||
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
|
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
|
||||||
"verifyLatestTag": "验证“Latest”标签",
|
"verifyLatestTag": "验证“Latest”标签",
|
||||||
"intermediateLinkRegex": "筛选首先访问的“中转”链接(正则表达式)",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "未找到“中转”链接",
|
"intermediateLinkNotFound": "未找到“中转”链接",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "禁用后台更新\n(如果已经全局启用)",
|
"exemptFromBackgroundUpdates": "禁用后台更新\n(如果已经全局启用)",
|
||||||
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
|
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
|
||||||
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
||||||
|
@ -7,7 +7,7 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
class Aptoide extends AppSource {
|
class Aptoide extends AppSource {
|
||||||
Aptoide() {
|
Aptoide() {
|
||||||
host = 'aptoide.com';
|
host = 'aptoide.com';
|
||||||
name = tr('Aptoide');
|
name = 'Aptoide';
|
||||||
allowSubDomains = true;
|
allowSubDomains = true;
|
||||||
naiveStandardVersionDetection = true;
|
naiveStandardVersionDetection = true;
|
||||||
}
|
}
|
||||||
|
@ -88,62 +88,77 @@ bool _isNumeric(String s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HTML extends AppSource {
|
class HTML extends AppSource {
|
||||||
|
var finalStepFormitems = [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('customLinkFilterRegex',
|
||||||
|
label: tr('customLinkFilterRegex'),
|
||||||
|
hint: 'download/(.*/)?(android|apk|mobile)',
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
return regExValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('versionExtractionRegEx',
|
||||||
|
label: tr('versionExtractionRegEx'),
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [(value) => regExValidator(value)]),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('matchGroupToUse',
|
||||||
|
label: tr('matchGroupToUse'),
|
||||||
|
required: false,
|
||||||
|
hint: '0',
|
||||||
|
textInputType: const TextInputType.numberWithOptions(),
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
if (value?.isEmpty == true) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
value ??= '0';
|
||||||
|
return intValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('versionExtractWholePage',
|
||||||
|
label: tr('versionExtractWholePage'))
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('supportFixedAPKURL',
|
||||||
|
defaultValue: true, label: tr('supportFixedAPKURL')),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
var commonFormItems = [
|
||||||
|
[GeneratedFormSwitch('filterByLinkText', label: tr('filterByLinkText'))],
|
||||||
|
[GeneratedFormSwitch('skipSort', label: tr('skipSort'))],
|
||||||
|
[GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('sortByLastLinkSegment',
|
||||||
|
label: tr('sortByLastLinkSegment'))
|
||||||
|
],
|
||||||
|
];
|
||||||
|
var intermediateFormItems = [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('customLinkFilterRegex',
|
||||||
|
label: tr('intermediateLinkRegex'),
|
||||||
|
hint: '([0-9]+.)*[0-9]+/\$',
|
||||||
|
required: true,
|
||||||
|
additionalValidators: [(value) => regExValidator(value)])
|
||||||
|
],
|
||||||
|
];
|
||||||
HTML() {
|
HTML() {
|
||||||
additionalSourceAppSpecificSettingFormItems = [
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
[
|
[
|
||||||
GeneratedFormSwitch('sortByFileNamesNotLinks',
|
GeneratedFormSubForm(
|
||||||
label: tr('sortByFileNamesNotLinks'))
|
'intermediateLink', [...intermediateFormItems, ...commonFormItems],
|
||||||
|
label: tr('intermediateLink'))
|
||||||
],
|
],
|
||||||
[GeneratedFormSwitch('skipSort', label: tr('skipSort'))],
|
finalStepFormitems[0],
|
||||||
[GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))],
|
...commonFormItems,
|
||||||
[
|
...finalStepFormitems.sublist(1)
|
||||||
GeneratedFormSwitch('supportFixedAPKURL',
|
|
||||||
defaultValue: true, label: tr('supportFixedAPKURL')),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('customLinkFilterRegex',
|
|
||||||
label: tr('customLinkFilterRegex'),
|
|
||||||
hint: 'download/(.*/)?(android|apk|mobile)',
|
|
||||||
required: false,
|
|
||||||
additionalValidators: [
|
|
||||||
(value) {
|
|
||||||
return regExValidator(value);
|
|
||||||
}
|
|
||||||
])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('intermediateLinkRegex',
|
|
||||||
label: tr('intermediateLinkRegex'),
|
|
||||||
hint: '([0-9]+.)*[0-9]+/\$',
|
|
||||||
required: false,
|
|
||||||
additionalValidators: [(value) => regExValidator(value)])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('versionExtractionRegEx',
|
|
||||||
label: tr('versionExtractionRegEx'),
|
|
||||||
required: false,
|
|
||||||
additionalValidators: [(value) => regExValidator(value)]),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('matchGroupToUse',
|
|
||||||
label: tr('matchGroupToUse'),
|
|
||||||
required: false,
|
|
||||||
hint: '0',
|
|
||||||
textInputType: const TextInputType.numberWithOptions(),
|
|
||||||
additionalValidators: [
|
|
||||||
(value) {
|
|
||||||
if (value?.isEmpty == true) {
|
|
||||||
value = null;
|
|
||||||
}
|
|
||||||
value ??= '0';
|
|
||||||
return intValidator(value);
|
|
||||||
}
|
|
||||||
])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormSwitch('versionExtractWholePage',
|
|
||||||
label: tr('versionExtractWholePage'))
|
|
||||||
]
|
|
||||||
];
|
];
|
||||||
overrideVersionDetectionFormDefault('noVersionDetection',
|
overrideVersionDetectionFormDefault('noVersionDetection',
|
||||||
disableStandard: false, disableRelDate: true);
|
disableStandard: false, disableRelDate: true);
|
||||||
@ -164,107 +179,120 @@ class HTML extends AppSource {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given an HTTP response, grab some links according to the common additional settings
|
||||||
|
// (those that apply to intermediate and final steps)
|
||||||
|
Future<List<MapEntry<String, String>>> grabLinksCommon(
|
||||||
|
Response res, Map<String, dynamic> additionalSettings) async {
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var html = parse(res.body);
|
||||||
|
List<MapEntry<String, String>> allLinks = html
|
||||||
|
.querySelectorAll('a')
|
||||||
|
.map((element) => MapEntry(
|
||||||
|
element.attributes['href'] ?? '',
|
||||||
|
element.text.isNotEmpty
|
||||||
|
? element.text
|
||||||
|
: (element.attributes['href'] ?? '').split('/').last))
|
||||||
|
.where((element) => element.key.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
if (allLinks.isEmpty) {
|
||||||
|
allLinks = RegExp(
|
||||||
|
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
|
||||||
|
.allMatches(res.body)
|
||||||
|
.map((match) =>
|
||||||
|
MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? ''))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
List<MapEntry<String, String>> links = [];
|
||||||
|
bool skipSort = additionalSettings['skipSort'] == true;
|
||||||
|
bool filterLinkByText = additionalSettings['filterByLinkText'] == true;
|
||||||
|
if ((additionalSettings['customLinkFilterRegex'] as String?)?.isNotEmpty ==
|
||||||
|
true) {
|
||||||
|
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
|
||||||
|
links = allLinks
|
||||||
|
.where((element) =>
|
||||||
|
reg.hasMatch(filterLinkByText ? element.value : element.key))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
links = allLinks
|
||||||
|
.where((element) =>
|
||||||
|
Uri.parse(filterLinkByText ? element.value : element.key)
|
||||||
|
.path
|
||||||
|
.toLowerCase()
|
||||||
|
.endsWith('.apk'))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
if (!skipSort) {
|
||||||
|
links.sort((a, b) => additionalSettings['sortByLastLinkSegment'] == true
|
||||||
|
? compareAlphaNumeric(
|
||||||
|
a.key.split('/').where((e) => e.isNotEmpty).last,
|
||||||
|
b.key.split('/').where((e) => e.isNotEmpty).last)
|
||||||
|
: compareAlphaNumeric(a.key, b.key));
|
||||||
|
}
|
||||||
|
if (additionalSettings['reverseSort'] == true) {
|
||||||
|
links = links.reversed.toList();
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
var uri = Uri.parse(standardUrl);
|
var currentUrl = standardUrl;
|
||||||
Response res = await sourceRequest(standardUrl);
|
for (int i = 0;
|
||||||
if (res.statusCode == 200) {
|
i < (additionalSettings['intermediateLink']?.length ?? 0);
|
||||||
var html = parse(res.body);
|
i++) {
|
||||||
List<String> allLinks = html
|
var intLinks = await grabLinksCommon(await sourceRequest(currentUrl),
|
||||||
.querySelectorAll('a')
|
additionalSettings['intermediateLink'][i]);
|
||||||
.map((element) => element.attributes['href'] ?? '')
|
if (intLinks.isEmpty) {
|
||||||
.where((element) => element.isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
if (allLinks.isEmpty) {
|
|
||||||
allLinks = RegExp(
|
|
||||||
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
|
|
||||||
.allMatches(res.body)
|
|
||||||
.map((match) => match.group(0)!)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
List<String> links = [];
|
|
||||||
bool skipSort = additionalSettings['skipSort'] == true;
|
|
||||||
if ((additionalSettings['intermediateLinkRegex'] as String?)
|
|
||||||
?.isNotEmpty ==
|
|
||||||
true) {
|
|
||||||
var reg = RegExp(additionalSettings['intermediateLinkRegex']);
|
|
||||||
links = allLinks.where((element) => reg.hasMatch(element)).toList();
|
|
||||||
if (!skipSort) {
|
|
||||||
links.sort((a, b) => compareAlphaNumeric(a, b));
|
|
||||||
}
|
|
||||||
if (links.isEmpty) {
|
|
||||||
throw ObtainiumError(tr('intermediateLinkNotFound'));
|
|
||||||
}
|
|
||||||
Map<String, dynamic> additionalSettingsTemp =
|
|
||||||
Map.from(additionalSettings);
|
|
||||||
additionalSettingsTemp['intermediateLinkRegex'] = null;
|
|
||||||
return getLatestAPKDetails(
|
|
||||||
ensureAbsoluteUrl(links.last, uri), additionalSettingsTemp);
|
|
||||||
}
|
|
||||||
if ((additionalSettings['customLinkFilterRegex'] as String?)
|
|
||||||
?.isNotEmpty ==
|
|
||||||
true) {
|
|
||||||
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
|
|
||||||
links = allLinks.where((element) => reg.hasMatch(element)).toList();
|
|
||||||
} else {
|
|
||||||
links = allLinks
|
|
||||||
.where((element) =>
|
|
||||||
Uri.parse(element).path.toLowerCase().endsWith('.apk'))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
if (!skipSort) {
|
|
||||||
links.sort((a, b) =>
|
|
||||||
additionalSettings['sortByFileNamesNotLinks'] == true
|
|
||||||
? compareAlphaNumeric(
|
|
||||||
a.split('/').where((e) => e.isNotEmpty).last,
|
|
||||||
b.split('/').where((e) => e.isNotEmpty).last)
|
|
||||||
: compareAlphaNumeric(a, b));
|
|
||||||
}
|
|
||||||
if (additionalSettings['reverseSort'] == true) {
|
|
||||||
links = links.reversed.toList();
|
|
||||||
}
|
|
||||||
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty ==
|
|
||||||
true) {
|
|
||||||
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
|
||||||
links = links.where((element) => reg.hasMatch(element)).toList();
|
|
||||||
}
|
|
||||||
if (links.isEmpty) {
|
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
|
} else {
|
||||||
|
currentUrl = intLinks.last.key;
|
||||||
}
|
}
|
||||||
var rel = links.last;
|
|
||||||
String? version;
|
|
||||||
if (additionalSettings['supportFixedAPKURL'] != true) {
|
|
||||||
version = rel.hashCode.toString();
|
|
||||||
}
|
|
||||||
var versionExtractionRegEx =
|
|
||||||
additionalSettings['versionExtractionRegEx'] as String?;
|
|
||||||
if (versionExtractionRegEx?.isNotEmpty == true) {
|
|
||||||
var match = RegExp(versionExtractionRegEx!).allMatches(
|
|
||||||
additionalSettings['versionExtractWholePage'] == true
|
|
||||||
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
|
|
||||||
: rel);
|
|
||||||
if (match.isEmpty) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
String matchGroupString =
|
|
||||||
(additionalSettings['matchGroupToUse'] as String).trim();
|
|
||||||
if (matchGroupString.isEmpty) {
|
|
||||||
matchGroupString = "0";
|
|
||||||
}
|
|
||||||
version = match.last.group(int.parse(matchGroupString));
|
|
||||||
if (version?.isEmpty == true) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uri = Uri.parse(currentUrl);
|
||||||
|
Response res = await sourceRequest(currentUrl);
|
||||||
|
var links = await grabLinksCommon(res, additionalSettings);
|
||||||
|
|
||||||
|
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) {
|
||||||
|
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
||||||
|
links = links.where((element) => reg.hasMatch(element.key)).toList();
|
||||||
|
}
|
||||||
|
if (links.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
var rel = links.last.key;
|
||||||
|
String? version;
|
||||||
|
if (additionalSettings['supportFixedAPKURL'] != true) {
|
||||||
|
version = rel.hashCode.toString();
|
||||||
|
}
|
||||||
|
var versionExtractionRegEx =
|
||||||
|
additionalSettings['versionExtractionRegEx'] as String?;
|
||||||
|
if (versionExtractionRegEx?.isNotEmpty == true) {
|
||||||
|
var match = RegExp(versionExtractionRegEx!).allMatches(
|
||||||
|
additionalSettings['versionExtractWholePage'] == true
|
||||||
|
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
|
||||||
|
: rel);
|
||||||
|
if (match.isEmpty) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
String matchGroupString =
|
||||||
|
(additionalSettings['matchGroupToUse'] as String).trim();
|
||||||
|
if (matchGroupString.isEmpty) {
|
||||||
|
matchGroupString = "0";
|
||||||
|
}
|
||||||
|
version = match.last.group(int.parse(matchGroupString));
|
||||||
|
if (version?.isEmpty == true) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rel = ensureAbsoluteUrl(rel, uri);
|
||||||
|
version ??= (await checkDownloadHash(rel)).toString();
|
||||||
|
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||||
|
AppNames(uri.host, tr('app')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import 'package:hsluv/hsluv.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:obtainium/components/generated_form_modal.dart';
|
import 'package:obtainium/components/generated_form_modal.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
abstract class GeneratedFormItem {
|
abstract class GeneratedFormItem {
|
||||||
late String key;
|
late String key;
|
||||||
@ -31,7 +32,8 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
|||||||
{super.label,
|
{super.label,
|
||||||
super.belowWidgets,
|
super.belowWidgets,
|
||||||
String super.defaultValue = '',
|
String super.defaultValue = '',
|
||||||
List<String? Function(String? value)> super.additionalValidators = const [],
|
List<String? Function(String? value)> super.additionalValidators =
|
||||||
|
const [],
|
||||||
this.required = true,
|
this.required = true,
|
||||||
this.max = 1,
|
this.max = 1,
|
||||||
this.hint,
|
this.hint,
|
||||||
@ -117,6 +119,18 @@ class GeneratedForm extends StatefulWidget {
|
|||||||
State<GeneratedForm> createState() => _GeneratedFormState();
|
State<GeneratedForm> createState() => _GeneratedFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GeneratedFormSubForm extends GeneratedFormItem {
|
||||||
|
final List<List<GeneratedFormItem>> items;
|
||||||
|
|
||||||
|
GeneratedFormSubForm(super.key, this.items,
|
||||||
|
{super.label, super.belowWidgets, super.defaultValue});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ensureType(val) {
|
||||||
|
return val; // Not easy to validate List<Map<String, dynamic>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generates a color in the HSLuv (Pastel) color space
|
// Generates a color in the HSLuv (Pastel) color space
|
||||||
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
|
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
|
||||||
Color generateRandomLightColor() {
|
Color generateRandomLightColor() {
|
||||||
@ -133,28 +147,39 @@ Color generateRandomLightColor() {
|
|||||||
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
|
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int generateRandomNumber(int seed1,
|
||||||
|
{int seed2 = 0, int seed3 = 0, max = 10000}) {
|
||||||
|
int combinedSeed = seed1.hashCode ^ seed2.hashCode ^ seed3.hashCode;
|
||||||
|
Random random = Random(combinedSeed);
|
||||||
|
int randomNumber = random.nextInt(max);
|
||||||
|
return randomNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateTextField(TextFormField tf) =>
|
||||||
|
(tf.key as GlobalKey<FormFieldState>).currentState?.isValid == true;
|
||||||
|
|
||||||
class _GeneratedFormState extends State<GeneratedForm> {
|
class _GeneratedFormState extends State<GeneratedForm> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
Map<String, dynamic> values = {};
|
Map<String, dynamic> values = {};
|
||||||
late List<List<Widget>> formInputs;
|
late List<List<Widget>> formInputs;
|
||||||
List<List<Widget>> rows = [];
|
List<List<Widget>> rows = [];
|
||||||
String? initKey;
|
String? initKey;
|
||||||
|
int forceUpdateKeyCount = 0;
|
||||||
|
|
||||||
// If any value changes, call this to update the parent with value and validity
|
// If any value changes, call this to update the parent with value and validity
|
||||||
void someValueChanged({bool isBuilding = false}) {
|
void someValueChanged({bool isBuilding = false, bool forceInvalid = false}) {
|
||||||
Map<String, dynamic> returnValues = values;
|
Map<String, dynamic> returnValues = values;
|
||||||
var valid = true;
|
var valid = true;
|
||||||
for (int r = 0; r < widget.items.length; r++) {
|
for (int r = 0; r < widget.items.length; r++) {
|
||||||
for (int i = 0; i < widget.items[r].length; i++) {
|
for (int i = 0; i < widget.items[r].length; i++) {
|
||||||
if (formInputs[r][i] is TextFormField) {
|
if (formInputs[r][i] is TextFormField) {
|
||||||
var fieldState =
|
valid = valid && validateTextField(formInputs[r][i] as TextFormField);
|
||||||
(formInputs[r][i].key as GlobalKey<FormFieldState>).currentState;
|
|
||||||
if (fieldState != null) {
|
|
||||||
valid = valid && fieldState.isValid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (forceInvalid) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
widget.onValueChanges(returnValues, valid, isBuilding);
|
widget.onValueChanges(returnValues, valid, isBuilding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +254,17 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else if (formItem is GeneratedFormSubForm) {
|
||||||
|
values[formItem.key] = [];
|
||||||
|
for (Map<String, dynamic> v
|
||||||
|
in ((formItem.defaultValue ?? []) as List<dynamic>)) {
|
||||||
|
var fullDefaults = getDefaultValuesFromFormItems(formItem.items);
|
||||||
|
for (var element in v.entries) {
|
||||||
|
fullDefaults[element.key] = element.value;
|
||||||
|
}
|
||||||
|
values[formItem.key].add(fullDefaults);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
} else {
|
} else {
|
||||||
return Container(); // Some input types added in build
|
return Container(); // Some input types added in build
|
||||||
}
|
}
|
||||||
@ -250,6 +286,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
}
|
}
|
||||||
for (var r = 0; r < formInputs.length; r++) {
|
for (var r = 0; r < formInputs.length; r++) {
|
||||||
for (var e = 0; e < formInputs[r].length; e++) {
|
for (var e = 0; e < formInputs[r].length; e++) {
|
||||||
|
String fieldKey = widget.items[r][e].key;
|
||||||
if (widget.items[r][e] is GeneratedFormSwitch) {
|
if (widget.items[r][e] is GeneratedFormSwitch) {
|
||||||
formInputs[r][e] = Row(
|
formInputs[r][e] = Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -259,10 +296,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
Switch(
|
Switch(
|
||||||
value: values[widget.items[r][e].key],
|
value: values[fieldKey],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
values[widget.items[r][e].key] = value;
|
values[fieldKey] = value;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -271,8 +308,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
} else if (widget.items[r][e] is GeneratedFormTagInput) {
|
} else if (widget.items[r][e] is GeneratedFormTagInput) {
|
||||||
formInputs[r][e] =
|
formInputs[r][e] =
|
||||||
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
if ((values[widget.items[r][e].key]
|
if ((values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.isNotEmpty ==
|
?.isNotEmpty ==
|
||||||
true &&
|
true &&
|
||||||
(widget.items[r][e] as GeneratedFormTagInput)
|
(widget.items[r][e] as GeneratedFormTagInput)
|
||||||
@ -295,8 +331,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
(widget.items[r][e] as GeneratedFormTagInput).alignment,
|
(widget.items[r][e] as GeneratedFormTagInput).alignment,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.isEmpty ==
|
?.isEmpty ==
|
||||||
true
|
true
|
||||||
? Text(
|
? Text(
|
||||||
@ -304,8 +339,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
.emptyMessage,
|
.emptyMessage,
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
...(values[widget.items[r][e].key]
|
...(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.entries
|
?.entries
|
||||||
.map((e2) {
|
.map((e2) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -318,11 +352,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
selected: e2.value.value,
|
selected: e2.value.value,
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
(values[widget.items[r][e].key] as Map<String,
|
(values[fieldKey] as Map<String,
|
||||||
MapEntry<int, bool>>)[e2.key] =
|
MapEntry<int, bool>>)[e2.key] =
|
||||||
MapEntry(
|
MapEntry(
|
||||||
(values[widget.items[r][e].key] as Map<
|
(values[fieldKey] as Map<String,
|
||||||
String,
|
|
||||||
MapEntry<int, bool>>)[e2.key]!
|
MapEntry<int, bool>>)[e2.key]!
|
||||||
.key,
|
.key,
|
||||||
value);
|
value);
|
||||||
@ -330,22 +363,18 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
as GeneratedFormTagInput)
|
as GeneratedFormTagInput)
|
||||||
.singleSelect &&
|
.singleSelect &&
|
||||||
value == true) {
|
value == true) {
|
||||||
for (var key in (values[
|
for (var key in (values[fieldKey]
|
||||||
widget.items[r][e].key]
|
|
||||||
as Map<String, MapEntry<int, bool>>)
|
as Map<String, MapEntry<int, bool>>)
|
||||||
.keys) {
|
.keys) {
|
||||||
if (key != e2.key) {
|
if (key != e2.key) {
|
||||||
(values[widget.items[r][e].key] as Map<
|
(values[fieldKey] as Map<
|
||||||
String,
|
String,
|
||||||
MapEntry<int, bool>>)[key] =
|
MapEntry<int,
|
||||||
MapEntry(
|
bool>>)[key] = MapEntry(
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String,
|
||||||
as Map<
|
MapEntry<int, bool>>)[key]!
|
||||||
String,
|
.key,
|
||||||
MapEntry<int,
|
false);
|
||||||
bool>>)[key]!
|
|
||||||
.key,
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,8 +384,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
));
|
));
|
||||||
}) ??
|
}) ??
|
||||||
[const SizedBox.shrink()],
|
[const SizedBox.shrink()],
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.values
|
?.values
|
||||||
.where((e) => e.value)
|
.where((e) => e.value)
|
||||||
.length ==
|
.length ==
|
||||||
@ -366,7 +394,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>;
|
as Map<String, MapEntry<int, bool>>;
|
||||||
// get selected category str where bool is true
|
// get selected category str where bool is true
|
||||||
final oldEntry = temp.entries
|
final oldEntry = temp.entries
|
||||||
@ -379,7 +407,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
// Update entry with new color, remain selected
|
// Update entry with new color, remain selected
|
||||||
temp.update(oldEntry.key,
|
temp.update(oldEntry.key,
|
||||||
(old) => MapEntry(newColor, old.value));
|
(old) => MapEntry(newColor, old.value));
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -388,8 +416,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
tooltip: tr('colour'),
|
tooltip: tr('colour'),
|
||||||
))
|
))
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.values
|
?.values
|
||||||
.where((e) => e.value)
|
.where((e) => e.value)
|
||||||
.isNotEmpty ==
|
.isNotEmpty ==
|
||||||
@ -400,10 +427,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
fn() {
|
fn() {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>;
|
as Map<String, MapEntry<int, bool>>;
|
||||||
temp.removeWhere((key, value) => value.value);
|
temp.removeWhere((key, value) => value.value);
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -454,7 +481,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
String? label = value?['label'];
|
String? label = value?['label'];
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>?;
|
as Map<String, MapEntry<int, bool>>?;
|
||||||
temp ??= {};
|
temp ??= {};
|
||||||
if (temp[label] == null) {
|
if (temp[label] == null) {
|
||||||
@ -467,7 +494,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
temp[label] = MapEntry(
|
temp[label] = MapEntry(
|
||||||
generateRandomLightColor().value,
|
generateRandomLightColor().value,
|
||||||
!(someSelected && singleSelect));
|
!(someSelected && singleSelect));
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -481,6 +508,93 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
} else if (widget.items[r][e] is GeneratedFormSubForm) {
|
||||||
|
List<Widget> subformColumn = [];
|
||||||
|
for (int i = 0; i < values[fieldKey].length; i++) {
|
||||||
|
var items = (widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.items
|
||||||
|
.map((x) => x.map((y) {
|
||||||
|
y.defaultValue = values[fieldKey]?[i]?[y.key];
|
||||||
|
return y;
|
||||||
|
}).toList())
|
||||||
|
.toList();
|
||||||
|
var internalFormKey = ValueKey(generateRandomNumber(
|
||||||
|
values[fieldKey].length,
|
||||||
|
seed2: i,
|
||||||
|
seed3: forceUpdateKeyCount));
|
||||||
|
subformColumn.add(Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
GeneratedForm(
|
||||||
|
key: internalFormKey,
|
||||||
|
items: items,
|
||||||
|
onValueChanges: (values, valid, isBuilding) {
|
||||||
|
if (valid) {
|
||||||
|
this.values[fieldKey]?[i] = values;
|
||||||
|
}
|
||||||
|
someValueChanged(
|
||||||
|
isBuilding: isBuilding, forceInvalid: !valid);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).colorScheme.error),
|
||||||
|
onPressed: (values[fieldKey].length > 0)
|
||||||
|
? () {
|
||||||
|
var temp = List.from(values[fieldKey]);
|
||||||
|
temp.removeAt(i);
|
||||||
|
values[fieldKey] = List.from(temp);
|
||||||
|
forceUpdateKeyCount++;
|
||||||
|
someValueChanged();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
label: Text(
|
||||||
|
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
||||||
|
),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete_outline_rounded,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
subformColumn.add(Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: values[fieldKey].length > 0 ? 24 : 0, top: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
values[fieldKey].add(getDefaultValuesFromFormItems(
|
||||||
|
(widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.items));
|
||||||
|
forceUpdateKeyCount++;
|
||||||
|
someValueChanged();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text((widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.label))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
if (values[fieldKey].length > 0) {
|
||||||
|
subformColumn.add(const Divider());
|
||||||
|
}
|
||||||
|
formInputs[r][e] = Column(children: subformColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,14 @@ import 'package:permission_handler/permission_handler.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
import 'package:background_fetch/background_fetch.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/easy_localization_controller.dart';
|
import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/localization.dart';
|
import 'package:easy_localization/src/localization.dart';
|
||||||
|
|
||||||
const String currentVersion = '0.14.41';
|
const String currentVersion = '0.15.0';
|
||||||
const String currentReleaseTag =
|
const String currentReleaseTag =
|
||||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||||
|
|
||||||
@ -76,6 +76,19 @@ Future<void> loadTranslations() async {
|
|||||||
fallbackTranslations: controller.fallbackTranslations);
|
fallbackTranslations: controller.fallbackTranslations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void backgroundFetchHeadlessTask(HeadlessTask task) async {
|
||||||
|
String taskId = task.taskId;
|
||||||
|
bool isTimeout = task.timeout;
|
||||||
|
if (isTimeout) {
|
||||||
|
print('BG update task timed out.');
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await bgUpdateCheck(taskId, null);
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
try {
|
try {
|
||||||
@ -93,7 +106,6 @@ void main() async {
|
|||||||
);
|
);
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
}
|
}
|
||||||
await AndroidAlarmManager.initialize();
|
|
||||||
runApp(MultiProvider(
|
runApp(MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
||||||
@ -108,6 +120,7 @@ void main() async {
|
|||||||
useOnlyLangCode: true,
|
useOnlyLangCode: true,
|
||||||
child: const Obtainium()),
|
child: const Obtainium()),
|
||||||
));
|
));
|
||||||
|
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultThemeColour = Colors.deepPurple;
|
var defaultThemeColour = Colors.deepPurple;
|
||||||
@ -122,6 +135,33 @@ class Obtainium extends StatefulWidget {
|
|||||||
class _ObtainiumState extends State<Obtainium> {
|
class _ObtainiumState extends State<Obtainium> {
|
||||||
var existingUpdateInterval = -1;
|
var existingUpdateInterval = -1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initPlatformState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initPlatformState() async {
|
||||||
|
await BackgroundFetch.configure(
|
||||||
|
BackgroundFetchConfig(
|
||||||
|
minimumFetchInterval: 15,
|
||||||
|
stopOnTerminate: false,
|
||||||
|
enableHeadless: true,
|
||||||
|
requiresBatteryNotLow: false,
|
||||||
|
requiresCharging: false,
|
||||||
|
requiresStorageNotLow: false,
|
||||||
|
requiresDeviceIdle: false,
|
||||||
|
requiredNetworkType: NetworkType.ANY), (String taskId) async {
|
||||||
|
// We don't want periodic tasks in the foreground - ignore
|
||||||
|
await bgUpdateCheck(taskId, null);
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
}, (String taskId) async {
|
||||||
|
context.read<LogsProvider>().add('BG update task timed out.');
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
||||||
@ -161,30 +201,6 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
context.locale.languageCode)) {
|
context.locale.languageCode)) {
|
||||||
settingsProvider.resetLocaleSafe(context);
|
settingsProvider.resetLocaleSafe(context);
|
||||||
}
|
}
|
||||||
// Register the background update task according to the user's setting
|
|
||||||
var actualUpdateInterval = settingsProvider.updateInterval;
|
|
||||||
if (existingUpdateInterval != actualUpdateInterval) {
|
|
||||||
if (actualUpdateInterval == 0) {
|
|
||||||
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
|
|
||||||
} else {
|
|
||||||
var settingChanged = existingUpdateInterval != -1;
|
|
||||||
var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
|
|
||||||
settingsProvider.lastBGCheckTime
|
|
||||||
.add(Duration(minutes: actualUpdateInterval + 60))
|
|
||||||
.isBefore(DateTime.now());
|
|
||||||
if (settingChanged || lastCheckWasTooLongAgo) {
|
|
||||||
logs.add(
|
|
||||||
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
|
|
||||||
AndroidAlarmManager.periodic(
|
|
||||||
Duration(minutes: actualUpdateInterval),
|
|
||||||
bgUpdateCheckAlarmId,
|
|
||||||
bgUpdateCheck,
|
|
||||||
rescheduleOnReboot: true,
|
|
||||||
wakeup: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existingUpdateInterval = actualUpdateInterval;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -608,38 +607,35 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
const Divider(
|
const Divider(
|
||||||
height: 32,
|
height: 32,
|
||||||
),
|
),
|
||||||
Padding(
|
// Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
// padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
child: Column(children: [
|
// child: Column(children: [
|
||||||
Row(
|
// Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
// children: [
|
||||||
Flexible(child: Text(tr('debugMenu'))),
|
// Flexible(child: Text(tr('debugMenu'))),
|
||||||
Switch(
|
// Switch(
|
||||||
value: settingsProvider.showDebugOpts,
|
// value: settingsProvider.showDebugOpts,
|
||||||
onChanged: (value) {
|
// onChanged: (value) {
|
||||||
settingsProvider.showDebugOpts = value;
|
// settingsProvider.showDebugOpts = value;
|
||||||
})
|
// })
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
if (settingsProvider.showDebugOpts)
|
// if (settingsProvider.showDebugOpts)
|
||||||
Column(
|
// Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
// children: [
|
||||||
height16,
|
// height16,
|
||||||
TextButton(
|
// TextButton(
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
AndroidAlarmManager.oneShot(
|
// bgUpdateCheck('taskId', null);
|
||||||
const Duration(seconds: 0),
|
// showMessage(tr('bgTaskStarted'), context);
|
||||||
bgUpdateCheckAlarmId + 200,
|
// },
|
||||||
bgUpdateCheck);
|
// child: Text(tr('runBgCheckNow')))
|
||||||
showMessage(tr('bgTaskStarted'), context);
|
// ],
|
||||||
},
|
// ),
|
||||||
child: Text(tr('runBgCheckNow')))
|
// ]),
|
||||||
],
|
// ),
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -8,7 +8,6 @@ import 'dart:math';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
|
||||||
import 'package:android_intent_plus/flag.dart';
|
import 'package:android_intent_plus/flag.dart';
|
||||||
import 'package:android_package_installer/android_package_installer.dart';
|
import 'package:android_package_installer/android_package_installer.dart';
|
||||||
import 'package:android_package_manager/android_package_manager.dart';
|
import 'package:android_package_manager/android_package_manager.dart';
|
||||||
@ -621,7 +620,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
|
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
|
||||||
Future<List<String>> downloadAndInstallLatestApps(
|
Future<List<String>> downloadAndInstallLatestApps(
|
||||||
List<String> appIds, BuildContext? context,
|
List<String> appIds, BuildContext? context,
|
||||||
{NotificationsProvider? notificationsProvider}) async {
|
{NotificationsProvider? notificationsProvider,
|
||||||
|
bool forceParallelDownloads = false}) async {
|
||||||
notificationsProvider =
|
notificationsProvider =
|
||||||
notificationsProvider ?? context?.read<NotificationsProvider>();
|
notificationsProvider ?? context?.read<NotificationsProvider>();
|
||||||
List<String> appsToInstall = [];
|
List<String> appsToInstall = [];
|
||||||
@ -742,7 +742,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settingsProvider.parallelDownloads) {
|
if (forceParallelDownloads || !settingsProvider.parallelDownloads) {
|
||||||
for (var id in appsToInstall) {
|
for (var id in appsToInstall) {
|
||||||
await updateFn(id);
|
await updateFn(id);
|
||||||
}
|
}
|
||||||
@ -1448,19 +1448,17 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
|
|||||||
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
|
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
|
||||||
/// In update mode, all apps in toCheck are checked for updates (in parallel).
|
/// In update mode, all apps in toCheck are checked for updates (in parallel).
|
||||||
/// If an update is available and it cannot be installed silently, the user is notified of the available update.
|
/// If an update is available and it cannot be installed silently, the user is notified of the available update.
|
||||||
/// If there are any errors, the task is run again for the remaining apps after a few minutes (based on the error with the longest retry interval).
|
/// If there are any errors, we recursively call the same function with retry count for the relevant apps decremented (if zero, the user is notified).
|
||||||
/// Any app that has reached it's retry limit, the user is notified that it could not be checked.
|
|
||||||
///
|
///
|
||||||
/// Once all update checks are complete, the task is run again in install mode.
|
/// Once all update checks are complete, the task is run again in install mode.
|
||||||
/// In this mode, all pending silent updates are downloaded and installed in the background (serially - one at a time).
|
/// In this mode, all pending silent updates are downloaded (in parallel) and installed in the background.
|
||||||
/// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried.
|
/// If there is an error, the user is notified.
|
||||||
/// If an app repeatedly fails to install up to its retry limit, the user is notified.
|
|
||||||
///
|
///
|
||||||
@pragma('vm:entry-point')
|
Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
|
||||||
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
// ignore: avoid_print
|
||||||
|
print('Started $taskId: ${params.toString()}');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
await AndroidAlarmManager.initialize();
|
|
||||||
await loadTranslations();
|
await loadTranslations();
|
||||||
|
|
||||||
LogsProvider logs = LogsProvider();
|
LogsProvider logs = LogsProvider();
|
||||||
@ -1469,11 +1467,20 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
await appsProvider.loadApps();
|
await appsProvider.loadApps();
|
||||||
|
|
||||||
int maxAttempts = 4;
|
int maxAttempts = 4;
|
||||||
|
int maxRetryWaitSeconds = 5;
|
||||||
|
|
||||||
|
var netResult = await (Connectivity().checkConnectivity());
|
||||||
|
if (netResult == ConnectivityResult.none) {
|
||||||
|
logs.add('BG update task: No network.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
params ??= {};
|
params ??= {};
|
||||||
if (params['toCheck'] == null) {
|
|
||||||
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
|
bool firstEverUpdateTask = DateTime.fromMillisecondsSinceEpoch(0)
|
||||||
}
|
.compareTo(appsProvider.settingsProvider.lastCompletedBGCheckTime) ==
|
||||||
|
0;
|
||||||
|
|
||||||
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
||||||
...(params['toCheck']
|
...(params['toCheck']
|
||||||
?.map((entry) => MapEntry<String, int>(
|
?.map((entry) => MapEntry<String, int>(
|
||||||
@ -1481,6 +1488,11 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
.toList() ??
|
.toList() ??
|
||||||
appsProvider
|
appsProvider
|
||||||
.getAppsSortedByUpdateCheckTime(
|
.getAppsSortedByUpdateCheckTime(
|
||||||
|
ignoreAppsCheckedAfter: params['toCheck'] == null
|
||||||
|
? firstEverUpdateTask
|
||||||
|
? null
|
||||||
|
: appsProvider.settingsProvider.lastCompletedBGCheckTime
|
||||||
|
: null,
|
||||||
onlyCheckInstalledOrTrackOnlyApps: appsProvider
|
onlyCheckInstalledOrTrackOnlyApps: appsProvider
|
||||||
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
|
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
|
||||||
.map((e) => MapEntry(e, 0)))
|
.map((e) => MapEntry(e, 0)))
|
||||||
@ -1493,51 +1505,34 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
(<List<MapEntry<String, int>>>[]))
|
(<List<MapEntry<String, int>>>[]))
|
||||||
];
|
];
|
||||||
|
|
||||||
var netResult = await (Connectivity().checkConnectivity());
|
|
||||||
|
|
||||||
if (netResult == ConnectivityResult.none) {
|
|
||||||
var networkBasedRetryInterval = 15;
|
|
||||||
var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
|
|
||||||
.add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
|
|
||||||
var potentialNetworkRetryCheck =
|
|
||||||
DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
|
|
||||||
var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
|
|
||||||
logs.add(
|
|
||||||
'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
|
|
||||||
AndroidAlarmManager.oneShot(
|
|
||||||
const Duration(minutes: 15), taskId + 1, bgUpdateCheck,
|
|
||||||
params: {
|
|
||||||
'toCheck': toCheck
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
'toInstall': toInstall
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var networkRestricted = false;
|
var networkRestricted = false;
|
||||||
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
||||||
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
||||||
(netResult != ConnectivityResult.ethernet);
|
(netResult != ConnectivityResult.ethernet);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool installMode =
|
if (toCheck.isNotEmpty) {
|
||||||
toCheck.isEmpty; // Task is either in update mode or install mode
|
// Task is either in update mode or install mode
|
||||||
|
|
||||||
logs.add(
|
|
||||||
'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
|
|
||||||
|
|
||||||
if (!installMode) {
|
|
||||||
// If in update mode, we check for updates.
|
// If in update mode, we check for updates.
|
||||||
// We divide the results into 4 groups:
|
// We divide the results into 4 groups:
|
||||||
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
|
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
|
||||||
// - toRetry - Apps with update check errors that will be retried in a while
|
|
||||||
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
|
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
|
||||||
// After grouping the updates, we take care of toNotify and toThrow first
|
// After grouping the updates, we take care of toNotify and toThrow first
|
||||||
// Then if toRetry is not empty, we schedule another update task to run in a while
|
// Then we run the function again in install mode (toCheck is empty)
|
||||||
// If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty)
|
|
||||||
|
var enoughTimePassed = appsProvider.settingsProvider.updateInterval != 0 &&
|
||||||
|
appsProvider.settingsProvider.lastCompletedBGCheckTime
|
||||||
|
.add(
|
||||||
|
Duration(minutes: appsProvider.settingsProvider.updateInterval))
|
||||||
|
.isBefore(DateTime.now());
|
||||||
|
if (!enoughTimePassed) {
|
||||||
|
// ignore: avoid_print
|
||||||
|
print(
|
||||||
|
'BG update task: Too early for another check (last check was ${appsProvider.settingsProvider.lastCompletedBGCheckTime.toIso8601String()}, interval is ${appsProvider.settingsProvider.updateInterval}).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logs.add('BG update task: Started (${toCheck.length}).');
|
||||||
|
|
||||||
// Init. vars.
|
// Init. vars.
|
||||||
List<App> updates = []; // All updates found (silent and non-silent)
|
List<App> updates = []; // All updates found (silent and non-silent)
|
||||||
@ -1545,8 +1540,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
[]; // All non-silent updates that the user will be notified about
|
[]; // All non-silent updates that the user will be notified about
|
||||||
List<MapEntry<String, int>> toRetry =
|
List<MapEntry<String, int>> toRetry =
|
||||||
[]; // All apps that got errors while checking
|
[]; // All apps that got errors while checking
|
||||||
var retryAfterXSeconds =
|
var retryAfterXSeconds = 0;
|
||||||
0; // How long to wait until the next attempt (if there are errors)
|
|
||||||
MultiAppMultiError?
|
MultiAppMultiError?
|
||||||
errors; // All errors including those that will lead to a retry
|
errors; // All errors including those that will lead to a retry
|
||||||
MultiAppMultiError toThrow =
|
MultiAppMultiError toThrow =
|
||||||
@ -1569,27 +1563,32 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
specificIds: toCheck.map((e) => e.key).toList(),
|
specificIds: toCheck.map((e) => e.key).toList(),
|
||||||
sp: appsProvider.settingsProvider);
|
sp: appsProvider.settingsProvider);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If there were errors, group them into toRetry and toThrow based on max retry count per app
|
|
||||||
if (e is Map) {
|
if (e is Map) {
|
||||||
updates = e['updates'];
|
updates = e['updates'];
|
||||||
errors = e['errors'];
|
errors = e['errors'];
|
||||||
errors!.rawErrors.forEach((key, err) {
|
errors!.rawErrors.forEach((key, err) {
|
||||||
logs.add(
|
logs.add(
|
||||||
'BG update task $taskId: Got error on checking for $key \'${err.toString()}\'.');
|
'BG update task: Got error on checking for $key \'${err.toString()}\'.');
|
||||||
|
|
||||||
var toCheckApp = toCheck.where((element) => element.key == key).first;
|
var toCheckApp = toCheck.where((element) => element.key == key).first;
|
||||||
if (toCheckApp.value < maxAttempts) {
|
if (toCheckApp.value < maxAttempts) {
|
||||||
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
|
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
|
||||||
// Next task interval is based on the error with the longest retry time
|
// Next task interval is based on the error with the longest retry time
|
||||||
var minRetryIntervalForThisApp = err is RateLimitError
|
int minRetryIntervalForThisApp = err is RateLimitError
|
||||||
? (err.remainingMinutes * 60)
|
? (err.remainingMinutes * 60)
|
||||||
: e is ClientException
|
: e is ClientException
|
||||||
? (15 * 60)
|
? (15 * 60)
|
||||||
: pow(toCheckApp.value + 1, 2).toInt();
|
: (toCheckApp.value + 1);
|
||||||
|
if (minRetryIntervalForThisApp > maxRetryWaitSeconds) {
|
||||||
|
minRetryIntervalForThisApp = maxRetryWaitSeconds;
|
||||||
|
}
|
||||||
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
|
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
|
||||||
retryAfterXSeconds = minRetryIntervalForThisApp;
|
retryAfterXSeconds = minRetryIntervalForThisApp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
if (err is! RateLimitError) {
|
||||||
|
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -1624,37 +1623,32 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
id: Random().nextInt(10000)));
|
id: Random().nextInt(10000)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there are update checks to retry, schedule a retry task
|
// if there are update checks to retry, schedule a retry task
|
||||||
|
logs.add('BG update task: Done checking for updates.');
|
||||||
if (toRetry.isNotEmpty) {
|
if (toRetry.isNotEmpty) {
|
||||||
logs.add(
|
logs.add(
|
||||||
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
|
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
|
||||||
AndroidAlarmManager.oneShot(
|
return await bgUpdateCheck(taskId, {
|
||||||
Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck,
|
'toCheck': toRetry
|
||||||
params: {
|
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||||
'toCheck': toRetry
|
.toList(),
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
'toInstall': toInstall
|
||||||
.toList(),
|
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||||
'toInstall': toInstall
|
.toList(),
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
});
|
||||||
.toList(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// If there are no more update checks, schedule an install task
|
// If there are no more update checks, call the function in install mode
|
||||||
logs.add(
|
logs.add('BG update task: Done checking for updates.');
|
||||||
'BG update task $taskId: Done. Scheduling install task to run immediately.');
|
return await bgUpdateCheck(taskId, {
|
||||||
AndroidAlarmManager.oneShot(
|
'toCheck': [],
|
||||||
const Duration(minutes: 0), taskId + 1, bgUpdateCheck,
|
'toInstall': toInstall
|
||||||
params: {
|
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||||
'toCheck': [],
|
.toList()
|
||||||
'toInstall': toInstall
|
});
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In install mode...
|
// In install mode...
|
||||||
// If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates
|
// If you haven't explicitly been given updates to install, grab all available silent updates
|
||||||
if (toInstall.isEmpty && !networkRestricted) {
|
if (toInstall.isEmpty && !networkRestricted) {
|
||||||
var temp = appsProvider.findExistingUpdates(installedOnly: true);
|
var temp = appsProvider.findExistingUpdates(installedOnly: true);
|
||||||
for (var i = 0; i < temp.length; i++) {
|
for (var i = 0; i < temp.length; i++) {
|
||||||
@ -1664,60 +1658,34 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var didCompleteInstalling = false;
|
if (toInstall.isNotEmpty) {
|
||||||
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
|
logs.add('BG install task: Started (${toInstall.length}).');
|
||||||
if (tempObtArr.isNotEmpty) {
|
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
|
||||||
// Move obtainium to the end of the list as it must always install last
|
if (tempObtArr.isNotEmpty) {
|
||||||
var obt = tempObtArr.first;
|
// Move obtainium to the end of the list as it must always install last
|
||||||
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
|
var obt = tempObtArr.first;
|
||||||
}
|
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
|
||||||
// Loop through all updates and install each
|
}
|
||||||
for (var i = 0; i < toInstall.length; i++) {
|
// Loop through all updates and install each
|
||||||
var appId = toInstall[i].key;
|
|
||||||
var retryCount = toInstall[i].value;
|
|
||||||
try {
|
try {
|
||||||
logs.add(
|
await appsProvider.downloadAndInstallLatestApps(
|
||||||
'BG install task $taskId: Attempting to update $appId in the background.');
|
toInstall.map((e) => e.key).toList(), null,
|
||||||
await appsProvider.downloadAndInstallLatestApps([appId], null,
|
notificationsProvider: notificationsProvider,
|
||||||
notificationsProvider: notificationsProvider);
|
forceParallelDownloads: true);
|
||||||
await Future.delayed(const Duration(
|
|
||||||
seconds:
|
|
||||||
5)); // Just in case task ending causes install fail (not clear)
|
|
||||||
if (i == (toCheck.length - 1)) {
|
|
||||||
didCompleteInstalling = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly
|
if (e is MultiAppMultiError) {
|
||||||
logs.add(
|
e.idsByErrorString.forEach((key, value) {
|
||||||
'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.');
|
notificationsProvider.notify(ErrorCheckingUpdatesNotification(
|
||||||
if (retryCount < maxAttempts) {
|
e.errorsAppsString(key, value)));
|
||||||
var remainingSeconds = retryCount;
|
});
|
||||||
logs.add(
|
|
||||||
'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
|
|
||||||
var remainingToInstall = moveStrToEndMapEntryWithCount(
|
|
||||||
toInstall.sublist(i), MapEntry(appId, retryCount + 1));
|
|
||||||
AndroidAlarmManager.oneShot(
|
|
||||||
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck,
|
|
||||||
params: {
|
|
||||||
'toCheck': toCheck
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
'toInstall': remainingToInstall
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
|
// We don't expect to ever get here in any situation so no need to catch (but log it in case)
|
||||||
toInstall.removeAt(i);
|
logs.add('Fatal error in BG install task: ${e.toString()}');
|
||||||
i--;
|
rethrow;
|
||||||
notificationsProvider
|
|
||||||
.notify(ErrorCheckingUpdatesNotification(e.toString()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
logs.add('BG install task: Done installing updates.');
|
||||||
if (didCompleteInstalling || toInstall.isEmpty) {
|
|
||||||
logs.add('BG install task $taskId: Done.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
appsProvider.settingsProvider.lastCompletedBGCheckTime = DateTime.now();
|
||||||
}
|
}
|
||||||
|
@ -52,8 +52,8 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InstallMethodSettings get installMethod {
|
InstallMethodSettings get installMethod {
|
||||||
return InstallMethodSettings
|
return InstallMethodSettings.values[
|
||||||
.values[prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
|
prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
|
||||||
}
|
}
|
||||||
|
|
||||||
set installMethod(InstallMethodSettings t) {
|
set installMethod(InstallMethodSettings t) {
|
||||||
@ -345,15 +345,15 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime get lastBGCheckTime {
|
DateTime get lastCompletedBGCheckTime {
|
||||||
int? temp = prefs?.getInt('lastBGCheckTime');
|
int? temp = prefs?.getInt('lastCompletedBGCheckTime');
|
||||||
return temp != null
|
return temp != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(temp)
|
? DateTime.fromMillisecondsSinceEpoch(temp)
|
||||||
: DateTime.fromMillisecondsSinceEpoch(0);
|
: DateTime.fromMillisecondsSinceEpoch(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
set lastBGCheckTime(DateTime val) {
|
set lastCompletedBGCheckTime(DateTime val) {
|
||||||
prefs?.setInt('lastBGCheckTime', val.millisecondsSinceEpoch);
|
prefs?.setInt('lastCompletedBGCheckTime', val.millisecondsSinceEpoch);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,10 +135,28 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
|||||||
if (additionalSettings['autoApkFilterByArch'] == null) {
|
if (additionalSettings['autoApkFilterByArch'] == null) {
|
||||||
additionalSettings['autoApkFilterByArch'] = false;
|
additionalSettings['autoApkFilterByArch'] = false;
|
||||||
}
|
}
|
||||||
// HTML 'fixed URL' support should be disabled if it previously did not exist
|
if (source.runtimeType == HTML().runtimeType) {
|
||||||
if (source.runtimeType == HTML().runtimeType &&
|
// HTML 'fixed URL' support should be disabled if it previously did not exist
|
||||||
originalAdditionalSettings['supportFixedAPKURL'] == null) {
|
if (originalAdditionalSettings['supportFixedAPKURL'] == null) {
|
||||||
additionalSettings['supportFixedAPKURL'] = false;
|
additionalSettings['supportFixedAPKURL'] = false;
|
||||||
|
}
|
||||||
|
// HTML key rename
|
||||||
|
if (originalAdditionalSettings['sortByFileNamesNotLinks'] != null) {
|
||||||
|
additionalSettings['sortByLastLinkSegment'] =
|
||||||
|
originalAdditionalSettings['sortByFileNamesNotLinks'];
|
||||||
|
}
|
||||||
|
// HTML single 'intermediate link' should be converted to multi-support version
|
||||||
|
if (originalAdditionalSettings['intermediateLinkRegex'] != null &&
|
||||||
|
additionalSettings['intermediateLink']?.isNotEmpty != true) {
|
||||||
|
additionalSettings['intermediateLink'] = [
|
||||||
|
{
|
||||||
|
'customLinkFilterRegex':
|
||||||
|
originalAdditionalSettings['intermediateLinkRegex'],
|
||||||
|
'filterByLinkText':
|
||||||
|
originalAdditionalSettings['intermediateLinkByText']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
json['additionalSettings'] = jsonEncode(additionalSettings);
|
json['additionalSettings'] = jsonEncode(additionalSettings);
|
||||||
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
||||||
|
24
pubspec.lock
24
pubspec.lock
@ -1,14 +1,6 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
android_alarm_manager_plus:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: android_alarm_manager_plus
|
|
||||||
sha256: "84720c8ad2758aabfbeafd24a8c355d8c8dd3aa52b01eaf3bb827c7210f61a91"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.4"
|
|
||||||
android_intent_plus:
|
android_intent_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -74,6 +66,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
|
background_fetch:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: background_fetch
|
||||||
|
sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -299,10 +299,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: bb5cd63ff7c91d6efe452e41d0d0ae6348925c82eafd10ce170ef585ea04776e
|
sha256: "892ada16046d641263f30c72e7432397088810a84f34479f6677494802a2b535"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "16.2.0"
|
version: "16.3.0"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -514,10 +514,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.14.41+235 # When changing this, update the tag in main() accordingly
|
version: 0.15.0+236 # When changing this, update the tag in main() accordingly
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@ -57,7 +57,6 @@ dependencies:
|
|||||||
ref: main
|
ref: main
|
||||||
android_package_manager: ^0.6.0
|
android_package_manager: ^0.6.0
|
||||||
share_plus: ^7.0.0
|
share_plus: ^7.0.0
|
||||||
android_alarm_manager_plus: ^3.0.0
|
|
||||||
sqflite: ^2.2.0+3
|
sqflite: ^2.2.0+3
|
||||||
easy_localization: ^3.0.1
|
easy_localization: ^3.0.1
|
||||||
android_intent_plus: ^4.0.0
|
android_intent_plus: ^4.0.0
|
||||||
@ -68,6 +67,7 @@ dependencies:
|
|||||||
shared_storage: ^0.8.0
|
shared_storage: ^0.8.0
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
app_links: ^3.5.0
|
app_links: ^3.5.0
|
||||||
|
background_fetch: ^1.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user