mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-30 21:13:28 +01:00 
			
		
		
		
	Merge branch 'main' into re7gog
This commit is contained in:
		| @@ -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) | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) { | |||||||
| } | } | ||||||
|  |  | ||||||
| android { | android { | ||||||
|     compileSdkVersion 34 |     compileSdkVersion rootProject.ext.compileSdkVersion | ||||||
|     ndkVersion flutter.ndkVersion |     ndkVersion flutter.ndkVersion | ||||||
|  |  | ||||||
|     compileOptions { |     compileOptions { | ||||||
| @@ -54,7 +54,7 @@ android { | |||||||
|         // You can update the following values to match your application needs. |         // You can update the following values to match your application needs. | ||||||
|         // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. |         // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. | ||||||
|         minSdkVersion 24 |         minSdkVersion 24 | ||||||
|         targetSdkVersion 34 |         targetSdkVersion rootProject.ext.targetSdkVersion | ||||||
|         versionCode flutterVersionCode.toInteger() |         versionCode flutterVersionCode.toInteger() | ||||||
|         versionName flutterVersionName |         versionName flutterVersionName | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|     package="dev.imranr.obtainium"> |     package="dev.imranr.obtainium"> | ||||||
|     <application |     <application | ||||||
|         android:label="Obtainium" |         android:label="Obtainium" | ||||||
|   | |||||||
| @@ -1,5 +1,10 @@ | |||||||
| buildscript { | buildscript { | ||||||
|     ext.kotlin_version = '1.7.10' |     ext.kotlin_version = '1.7.10' | ||||||
|  |     ext { | ||||||
|  |         compileSdkVersion   = 34                // or latest | ||||||
|  |         targetSdkVersion    = 34                // or latest | ||||||
|  |         appCompatVersion    = "1.4.2"           // or latest | ||||||
|  |     } | ||||||
|     repositories { |     repositories { | ||||||
|         google() |         google() | ||||||
|         mavenCentral() |         mavenCentral() | ||||||
| @@ -16,6 +21,10 @@ allprojects { | |||||||
|     repositories { |     repositories { | ||||||
|         google() |         google() | ||||||
|         mavenCentral() |         mavenCentral() | ||||||
|  |         maven { | ||||||
|  |             // [required] background_fetch | ||||||
|  |             url "${project(':background_fetch').projectDir}/libs" | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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." | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -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?" | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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?" | ||||||
|   | |||||||
| @@ -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?" | ||||||
|   | |||||||
| @@ -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を自動で選択する", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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?" | ||||||
|   | |||||||
| @@ -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 с актуальной версией кода", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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ç", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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": "是否删除应用?" | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -88,18 +88,7 @@ bool _isNumeric(String s) { | |||||||
| } | } | ||||||
|  |  | ||||||
| class HTML extends AppSource { | class HTML extends AppSource { | ||||||
|   HTML() { |   var finalStepFormitems = [ | ||||||
|     additionalSourceAppSpecificSettingFormItems = [ |  | ||||||
|       [ |  | ||||||
|         GeneratedFormSwitch('sortByFileNamesNotLinks', |  | ||||||
|             label: tr('sortByFileNamesNotLinks')) |  | ||||||
|       ], |  | ||||||
|       [GeneratedFormSwitch('skipSort', label: tr('skipSort'))], |  | ||||||
|       [GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))], |  | ||||||
|       [ |  | ||||||
|         GeneratedFormSwitch('supportFixedAPKURL', |  | ||||||
|             defaultValue: true, label: tr('supportFixedAPKURL')), |  | ||||||
|       ], |  | ||||||
|     [ |     [ | ||||||
|       GeneratedFormTextField('customLinkFilterRegex', |       GeneratedFormTextField('customLinkFilterRegex', | ||||||
|           label: tr('customLinkFilterRegex'), |           label: tr('customLinkFilterRegex'), | ||||||
| @@ -111,13 +100,6 @@ class HTML extends AppSource { | |||||||
|             } |             } | ||||||
|           ]) |           ]) | ||||||
|     ], |     ], | ||||||
|       [ |  | ||||||
|         GeneratedFormTextField('intermediateLinkRegex', |  | ||||||
|             label: tr('intermediateLinkRegex'), |  | ||||||
|             hint: '([0-9]+.)*[0-9]+/\$', |  | ||||||
|             required: false, |  | ||||||
|             additionalValidators: [(value) => regExValidator(value)]) |  | ||||||
|       ], |  | ||||||
|     [ |     [ | ||||||
|       GeneratedFormTextField('versionExtractionRegEx', |       GeneratedFormTextField('versionExtractionRegEx', | ||||||
|           label: tr('versionExtractionRegEx'), |           label: tr('versionExtractionRegEx'), | ||||||
| @@ -143,7 +125,40 @@ class HTML extends AppSource { | |||||||
|     [ |     [ | ||||||
|       GeneratedFormSwitch('versionExtractWholePage', |       GeneratedFormSwitch('versionExtractWholePage', | ||||||
|           label: tr('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() { | ||||||
|  |     additionalSourceAppSpecificSettingFormItems = [ | ||||||
|  |       [ | ||||||
|  |         GeneratedFormSubForm( | ||||||
|  |             'intermediateLink', [...intermediateFormItems, ...commonFormItems], | ||||||
|  |             label: tr('intermediateLink')) | ||||||
|  |       ], | ||||||
|  |       finalStepFormitems[0], | ||||||
|  |       ...commonFormItems, | ||||||
|  |       ...finalStepFormitems.sublist(1) | ||||||
|     ]; |     ]; | ||||||
|     overrideVersionDetectionFormDefault('noVersionDetection', |     overrideVersionDetectionFormDefault('noVersionDetection', | ||||||
|         disableStandard: false, disableRelDate: true); |         disableStandard: false, disableRelDate: true); | ||||||
| @@ -164,77 +179,93 @@ class HTML extends AppSource { | |||||||
|     return url; |     return url; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   // Given an HTTP response, grab some links according to the common additional settings | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   // (those that apply to intermediate and final steps) | ||||||
|     String standardUrl, |   Future<List<MapEntry<String, String>>> grabLinksCommon( | ||||||
|     Map<String, dynamic> additionalSettings, |       Response res, Map<String, dynamic> additionalSettings) async { | ||||||
|   ) async { |     if (res.statusCode != 200) { | ||||||
|     var uri = Uri.parse(standardUrl); |       throw getObtainiumHttpError(res); | ||||||
|     Response res = await sourceRequest(standardUrl); |     } | ||||||
|     if (res.statusCode == 200) { |  | ||||||
|     var html = parse(res.body); |     var html = parse(res.body); | ||||||
|       List<String> allLinks = html |     List<MapEntry<String, String>> allLinks = html | ||||||
|         .querySelectorAll('a') |         .querySelectorAll('a') | ||||||
|           .map((element) => element.attributes['href'] ?? '') |         .map((element) => MapEntry( | ||||||
|           .where((element) => element.isNotEmpty) |             element.attributes['href'] ?? '', | ||||||
|  |             element.text.isNotEmpty | ||||||
|  |                 ? element.text | ||||||
|  |                 : (element.attributes['href'] ?? '').split('/').last)) | ||||||
|  |         .where((element) => element.key.isNotEmpty) | ||||||
|         .toList(); |         .toList(); | ||||||
|     if (allLinks.isEmpty) { |     if (allLinks.isEmpty) { | ||||||
|       allLinks = RegExp( |       allLinks = RegExp( | ||||||
|               r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?') |               r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?') | ||||||
|           .allMatches(res.body) |           .allMatches(res.body) | ||||||
|             .map((match) => match.group(0)!) |           .map((match) => | ||||||
|  |               MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? '')) | ||||||
|           .toList(); |           .toList(); | ||||||
|     } |     } | ||||||
|       List<String> links = []; |     List<MapEntry<String, String>> links = []; | ||||||
|     bool skipSort = additionalSettings['skipSort'] == true; |     bool skipSort = additionalSettings['skipSort'] == true; | ||||||
|       if ((additionalSettings['intermediateLinkRegex'] as String?) |     bool filterLinkByText = additionalSettings['filterByLinkText'] == true; | ||||||
|               ?.isNotEmpty == |     if ((additionalSettings['customLinkFilterRegex'] 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) { |         true) { | ||||||
|       var reg = RegExp(additionalSettings['customLinkFilterRegex']); |       var reg = RegExp(additionalSettings['customLinkFilterRegex']); | ||||||
|         links = allLinks.where((element) => reg.hasMatch(element)).toList(); |       links = allLinks | ||||||
|  |           .where((element) => | ||||||
|  |               reg.hasMatch(filterLinkByText ? element.value : element.key)) | ||||||
|  |           .toList(); | ||||||
|     } else { |     } else { | ||||||
|       links = allLinks |       links = allLinks | ||||||
|           .where((element) => |           .where((element) => | ||||||
|                 Uri.parse(element).path.toLowerCase().endsWith('.apk')) |               Uri.parse(filterLinkByText ? element.value : element.key) | ||||||
|  |                   .path | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .endsWith('.apk')) | ||||||
|           .toList(); |           .toList(); | ||||||
|     } |     } | ||||||
|     if (!skipSort) { |     if (!skipSort) { | ||||||
|         links.sort((a, b) => |       links.sort((a, b) => additionalSettings['sortByLastLinkSegment'] == true | ||||||
|             additionalSettings['sortByFileNamesNotLinks'] == true |  | ||||||
|           ? compareAlphaNumeric( |           ? compareAlphaNumeric( | ||||||
|                     a.split('/').where((e) => e.isNotEmpty).last, |               a.key.split('/').where((e) => e.isNotEmpty).last, | ||||||
|                     b.split('/').where((e) => e.isNotEmpty).last) |               b.key.split('/').where((e) => e.isNotEmpty).last) | ||||||
|                 : compareAlphaNumeric(a, b)); |           : compareAlphaNumeric(a.key, b.key)); | ||||||
|     } |     } | ||||||
|     if (additionalSettings['reverseSort'] == true) { |     if (additionalSettings['reverseSort'] == true) { | ||||||
|       links = links.reversed.toList(); |       links = links.reversed.toList(); | ||||||
|     } |     } | ||||||
|       if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == |     return links; | ||||||
|           true) { |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<APKDetails> getLatestAPKDetails( | ||||||
|  |     String standardUrl, | ||||||
|  |     Map<String, dynamic> additionalSettings, | ||||||
|  |   ) async { | ||||||
|  |     var currentUrl = standardUrl; | ||||||
|  |     for (int i = 0; | ||||||
|  |         i < (additionalSettings['intermediateLink']?.length ?? 0); | ||||||
|  |         i++) { | ||||||
|  |       var intLinks = await grabLinksCommon(await sourceRequest(currentUrl), | ||||||
|  |           additionalSettings['intermediateLink'][i]); | ||||||
|  |       if (intLinks.isEmpty) { | ||||||
|  |         throw NoReleasesError(); | ||||||
|  |       } else { | ||||||
|  |         currentUrl = intLinks.last.key; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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']); |       var reg = RegExp(additionalSettings['apkFilterRegEx']); | ||||||
|         links = links.where((element) => reg.hasMatch(element)).toList(); |       links = links.where((element) => reg.hasMatch(element.key)).toList(); | ||||||
|     } |     } | ||||||
|     if (links.isEmpty) { |     if (links.isEmpty) { | ||||||
|       throw NoReleasesError(); |       throw NoReleasesError(); | ||||||
|     } |     } | ||||||
|       var rel = links.last; |     var rel = links.last.key; | ||||||
|     String? version; |     String? version; | ||||||
|     if (additionalSettings['supportFixedAPKURL'] != true) { |     if (additionalSettings['supportFixedAPKURL'] != true) { | ||||||
|       version = rel.hashCode.toString(); |       version = rel.hashCode.toString(); | ||||||
| @@ -263,8 +294,5 @@ class HTML extends AppSource { | |||||||
|     version ??= (await checkDownloadHash(rel)).toString(); |     version ??= (await checkDownloadHash(rel)).toString(); | ||||||
|     return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(), |     return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(), | ||||||
|         AppNames(uri.host, tr('app'))); |         AppNames(uri.host, tr('app'))); | ||||||
|     } else { |  | ||||||
|       throw getObtainiumHttpError(res); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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,27 +147,38 @@ 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,20 +363,16 @@ 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, |  | ||||||
|                                               MapEntry<int, bool>>)[key] = |  | ||||||
|                                           MapEntry( |  | ||||||
|                                               (values[widget.items[r][e].key] |  | ||||||
|                                                       as Map< |  | ||||||
|                                           String, |                                           String, | ||||||
|                                           MapEntry<int, |                                           MapEntry<int, | ||||||
|                                                               bool>>)[key]! |                                               bool>>)[key] = MapEntry( | ||||||
|  |                                           (values[fieldKey] as Map<String, | ||||||
|  |                                                   MapEntry<int, bool>>)[key]! | ||||||
|                                               .key, |                                               .key, | ||||||
|                                           false); |                                           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); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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") { | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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'))) |                 //   ]), | ||||||
|                         ], |                 // ), | ||||||
|                       ), |  | ||||||
|                   ]), |  | ||||||
|                 ), |  | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|           ) |           ) | ||||||
|   | |||||||
| @@ -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,28 +1563,33 @@ 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 { | ||||||
|  |             if (err is! RateLimitError) { | ||||||
|               toThrow.add(key, err, appName: errors?.appIdNames[key]); |               toThrow.add(key, err, appName: errors?.appIdNames[key]); | ||||||
|             } |             } | ||||||
|  |           } | ||||||
|         }); |         }); | ||||||
|       } else { |       } else { | ||||||
|         // We don't expect to ever get here in any situation so no need to catch (but log it in case) |         // We don't expect to ever get here in any situation so no need to catch (but log it in case) | ||||||
| @@ -1624,14 +1623,12 @@ 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, |  | ||||||
|           params: { |  | ||||||
|         'toCheck': toRetry |         'toCheck': toRetry | ||||||
|             .map((entry) => {'key': entry.key, 'value': entry.value}) |             .map((entry) => {'key': entry.key, 'value': entry.value}) | ||||||
|             .toList(), |             .toList(), | ||||||
| @@ -1640,12 +1637,9 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async { | |||||||
|             .toList(), |             .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( |  | ||||||
|           const Duration(minutes: 0), taskId + 1, bgUpdateCheck, |  | ||||||
|           params: { |  | ||||||
|         'toCheck': [], |         'toCheck': [], | ||||||
|         'toInstall': toInstall |         'toInstall': toInstall | ||||||
|             .map((entry) => {'key': entry.key, 'value': entry.value}) |             .map((entry) => {'key': entry.key, 'value': entry.value}) | ||||||
| @@ -1654,7 +1648,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async { | |||||||
|     } |     } | ||||||
|   } 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,7 +1658,8 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     var didCompleteInstalling = false; |     if (toInstall.isNotEmpty) { | ||||||
|  |       logs.add('BG install task: Started (${toInstall.length}).'); | ||||||
|       var tempObtArr = toInstall.where((element) => element.key == obtainiumId); |       var tempObtArr = toInstall.where((element) => element.key == obtainiumId); | ||||||
|       if (tempObtArr.isNotEmpty) { |       if (tempObtArr.isNotEmpty) { | ||||||
|         // Move obtainium to the end of the list as it must always install last |         // Move obtainium to the end of the list as it must always install last | ||||||
| @@ -1672,52 +1667,25 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async { | |||||||
|         toInstall = moveStrToEndMapEntryWithCount(toInstall, obt); |         toInstall = moveStrToEndMapEntryWithCount(toInstall, obt); | ||||||
|       } |       } | ||||||
|       // Loop through all updates and install each |       // Loop through all updates and install each | ||||||
|     for (var i = 0; i < toInstall.length; i++) { |  | ||||||
|       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(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -135,11 +135,35 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) { | |||||||
|   if (additionalSettings['autoApkFilterByArch'] == null) { |   if (additionalSettings['autoApkFilterByArch'] == null) { | ||||||
|     additionalSettings['autoApkFilterByArch'] = false; |     additionalSettings['autoApkFilterByArch'] = false; | ||||||
|   } |   } | ||||||
|  |   if (source.runtimeType == HTML().runtimeType) { | ||||||
|     // HTML 'fixed URL' support should be disabled if it previously did not exist |     // HTML 'fixed URL' support should be disabled if it previously did not exist | ||||||
|   if (source.runtimeType == HTML().runtimeType && |     if (originalAdditionalSettings['supportFixedAPKURL'] == null) { | ||||||
|       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 | ||||||
|   // This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid) |   // This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid) | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -1,14 +1,6 @@ | |||||||
| # Generated by pub | # Generated by pub | ||||||
| # See https://dart.dev/tools/pub/glossary#lockfile | # See https://dart.dev/tools/pub/glossary#lockfile | ||||||
| packages: | packages: | ||||||
|   android_alarm_manager_plus: |  | ||||||
|     dependency: "direct main" |  | ||||||
|     description: |  | ||||||
|       name: android_alarm_manager_plus |  | ||||||
|       sha256: "84720c8ad2758aabfbeafd24a8c355d8c8dd3aa52b01eaf3bb827c7210f61a91" |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "3.0.4" |  | ||||||
|   android_intent_plus: |   android_intent_plus: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -74,6 +66,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.11.0" |     version: "2.11.0" | ||||||
|  |   background_fetch: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: background_fetch | ||||||
|  |       sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4 | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.2.1" | ||||||
|   boolean_selector: |   boolean_selector: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -299,10 +299,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_local_notifications |       name: flutter_local_notifications | ||||||
|       sha256: bb5cd63ff7c91d6efe452e41d0d0ae6348925c82eafd10ce170ef585ea04776e |       sha256: "892ada16046d641263f30c72e7432397088810a84f34479f6677494802a2b535" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "16.2.0" |     version: "16.3.0" | ||||||
|   flutter_local_notifications_linux: |   flutter_local_notifications_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -514,10 +514,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: path_provider_android |       name: path_provider_android | ||||||
|       sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 |       sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.2.1" |     version: "2.2.2" | ||||||
|   path_provider_foundation: |   path_provider_foundation: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | |||||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||||
| # In Windows, build-name is used as the major, minor, and patch parts | # In Windows, build-name is used as the major, minor, and patch parts | ||||||
| # of the product and file versions while build-number is used as the build suffix. | # of the product and file versions while build-number is used as the build suffix. | ||||||
| version: 0.14.41+235 # When changing this, update the tag in main() accordingly | version: 0.15.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: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user