Merge branch 'main' into re7gog

This commit is contained in:
Григорий Величко
2024-01-01 18:50:24 +03:00
committed by GitHub
46 changed files with 873 additions and 638 deletions

View File

@ -30,6 +30,7 @@ Currently supported App sources:
- [Signal](https://signal.org/) - [Signal](https://signal.org/)
- [VLC](https://videolan.org/) - [VLC](https://videolan.org/)
- Other - App-Specific: - Other - App-Specific:
- [WhatsApp](https://whatsapp.com)
- [Telegram App](https://telegram.org) - [Telegram App](https://telegram.org)
- [Steam Mobile Apps](https://store.steampowered.com/mobile) - [Steam Mobile Apps](https://store.steampowered.com/mobile)
- [Neutron Code](https://neutroncode.com) - [Neutron Code](https://neutroncode.com)

View File

@ -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
} }

View File

@ -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"

View File

@ -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"
}
} }
} }

View File

@ -235,7 +235,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",
@ -245,8 +245,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",

View File

@ -9,36 +9,36 @@
"placeholder": "Zástupce", "placeholder": "Zástupce",
"someErrors": "Vyskytly se nějaké chyby", "someErrors": "Vyskytly se nějaké chyby",
"unexpectedError": "Neočekávaná chyba", "unexpectedError": "Neočekávaná chyba",
"ok": "Okay", "ok": "Ok",
"and": "a", "and": "a",
"githubPATLabel": "GitHub Personal Access Token (Raises Rate Limit)", "githubPATLabel": "GitHub Personal Access Token (zvyšuje limit rychlosti)",
"includePrereleases": "includepreleases", "includePrereleases": "Zahrnout předběžné verze",
"fallbackToOlderReleases": "Fallback to older releases", "fallbackToOlderReleases": "Přechod na starší verze",
"filterReleaseTitlesByRegEx": "Názvy vydání podle regulárního výrazu\filtr", "filterReleaseTitlesByRegEx": "Filtrovat názvy verzí podle regulárního výrazu",
"invalidRegEx": "Neplatný regulární výraz", "invalidRegEx": "Neplatný regulární výraz",
"noDescription": "Žádný popis", "noDescription": "Žádný popis",
"cancel": "Zrušit", "cancel": "Zrušit",
"continue": "Pokračovat", "continue": "Pokračovat",
"requiredInBracets": "(Required)", "requiredInBracets": "(Required)",
"dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST", "dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST",
"color": "barva", "colour": "Barva",
"githubStarredRepos": "GitHub Starred Repos", "githubStarredRepos": "GitHub Starred Repos",
"uname": "username", "uname": "Uživatelské jméno",
"wrongArgNum": "Špatný počet předložených argumentů", "wrongArgNum": "Nesprávný počet zadaných argumentů",
"xIsTrackOnly": "{} je určeno pouze pro sledování", "xIsTrackOnly":"{} je určeno pouze pro sledování",
"source": "zdroj", "source": "Zdroj",
"app": "App", "app": "App",
"appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou 'Jen sledovány'.", "appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou Jen sledovány.",
"youPickedTrackOnly": "Vybrali jste možnost 'Jen sledovat'.", "youPickedTrackOnly": "Vybrali jste možnost Jen sledovat.",
"trackOnlyAppDescription": "Aplikace je sledována kvůli aktualizacím, ale Obtainium ji nebude stahovat ani instalovat.", "trackOnlyAppDescription": "Aplikace je sledována kvůli aktualizacím, ale Obtainium ji nebude stahovat ani instalovat.",
"cancelled": "Zrušeno", "cancelled": "Zrušeno",
"appAlreadyAdded": "Aplikace již přidána", "appAlreadyAdded": "Aplikace již přidána",
"alreadyUpToDateQuestion": "App already up to date?", "alreadyUpToDateQuestion": "App already up to date?",
"addApp": "Přidat aplikaci", "addApp": "Přidat aplikaci",
"appSourceURL": "zdrojová adresa URL aplikace", "appSourceURL": "Zdrojová adresa URL aplikace",
"error": "Chyba", "error": "Chyba",
"add": "Přidat", "add": "Přidat",
"searchSomeSourcesLabel": "Vyhledávání (pouze konkrétní zdroje)", "searchSomeSourcesLabel": "Vyhledávání (pouze pro určité zdroje)",
"search": "Hledat", "search": "Hledat",
"additionalOptsFor": "Další možnosti pro {}", "additionalOptsFor": "Další možnosti pro {}",
"supportedSources": "Podporované zdroje", "supportedSources": "Podporované zdroje",
@ -46,45 +46,45 @@
"searchableInBrackets": "(s možností vyhledávání)", "searchableInBrackets": "(s možností vyhledávání)",
"appsString": "Apky", "appsString": "Apky",
"noApps": "Žádné aplikace", "noApps": "Žádné aplikace",
"noAppsForFilter": "žádné aplikace pro vybraný filtr", "noAppsForFilter": "Žádné aplikace pro vybraný filtr",
"byX": "By {}", "byX": "Od {}",
"percentProgress": "Pokrok: {}%", "percentProgress": "Pokrok: {}%",
"pleaseWait": "Počkejte prosím", "pleaseWait": "Počkejte prosím",
"updateAvailable": "Aktualizace je k dispozici", "updateAvailable": "Aktualizace je k dispozici",
"estimateInBracketsShort": "(approx.)", "estimateInBracketsShort": "(approx.)",
"notInstalled": "Není nainstalováno", "notInstalled": "Není nainstalováno",
"estimateInBrackets": "(přibližně)", "estimateInBrackets": "(přibližně)",
"selectAll": "Vybrat Vše", "selectAll": "Vybrat vše",
"deselectX": "{} deselected", "deselectX": "{} deselected",
"xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.", "xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.",
"removeSelectedAppsQuestion": "Odebrat vybrané aplikace?", "removeSelectedAppsQuestion": "Odebrat vybrané aplikace?",
"removeSelectedApps": "Odebrat vybrané aplikace", "removeSelectedApps": "Odebrat vybrané aplikace",
"updateX": "Aktualizovat {}", "updateX": "Aktualizovat {}",
"installX": "Instalovat {}", "installX": "Instalovat {}",
"markXTrackOnlyAsUpdated": "Označit {}\n(Track-Only)\njako aktualizované", "markXTrackOnlyAsUpdated": "Označit {}\n(Jen sledované)\njako aktualizované",
"changeX": "Změnit {}", "changeX": "Změnit {}",
"installUpdateApps": "Instalovat/aktualizovat aplikace", "installUpdateApps": "Instalovat/aktualizovat aplikace",
"installUpdateSelectedApps": "Instalovat/aktualizovat vybrané aplikace", "installUpdateSelectedApps": "Instalovat/aktualizovat vybrané aplikace",
"markXSelectedAppsAsUpdated": "označit {} vybrané aplikace jako aktuální?", "markXSelectedAppsAsUpdated": "Označit {} vybrané aplikace jako aktuální?",
"no": "Ne", "no": "Ne",
"yes": "ano", "yes": "Ano",
"markSelectedAppsUpdated": "označit vybrané aplikace jako aktuální", "markSelectedAppsUpdated": "Označit vybrané aplikace jako aktuální",
"pinToTop": "Připnout nahoru", "pinToTop": "Připnout nahoru",
"unpinFromTop": "'Unpin Top'", "unpinFromTop": "Odepnout shora",
"resetInstallStatusForSelectedAppsQuestion": "Obnovit stav instalace vybraných aplikací?", "resetInstallStatusForSelectedAppsQuestion": "Obnovit stav instalace vybraných aplikací?",
"installStatusOfXWillBeResetExplanation": "Stav instalace vybraných aplikací bude resetován. To může být užitečné, pokud je verze aplikace zobrazená v Obtainium nesprávná z důvodu neúspěšných aktualizací nebo jiných problémů.", "installStatusOfXWillBeResetExplanation": "Stav instalace vybraných aplikací bude resetován. To může být užitečné, pokud je verze aplikace zobrazená v Obtainium nesprávná z důvodu neúspěšných aktualizací nebo jiných problémů.",
"shareSelectedAppURLs": "Sdílet adresy URL vybraných aplikací", "shareSelectedAppURLs": "Sdílet adresy URL vybraných aplikací",
"resetInstallStatus": "Obnovení stavu instalace", "resetInstallStatus": "Obnovit stav instalace",
"more": "more", "more": "Více",
"removeOutdatedFilter": "Odstranit filtr aplikace 'Not Current'", "removeOutdatedFilter": "Odstranit filtr Neaktuální",
"showOutdatedOnly": "Zobrazit pouze aplikace, které nejsou aktuální", "showOutdatedOnly": "Zobrazovat pouze zastaralé aplikace",
"filter": "Filtr", "filter": "Filtr",
"filterActive": "Filtr *", "filterActive": "Filtr *",
"filterApps": "Filtrovat aplikace", "filterApps": "Filtrovat aplikace",
"appName": "název aplikace", "appName": "Název aplikace",
"author": "Autor", "author": "Autor",
"upToDateApps": "Apps with current version", "upToDateApps": "Aktuální apky",
"nonInstalledApps": "Apps not installed", "nonInstalledApps": "Neinstalované apky",
"importExport": "Import/Export", "importExport": "Import/Export",
"settings": "Nastavení", "settings": "Nastavení",
"exportedTo": "Exportováno do {}", "exportedTo": "Exportováno do {}",
@ -93,75 +93,75 @@
"importedX": "Importováno {}", "importedX": "Importováno {}",
"obtainiumImport": "Obtainium Import", "obtainiumImport": "Obtainium Import",
"importFromURLList": "Import ze seznamu URL", "importFromURLList": "Import ze seznamu URL",
"searchQuery": "Search Query", "searchQuery": "Vyhledávací dotaz",
"appURLList": "App URL List", "appURLList": "Seznam adres aplikací",
"line": "line", "line": "Linka",
"searchX": "Search {}", "searchX": "Search {}",
"noResults": "Nebyly nalezeny žádné výsledky", "noResults": "Nebyly nalezeny žádné výsledky",
"importX": "Import {}", "importX": "Import {}",
"importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalované\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.", "importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalovány\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.",
"importErrors": "Import Errors", "importErrors": "Chyba importu",
"importedXOfYApps": "{}importováno {}aplikací.", "importedXOfYApps": "{}importováno z {} aplikací.",
"followingURLsHadErrors": "U následujících adres URL došlo k chybám:", "followingURLsHadErrors": "U následujících adres došlo k chybám:",
"selectURL": "Select URL", "selectURL": "Vybrat adresu",
"selectURLs": "Select URLs", "selectURLs": "Select adresy",
"pick": "Vybrat", "pick": "Vybrat",
"theme": "Téma", "theme": "Téma",
"dark": "Tmavé", "dark": "Tmavé",
"light": "Světlé", "light": "Světlé",
"followSystem": "Follow System", "followSystem": "Jako systém",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Použít čistě černé tmavé téma", "useBlackTheme": "Použít čistě černé tmavé téma",
"appSortBy": "Seřadit aplikaci podle", "appSortBy": "Seřadit podle",
"authorName": "autor/jméno", "authorName": "Autor/Jméno",
"nameAuthor": "jméno/autor", "nameAuthor": "Jméno/Autor",
"asAdded": "AsAdded", "asAdded": "Přidáno",
"appSortOrder": "Sort App By", "appSortOrder": "Seřadit",
"ascending": "Vzestupně", "ascending": "Vzestupně",
"descending": "Sestupně", "descending": "Sestupně",
"bgUpdateCheckInterval": "Background Update Check Interval", "bgUpdateCheckInterval": "Interval kontroly aktualizace na pozadí",
"neverManualOnly": "Nikdy - pouze ručně", "neverManualOnly": "Nikdy - pouze ručně",
"appearance": "Vzhled", "appearance": "Vzhled",
"showWebInAppView": "Zobrazit zdrojové webové stránky v zobrazení aplikace", "showWebInAppView": "Zobrazit zdrojové webové stránky v zobrazení aplikace",
"pinUpdates": "Připnout aplikace s aktualizacemi nahoře", "pinUpdates": "Připnout aplikace s aktualizacemi nahoru",
"updates": "Updates", "updates": "Updates",
"sourceSpecific": "source specific", "sourceSpecific": "Specifické pro zdroj",
"appSource": "zdroj aplikace", "appSource": "Zdroj aplikace",
"noLogs": "Žádné protokoly", "noLogs": "Žádné protokoly",
"appLogs": "App Logs", "appLogs": "Záznamy apky",
"close": "Zavřít", "close": "Zavřít",
"share": "Sdílet", "share": "Sdílet",
"appNotFound": "App not found", "appNotFound": "Aplikace nenalezena",
"obtainiumExportHyphenatedLowercase": "obtainium-export", "obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Vybrat APK", "pickAnAPK": "Vybrat APK",
"appHasMoreThanOnePackage": "{} má více než jeden balíček:", "appHasMoreThanOnePackage": "{} má více než jeden balíček:",
"deviceSupportsXArch": "Vaše zařízení podporuje architekturu CPU {}.", "deviceSupportsXArch": "Vaše zařízení podporuje architekturu CPU {}.",
"deviceSupportsFollowingArchs": "Vaše zařízení podporuje následující architektury CPU:", "deviceSupportsFollowingArchs": "Vaše zařízení podporuje následující architektury CPU:",
"warning": "Varování", "warning": "Varování",
"sourceIsXButPackageFromYPrompt": "The app source is '{}' but the release package is from '{}'. Pokračovat?", "sourceIsXButPackageFromYPrompt": "Zdroj aplikace je '{}', ale balíček pro vydání je z '{}'. Pokračovat?",
"updatesAvailable": "dostupné aktualizace", "updatesAvailable": "Dostupné aktualizace",
"updatesAvailableNotifDescription": "Upozorňuje uživatele, že jsou k dispozici aktualizace pro jednu nebo více aplikací sledovaných Obtainium", "updatesAvailableNotifDescription": "Upozorňuje uživatele, že jsou k dispozici aktualizace pro jednu nebo více aplikací sledovaných Obtainium",
"noNewUpdates": "Žádné nové aktualizace.", "noNewUpdates": "Žádné nové aktualizace.",
"xHasAnUpdate": "{} má aktualizaci.", "xHasAnUpdate": "{} má aktualizaci.",
"appsUpdated": "Aplikace aktualizovány", "appsUpdated": "Aplikace aktualizovány",
"appsUpdatedNotifDescription": "Upozorňuje uživatele, že byly provedeny aktualizace jedné nebo více aplikací na pozadí", "appsUpdatedNotifDescription": "Upozornit, že byly provedeny aktualizace jedné nebo více aplikací na pozadí",
"xWasUpdatedToY": "{} byl aktualizován na {}", "xWasUpdatedToY": "{} byla aktualizována na {}",
"errorCheckingUpdates": "Chybová kontrola aktualizací", "errorCheckingUpdates": "Chyba kontroly aktualizací",
"errorCheckingUpdatesNotifDescription": "Oznámení zobrazené při neúspěšné kontrole aktualizací na pozadí", "errorCheckingUpdatesNotifDescription": "Zobrazit oznámení při neúspěšné kontrole aktualizací na pozadí",
"appsRemoved": "Odstraněné aplikace", "appsRemoved": "Odstraněné aplikace",
"appsRemovedNotifDescription": "Oznámení uživateli, že jedna nebo více aplikací byly odstraněny z důvodu chyb při načítání", "appsRemovedNotifDescription": "Oznámit, že jedna nebo více aplikací bylo odstraněno z důvodu chyb při načítání",
"xWasRemovedDueToErrorY": "{} byla odstraněna z důvodu následující chyby: {}", "xWasRemovedDueToErrorY": "{} byla odstraněna z důvodu následující chyby: {}",
"completeAppInstallation": "Dokončit instalaci aplikace", "completeAppInstallation": "Dokončit instalaci aplikace",
"obtainiumMustBeOpenToInstallApps": "Obtainium musí být otevřeno, aby bylo možné instalovat aplikace", "obtainiumMustBeOpenToInstallApps": "Obtainium musí být otevřeno, aby bylo možné instalovat aplikace",
"completeAppInstallationNotifDescription": "Vyzvat uživatele k návratu do Obtainium pro dokončení instalace aplikací", "completeAppInstallationNotifDescription": "Vyzvat k návratu do Obtainium pro dokončení instalace aplikací",
"checkingForUpdates": "Zkontrolovat aktualizace", "checkingForUpdates": "Zkontrolovat aktualizace",
"checkingForUpdatesNotifDescription": "Dočasné oznámení zobrazené při kontrole aktualizací", "checkingForUpdatesNotifDescription": "Dočasné oznámení zobrazené při kontrole aktualizací",
"pleaseAllowInstallPerm": "Povolte prosím Obtainium instalovat aplikace", "pleaseAllowInstallPerm": "Povolte prosím Obtainium instalovat aplikace",
"trackOnly": "Jen sledovat", "trackOnly": "Jen sledovat",
"errorWithHttpStatusCode": "error {}", "errorWithHttpStatusCode": "Chyba {}",
"versionCorrectionDisabled": "Oprava verze zakázána (zásuvný modul zřejmě nefunguje)", "versionCorrectionDisabled": "Oprava verze zakázána (zásuvný modul zřejmě nefunguje)",
"unknown": "Unknown", "unknown": "Neznám",
"none": "None", "none": "None",
"never": "Nikdy", "never": "Nikdy",
"latestVersionX": "Nejnovější verze: {}", "latestVersionX": "Nejnovější verze: {}",
@ -169,12 +169,12 @@
"lastUpdateCheckX": "Poslední kontrola aktualizace: {}", "lastUpdateCheckX": "Poslední kontrola aktualizace: {}",
"remove": "Odebrat", "remove": "Odebrat",
"yesMarkUpdated": "Ano, označit jako aktualizované", "yesMarkUpdated": "Ano, označit jako aktualizované",
"fdroid": "F-Droid Official", "fdroid": "Oficiální repozitář F-Droid",
"appIdOrName": "App ID or Name", "appIdOrName": "ID nebo název apky",
"appId": "App ID", "appId": "App ID",
"appWithIdOrNameNotFound": "Žádná aplikace s tímto ID nebo názvem nebyla nalezena", "appWithIdOrNameNotFound": "Žádná aplikace s tímto ID nebo názvem nebyla nalezena",
"reposHaveMultipleApps": "Repozitáře mohou obsahovat více aplikací", "reposHaveMultipleApps": "Repozitáře mohou obsahovat více aplikací",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo", "fdroidThirdPartyRepo": "F-Droid repozitář třetí strany",
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
@ -182,104 +182,111 @@
"markInstalled": "Označit jako nainstalovaný", "markInstalled": "Označit jako nainstalovaný",
"update": "Aktualizovat", "update": "Aktualizovat",
"markUpdated": "Označit jako aktuální", "markUpdated": "Označit jako aktuální",
"additionalOptions": "Additional Options", "additionalOptions": "Další možnosti",
"disableVersionDetection": "Zakázat detekci verze", "disableVersionDetection": "Deaktivovat detekci verze",
"noVersionDetectionExplanation": "Tato volba by měla být použita pouze u aplikací, kde detekce verzí nefunguje správně.", "noVersionDetectionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně.",
"downloadingX": "download {}", "downloadingX": "Stáhnout {}",
"downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace", "downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace",
"noAPKFound": "Žádná APK nebyla nalezena", "noAPKFound": "Žádná APK nebyla nalezena",
"noVersionDetection": "Žádná detekce verze", "noVersionDetection": "Žádná detekce verze",
"categorize": "Kategorizovat", "categorize": "Kategorizovat",
"categories": "Kategorie", "categories": "Kategorie",
"category": "kategorie", "category": "Kategorie",
"noCategory": "Žádná kategorie", "noCategory": "Žádná kategorie",
"noCategories": "Žádné kategorie", "noCategories": "Žádné kategorie",
"deleteCategoriesQuestion": "Smazat kategorie?", "deleteCategoriesQuestion": "Smazat kategorie?",
"categoryDeleteWarning": "Všechny aplikace v odstraněných kategoriích budou nastaveny na nekategorizované.", "categoryDeleteWarning": "Všechny aplikace v odstraněných kategoriích budou nastaveny na nekategorizované.",
"addCategory": "přidat kategorii", "addCategory": "Přidat kategorii",
"label": "štítek", "label": "Štítek",
"language": "Jazyk", "language": "Jazyk",
"copiedToClipboard": "zkopírováno do schránky", "copiedToClipboard": "Zkopírováno do schránky",
"storagePermissionDenied": "povolení k ukládání odepřeno", "storagePermissionDenied": "Oprávnění k ukládání odepřeno",
"selectedCategorizeWarning": "Toto nahradí všechna stávající nastavení kategorií pro vybrané aplikace.", "selectedCategorizeWarning": "Toto nahradí všechna stávající nastavení kategorií pro vybrané aplikace.",
"filterAPKsByRegEx": "Filtrovat APK podle regulárního výrazu", "filterAPKsByRegEx": "Filtrovat APK podle regulárního výrazu",
"removeFromObtainium": "Odebrat z Obtainium", "removeFromObtainium": "Odebrat z Obtainium",
"uninstallFromDevice": "Odinstalovat ze zařízení", "uninstallFromDevice": "Odinstalovat ze zařízení",
"onlyWorksWithNonVersionDetectApps": "Funguje pouze pro aplikace s vypnutou detekcí verze.", "onlyWorksWithNonVersionDetectApps": "Funguje pouze pro aplikace s vypnutou detekcí verze.",
"releaseDateAsVersion": "Použít datum vydání jako verzi", "releaseDateAsVersion": "Použít datum vydání jako verzi",
"releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikací, u kterých detekce verze nefunguje správně, ale je k dispozici datum vydání.", "releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně, ale je k dispozici datum vydání.",
"changes": "Změny", "changes": "Změny",
"releaseDate": "datum vydání", "releaseDate": "Datum vydání",
"importFromURLsInFile": "Importovat adresy URL ze souboru (např. OPML)", "importFromURLsInFile": "Importovat adresy URL ze souboru (např. OPML)",
"versionDetection": "detekce verze", "versionDetection": "Detekce verze",
"standardVersionDetection": "standardní detekce verze", "standardVersionDetection": "Standardní detekce verze",
"groupByCategory": "Seskupit podle kategorie", "groupByCategory": "Seskupit podle kategorie",
"autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru", "autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru",
"overrideSource": "Přepsat zdroj", "overrideSource": "Přepsat zdroj",
"dontShowAgain": "Nezobrazovat znovu", "dontShowAgain": "Nezobrazovat znovu",
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Track Only'", "dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'",
"dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK", "dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK",
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace", "moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)", "gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
"about": "About", "about": "O",
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)", "requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
"checkOnStart": "Zkontrolovat jednou při spuštění", "checkOnStart": "Zkontrolovat jednou při spuštění",
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu", "tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace", "removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",
"pickHighestVersionCode": "Automaticky vybrat APK s kódem nejvyšší verze", "pickHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
"checkUpdateOnDetailPage": "Zkontrolovat aktualizace při otevření stránky s podrobnostmi aplikace", "checkUpdateOnDetailPage": "Zkontrolovat aktualizaci při otevření stránky s podrobnostmi aplikace",
"disablePageTransitions": "Zakázat animace pro přechody stránek", "disablePageTransitions": "Zakázat animace pro přechody stránek",
"reversePageTransitions": "Obrátit animace pro přechody stránek", "reversePageTransitions": "Obrátit animace pro přechody stránek",
"minStarCount": "Minimální počet hvězdiček", "minStarCount": "Minimální počet hvězdiček",
"addInfoBelow": "Přidat tuto informaci na konec stránky", "addInfoBelow": "Přidat tuto informaci na konec stránky.",
"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": "Seřadit pouze podle poslední části odkazu",
"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í",
"appsPossiblyUpdatedNotifDescription": "Upozorňuje uživatele, že na pozadí mohly být provedeny aktualizace jedné nebo více aplikací", "appsPossiblyUpdatedNotifDescription": "Upozorňuje uživatele, že na pozadí mohly být provedeny aktualizace jedné nebo více aplikací",
"xWasPossiblyUpdatedToY": "{} mohlo být aktualizováno na {}.", "xWasPossiblyUpdatedToY":"{} mohlo být aktualizováno na {}.",
"enableBackgroundUpdates": "Povolit aktualizace na pozadí", "enableBackgroundUpdates": "Povolit aktualizace na pozadí",
"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řeno Obtainium.",
"verifyLatestTag": "Ověřit značku 'latest'", "verifyLatestTag": "Zkontrolovat značku latest",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First", "intermediateLinkRegex": "Filtrovat mezipropojení, které by mělo být navštíveno jako první",
"intermediateLinkNotFound": "Intermediate link not found", "filterByLinkText": "Filtrovat odkazy podle textu odkazu",
"exemptFromBackgroundUpdates": "Vyloučit aktualizace na pozadí (pokud jsou povoleny)", "intermediateLinkNotFound": "Připojený odkaz nenalezen",
"bgUpdatesOnWiFiOnly": "Zakázat aktualizace na pozadí, pokud není přítomna Wi-Fi", "intermediateLink": "Připojený odkaz",
"autoSelectHighestVersionCode": "Automatický výběr nejvyššího kódu verze APK", "exemptFromBackgroundUpdates": "Vyloučit z aktualizací na pozadí (je-li povoleno)",
"versionExtractionRegEx": "Version Extraction RegEx", "bgUpdatesOnWiFiOnly": "Deaktivovat aktualizace na pozadí, pokud není k dispozici Wi-Fi",
"matchGroupToUse": "Match Group to Use", "autoSelectHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
"versionExtractionRegEx": "Extrakce verze pomocí RegEx",
"matchGroupToUse": "Odpovídá použité skupině",
"highlightTouchTargets": "Zvýraznit méně zjevné cíle dotyku", "highlightTouchTargets": "Zvýraznit méně zjevné cíle dotyku",
"pickExportDir": "Vybrat adresář pro export", "pickExportDir": "Vybrat adresář pro export",
"autoExportOnChanges": "Automatický export při změnách", "autoExportOnChanges": "Automatický export při změně",
"includeSettings": "Include settings", "includeSettings": "Zahrnout nastavení",
"filterVersionsByRegEx": "Filtrovat verze podle regulárního výrazu", "filterVersionsByRegEx": "Filtrovat verze podle regulárních výrazů",
"trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovaný kód verze APK", "trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovanou verzi APK",
"dontSortReleasesList": "Retain release order from API", "dontSortReleasesList": "Seřadit vydání z rozhraní API",
"reverseSort": "Reverse sorting", "reverseSort": "Obrácené třídění",
"takeFirstLink": "Take first link", "takeFirstLink": "Použít první odkaz",
"skipSort": "Skip sorting", "skipSort": "Přeskočit třídění",
"debugMenu": "Debug Menu", "debugMenu": "Nabídka ladění",
"bgTaskStarted": "Background task started - check logs.", "bgTaskStarted": "Spuštěna úloha na pozadí - zkontrolujte protokoly.",
"runBgCheckNow": "Run Background Update Check Now", "runBgCheckNow": "Spustit kontrolu aktualizací na pozadí nyní",
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page", "versionExtractWholePage": "Použít extrakci verze pomocí RegEx na celou stránku",
"installing": "Installing", "installing": "Instaluji",
"skipUpdateNotifications": "Skip update notifications", "skipUpdateNotifications": "Neposkytovat oznámení o aktualizaci",
"updatesAvailableNotifChannel": "dostupné aktualizace", "updatesAvailableNotifChannel": "Dostupné aktualizace",
"appsUpdatedNotifChannel": "Aplikace aktualizovány", "appsUpdatedNotifChannel": "Apky aktualizovány",
"appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizaci aplikací", "appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizace aplikací",
"errorCheckingUpdatesNotifChannel": "Chybová kontrola aktualizací", "errorCheckingUpdatesNotifChannel": "Chyba při kontrole aktualizací",
"appsRemovedNotifChannel": "Odstraněné aplikace", "appsRemovedNotifChannel": "Odstraněné apky",
"downloadingXNotifChannel": "download {}", "downloadingXNotifChannel": "Stáhnout {}",
"completeAppInstallationNotifChannel": "Dokončit instalaci aplikace", "completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
"checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace", "checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates", "onlyCheckInstalledOrTrackOnlyApps": "Na aktualizace kontrolovat pouze nainstalované aplikace a aplikace označené Track only",
"supportFixedAPKURL": "Support fixed APK URLs", "supportFixedAPKURL": "Odhadnout novější verzi na základě prvních třiceti číslic kontrolního součtu adresy URL APK, pokud není podporována jinak",
"selectX": "Select {}", "selectX": "Vybrat {}",
"parallelDownloads": "Allow parallel downloads", "parallelDownloads": "Povolit souběžné stahování",
"installMethod": "Metoda instalace",
"normal": "Normální",
"shizuku": "Shizuku",
"root": "Správce",
"shizukuBinderNotFound": "Shizuku neběží",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Odstranit Apku?", "one": "Odstranit Apku?",
"other": "Odstranit Apky?" "other": "Odstranit Apky?"
@ -289,47 +296,47 @@
"other": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minut" "other": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minut"
}, },
"bgUpdateGotErrorRetryInMinutes": { "bgUpdateGotErrorRetryInMinutes": {
"one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování pokusu bude naplánováno za {} minut", "one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut",
"other": "Během kontroly aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut" "other": "Při kontrole aktualizací na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut"
}, },
"bgCheckFoundUpdatesWillNotifyIfNeeded": { "bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Při kontrole aktualizací na pozadí nalezena {}aktualizace - v případě potřeby upozorní uživatele", "one": "Při kontrole aktualizací na pozadí nalezena {}aktualizace - v případě potřeby upozorní uživatele",
"other": "Kontrola aktualizací na pozadí našla {} aktualizací - v případě potřeby upozorní uživatele" "other": "Kontrola aktualizací na pozadí nalezla {} aktualizací - v případě potřeby upozorní uživatele"
}, },
"apps": { "apps": {
"one": "{} App", "one": "{} Apka",
"other": "{} apps" "other": "{} Apky"
}, },
"url": { "url": {
"jedna": "{} URL", "one": "{} Adresa",
"other": "{} URLs" "other": "{} Adres"
}, },
"minute": { "minute": {
"one": "{} minute", "one": "{} Minuta",
"other": "{} minutes" "other": "{} Minut"
}, },
"hour": { "hour": {
"jedna": "{} hodina", "one": "{} Hodina",
"other": "{} hours" "other": "{} Hodin"
}, },
"day": { "day": {
"jedna": "{} den", "one": "{} Den",
"other": "{} dny" "other": "{} Dnů"
}, },
"clearedNLogsBeforeXAfterY": { "clearedNLogsBeforeXAfterY": {
"one": "{n} log vymazán (před = {před}, po = {po})", "one": "{n} Záznam vymazán (před = {before}, po = {after})",
"other": "{n} logů vymazáno (před = {před}, po = {po})" "other": "{n} Záznamů vymazáno (před = {before}, po = {after})"
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "{} a 1 další aplikace mají aktualizace.", "one": "{} a 1 další aplikace mají aktualizace.",
"other": "{} a {} další aplikace mají aktualizace." "other": "{} a {} další aplikace mají aktualizace."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} a {} další aplikace mají aktualizace.", "one": "{} a 1 další aplikace mají aktualizace.",
"další": "{} a {} další aplikace byly aktualizovány." "other": "{} a {} další aplikace byly aktualizovány."
}, },
"xAndNMoreUpdatesPossiblyInstalled": { "xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} a {} další aplikace byly možná aktualizovány", "one": "{} a 1 další aplikace možno aktualizovat",
"other": "{} a {} další aplikace mohly být aktualizovány." "other": "{} a {} další aplikace mohou být aktualizovány."
} }
} }

View File

@ -235,7 +235,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": "Sortiere nur nach dem letzten Teil des Links",
"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",
@ -246,7 +246,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": "Filtere Links durch Linktext",
"intermediateLinkNotFound": "„Zwischen“-Link nicht gefunden",
"intermediateLink": "„Zwischen“-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",
@ -255,13 +257,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",
@ -279,7 +281,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?"

View File

@ -235,7 +235,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",
@ -245,8 +245,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",

View File

@ -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,7 +102,7 @@
"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:",
"selectURL": "Seleccionar URL", "selectURL": "Seleccionar URL",
"selectURLs": "Seleccionar URLs", "selectURLs": "Seleccionar URLs",
"pick": "Escoger", "pick": "Escoger",
@ -112,7 +112,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",
@ -134,10 +134,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",
@ -157,7 +157,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)",
@ -206,15 +206,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'",
@ -232,11 +232,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",
@ -244,10 +244,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",
@ -257,7 +259,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",
@ -267,7 +269,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",

View File

@ -235,7 +235,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": "به‌روزرسانی برنامه انجام شد",
@ -245,8 +245,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",

View File

@ -235,7 +235,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",
@ -245,8 +245,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",

View File

@ -235,7 +235,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": "Rendezés csak a link utolsó szegmense szerint",
"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",
@ -244,8 +244,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",
@ -279,7 +281,7 @@
"onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért", "onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
"supportFixedAPKURL": "Támogatja a rögzített APK URL-eket", "supportFixedAPKURL": "Támogatja a rögzített APK URL-eket",
"selectX": "Kiválaszt {}", "selectX": "Kiválaszt {}",
"parallelDownloads": "Allow parallel downloads", "parallelDownloads": "Párhuzamos letöltéseket enged",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?" "other": "Eltávolítja az alkalmazást?"

View File

@ -235,7 +235,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",
@ -245,8 +245,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",
@ -255,13 +257,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",
@ -277,9 +279,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?"

View File

@ -235,7 +235,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": "アプリのアップデートを試行",
@ -245,8 +245,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を自動で選択する",

View File

@ -235,7 +235,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",
@ -245,8 +245,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",

View File

@ -235,7 +235,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",
@ -245,8 +245,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",

View File

@ -235,7 +235,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",
@ -245,8 +245,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",
@ -276,10 +278,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?"

View File

@ -235,7 +235,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": "Попытки обновления приложений",
@ -245,8 +245,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 с актуальной версией кода",

View File

@ -235,7 +235,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",
@ -245,8 +245,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",

View File

@ -235,7 +235,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",
@ -245,8 +245,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ç",

View File

@ -235,7 +235,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",
@ -245,8 +245,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",

View File

@ -222,7 +222,7 @@
"moveNonInstalledAppsToBottom": "将未安装应用置底", "moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)", "gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
"about": "相关文档", "about": "相关文档",
"requiresCredentialsInSettings": "{}: 此功能需要额外的凭据(在“设置”中添加)", "requiresCredentialsInSettings": "{}此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查", "checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID", "tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
"removeOnExternalUninstall": "自动删除已卸载的外部应用", "removeOnExternalUninstall": "自动删除已卸载的外部应用",
@ -235,9 +235,9 @@
"addInfoInSettings": "在“设置”中添加此凭据。", "addInfoInSettings": "在“设置”中添加此凭据。",
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。", "githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。", "gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
"sortByFileNamesNotLinks": "使用文件名代替链接进行排序", "sortByLastLinkSegment": "仅根据链接的末尾部分进行筛选",
"filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)", "filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)",
"customLinkFilterRegex": "筛选自定义来源 APK 文件链接\n正则表达式默认匹配模式为“.apk$”)", "customLinkFilterRegex": "筛选自定义来源 APK 文件链接\n正则表达式默认匹配模式为“.apk$”)",
"appsPossiblyUpdated": "已尝试更新应用", "appsPossiblyUpdated": "已尝试更新应用",
"appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知", "appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知",
"xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。", "xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。",
@ -245,27 +245,29 @@
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。", "backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。", "backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
"verifyLatestTag": "验证“Latest”标签", "verifyLatestTag": "验证“Latest”标签",
"intermediateLinkRegex": "筛选首先访问的“中转链接(正则表达式)", "intermediateLinkRegex": "筛选中转链接(正则表达式)",
"intermediateLinkNotFound": "未找到“中转”链接", "filterByLinkText": "根据链接文本进行筛选",
"exemptFromBackgroundUpdates": "禁用后台更新\n如果已经全局启用", "intermediateLinkNotFound": "未找到中转链接",
"intermediateLink": "中转链接",
"exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用)",
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新", "bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件", "autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
"versionExtractionRegEx": "提取版本号(正则表达式)", "versionExtractionRegEx": "版本号提取规则(正则表达式)",
"matchGroupToUse": "引用的捕获组", "matchGroupToUse": "引用的捕获组",
"highlightTouchTargets": "突出展示不明显的触摸区域", "highlightTouchTargets": "突出展示不明显的触摸区域",
"pickExportDir": "选择导出文件夹", "pickExportDir": "选择导出文件夹",
"autoExportOnChanges": "数据变更时自动导出", "autoExportOnChanges": "数据变更时自动导出",
"includeSettings": "Include settings", "includeSettings": "同时导出应用设置",
"filterVersionsByRegEx": "筛选版本号(正则表达式)", "filterVersionsByRegEx": "筛选版本号(正则表达式)",
"trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件", "trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件",
"dontSortReleasesList": "保持来自 API 的发行顺序", "dontSortReleasesList": "保持来自 API 的发行顺序",
"reverseSort": "反转排序", "reverseSort": "反转排序",
"takeFirstLink": "Take first link", "takeFirstLink": "选取第一个链接",
"skipSort": "Skip sorting", "skipSort": "不进行排序",
"debugMenu": "调试选项", "debugMenu": "调试选项",
"bgTaskStarted": "后台任务已启动 - 详见日志", "bgTaskStarted": "后台任务已启动 - 详见日志",
"runBgCheckNow": "立即进行后台更新检查", "runBgCheckNow": "立即进行后台更新检查",
"versionExtractWholePage": "将提取版本号的正则表达式应用于整页面", "versionExtractWholePage": "将版本号提取规则应用于整页面",
"installing": "正在安装", "installing": "正在安装",
"skipUpdateNotifications": "忽略更新通知", "skipUpdateNotifications": "忽略更新通知",
"updatesAvailableNotifChannel": "更新可用", "updatesAvailableNotifChannel": "更新可用",
@ -277,9 +279,14 @@
"completeAppInstallationNotifChannel": "完成应用安装", "completeAppInstallationNotifChannel": "完成应用安装",
"checkingForUpdatesNotifChannel": "正在检查更新", "checkingForUpdatesNotifChannel": "正在检查更新",
"onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查", "onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查",
"supportFixedAPKURL": "Support fixed APK URLs", "supportFixedAPKURL": "支持固定的 APK 文件链接",
"selectX": "Select {}", "selectX": "选择 {}",
"parallelDownloads": "Allow parallel downloads", "parallelDownloads": "启用并行下载",
"installMethod": "安装方式",
"normal": "常规",
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku 服务未运行",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"

View File

@ -10,7 +10,7 @@ class APKCombo extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/+[^/]+/+[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+');
var match = standardUrlRegEx.firstMatch(url.toLowerCase()); var match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -34,7 +34,7 @@ class APKPure extends AppSource {
url = 'https://$host${Uri.parse(url).path}'; url = 'https://$host${Uri.parse(url).path}';
} }
RegExp standardUrlRegExA = RegExp standardUrlRegExA =
RegExp('^https?://$host/+[^/]+/+[^/]+(/+[^/]+)?'); RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+(/+[^/]+)?');
match = standardUrlRegExA.firstMatch(url.toLowerCase()); match = standardUrlRegExA.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -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;
} }

View File

@ -16,7 +16,7 @@ class Codeberg extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -38,13 +38,14 @@ class FDroid extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB = RegExp standardUrlRegExB =
RegExp('^https?://$host/+[^/]+/+packages/+[^/]+'); RegExp('^https?://(www\\.)?$host/+[^/]+/+packages/+[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
if (match != null) { if (match != null) {
url = url =
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}'; 'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
} }
RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+'); RegExp standardUrlRegExA =
RegExp('^https?://(www\\.)?$host/+packages/+[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase()); match = standardUrlRegExA.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -149,7 +149,7 @@ class GitHub extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -52,7 +52,7 @@ class GitLab extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -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')));
} }
} }

View File

@ -13,7 +13,7 @@ class HuaweiAppGallery extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/app/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/app/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -11,7 +11,7 @@ class Mullvad extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host'); RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -10,7 +10,8 @@ class NeutronCode extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+'); RegExp standardUrlRegEx =
RegExp('^https?://(www\\.)?$host/downloads/file/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -10,13 +10,14 @@ class SourceForge extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB = RegExp('^https?://$host/p/[^/]+'); RegExp standardUrlRegExB = RegExp('^https?://(www\\.)?$host/p/[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
if (match != null) { if (match != null) {
url = url =
'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}'; 'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}';
} }
RegExp standardUrlRegExA = RegExp('^https?://$host/projects/[^/]+'); RegExp standardUrlRegExA =
RegExp('^https?://(www\\.)?$host/projects/[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase()); match = standardUrlRegExA.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -20,7 +20,7 @@ class SourceHut extends AppSource {
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw InvalidURLError(name); throw InvalidURLError(name);

View File

@ -6,6 +6,8 @@ import 'package:obtainium/providers/source_provider.dart';
class WhatsApp extends AppSource { class WhatsApp extends AppSource {
WhatsApp() { WhatsApp() {
host = 'whatsapp.com'; host = 'whatsapp.com';
overrideVersionDetectionFormDefault('noVersionDetection',
disableStandard: true, disableRelDate: true);
} }
@override @override

View File

@ -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);
} }
} }
} }

View File

@ -13,14 +13,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.3';
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
@ -77,6 +77,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 {
@ -94,7 +107,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()),
@ -109,6 +121,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;
@ -123,6 +136,32 @@ 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 {
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>();
@ -162,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;
}
settingsProvider.addListener(() async { settingsProvider.addListener(() async {
if (settingsProvider.tryUseSystemFont && if (settingsProvider.tryUseSystemFont &&
settingsProvider.appFont == "Metropolis") { settingsProvider.appFont == "Metropolis") {

View File

@ -286,10 +286,14 @@ class AddAppPageState extends State<AddAppPage> {
selectedByDefault: true, selectedByDefault: true,
onlyOneSelectionAllowed: false, onlyOneSelectionAllowed: false,
titlesAreLinks: false, titlesAreLinks: false,
deselectThese: settingsProvider.searchDeselected,
); );
}) ?? }) ??
[]; [];
if (searchSources.isNotEmpty) { if (searchSources.isNotEmpty) {
settingsProvider.searchDeselected = sourceStrings.keys
.where((s) => !searchSources.contains(s))
.toList();
var results = await Future.wait(sourceProvider.sources var results = await Future.wait(sourceProvider.sources
.where((e) => searchSources.contains(e.name)) .where((e) => searchSources.contains(e.name))
.map((e) async { .map((e) async {
@ -306,7 +310,6 @@ class AddAppPageState extends State<AddAppPage> {
} }
})); }));
// .then((results) async {
// Interleave results instead of simple reduce // Interleave results instead of simple reduce
Map<String, List<String>> res = {}; Map<String, List<String>> res = {};
var si = 0; var si = 0;

View File

@ -604,11 +604,13 @@ class SelectionModal extends StatefulWidget {
this.selectedByDefault = true, this.selectedByDefault = true,
this.onlyOneSelectionAllowed = false, this.onlyOneSelectionAllowed = false,
this.titlesAreLinks = true, this.titlesAreLinks = true,
this.title}); this.title,
this.deselectThese = const []});
String? title; String? title;
Map<String, List<String>> entries; Map<String, List<String>> entries;
bool selectedByDefault; bool selectedByDefault;
List<String> deselectThese;
bool onlyOneSelectionAllowed; bool onlyOneSelectionAllowed;
bool titlesAreLinks; bool titlesAreLinks;
@ -622,9 +624,13 @@ class _SelectionModalState extends State<SelectionModal> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
for (var url in widget.entries.entries) { for (var entry in widget.entries.entries) {
entrySelections.putIfAbsent(url, entrySelections.putIfAbsent(
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed); entry,
() =>
widget.selectedByDefault &&
!widget.onlyOneSelectionAllowed &&
!widget.deselectThese.contains(entry.key));
} }
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) { if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
selectOnlyOne(widget.entries.entries.first.key); selectOnlyOne(widget.entries.entries.first.key);

View File

@ -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';
@ -619,38 +618,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'))) // ]),
], // ),
),
]),
),
], ],
), ),
) )

View File

@ -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();
} }

View File

@ -70,8 +70,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) {
@ -363,15 +363,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();
} }
@ -464,4 +464,13 @@ class SettingsProvider with ChangeNotifier {
prefs?.setBool('parallelDownloads', val); prefs?.setBool('parallelDownloads', val);
notifyListeners(); notifyListeners();
} }
List<String> get searchDeselected {
return prefs?.getStringList('searchDeselected') ?? [];
}
set searchDeselected(List<String> list) {
prefs?.setStringList('searchDeselected', list);
notifyListeners();
}
} }

View File

@ -135,10 +135,34 @@ 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['intermediateLinkRegex']?.isNotEmpty != true) {
additionalSettings['intermediateLink'] = [
{
'customLinkFilterRegex':
originalAdditionalSettings['intermediateLinkRegex'],
'filterByLinkText':
originalAdditionalSettings['intermediateLinkByText']
}
];
}
if ((additionalSettings['intermediateLink']?.length ?? 0) > 0) {
additionalSettings['intermediateLink'] =
additionalSettings['intermediateLink'].where((e) {
return e['customLinkFilterRegex']?.isNotEmpty == true;
}).toList();
}
} }
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

View File

@ -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:

View File

@ -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.3+239 # 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: