Compare commits

...

63 Commits

Author SHA1 Message Date
Imran
8c2a97e092 Merge pull request #1544 from ImranR98/dev
- Don't trust content-length header from sites w/o accept-ranges support (#1542) + bugfixes
- Allow for spaces in HTML link and version filters (#1537)
2024-04-15 19:16:10 -04:00
Imran Remtulla
34d571f586 Merge remote-tracking branch 'origin/main' into dev 2024-04-15 19:14:47 -04:00
Imran Remtulla
a20b87889b Update packages, increment version 2024-04-15 19:14:39 -04:00
Imran
c7319cd958 Merge pull request #1540 from o101010/main
Corrects french translations
2024-04-15 19:09:50 -04:00
Imran
d61fd800ef Merge pull request #1543 from yurtpage/main
fastlane: i18n ru for app description
2024-04-15 19:09:26 -04:00
Imran Remtulla
12dda8bfa9 Allow for spaces in HTML link and version filters (#1537) 2024-04-15 19:06:24 -04:00
Imran Remtulla
0657f832e1 Don't trust content-length header from sites w/o accept-ranges support (#1542) + bugfixes 2024-04-15 18:39:21 -04:00
Yurt Page
6431357b15 ru.json: translate apk.other 2024-04-16 01:16:08 +03:00
Yurt Page
dcc42bdfe5 fastlane: i18n ru for app description 2024-04-16 01:09:18 +03:00
Imran
85718dc3a3 Update README.md 2024-04-15 17:53:55 -04:00
Imran
9dff352796 Update README.md 2024-04-15 17:52:35 -04:00
Imran
c5477767a0 Update README.md 2024-04-15 17:52:08 -04:00
o101010
4d9f05aa87 Corrects french translations
Corrects french translations in fr.json.
2024-04-14 20:52:51 +02:00
Imran
d1a2831922 Merge pull request #1535 from ImranR98/dev
Update packages, increment version
2024-04-13 03:09:10 -04:00
Imran Remtulla
b042050ea3 Update packages, increment version 2024-04-13 03:05:44 -04:00
Imran
2d43dfe0a7 Merge pull request #1525 from gidano/main
Update hu.json
2024-04-13 03:04:13 -04:00
Imran
b1a740223c Merge pull request #1529 from DwainZwerg/patch-14
Update de.json
2024-04-13 03:04:02 -04:00
Imran
41c98d97b1 Merge pull request #1532 from gepbird/fix-generated-form-update
Fix out of bounds array access in GeneratedForm update
2024-04-13 03:03:47 -04:00
Gutyina Gergő
ccc0e7696b Fix out of bounds array access in GeneratedForm update 2024-04-08 21:45:19 +02:00
DwainZwerg
6d41ed8011 Update de.json
Corrected translation
2024-04-08 08:44:28 +02:00
gidano
c1476a7d58 Update hu.json 2024-04-07 09:37:28 +02:00
Imran
b1ee39be3b Merge pull request #1524 from ImranR98/dev
Add release asset download button (#1493) + Minor UI change
2024-04-07 02:25:27 -04:00
Imran Remtulla
c2ccdd3d05 Merge remote-tracking branch 'origin/main' into dev 2024-04-07 02:21:45 -04:00
Imran Remtulla
a621f1dfb4 Update packages, increment version 2024-04-07 02:21:38 -04:00
Imran Remtulla
8527ad0007 Bugfix in release asset download 2024-04-07 02:19:40 -04:00
Imran Remtulla
377ecef1b4 More resilient release asset import (if Downloads inaccessible, use export dir) 2024-04-07 02:18:26 -04:00
Imran Remtulla
1f9921e6ff UI improvements 2024-04-07 02:13:36 -04:00
Imran Remtulla
00988ed04d Improve release asset download UI 2024-04-07 01:41:35 -04:00
Imran Remtulla
3d1113c057 Add release asset download button (#1493) 2024-04-07 01:28:45 -04:00
Imran
1c81f0c1e1 Merge pull request #1516 from ImranR98/dev
* Add support for GitLab CI artifact links in releases (#1506)
* Improve load speed on return to foreground
2024-04-03 18:15:44 -04:00
Imran Remtulla
b26043ef5e Merge remote-tracking branch 'origin/main' into dev 2024-04-03 18:14:04 -04:00
Imran
e3ad144c06 Merge pull request #1503 from ngocanhtve/main
Update vi.json
2024-04-03 18:13:53 -04:00
Imran Remtulla
fdb45c48ce Upgrade packages, increment version 2024-04-03 18:12:42 -04:00
Imran Remtulla
8619cfa819 Add support for GitLab CI artifact links in releases (#1506) 2024-04-02 14:07:32 -04:00
Imran Remtulla
1fe4cdd648 Improve load speed on return to foreground 2024-04-02 13:45:32 -04:00
ngocanhtve
ce22197ec9 Update vi.json
Complete.
2024-03-29 12:07:13 +07:00
Imran
f3d2dfe386 Update release.yml 2024-03-29 00:23:06 -04:00
Imran
677790af9c Update release.yml 2024-03-29 00:22:43 -04:00
Imran
85941349d8 Update release.yml 2024-03-29 00:17:35 -04:00
Imran
db755e0f90 Update release.yml 2024-03-29 00:14:26 -04:00
Imran
1019a01249 Update release.yml 2024-03-29 00:02:11 -04:00
Imran
35da44bc64 Update release.yml 2024-03-29 00:00:53 -04:00
Imran
b7ce07f965 Merge pull request #1502 from ImranR98/dev
- Added 'Share new Apps with AppVerifier' (#255)
- **Removed SourceForge (#1487)**
- Delete downloaded APK on install fail (#1495)
- Fix GitLab's broken track-only mode (#1496)
- Auto-delete download if likely invalid (#1498)
2024-03-28 23:43:25 -04:00
Imran Remtulla
f2c15c5c8e Update packages and Flutter, increment version 2024-03-28 23:40:36 -04:00
Imran Remtulla
8f0a6b7635 Merge remote-tracking branch 'origin/main' into dev 2024-03-28 23:40:11 -04:00
Imran Remtulla
491c42d68b Removed SourceForge (#1487) 2024-03-28 23:27:22 -04:00
Imran Remtulla
a883857230 Auto-delete download if likely invalid (#1498) 2024-03-28 23:13:45 -04:00
Imran Remtulla
6468d0edcc Fix GitLab's broken track-only mode (#1496) 2024-03-28 23:05:16 -04:00
Imran Remtulla
48eeeb4f00 Merge remote-tracking branch 'origin/main' into dev 2024-03-28 22:29:52 -04:00
Imran
0b25f25669 Merge pull request #1486 from fitojb/patch-1
Fixes to Spanish translation
2024-03-28 22:29:45 -04:00
Imran Remtulla
d76b7375cb Added 'Share new Apps with AppVerifier' (#255) 2024-03-28 22:28:55 -04:00
Imran Remtulla
f76aa51b54 Delete downloaded APK on install fail (#1495) 2024-03-23 19:26:39 -04:00
Adolfo Jayme-Barrientos
9e19a293d4 Fixes to Spanish translation
Includes assorted typography and treatment fixes.
2024-03-18 17:52:08 -06:00
Imran
381758550c Merge pull request #1478 from ImranR98/dev
Resume failed downloads when possible instead of starting again (#634), Typo: 'Installed' not 'Updated' (#1469) + Bugfix: Don't incorrectly show message when user cancel, Add changelog button to app page (#1474)
2024-03-17 05:15:46 -04:00
Imran Remtulla
da496f7c53 GitHub actions: Don't add '-beta' to beta tags (pre-release mark is enough) 2024-03-17 05:14:08 -04:00
Imran Remtulla
f905aa7dfd Made required changes according to https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply 2024-03-17 05:12:31 -04:00
Imran Remtulla
0db27c773b Upgrade packages, increment version 2024-03-17 04:52:17 -04:00
Imran Remtulla
4e968826ba Add changelog button to app page (#1474) 2024-03-17 04:15:43 -04:00
Imran Remtulla
e95f575530 Typo: 'Installed' not 'Updated' (#1469) + Bugfix: Don't incorrectly show message when user cancels 2024-03-17 04:01:09 -04:00
Imran Remtulla
3943caeedb Resume failed downloads when possible (#634) 2024-03-17 03:04:05 -04:00
Imran
4495051813 Merge pull request #1464 from dik08razz/main
Update uk.json
2024-03-10 14:40:28 -04:00
dik08razz
558d23b8c0 Update uk.json 2024-03-09 23:28:12 +02:00
dik08razz
2e90d1fb18 Update uk.json 2024-03-09 22:37:27 +02:00
41 changed files with 1145 additions and 657 deletions

View File

@@ -15,7 +15,16 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
- uses: actions/setup-java@v4
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Flutter Doctor
id: flutter_doctor
run: |
flutter doctor -v
- name: Import GPG key - name: Import GPG key
id: import_pgp_key id: import_pgp_key
uses: crazy-max/ghaction-import-gpg@v6 uses: crazy-max/ghaction-import-gpg@v6
@@ -42,7 +51,6 @@ jobs:
if [ ${{ inputs.beta }} == true ]; then BETA=true; else BETA=false; fi if [ ${{ inputs.beta }} == true ]; then BETA=true; else BETA=false; fi
echo "beta=$BETA" >> $GITHUB_OUTPUT echo "beta=$BETA" >> $GITHUB_OUTPUT
TAG="v$VERSION" TAG="v$VERSION"
if [ $BETA == true ]; then TAG="$TAG"-beta; fi
echo "tag=$TAG" >> $GITHUB_OUTPUT echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Build APKs - name: Build APKs

View File

@@ -4,11 +4,13 @@
Get Android App Updates Directly From the Source. Get Android App Updates Directly From the Source.
Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available. Obtainium allows you to install and update apps directly from their releases pages, and receive notifications when new releases are made available.
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0) More info:
- [Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
Read the Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki) - [AppVerifier](https://github.com/soupslurpr/AppVerifier) - App verification tool (recommended, integrates with Obtainium)
- [apps.obtainium.imranr.dev](https://apps.obtainium.imranr.dev/) - Crowdsourced app configurations
- [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0) - Original motivation for this app
Currently supported App sources: Currently supported App sources:
- Open Source - General: - Open Source - General:
@@ -18,7 +20,6 @@ Currently supported App sources:
- [F-Droid](https://f-droid.org/) - [F-Droid](https://f-droid.org/)
- Third Party F-Droid Repos - Third Party F-Droid Repos
- [IzzyOnDroid](https://android.izzysoft.de/) - [IzzyOnDroid](https://android.izzysoft.de/)
- [SourceForge](https://sourceforge.net/)
- [SourceHut](https://git.sr.ht/) - [SourceHut](https://git.sr.ht/)
- Other - General: - Other - General:
- [APKPure](https://apkpure.net/) - [APKPure](https://apkpure.net/)

View File

@@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@@ -21,11 +22,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'dev.rikka.tools.refine'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
@@ -33,7 +29,8 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion namespace "dev.imranr.obtainium"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@@ -54,7 +51,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 rootProject.ext.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }

View File

@@ -1,22 +1,3 @@
buildscript {
ext.kotlin_version = '1.8.10'
ext {
compileSdkVersion = 34 // or latest
targetSdkVersion = 34 // or latest
appCompatVersion = "1.4.2" // or latest
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.4.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "dev.rikka.tools.refine:gradle-plugin:4.3.1"
}
}
allprojects { allprojects {
repositories { repositories {
google() google()

View File

@@ -1,11 +1,25 @@
include ':app' pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
def properties = new Properties()
assert localPropertiesFile.exists() repositories {
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk") plugins {
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" id "com.android.application" version "7.4.2" apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
}
include ":app"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Onemogući detekciju verzije", "disableVersionDetection": "Onemogući detekciju verzije",
"noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.", "noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.",
"downloadingX": "Preuzimanje {}", "downloadingX": "Preuzimanje {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije", "downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije",
"noAPKFound": "APK nije pronađen", "noAPKFound": "APK nije pronađen",
"noVersionDetection": "Nema detekcije verzije", "noVersionDetection": "Nema detekcije verzije",
@@ -299,6 +302,8 @@
"note": "Note", "note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "The APK could not be parsed (incompatible or partial download)",
"beforeNewInstallsShareToAppVerifier": "Share new Apps with AppVerifier (if available)",
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Želite li ukloniti aplikaciju?", "one": "Želite li ukloniti aplikaciju?",
"other": "Želite li ukloniti aplikacije?" "other": "Želite li ukloniti aplikacije?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Deaktivovat detekci verze", "disableVersionDetection": "Deaktivovat detekci verze",
"noVersionDetectionExplanation": "Tato možnost by měla být použita pouze u aplikace, 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": "Stáhnout {}", "downloadingX": "Stáhnout {}",
"downloadX": "Stáhnout {}",
"downloadedX": "Staženo {}",
"releaseAsset": "Vydání aktiva",
"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",
@@ -218,7 +221,7 @@
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'", "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", "gitlabPATLabel": "Osobní přístupový token GitLab",
"about": "O", "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í",
@@ -299,6 +302,8 @@
"note": "Poznámka", "note": "Poznámka",
"selfHostedNote": "Rozbalovací seznam \"{}\" lze použít k dosažení vlastních/obvyklých instancí libovolného zdroje.", "selfHostedNote": "Rozbalovací seznam \"{}\" lze použít k dosažení vlastních/obvyklých instancí libovolného zdroje.",
"badDownload": "APK nelze analyzovat (nekompatibilní nebo částečné stažení)", "badDownload": "APK nelze analyzovat (nekompatibilní nebo částečné stažení)",
"beforeNewInstallsShareToAppVerifier": "Sdílení nových aplikací s aplikací AppVerifier (pokud je k dispozici)",
"appVerifierInstructionToast": "Sdílejte do aplikace AppVerifier a po dokončení se sem vraťte.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Odstranit Apku?", "one": "Odstranit Apku?",
"other": "Odstranit Apky?" "other": "Odstranit Apky?"

View File

@@ -19,7 +19,7 @@
"noDescription": "Keine Beschreibung", "noDescription": "Keine Beschreibung",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"continue": "Weiter", "continue": "Weiter",
"requiredInBrackets": "(Benötigt)", "requiredInBrackets": "(wird benötigt)",
"dropdownNoOptsError": "FEHLER: DROPDOWN MUSS MINDESTENS EINE OPTION HABEN", "dropdownNoOptsError": "FEHLER: DROPDOWN MUSS MINDESTENS EINE OPTION HABEN",
"colour": "Farbe", "colour": "Farbe",
"githubStarredRepos": "GitHub Starred Repos", "githubStarredRepos": "GitHub Starred Repos",
@@ -183,6 +183,9 @@
"disableVersionDetection": "Versionsermittlung deaktivieren", "disableVersionDetection": "Versionsermittlung deaktivieren",
"noVersionDetectionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert.", "noVersionDetectionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert.",
"downloadingX": "Lade {} herunter", "downloadingX": "Lade {} herunter",
"downloadX": "{} herunterladen",
"downloadedX": "{} heruntergeladen",
"releaseAsset": "release Asset",
"downloadNotifDescription": "Benachrichtigt den Nutzer über den Fortschritt beim Herunterladen einer App", "downloadNotifDescription": "Benachrichtigt den Nutzer über den Fortschritt beim Herunterladen einer App",
"noAPKFound": "Keine APK gefunden", "noAPKFound": "Keine APK gefunden",
"noVersionDetection": "Keine Versionserkennung", "noVersionDetection": "Keine Versionserkennung",
@@ -299,6 +302,8 @@
"note": "Hinweis", "note": "Hinweis",
"selfHostedNote": "Das „{}“-Dropdown-Menü kann verwendet werden, um selbst gehostete/angepasste Instanzen einer beliebigen Quelle zu erreichen.", "selfHostedNote": "Das „{}“-Dropdown-Menü kann verwendet werden, um selbst gehostete/angepasste Instanzen einer beliebigen Quelle zu erreichen.",
"badDownload": "Die APK konnte nicht geparst werden (inkompatibler oder teilweiser Download)", "badDownload": "Die APK konnte nicht geparst werden (inkompatibler oder teilweiser Download)",
"beforeNewInstallsShareToAppVerifier": "Neue Apps mit AppVerifier teilen (falls verfügbar)",
"appVerifierInstructionToast": "Geben Sie die Daten an AppVerifier weiter und kehren Sie dann hierher zurück, wenn Sie fertig sind.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "Apps entfernen?" "other": "Apps entfernen?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Disable Version Detection", "disableVersionDetection": "Disable Version Detection",
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
"downloadingX": "Downloading {}", "downloadingX": "Downloading {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Notifies the user of the progress in downloading an App", "downloadNotifDescription": "Notifies the user of the progress in downloading an App",
"noAPKFound": "No APK found", "noAPKFound": "No APK found",
"noVersionDetection": "No version detection", "noVersionDetection": "No version detection",
@@ -299,6 +302,8 @@
"note": "Note", "note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "The APK could not be parsed (incompatible or partial download)",
"beforeNewInstallsShareToAppVerifier": "Share new Apps with AppVerifier (if available)",
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "other": "Remove Apps?"

View File

@@ -1,15 +1,15 @@
{ {
"invalidURLForSource": "URL de la aplicación {} no es válida", "invalidURLForSource": "El URL de la aplicación {} no es válido",
"noReleaseFound": "No se ha podido encontrar una versión válida", "noReleaseFound": "No se ha podido encontrar una versión válida",
"noVersionFound": "No se ha podido determinar la versión", "noVersionFound": "No se ha podido determinar la versión",
"urlMatchesNoSource": "La URL no coincide con ninguna fuente conocida", "urlMatchesNoSource": "El URL no coincide con ninguna fuente conocida",
"cantInstallOlderVersion": "No se puede instalar una versión previa de la aplicación", "cantInstallOlderVersion": "No se puede instalar una versión previa de la aplicación",
"appIdMismatch": "La ID del paquete descargado no coincide con la ID de la aplicación instalada", "appIdMismatch": "El id. del paquete descargado no coincide con la ID de la aplicación instalada",
"functionNotImplemented": "Esta clase no ha implementado esta función", "functionNotImplemented": "Esta clase no ha implementado esta función",
"placeholder": "Espacio reservado", "placeholder": "Espacio reservado",
"someErrors": "Han ocurrido algunos errores", "someErrors": "Han ocurrido algunos errores",
"unexpectedError": "Error inesperado", "unexpectedError": "Error inesperado",
"ok": "OK", "ok": "Aceptar",
"and": "y", "and": "y",
"githubPATLabel": "Token de acceso personal a GitHub\n(reduce tiempos de espera)", "githubPATLabel": "Token de acceso personal a GitHub\n(reduce tiempos de espera)",
"includePrereleases": "Incluir versiones preliminares", "includePrereleases": "Incluir versiones preliminares",
@@ -34,75 +34,75 @@
"cancelled": "Cancelado", "cancelled": "Cancelado",
"appAlreadyAdded": "Aplicación añadida anteriormente", "appAlreadyAdded": "Aplicación añadida anteriormente",
"alreadyUpToDateQuestion": "¿Aplicación actualizada previamente?", "alreadyUpToDateQuestion": "¿Aplicación actualizada previamente?",
"addApp": "Añadir Aplicación", "addApp": "Añadir aplicación",
"appSourceURL": "URL de origen de la aplicación", "appSourceURL": "URL de origen de la aplicación",
"error": "Error", "error": "Error",
"add": "Añadir", "add": "Añadir",
"searchSomeSourcesLabel": "Buscar (solo algunas fuentes)", "searchSomeSourcesLabel": "Buscar (solo algunas fuentes)",
"search": "Buscar", "search": "Buscar",
"additionalOptsFor": "Opciones Adicionales para {}", "additionalOptsFor": "Opciones adicionales para {}",
"supportedSources": "Fuentes Soportadas", "supportedSources": "Fuentes admitidas",
"trackOnlyInBrackets": "(Solo seguimiento)", "trackOnlyInBrackets": "(Solo seguimiento)",
"searchableInBrackets": "(soporta búsqueda)", "searchableInBrackets": "(permite 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": "Espere un momento",
"updateAvailable": "Actualización Disponible", "updateAvailable": "Actualización disponible",
"notInstalled": "No Instalado", "notInstalled": "No instalado",
"pseudoVersion": "pseudoversión", "pseudoVersion": "pseudoversión",
"selectAll": "Seleccionar Todo", "selectAll": "Seleccionar todo",
"deselectX": "Deseleccionar {}", "deselectX": "Deseleccionar {}",
"xWillBeRemovedButRemainInstalled": "{} se eliminada de Obtainium pero continuará instalada en el dispositivo.", "xWillBeRemovedButRemainInstalled": "{} se elimina de Obtainium pero continuará instalada en el dispositivo.",
"removeSelectedAppsQuestion": "¿Eliminar aplicaciones seleccionadas?", "removeSelectedAppsQuestion": "¿Eliminar aplicaciones seleccionadas?",
"removeSelectedApps": "Eliminar Aplicaciones Seleccionadas", "removeSelectedApps": "Eliminar aplicaciones seleccionadas",
"updateX": "Actualizar {}", "updateX": "Actualizar {}",
"installX": "Instalar {}", "installX": "Instalar {}",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo seguimiento)\ncomo actualizada", "markXTrackOnlyAsUpdated": "Marcar {}\n(Solo seguimiento)\ncomo actualizada",
"changeX": "Cambiar {}", "changeX": "Cambiar {}",
"installUpdateApps": "Instalar/Actualizar aplicaciones", "installUpdateApps": "Instalar/actualizar aplicaciones",
"installUpdateSelectedApps": "Instalar/Actualizar aplicaciones seleccionadas", "installUpdateSelectedApps": "Instalar/actualizar aplicaciones seleccionadas",
"markXSelectedAppsAsUpdated": "¿Marcar {} aplicaciones seleccionadas como actualizadas?", "markXSelectedAppsAsUpdated": "¿Marcar {} aplicaciones seleccionadas como actualizadas?",
"no": "No", "no": "No",
"yes": "Sí", "yes": "Sí",
"markSelectedAppsUpdated": "Marcar aplicaciones seleccionadas como actualizadas", "markSelectedAppsUpdated": "Marcar aplicaciones seleccionadas como actualizadas",
"pinToTop": "Fijar arriba", "pinToTop": "Anclar al principio",
"unpinFromTop": "Desfijar de arriba", "unpinFromTop": "Desanclar del principio",
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar estado de instalación para las aplicaciones seleccionadas?", "resetInstallStatusForSelectedAppsQuestion": "¿Restuarar estado de instalación para las aplicaciones seleccionadas?",
"installStatusOfXWillBeResetExplanation": "Se restaurará el estado de instalación de las aplicaciones seleccionadas.\n\nEsto puede ser de útil cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.", "installStatusOfXWillBeResetExplanation": "Se restaurará el estado de instalación de las aplicaciones seleccionadas.\n\nEsto puede ser de útil cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
"customLinkMessage": "Estos enlaces funcionan en dispositivos con Obtainium instalado", "customLinkMessage": "Estos enlaces funcionan en dispositivos con Obtainium instalado",
"shareAppConfigLinks": "Compartir la configuración de la aplicación como enlace HTML", "shareAppConfigLinks": "Compartir la configuración de la aplicación como enlace HTML",
"shareSelectedAppURLs": "Compartir URLs de las aplicaciones seleccionadas", "shareSelectedAppURLs": "Compartir URL de las aplicaciones seleccionadas",
"resetInstallStatus": "Restaurar estado de instalación", "resetInstallStatus": "Restaurar estado de instalación",
"more": "Más", "more": "Más",
"removeOutdatedFilter": "Elimiar filtro de aplicaciones desactualizado", "removeOutdatedFilter": "Eliminar filtro de aplicaciones desactualizado",
"showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas", "showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas",
"filter": "Filtrar", "filter": "Filtrar",
"filterApps": "Filtrar Actualizaciones", "filterApps": "Filtrar actualizaciones",
"appName": "Nombre de la aplicación", "appName": "Nombre de la aplicación",
"author": "Autor", "author": "Autor",
"upToDateApps": "Aplicaciones Actualizadas", "upToDateApps": "Aplicaciones actualizadas",
"nonInstalledApps": "Aplicaciones No Instaladas", "nonInstalledApps": "Aplicaciones no instaladas",
"importExport": "Importar/Exportar", "importExport": "Importar/exportar",
"settings": "Ajustes", "settings": "Ajustes",
"exportedTo": "Exportado a {}", "exportedTo": "Exportado a {}",
"obtainiumExport": "Exportar Obtainium", "obtainiumExport": "Exportar Obtainium",
"invalidInput": "Input incorrecto", "invalidInput": "Entrada no válida",
"importedX": "Importado {}", "importedX": "Importado {}",
"obtainiumImport": "Importar Obtainium", "obtainiumImport": "Importar Obtainium",
"importFromURLList": "Importar desde lista de URLs", "importFromURLList": "Importar desde lista de URL",
"searchQuery": "Consulta de Búsqueda", "searchQuery": "Término de búsqueda",
"appURLList": "Lista de URLs de Aplicaciones", "appURLList": "Lista de URL de aplicaciones",
"line": "Línea", "line": "Línea",
"searchX": "Buscar {}", "searchX": "Buscar {}",
"noResults": "Resultados no encontrados", "noResults": "No se encontró ningún resultado",
"importX": "Importar desde {}", "importX": "Importar desde {}",
"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álelas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a los URL 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 han tenido problemas:", "followingURLsHadErrors": "Los URL siguientes han tenido problemas:",
"selectURL": "Seleccionar URL", "selectURL": "Seleccionar URL",
"selectURLs": "Seleccionar URLs", "selectURLs": "Seleccionar URLs",
"pick": "Escoger", "pick": "Escoger",
@@ -110,9 +110,9 @@
"dark": "Oscuro", "dark": "Oscuro",
"light": "Claro", "light": "Claro",
"followSystem": "Seguir al sistema", "followSystem": "Seguir al sistema",
"useBlackTheme": "Negro puro en tema Oscuro", "useBlackTheme": "Negro puro en tema oscuro",
"appSortBy": "Ordenar Apps Por", "appSortBy": "Ordenar aplicaciones por",
"authorName": "Autor/Nombre", "authorName": "Autor/nombre",
"nameAuthor": "Nombre/Autor", "nameAuthor": "Nombre/Autor",
"asAdded": "Según se Añadieron", "asAdded": "Según se Añadieron",
"appSortOrder": "Orden de Clasificación", "appSortOrder": "Orden de Clasificación",
@@ -122,22 +122,22 @@
"neverManualOnly": "Nunca, solo manual", "neverManualOnly": "Nunca, solo manual",
"appearance": "Apariencia", "appearance": "Apariencia",
"showWebInAppView": "Mostrar vista de la web de origen", "showWebInAppView": "Mostrar vista de la web de origen",
"pinUpdates": "Fijar actualizaciones al principio", "pinUpdates": "Anclar actualizaciones al principio",
"updates": "Actualizaciones", "updates": "Actualizaciones",
"sourceSpecific": "Fuente Específica", "sourceSpecific": "Fuente específica",
"appSource": "Obtainium en GitHub", "appSource": "Obtainium en GitHub",
"noLogs": "Sin Logs", "noLogs": "Ningún registro",
"appLogs": "Logs", "appLogs": "Registros",
"close": "Cerrar", "close": "Cerrar",
"share": "Compartir", "share": "Compartir",
"appNotFound": "Aplicación no encontrada", "appNotFound": "Aplicación no encontrada",
"obtainiumExportHyphenatedLowercase": "obtainium-exportación", "obtainiumExportHyphenatedLowercase": "obtainium-exportación",
"pickAnAPK": "Seleccione una APK", "pickAnAPK": "Seleccione una APK",
"appHasMoreThanOnePackage": "{} tiene más de un paquete:", "appHasMoreThanOnePackage": "{} tiene más de un paquete:",
"deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.", "deviceSupportsXArch": "Su dispositivo admite las siguientes arquitecturas de procesador: {}.",
"deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:", "deviceSupportsFollowingArchs": "Su dispositivo admite 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",
"updatesAvailableNotifDescription": "Notifica al usuario de que hay actualizaciones para una o más aplicaciones monitoreadas por Obtainium", "updatesAvailableNotifDescription": "Notifica al usuario de que hay actualizaciones para una o más aplicaciones monitoreadas por Obtainium",
"noNewUpdates": "No hay nuevas actualizaciones.", "noNewUpdates": "No hay nuevas actualizaciones.",
@@ -145,17 +145,17 @@
"appsUpdated": "Aplicaciones actualizadas", "appsUpdated": "Aplicaciones actualizadas",
"appsUpdatedNotifDescription": "Notifica al usuario de que una o más aplicaciones han sido actualizadas en segundo plano", "appsUpdatedNotifDescription": "Notifica al usuario de que una o más aplicaciones han sido actualizadas en segundo plano",
"xWasUpdatedToY": "{} ha sido actualizada a {}.", "xWasUpdatedToY": "{} ha sido actualizada a {}.",
"errorCheckingUpdates": "Error buscando ectualizaciones", "errorCheckingUpdates": "Error al buscar actualizaciones",
"errorCheckingUpdatesNotifDescription": "Una notificación que muestra cuándo la comprobación de actualizaciones en segundo plano falla", "errorCheckingUpdatesNotifDescription": "Una notificación que muestra cuándo la comprobación de actualizaciones en segundo plano falla",
"appsRemoved": "Aplicaciones eliminadas", "appsRemoved": "Aplicaciones eliminadas",
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas", "appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}", "xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
"completeAppInstallation": "Instalación completa de la aplicación", "completeAppInstallation": "Instalación completa de la aplicación",
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierto para instalar aplicaciones", "obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierto para instalar aplicaciones",
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación", "completeAppInstallationNotifDescription": "Le pide 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, permita que Obtainium instale aplicaciones", "pleaseAllowInstallPerm": "Permita que Obtainium instale aplicaciones",
"trackOnly": "Solo para seguimiento", "trackOnly": "Solo para 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)",
@@ -173,8 +173,8 @@
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre", "appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones", "reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
"fdroidThirdPartyRepo": "Repositorio de tercera parte F-Droid", "fdroidThirdPartyRepo": "Repositorio de tercera parte F-Droid",
"steamMobile": "Móvil de vapor", "steamMobile": "Steam para móviles",
"steamChat": "Chat de vapor", "steamChat": "Chat de Steam",
"install": "Instalar", "install": "Instalar",
"markInstalled": "Marcar como instalada", "markInstalled": "Marcar como instalada",
"update": "Actualizar", "update": "Actualizar",
@@ -183,6 +183,9 @@
"disableVersionDetection": "Desactivar la detección de versiones", "disableVersionDetection": "Desactivar la detección de versiones",
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda que no funcionar correctamente.", "noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda que no funcionar correctamente.",
"downloadingX": "Descargando {}", "downloadingX": "Descargando {}",
"downloadX": "Descargar {}",
"downloadedX": "Descargado {}",
"releaseAsset": "Liberar activos",
"downloadNotifDescription": "Notifica al usuario del progreso de descarga de una aplicación", "downloadNotifDescription": "Notifica al usuario del progreso de descarga de una aplicación",
"noAPKFound": "No se encontró el paquete de instalación APK", "noAPKFound": "No se encontró el paquete de instalación APK",
"noVersionDetection": "Sin detección de versiones", "noVersionDetection": "Sin detección de versiones",
@@ -192,14 +195,14 @@
"noCategory": "Sin categoría", "noCategory": "Sin categoría",
"noCategories": "Sin categorías", "noCategories": "Sin categorías",
"deleteCategoriesQuestion": "¿Eliminar categorías?", "deleteCategoriesQuestion": "¿Eliminar categorías?",
"categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas serán marcadas como 'Sin categoría'.", "categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas se marcarán como «Sin categoría».",
"addCategory": "Añadir categoría", "addCategory": "Añadir categoría",
"label": "Nombre", "label": "Nombre",
"language": "Idioma", "language": "Idioma",
"copiedToClipboard": "Copiado al portapapeles", "copiedToClipboard": "Se copió en el portapapeles",
"storagePermissionDenied": "Permiso de almacenamiento rechazado", "storagePermissionDenied": "Permiso de almacenamiento rechazado",
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaciones seleccionadas.", "selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaciones seleccionadas.",
"filterAPKsByRegEx": "Filtrar por APKs", "filterAPKsByRegEx": "Filtrar por APK",
"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.",
@@ -212,8 +215,8 @@
"versionDetection": "Detección de versiones", "versionDetection": "Detección de versiones",
"standardVersionDetection": "Por versión", "standardVersionDetection": "Por versión",
"groupByCategory": "Agrupar por categoría", "groupByCategory": "Agrupar por categoría",
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador (si es posible)", "autoApkFilterByArch": "Filtrar APK por arquitectura del procesador (si es posible)",
"overrideSource": "Sobrescribir Fuente", "overrideSource": "Anular fuente",
"dontShowAgain": "No mostrar de nuevo", "dontShowAgain": "No mostrar de nuevo",
"dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps en 'solo seguimiento'", "dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps en 'solo seguimiento'",
"dontShowAPKOriginWarnings": "No mostrar avisos sobre las fuentes de las APKs", "dontShowAPKOriginWarnings": "No mostrar avisos sobre las fuentes de las APKs",
@@ -262,7 +265,7 @@
"takeFirstLink": "Usar primer enlace", "takeFirstLink": "Usar primer enlace",
"skipSort": "Omitir orden", "skipSort": "Omitir orden",
"debugMenu": "Menu Depurar", "debugMenu": "Menu Depurar",
"bgTaskStarted": "Iniciada tarea en segundo plano - revisa los logs.", "bgTaskStarted": "Iniciada tarea en segundo plano; revise los registros.",
"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",
@@ -297,23 +300,25 @@
"latest": "Versión más reciente", "latest": "Versión más reciente",
"invertRegEx": "Invertir expresión regular", "invertRegEx": "Invertir expresión regular",
"note": "Nota", "note": "Nota",
"selfHostedNote": "El desplegable \"{}\" puede utilizarse para acceder a instancias autoalojadas/personalizadas de cualquier fuente.", "selfHostedNote": "El desplegable «{}» puede utilizarse para acceder a instancias autoalojadas/personalizadas de cualquier fuente.",
"badDownload": "No se ha podido analizar el APK (incompatible o descarga parcial)", "badDownload": "No se ha podido analizar el APK (incompatible o descarga parcial)",
"beforeNewInstallsShareToAppVerifier": "Compartir nuevas aplicaciones con AppVerifier (si está disponible)",
"appVerifierInstructionToast": "Comparta con AppVerifier y vuelva aquí cuando esté listo.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "¿Eliminar Aplicación?", "one": "¿Eliminar aplicación?",
"other": "¿Eliminar Aplicaciones?" "other": "¿Eliminar aplicaciones?"
}, },
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Muchas peticiones (limitado) - prueba de nuevo en {} minuto", "one": "Muchas peticiones (limitado); pruebe de nuevo en {} minuto",
"other": "Muchas peticiones (limitado) - prueba de nuevo en {} minutos" "other": "Muchas peticiones (limitado); pruebe de nuevo en {} minutos"
}, },
"bgUpdateGotErrorRetryInMinutes": { "bgUpdateGotErrorRetryInMinutes": {
"one": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}, se volverá a probar en {} minuto", "one": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}; se volverá a probar en {} minuto",
"other": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}, se volverá a probar en {} minutos" "other": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}; se volverá a probar en {} minutos"
}, },
"bgCheckFoundUpdatesWillNotifyIfNeeded": { "bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualización - se notificará al usuario si es necesario", "one": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualización; se le notificará si es necesario",
"other": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualizaciones - se notificará al usuario si es necesario" "other": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualizaciones; se le notificará si es necesario"
}, },
"apps": { "apps": {
"one": "{} Aplicación", "one": "{} Aplicación",
@@ -336,16 +341,16 @@
"other": "{} días" "other": "{} días"
}, },
"clearedNLogsBeforeXAfterY": { "clearedNLogsBeforeXAfterY": {
"one": "Eliminado {n} log (previo a = {before}, posterior a = {after})", "one": "Eliminado {n} registro (previo a = {before}, posterior a = {after})",
"other": "Eliminados {n} logs (previos a = {before}, posteriores a = {after})" "other": "Eliminados {n} registros (previos a = {before}, posteriores a = {after})"
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "{} y 1 aplicación más tiene actualizaciones.", "one": "{} y 1 aplicación más tiene actualizaciones.",
"other": "{} y {} aplicaciones más tienen actualizaciones." "other": "{} y {} aplicaciones más tienen actualizaciones."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} y 1 aplicación más han sido actualizadas.", "one": "{} y 1 aplicación más se han actualizado.",
"other": "{} y {} aplicaciones más han sido actualizadas." "other": "{} y {} aplicaciones más se han actualizado."
}, },
"xAndNMoreUpdatesPossiblyInstalled": { "xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} y 1 aplicación más podría haber sido actualizada.", "one": "{} y 1 aplicación más podría haber sido actualizada.",

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "غیرفعال کردن تشخیص نسخه", "disableVersionDetection": "غیرفعال کردن تشخیص نسخه",
"noVersionDetectionExplanation": "این گزینه فقط باید برای برنامه هایی استفاده شود که تشخیص نسخه به درستی کار نمی کند.", "noVersionDetectionExplanation": "این گزینه فقط باید برای برنامه هایی استفاده شود که تشخیص نسخه به درستی کار نمی کند.",
"downloadingX": "در حال دانلود {}", "downloadingX": "در حال دانلود {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "کاربر را از پیشرفت دانلود یک برنامه مطلع می کند", "downloadNotifDescription": "کاربر را از پیشرفت دانلود یک برنامه مطلع می کند",
"noAPKFound": "APK پیدا نشد فایل", "noAPKFound": "APK پیدا نشد فایل",
"noVersionDetection": "بدون تشخیص نسخه", "noVersionDetection": "بدون تشخیص نسخه",
@@ -299,6 +302,8 @@
"note": "Note", "note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "The APK could not be parsed (incompatible or partial download)",
"beforeNewInstallsShareToAppVerifier": "Share new Apps with AppVerifier (if available)",
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "برنامه حذف شود؟", "one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟" "other": "برنامه ها حذف شوند؟"

View File

@@ -1,7 +1,7 @@
{ {
"invalidURLForSource": "URL d'application {} invalide", "invalidURLForSource": "URL d'application {} invalide",
"noReleaseFound": "Impossible de trouver une version appropriée", "noReleaseFound": "Impossible de trouver une version adaptée",
"noVersionFound": "Impossible de déterminer la version de la version", "noVersionFound": "Impossible de déterminer la variante de la version",
"urlMatchesNoSource": "L'URL ne correspond pas à une source connue", "urlMatchesNoSource": "L'URL ne correspond pas à une source connue",
"cantInstallOlderVersion": "Impossible d'installer une ancienne version d'une application", "cantInstallOlderVersion": "Impossible d'installer une ancienne version d'une application",
"appIdMismatch": "L'ID de paquet téléchargé ne correspond pas à l'ID de l'application existante", "appIdMismatch": "L'ID de paquet téléchargé ne correspond pas à l'ID de l'application existante",
@@ -43,7 +43,7 @@
"additionalOptsFor": "Options supplémentaires pour {}", "additionalOptsFor": "Options supplémentaires pour {}",
"supportedSources": "Sources prises en charge ", "supportedSources": "Sources prises en charge ",
"trackOnlyInBrackets": "(Suivi uniquement)", "trackOnlyInBrackets": "(Suivi uniquement)",
"searchableInBrackets": "(Recherchable)", "searchableInBrackets": "(Intérrogeable)",
"appsString": "Applications", "appsString": "Applications",
"noApps": "Aucune application", "noApps": "Aucune application",
"noAppsForFilter": "Aucune application pour le filtre", "noAppsForFilter": "Aucune application pour le filtre",
@@ -51,7 +51,7 @@
"percentProgress": "Progrès: {}%", "percentProgress": "Progrès: {}%",
"pleaseWait": "Veuillez patienter", "pleaseWait": "Veuillez patienter",
"updateAvailable": "Mise à jour disponible", "updateAvailable": "Mise à jour disponible",
"notInstalled": "Pas installé", "notInstalled": "Non installé",
"pseudoVersion": "pseudo-version", "pseudoVersion": "pseudo-version",
"selectAll": "Tout sélectionner", "selectAll": "Tout sélectionner",
"deselectX": "Déselectionner {}", "deselectX": "Déselectionner {}",
@@ -60,22 +60,22 @@
"removeSelectedApps": "Supprimer les applications sélectionnées", "removeSelectedApps": "Supprimer les applications sélectionnées",
"updateX": "Mise à jour {}", "updateX": "Mise à jour {}",
"installX": "Installer {}", "installX": "Installer {}",
"markXTrackOnlyAsUpdated": "Marquer {}\n(Suivi uniquement)\nas mis à jour", "markXTrackOnlyAsUpdated": "Marquer {}\n(Suivi uniquement)\n comme mis à jour",
"changeX": "Changer {}", "changeX": "Changer {}",
"installUpdateApps": "Installer/Mettre à jour les applications", "installUpdateApps": "Installer/Mettre à jour les applications",
"installUpdateSelectedApps": "Installer/Mettre à jour les applications sélectionnées", "installUpdateSelectedApps": "Installer/Mettre à jour les applications sélectionnées",
"markXSelectedAppsAsUpdated": "Marquer {} les applications sélectionnées comme mises à jour ?", "markXSelectedAppsAsUpdated": "Marquer {} les applications sélectionnées comme étant à jour ?",
"no": "Non", "no": "Non",
"yes": "Oui", "yes": "Oui",
"markSelectedAppsUpdated": "Marquer les applications sélectionnées comme mises à jour", "markSelectedAppsUpdated": "Marquer les applications sélectionnées comme étant à jour",
"pinToTop": "Épingler en haut", "pinToTop": "Épingler en haut",
"unpinFromTop": "Détacher du haut", "unpinFromTop": "Désépingler du haut",
"resetInstallStatusForSelectedAppsQuestion": "Réinitialiser ltat d'installation des applications sélectionnées ?", "resetInstallStatusForSelectedAppsQuestion": "Réinitialiser le statu d'installation des applications sélectionnées ?",
"installStatusOfXWillBeResetExplanation": "Ltat d'installation de toutes les applications sélectionnées sera réinitialisé.\n\nCela peut aider lorsque la version de l'application affichée dans Obtainium est incorrecte en raison d'échecs de mises à jour ou d'autres problèmes.", "installStatusOfXWillBeResetExplanation": "Le statu d'installation de toutes les applications sélectionnées sera réinitialisé.\n\nCela peut aider lorsque la version de l'application affichée dans Obtainium est incorrecte en raison d'échecs de mises à jour ou d'autres problèmes.",
"customLinkMessage": "Ces liens fonctionnent sur les appareils sur lesquels Obtenirium est installé", "customLinkMessage": "Ces liens fonctionnent sur les appareils sur lesquels Obtainium est installé",
"shareAppConfigLinks": "Partager la configuration de l'application sous forme de lien HTML", "shareAppConfigLinks": "Partager la configuration de l'application sous forme de lien HTML",
"shareSelectedAppURLs": "Partager les URL d'application sélectionnées", "shareSelectedAppURLs": "Partager les URL d'application sélectionnées",
"resetInstallStatus": "Réinitialiser le statut d'installation", "resetInstallStatus": "Réinitialiser le statu d'installation",
"more": "Plus", "more": "Plus",
"removeOutdatedFilter": "Supprimer le filtre d'application obsolète", "removeOutdatedFilter": "Supprimer le filtre d'application obsolète",
"showOutdatedOnly": "Afficher uniquement les applications obsolètes", "showOutdatedOnly": "Afficher uniquement les applications obsolètes",
@@ -88,12 +88,12 @@
"importExport": "Importer/Exporter", "importExport": "Importer/Exporter",
"settings": "Paramètres", "settings": "Paramètres",
"exportedTo": "Exporté vers {}", "exportedTo": "Exporté vers {}",
"obtainiumExport": "Exportation d'Obtainium", "obtainiumExport": "Exporter d'Obtainium",
"invalidInput": "Entrée invalide", "invalidInput": "Entrée invalide",
"importedX": "Importé {}", "importedX": "Importé {}",
"obtainiumImport": "Importation d'Obtainium", "obtainiumImport": "Importer d'Obtainium",
"importFromURLList": "Importer à partir de la liste d'URL", "importFromURLList": "Importer à partir de la liste d'URL",
"searchQuery": "Requête de recherche", "searchQuery": "Requête",
"appURLList": "Liste d'URL d'application", "appURLList": "Liste d'URL d'application",
"line": "Queue", "line": "Queue",
"searchX": "Rechercher {}", "searchX": "Rechercher {}",
@@ -110,14 +110,14 @@
"dark": "Sombre", "dark": "Sombre",
"light": "Clair", "light": "Clair",
"followSystem": "Suivre le système", "followSystem": "Suivre le système",
"useBlackTheme": "Utilisez le thème noir pur et sombre", "useBlackTheme": "Utilisez le thème noir pur",
"appSortBy": "Applications triées par", "appSortBy": "Applications triées par",
"authorName": "Auteur/Nom", "authorName": "Auteur/Nom",
"nameAuthor": "Nom/Auteur", "nameAuthor": "Nom/Auteur",
"asAdded": "Comme ajouté", "asAdded": "Comme ajouté",
"appSortOrder": "Ordre de tri des applications", "appSortOrder": "Ordre de tri des applications",
"ascending": "Ascendant", "ascending": "Ascendant",
"descending": "Descendanr", "descending": "Descendant",
"bgUpdateCheckInterval": "Intervalle de vérification des mises à jour en arrière-plan", "bgUpdateCheckInterval": "Intervalle de vérification des mises à jour en arrière-plan",
"neverManualOnly": "Jamais - Manuel uniquement", "neverManualOnly": "Jamais - Manuel uniquement",
"appearance": "Apparence", "appearance": "Apparence",
@@ -131,13 +131,13 @@
"close": "Fermer", "close": "Fermer",
"share": "Partager", "share": "Partager",
"appNotFound": "Application introuvable", "appNotFound": "Application introuvable",
"obtainiumExportHyphenatedLowercase": "exportation d'obtainium", "obtainiumExportHyphenatedLowercase": "exportation d'Obtainium",
"pickAnAPK": "Choisissez un APK", "pickAnAPK": "Choisissez un APK",
"appHasMoreThanOnePackage": "{} a plus d'un paquet :", "appHasMoreThanOnePackage": "{} a plus d'un paquet :",
"deviceSupportsXArch": "Votre appareil prend en charge l'architecture de processeur {}.", "deviceSupportsXArch": "Votre appareil prend en charge l'architecture CPU {}.",
"deviceSupportsFollowingArchs": "Votre appareil prend en charge les architectures CPU suivantes :", "deviceSupportsFollowingArchs": "Votre appareil prend en charge les architectures CPU suivantes :",
"warning": "Avertissement", "warning": "Avertissement",
"sourceIsXButPackageFromYPrompt": "La source de l'application est '{}' mais le paquet de version provient de '{}'. Continuer?", "sourceIsXButPackageFromYPrompt": "La source de l'application est '{}' mais la version du paquet provient de '{}'. Continuer?",
"updatesAvailable": "Mises à jour disponibles", "updatesAvailable": "Mises à jour disponibles",
"updatesAvailableNotifDescription": "Avertit l'utilisateur que des mises à jour sont disponibles pour une ou plusieurs applications suivies par Obtainium", "updatesAvailableNotifDescription": "Avertit l'utilisateur que des mises à jour sont disponibles pour une ou plusieurs applications suivies par Obtainium",
"noNewUpdates": "Aucune nouvelle mise à jour.", "noNewUpdates": "Aucune nouvelle mise à jour.",
@@ -179,13 +179,16 @@
"markInstalled": "Marquer installée", "markInstalled": "Marquer installée",
"update": "Mettre à jour", "update": "Mettre à jour",
"markUpdated": "Marquer à jour", "markUpdated": "Marquer à jour",
"additionalOptions": "Options additionelles", "additionalOptions": "Options additionnelles",
"disableVersionDetection": "Désactiver la détection de version", "disableVersionDetection": "Désactiver la détection de version",
"noVersionDetectionExplanation": "Cette option ne doit être utilisée que pour les applications où la détection de version ne fonctionne pas correctement.", "noVersionDetectionExplanation": "Cette option ne doit être utilisée que pour les applications où la détection de version ne fonctionne pas correctement.",
"downloadingX": "Téléchargement {}", "downloadingX": "Téléchargement {}",
"downloadX": "Télécharger {}",
"downloadedX": "Téléchargé {}",
"releaseAsset": "Actif libéré",
"downloadNotifDescription": "Avertit l'utilisateur de la progression du téléchargement d'une application", "downloadNotifDescription": "Avertit l'utilisateur de la progression du téléchargement d'une application",
"noAPKFound": "Aucun APK trouvé", "noAPKFound": "Aucun APK trouvé",
"noVersionDetection": "Pas de détection de version", "noVersionDetection": "Aucune de détection de version",
"categorize": "Catégoriser", "categorize": "Catégoriser",
"categories": "Catégories", "categories": "Catégories",
"category": "Catégorie", "category": "Catégorie",
@@ -198,13 +201,13 @@
"language": "Langue", "language": "Langue",
"copiedToClipboard": "Copié dans le presse-papier", "copiedToClipboard": "Copié dans le presse-papier",
"storagePermissionDenied": "Autorisation de stockage refusée", "storagePermissionDenied": "Autorisation de stockage refusée",
"selectedCategorizeWarning": "Cela remplacera tous les paramètres de catégorie existants pour les applications sélectionnées.", "selectedCategorizeWarning": "Cela remplacera toutes les catégorie définies pour les applications sélectionnées.",
"filterAPKsByRegEx": "Filtrer les APK par expression régulière", "filterAPKsByRegEx": "Filtrer les APK par expression régulière",
"removeFromObtainium": "Supprimer d'Obtainium", "removeFromObtainium": "Supprimer d'Obtainium",
"uninstallFromDevice": "Désinstaller de l'appareil", "uninstallFromDevice": "Désinstaller de l'appareil",
"onlyWorksWithNonVersionDetectApps": "Fonctionne uniquement pour les applications avec la détection de version désactivée.", "onlyWorksWithNonVersionDetectApps": "Fonctionne uniquement pour les applications avec la détection de version désactivée.",
"releaseDateAsVersion": "Utiliser la date de sortie comme version", "releaseDateAsVersion": "Utiliser la date de sortie comme version",
"releaseDateAsVersionExplanation": "Cette option ne doit être utilisée que pour les applications où la détection de version ne fonctionne pas correctement, mais une date de sortie est disponible.", "releaseDateAsVersionExplanation": "Cette option ne doit être utilisée que pour les applications où la détection de version ne fonctionne pas correctement, mais dont une date de sortie est disponible.",
"changes": "Changements", "changes": "Changements",
"releaseDate": "Date de sortie", "releaseDate": "Date de sortie",
"importFromURLsInFile": "Importer à partir d'URL dans un fichier (comme OPML)", "importFromURLsInFile": "Importer à partir d'URL dans un fichier (comme OPML)",
@@ -212,59 +215,59 @@
"versionDetection": "Détection des versions", "versionDetection": "Détection des versions",
"standardVersionDetection": "Détection de version standard", "standardVersionDetection": "Détection de version standard",
"groupByCategory": "Regrouper par catégorie", "groupByCategory": "Regrouper par catégorie",
"autoApkFilterByArch": "Essayez de filtrer les APK par architecture CPU si possible", "autoApkFilterByArch": "Si possible, essayez de filtrer les APK par architecture CPU",
"overrideSource": "Remplacer la source", "overrideSource": "Remplacer la source",
"dontShowAgain": "Ne montre plus ça", "dontShowAgain": "Ne plus montrer",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning", "dontShowTrackOnlyWarnings": "Ne pas afficher l'avertissement 'Track-Only'",
"dontShowAPKOriginWarnings": "Ne pas afficher les avertissements sur l'origine de l'APK", "dontShowAPKOriginWarnings": "Ne pas afficher les avertissements sur l'origine de l'APK",
"moveNonInstalledAppsToBottom": "Déplacer les applications non installées vers le bas de la vue Applications", "moveNonInstalledAppsToBottom": "Déplacer les applications non installées vers le bas de la vue Applications",
"gitlabPATLabel": "Jeton d'accès personnel GitLab", "gitlabPATLabel": "Jeton d'accès personnel GitLab",
"about": "À propos de", "about": "À propos de",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "{}: Cela nécessite des identifiants supplémentaires (dans Paramètres)",
"checkOnStart": "Vérifier les mises à jour au démarrage", "checkOnStart": "Vérifier les mises à jour au démarrage",
"tryInferAppIdFromCode": "Essayez de déduire l'ID de l'application à partir du code source", "tryInferAppIdFromCode": "Essayez de déduire l'ID de l'application à partir du code source",
"removeOnExternalUninstall": "Supprimer automatiquement les applications désinstallées en externe", "removeOnExternalUninstall": "Supprimer automatiquement les applications désinstallées en externe",
"pickHighestVersionCode": "Sélectionner automatiquement le code APK de la version la plus élevée", "pickHighestVersionCode": "Sélectionner automatiquement le code de version de l'APK la plus élevée",
"checkUpdateOnDetailPage": "Vérifier les mises à jour lors de l'ouverture d'une page de détails d'application", "checkUpdateOnDetailPage": "Vérifier les mises à jour lors de l'ouverture de la page détaillée d'une application",
"disablePageTransitions": "Désactiver les animations de transition de page", "disablePageTransitions": "Désactiver les animations de transition de page",
"reversePageTransitions": "Animations de transition de page inversée", "reversePageTransitions": "Inverser les animations de transition de page",
"minStarCount": "Nombre minimum d'étoiles", "minStarCount": "Nombre minimum d'étoiles",
"addInfoBelow": "Ajoutez ces informations ci-dessous.", "addInfoBelow": "Ajoutez ces informations ci-dessous.",
"addInfoInSettings": "Ajoutez ces informations dans les paramètres.", "addInfoInSettings": "Ajoutez ces informations dans les paramètres.",
"githubSourceNote": "La limitation du débit GitHub peut être évitée à l'aide d'une clé API.", "githubSourceNote": "La limite de débit GitHub peut être évitée à l'aide d'une clé API.",
"sortByLastLinkSegment": "Trier uniquement sur le dernier segment du lien", "sortByLastLinkSegment": "Trier uniquement sur le dernier segment du lien",
"filterReleaseNotesByRegEx": "Filtrer les notes de version par expression régulière", "filterReleaseNotesByRegEx": "Filtrer les notes de version par expression régulière",
"customLinkFilterRegex": "Filtre de lien APK personnalisé par expression régulière (par défaut '.apk$')", "customLinkFilterRegex": "Filtre du lien APK personnalisé par expression régulière (par défaut '.apk$')",
"appsPossiblyUpdated": "Tentative de mise à jour de l'application", "appsPossiblyUpdated": "Tentative de mise à jour de l'application",
"appsPossiblyUpdatedNotifDescription": "Avertit l'utilisateur que des mises à jour d'une ou plusieurs applications ont été potentiellement appliquées en arrière-plan", "appsPossiblyUpdatedNotifDescription": "Avertit l'utilisateur que des mises à jour d'une ou plusieurs applications ont été potentiellement appliquées en arrière-plan",
"xWasPossiblyUpdatedToY": "{} a peut-être été mis à jour vers {}.", "xWasPossiblyUpdatedToY": "{} a peut-être été mis à jour vers {}.",
"enableBackgroundUpdates": "Activer les mises à jour en arrière-plan", "enableBackgroundUpdates": "Activer les mises à jour en arrière-plan",
"backgroundUpdateReqsExplanation": "Les mises à jour en arrière-plan peuvent ne pas être possibles pour toutes les applications.", "backgroundUpdateReqsExplanation": "Les mises à jour en arrière-plan peuvent ne pas être possibles pour toutes les applications.",
"backgroundUpdateLimitsExplanation": "Le succès d'une installation en arrière-plan ne peut être déterminé qu'à l'ouverture d'Obetium.", "backgroundUpdateLimitsExplanation": "Le succès d'une installation en arrière-plan ne peut être déterminé qu'à l'ouverture d'Obtainium.",
"verifyLatestTag": "Vérifiez la balise 'dernière'", "verifyLatestTag": "Vérifiez la balise 'Latest'",
"intermediateLinkRegex": " Filtrer un lien \" intermédiaire \" à visiter ", "intermediateLinkRegex": " Filtrer un lien \" intermédiaire \" à visiter ",
"filterByLinkText": "Filtrer les liens par texte de lien", "filterByLinkText": "Filtrer les liens par le texte du lien",
"intermediateLinkNotFound": "Lien intermédiaire introuvable", "intermediateLinkNotFound": "Lien intermédiaire introuvable",
"intermediateLink": "Lien intermédiaire", "intermediateLink": "Lien intermédiaire",
"exemptFromBackgroundUpdates": "Exempt des mises à jour en arrière-plan (si activé)", "exemptFromBackgroundUpdates": "Exempté des mises à jour en arrière-plan (si activé)",
"bgUpdatesOnWiFiOnly": "Désactiver les mises à jour en arrière-plan lorsque vous n'êtes pas connecté au WiFi", "bgUpdatesOnWiFiOnly": "Désactiver les mises à jour en arrière-plan lorsque vous n'êtes pas connecté au WiFi",
"autoSelectHighestVersionCode": "Sélection automatique de la version la plus élevéeCode APK", "autoSelectHighestVersionCode": "Sélection automatique du code de version de l'APK la plus élevée",
"versionExtractionRegEx": "Version Extraction RegEx", "versionExtractionRegEx": "Expression régulière d'extraction de version",
"matchGroupToUse": "Match Group to Use", "matchGroupToUse": "Groupe de correspondance pour l'expression régulière d'extraction de version",
"highlightTouchTargets": "Mettez en évidence les cibles tactiles moins évidentes", "highlightTouchTargets": "Mettre en évidence les cibles tactiles moins évidentes",
"pickExportDir": "Choisir le répertoire d'exportation", "pickExportDir": "Choisir le répertoire d'exportation",
"autoExportOnChanges": "Exportation automatique sur modifications", "autoExportOnChanges": "Exporter automatiquement après modification",
"includeSettings": "Inclure les paramètres", "includeSettings": "Inclure les paramètres",
"filterVersionsByRegEx": "Filtrer les versions par expression régulière", "filterVersionsByRegEx": "Filtrer les versions par expression régulière",
"trySelectingSuggestedVersionCode": "Essayez de sélectionner la version suggéréeCode APK", "trySelectingSuggestedVersionCode": "Essayez de sélectionner le code de la version APK suggérée",
"dontSortReleasesList": "Conserver la commande de version de l'API", "dontSortReleasesList": "Conserver l'ordre des version de l'API",
"reverseSort": "Tri inversé", "reverseSort": "Tri inversé",
"takeFirstLink": "Prendre le premier lien", "takeFirstLink": "Prendre le premier lien",
"skipSort": "Sauter le tri", "skipSort": "Sauter le tri",
"debugMenu": "Menu de débogage", "debugMenu": "Menu de débogage",
"bgTaskStarted": "Tâche en arrière-plan démarrée - vérifier les journaux.", "bgTaskStarted": "Tâche en arrière-plan démarrée - vérifier les journaux.",
"runBgCheckNow": "Exécuter la vérification de la mise à jour en arrière-plan maintenant", "runBgCheckNow": "Exécuter maintenant la vérification de la mise à jour en arrière-plan",
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page", "versionExtractWholePage": "Appliquer l'expression régulière d'extraction de version sur l'ensemble de la page",
"installing": "Installation", "installing": "Installation",
"skipUpdateNotifications": "Ignorer les notifications de mise à jour", "skipUpdateNotifications": "Ignorer les notifications de mise à jour",
"updatesAvailableNotifChannel": "Mises à jour disponibles", "updatesAvailableNotifChannel": "Mises à jour disponibles",
@@ -275,19 +278,19 @@
"downloadingXNotifChannel": "Téléchargement {}", "downloadingXNotifChannel": "Téléchargement {}",
"completeAppInstallationNotifChannel": "Installation complète de l'application", "completeAppInstallationNotifChannel": "Installation complète de l'application",
"checkingForUpdatesNotifChannel": "Vérification des mises à jour", "checkingForUpdatesNotifChannel": "Vérification des mises à jour",
"onlyCheckInstalledOrTrackOnlyApps": "Vérifiez uniquement les mises à jour des applications installées et de suivi uniquement", "onlyCheckInstalledOrTrackOnlyApps": "Vérifiez uniquement les mises à jour des applications installées et 'Track-Only'",
"supportFixedAPKURL": "Prise en charge des URL APK fixes", "supportFixedAPKURL": "Prise en charge des URL APK fixes",
"selectX": "Sélectionner {}", "selectX": "Sélectionner {}",
"parallelDownloads": "Autoriser les téléchargements parallèles", "parallelDownloads": "Autoriser les téléchargements parallèles",
"installMethod": "Méthode d'installation", "installMethod": "Méthode d'installation",
"normal": "Normale", "normal": "Normale",
"root": "Racine", "root": "Racine",
"shizukuBinderNotFound": "Shizuku is not running", "shizukuBinderNotFound": "Service Shizuku compatible non trouvé",
"useSystemFont": "Utiliser la police système", "useSystemFont": "Utiliser la police du système",
"systemFontError": "Erreur de chargement de la police système : {}", "systemFontError": "Erreur de chargement de la police du système : {}",
"useVersionCodeAsOSVersion": "Utiliser le code de version de l'application comme version détectée par le système d'exploitation", "useVersionCodeAsOSVersion": "Utiliser le code de version de l'application comme version détectée par le système d'exploitation",
"requestHeader": "En-tête de demande", "requestHeader": "En-tête de demande",
"useLatestAssetDateAsReleaseDate": "Utiliser le dernier téléchargement d'élément comme date de sortie", "useLatestAssetDateAsReleaseDate": "Utiliser le dernier élément téléversé comme date de sortie",
"defaultPseudoVersioningMethod": "Méthode de pseudo-version par défaut", "defaultPseudoVersioningMethod": "Méthode de pseudo-version par défaut",
"partialAPKHash": "Hash APK partiel", "partialAPKHash": "Hash APK partiel",
"APKLinkHash": "Hash de lien APK", "APKLinkHash": "Hash de lien APK",
@@ -299,6 +302,8 @@
"note": "Note", "note": "Note",
"selfHostedNote": "La liste déroulante \"{}\" peut être utilisée pour accéder aux instances auto-hébergées/personnalisées de n'importe quelle source.", "selfHostedNote": "La liste déroulante \"{}\" peut être utilisée pour accéder aux instances auto-hébergées/personnalisées de n'importe quelle source.",
"badDownload": "L'APK n'a pas pu être analysé (téléchargement incompatible ou partiel)", "badDownload": "L'APK n'a pas pu être analysé (téléchargement incompatible ou partiel)",
"beforeNewInstallsShareToAppVerifier": "Partager les nouvelles applications avec AppVerifier (si disponible)",
"appVerifierInstructionToast": "Partagez avec AppVerifier, puis revenez ici lorsque vous êtes prêt.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application ?", "one": "Supprimer l'application ?",
"other": "Supprimer les applications ?" "other": "Supprimer les applications ?"
@@ -308,12 +313,12 @@
"other": "Trop de demandes (taux limité) - réessayez dans {} minutes" "other": "Trop de demandes (taux limité) - réessayez dans {} minutes"
}, },
"bgUpdateGotErrorRetryInMinutes": { "bgUpdateGotErrorRetryInMinutes": {
"one": "La vérification de la mise à jour en arrière-plan a rencontré un {}, planifiera une nouvelle tentative de vérification dans {} minute", "one": "La vérification de la mise à jour en arrière-plan a rencontré un {}, une nouvelle tentative de vérification sera planifié dans {} minute",
"other": "La vérification de la mise à jour en arrière-plan a rencontré un {}, planifiera une nouvelle tentative de vérification dans {} minutes" "other": "La vérification de la mise à jour en arrière-plan a rencontré un {}, une nouvelle tentative de vérification sera planifié dans {} minute"
}, },
"bgCheckFoundUpdatesWillNotifyIfNeeded": { "bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "La vérification des mises à jour en arrière-plan trouvée {} mise à jour - avertira l'utilisateur si nécessaire", "one": "La vérification des mises à jour en arrière-plan a trouvée {} mise à jour - l'utilisateur sera notifié si nécessaire",
"other": "La vérification des mises à jour en arrière-plan a trouvé {} mises à jour - avertira l'utilisateur si nécessaire" "other": "La vérification des mises à jour en arrière-plan a trouvé {} mises à jour - l'utilisateur sera notifié si nécessaire"
}, },
"apps": { "apps": {
"one": "{} Application", "one": "{} Application",

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Verzió érzékelés letiltása", "disableVersionDetection": "Verzió érzékelés letiltása",
"noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.", "noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.",
"downloadingX": "{} letöltés", "downloadingX": "{} letöltés",
"downloadX": "Letöltés {}",
"downloadedX": "Letöltés {}",
"releaseAsset": "Kiadási tartalom",
"downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról", "downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról",
"noAPKFound": "Nem található APK", "noAPKFound": "Nem található APK",
"noVersionDetection": "Nincs verzió érzékelés", "noVersionDetection": "Nincs verzió érzékelés",
@@ -218,7 +221,7 @@
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést", "dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
"dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket", "dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket",
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára", "moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
"gitlabPATLabel": "GitLab Personal Access Token", "gitlabPATLabel": "GitLab személyes hozzáférési token",
"about": "Rólunk", "about": "Rólunk",
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)", "requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
"checkOnStart": "Egyszer az alkalmazás indításakor is", "checkOnStart": "Egyszer az alkalmazás indításakor is",
@@ -299,6 +302,8 @@
"note": "Megjegyzés:", "note": "Megjegyzés:",
"selfHostedNote": "A \"{}\" legördülő menü használható bármely forrás saját üzemeltetésű/egyéni példányainak eléréséhez.", "selfHostedNote": "A \"{}\" legördülő menü használható bármely forrás saját üzemeltetésű/egyéni példányainak eléréséhez.",
"badDownload": "Az APK-t nem lehetett elemezni (inkompatibilis vagy részleges letöltés)", "badDownload": "Az APK-t nem lehetett elemezni (inkompatibilis vagy részleges letöltés)",
"beforeNewInstallsShareToAppVerifier": "Új alkalmazások megosztása az AppVerifierrel (ha elérhető)",
"appVerifierInstructionToast": "Ossza meg az AppVerifierrel, majd térjen vissza ide, ha kész.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazásokat?" "other": "Eltávolítja az alkalmazásokat?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Disattiva il rilevamento della versione", "disableVersionDetection": "Disattiva il rilevamento della versione",
"noVersionDetectionExplanation": "Questa opzione dovrebbe essere usata solo per le app la cui versione non viene rilevata correttamente.", "noVersionDetectionExplanation": "Questa opzione dovrebbe essere usata solo per le app la cui versione non viene rilevata correttamente.",
"downloadingX": "Scaricamento di {} in corso", "downloadingX": "Scaricamento di {} in corso",
"downloadX": "Scarica {}",
"downloadedX": "Scaricato {}",
"releaseAsset": "Rilascio Asset",
"downloadNotifDescription": "Notifica all'utente lo stato di avanzamento del download di un'app", "downloadNotifDescription": "Notifica all'utente lo stato di avanzamento del download di un'app",
"noAPKFound": "Nessun APK trovato", "noAPKFound": "Nessun APK trovato",
"noVersionDetection": "Disattiva rilevamento di versione", "noVersionDetection": "Disattiva rilevamento di versione",
@@ -218,7 +221,7 @@
"dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'", "dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'",
"dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK", "dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK",
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista", "moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
"gitlabPATLabel": "GitLab Personal Access Token", "gitlabPATLabel": "GitLab Token di accesso personale",
"about": "Informazioni", "about": "Informazioni",
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)", "requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
"checkOnStart": "Controlla una volta all'avvio", "checkOnStart": "Controlla una volta all'avvio",
@@ -299,6 +302,8 @@
"note": "Nota", "note": "Nota",
"selfHostedNote": "Il menu a tendina \"{}\" può essere usato per raggiungere istanze autogestite/personali di qualsiasi fonte.", "selfHostedNote": "Il menu a tendina \"{}\" può essere usato per raggiungere istanze autogestite/personali di qualsiasi fonte.",
"badDownload": "Non è stato possibile analizzare l'APK (download incompatibile o parziale).", "badDownload": "Non è stato possibile analizzare l'APK (download incompatibile o parziale).",
"beforeNewInstallsShareToAppVerifier": "Condividere le nuove applicazioni con AppVerifier (se disponibile)",
"appVerifierInstructionToast": "Condividete con AppVerifier, quindi tornate qui quando siete pronti.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Rimuovere l'app?", "one": "Rimuovere l'app?",
"other": "Rimuovere le app?" "other": "Rimuovere le app?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "バージョン検出を無効にする", "disableVersionDetection": "バージョン検出を無効にする",
"noVersionDetectionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリにのみ使用する必要があります。", "noVersionDetectionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリにのみ使用する必要があります。",
"downloadingX": "{} をダウンロード中", "downloadingX": "{} をダウンロード中",
"downloadX": "ダウンロード",
"downloadedX": "ダウンロード",
"releaseAsset": "リリース資産",
"downloadNotifDescription": "アプリのダウンロード状況を通知する", "downloadNotifDescription": "アプリのダウンロード状況を通知する",
"noAPKFound": "APKが見つかりません", "noAPKFound": "APKが見つかりません",
"noVersionDetection": "バージョン検出を行わない", "noVersionDetection": "バージョン検出を行わない",
@@ -299,6 +302,8 @@
"note": "注", "note": "注",
"selfHostedNote": "ドロップダウン\"{}\"を使用すると、あらゆるソースのセルフホスト/カスタムインスタンスにアクセスできます。", "selfHostedNote": "ドロップダウン\"{}\"を使用すると、あらゆるソースのセルフホスト/カスタムインスタンスにアクセスできます。",
"badDownload": "APK を解析できませんでした(互換性がないか、部分的にダウンロードされています)。", "badDownload": "APK を解析できませんでした(互換性がないか、部分的にダウンロードされています)。",
"beforeNewInstallsShareToAppVerifier": "AppVerifierで新しいアプリを共有する利用可能な場合",
"appVerifierInstructionToast": "AppVerifierに共有し、準備ができたらここに戻ってください。",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Versieherkenning uitschakelen", "disableVersionDetection": "Versieherkenning uitschakelen",
"noVersionDetectionExplanation": "Deze optie moet alleen worden gebruikt voor apps waar versieherkenning niet correct werkt.", "noVersionDetectionExplanation": "Deze optie moet alleen worden gebruikt voor apps waar versieherkenning niet correct werkt.",
"downloadingX": "Downloaden {}", "downloadingX": "Downloaden {}",
"downloadX": "Downloaden",
"downloadedX": "Gedownload {}",
"releaseAsset": "Release Activa",
"downloadNotifDescription": "Stelt de gebruiker op de hoogte van de voortgang bij het downloaden van een app", "downloadNotifDescription": "Stelt de gebruiker op de hoogte van de voortgang bij het downloaden van een app",
"noAPKFound": "Geen APK gevonden", "noAPKFound": "Geen APK gevonden",
"noVersionDetection": "Geen versieherkenning", "noVersionDetection": "Geen versieherkenning",
@@ -218,7 +221,7 @@
"dontShowTrackOnlyWarnings": "Geen waarschuwingen voor 'Track-Only' weergeven", "dontShowTrackOnlyWarnings": "Geen waarschuwingen voor 'Track-Only' weergeven",
"dontShowAPKOriginWarnings": "APK-herkomstwaarschuwingen niet weergeven", "dontShowAPKOriginWarnings": "APK-herkomstwaarschuwingen niet weergeven",
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave", "moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
"gitlabPATLabel": "GitLab Personal Access Token", "gitlabPATLabel": "GitLab persoonlijk toegangskenmerk",
"about": "Over", "about": "Over",
"requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)", "requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
"checkOnStart": "Controleren op updates bij opstarten", "checkOnStart": "Controleren op updates bij opstarten",
@@ -299,6 +302,8 @@
"note": "Opmerking", "note": "Opmerking",
"selfHostedNote": "De \"{}\" dropdown kan gebruikt worden om zelf gehoste/aangepaste instanties van elke bron te bereiken.", "selfHostedNote": "De \"{}\" dropdown kan gebruikt worden om zelf gehoste/aangepaste instanties van elke bron te bereiken.",
"badDownload": "De APK kon niet worden verwerkt (incompatibele of gedeeltelijke download)", "badDownload": "De APK kon niet worden verwerkt (incompatibele of gedeeltelijke download)",
"beforeNewInstallsShareToAppVerifier": "Nieuwe Apps delen met AppVerifier (indien beschikbaar)",
"appVerifierInstructionToast": "Deel naar AppVerifier en keer hier terug als je klaar bent.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App verwijderen?", "one": "App verwijderen?",
"other": "Apps verwijderen?" "other": "Apps verwijderen?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Wyłącz wykrywanie wersji", "disableVersionDetection": "Wyłącz wykrywanie wersji",
"noVersionDetectionExplanation": "Opcja ta powinna być używana tylko w przypadku aplikacji, w których wykrywanie wersji nie działa poprawnie.", "noVersionDetectionExplanation": "Opcja ta powinna być używana tylko w przypadku aplikacji, w których wykrywanie wersji nie działa poprawnie.",
"downloadingX": "Pobieranie {}", "downloadingX": "Pobieranie {}",
"downloadX": "Pobierz {}",
"downloadedX": "Pobrano {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Informuje o postępach w pobieraniu aplikacji", "downloadNotifDescription": "Informuje o postępach w pobieraniu aplikacji",
"noAPKFound": "Nie znaleziono pakietu APK", "noAPKFound": "Nie znaleziono pakietu APK",
"noVersionDetection": "Bez wykrywania wersji", "noVersionDetection": "Bez wykrywania wersji",
@@ -299,6 +302,8 @@
"note": "Uwaga", "note": "Uwaga",
"selfHostedNote": "Lista rozwijana \"{}\" może być używana do uzyskiwania dostępu do samodzielnie hostowanych / niestandardowych instancji dowolnego źródła.", "selfHostedNote": "Lista rozwijana \"{}\" może być używana do uzyskiwania dostępu do samodzielnie hostowanych / niestandardowych instancji dowolnego źródła.",
"badDownload": "Nie można przeanalizować pliku APK (niekompatybilny lub częściowo pobrany).", "badDownload": "Nie można przeanalizować pliku APK (niekompatybilny lub częściowo pobrany).",
"beforeNewInstallsShareToAppVerifier": "Udostępnianie nowych aplikacji za pomocą AppVerifier (jeśli dostępne)",
"appVerifierInstructionToast": "Udostępnij w AppVerifier, a następnie wróć tutaj, gdy będziesz gotowy.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Usunąć aplikację?", "one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?", "few": "Usunąć aplikacje?",

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Desativar detecção de versão", "disableVersionDetection": "Desativar detecção de versão",
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por aplicativos onde a detecção de versão não funciona corretamente.", "noVersionDetectionExplanation": "Essa opção deve apenas ser usada por aplicativos onde a detecção de versão não funciona corretamente.",
"downloadingX": "Baixando {}", "downloadingX": "Baixando {}",
"downloadX": "Descarregar {}",
"downloadedX": "Descarregado {}",
"releaseAsset": "Libertação de activos",
"downloadNotifDescription": "Notifica o usuário o progresso do download de um aplicativo", "downloadNotifDescription": "Notifica o usuário o progresso do download de um aplicativo",
"noAPKFound": "APK não encontrado", "noAPKFound": "APK não encontrado",
"noVersionDetection": "Sem detecção de versão", "noVersionDetection": "Sem detecção de versão",
@@ -299,6 +302,8 @@
"note": "Nota", "note": "Nota",
"selfHostedNote": "O menu suspenso \"{}\" pode ser usado para acessar instâncias auto-hospedadas/personalizadas de qualquer fonte.", "selfHostedNote": "O menu suspenso \"{}\" pode ser usado para acessar instâncias auto-hospedadas/personalizadas de qualquer fonte.",
"badDownload": "Não foi possível analisar o APK (transferência incompatível ou parcial)", "badDownload": "Não foi possível analisar o APK (transferência incompatível ou parcial)",
"beforeNewInstallsShareToAppVerifier": "Partilhar novas aplicações com o AppVerifier (se disponível)",
"appVerifierInstructionToast": "Partilhe com o AppVerifier e, em seguida, regresse aqui quando estiver pronto.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remover aplicativo?", "one": "Remover aplicativo?",
"other": "Remover aplicativos?" "other": "Remover aplicativos?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Отключить обнаружение версии", "disableVersionDetection": "Отключить обнаружение версии",
"noVersionDetectionExplanation": "Эта настройка должна использоваться только для приложений, где обнаружение версии не работает корректно", "noVersionDetectionExplanation": "Эта настройка должна использоваться только для приложений, где обнаружение версии не работает корректно",
"downloadingX": "Загрузка {}", "downloadingX": "Загрузка {}",
"downloadX": "Скачать {}",
"downloadedX": "Загружено {}",
"releaseAsset": "Освобождение актива",
"downloadNotifDescription": "Уведомляет пользователя о прогрессе загрузки приложения", "downloadNotifDescription": "Уведомляет пользователя о прогрессе загрузки приложения",
"noAPKFound": "APK не найден", "noAPKFound": "APK не найден",
"noVersionDetection": "Обнаружение версий отключено", "noVersionDetection": "Обнаружение версий отключено",
@@ -299,6 +302,8 @@
"note": "Примечание", "note": "Примечание",
"selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.", "selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.",
"badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)", "badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)",
"beforeNewInstallsShareToAppVerifier": "Поделитесь новыми приложениями с AppVerifier (если доступно)",
"appVerifierInstructionToast": "Поделитесь с AppVerifier, а затем вернитесь сюда, когда будете готовы.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Удалить приложение?", "one": "Удалить приложение?",
"other": "Удалить приложения?" "other": "Удалить приложения?"
@@ -353,6 +358,6 @@
}, },
"apk": { "apk": {
"one": "{} APK", "one": "{} APK",
"other": "{} APKs" "other": "{} APKи"
} }
} }

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Inaktivera versionsdetektering", "disableVersionDetection": "Inaktivera versionsdetektering",
"noVersionDetectionExplanation": "Det här alternativet bör endast användas för appar där versionsidentifiering inte fungerar korrekt.", "noVersionDetectionExplanation": "Det här alternativet bör endast användas för appar där versionsidentifiering inte fungerar korrekt.",
"downloadingX": "Laddar ner {}", "downloadingX": "Laddar ner {}",
"downloadX": "Ladda ner {}",
"downloadedX": "Nedladdad {}",
"releaseAsset": "Frigör tillgång",
"downloadNotifDescription": "Meddelar användaren om framstegen med att ladda ner en app", "downloadNotifDescription": "Meddelar användaren om framstegen med att ladda ner en app",
"noAPKFound": "Ingen APK funnen", "noAPKFound": "Ingen APK funnen",
"noVersionDetection": "Ingen versiondetektering", "noVersionDetection": "Ingen versiondetektering",
@@ -218,7 +221,7 @@
"dontShowTrackOnlyWarnings": "Visa inte 'Följ-Endast' varningar", "dontShowTrackOnlyWarnings": "Visa inte 'Följ-Endast' varningar",
"dontShowAPKOriginWarnings": "Visa inte APK-ursprung varningar", "dontShowAPKOriginWarnings": "Visa inte APK-ursprung varningar",
"moveNonInstalledAppsToBottom": "Flytta icke-installerade appar till botten av appvyn", "moveNonInstalledAppsToBottom": "Flytta icke-installerade appar till botten av appvyn",
"gitlabPATLabel": "GitLab Personal Access Token", "gitlabPATLabel": "Personligt åtkomsttoken för GitLab",
"about": "Om", "about": "Om",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Kolla efter uppdateringar vid start", "checkOnStart": "Kolla efter uppdateringar vid start",
@@ -299,6 +302,8 @@
"note": "Anmärkning", "note": "Anmärkning",
"selfHostedNote": "Rullgardinsmenyn \"{}\" kan användas för att nå självhostade/anpassade instanser av valfri källa.", "selfHostedNote": "Rullgardinsmenyn \"{}\" kan användas för att nå självhostade/anpassade instanser av valfri källa.",
"badDownload": "APK kunde inte analyseras (inkompatibel eller partiell nedladdning)", "badDownload": "APK kunde inte analyseras (inkompatibel eller partiell nedladdning)",
"beforeNewInstallsShareToAppVerifier": "Dela nya appar med AppVerifier (om tillgängligt)",
"appVerifierInstructionToast": "Dela till AppVerifier och återvänd sedan hit när du är klar.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Ta Bort App?", "one": "Ta Bort App?",
"other": "Ta Bort Appar?" "other": "Ta Bort Appar?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "Sürüm Algılama Devre Dışı", "disableVersionDetection": "Sürüm Algılama Devre Dışı",
"noVersionDetectionExplanation": "Bu seçenek, sürüm algılamanın doğru çalışmadığı uygulamalar için kullanılmalıdır.", "noVersionDetectionExplanation": "Bu seçenek, sürüm algılamanın doğru çalışmadığı uygulamalar için kullanılmalıdır.",
"downloadingX": "{} İndiriliyor", "downloadingX": "{} İndiriliyor",
"downloadX": "İndir {}",
"downloadedX": "İndirildi {}",
"releaseAsset": "Varlık Serbest Bırakma",
"downloadNotifDescription": "Bir uygulamanın indirme sürecinde ilerlemeyi bildiren bir bildirim", "downloadNotifDescription": "Bir uygulamanın indirme sürecinde ilerlemeyi bildiren bir bildirim",
"noAPKFound": "APK bulunamadı", "noAPKFound": "APK bulunamadı",
"noVersionDetection": "Sürüm Algılanamıyor", "noVersionDetection": "Sürüm Algılanamıyor",
@@ -299,6 +302,8 @@
"note": "Not", "note": "Not",
"selfHostedNote": "\"{}\" açılır menüsü, herhangi bir kaynağın kendi kendine barındırılan/özel örneklerine ulaşmak için kullanılabilir.", "selfHostedNote": "\"{}\" açılır menüsü, herhangi bir kaynağın kendi kendine barındırılan/özel örneklerine ulaşmak için kullanılabilir.",
"badDownload": "APK ayrıştırılamadı (uyumsuz veya kısmi indirme)", "badDownload": "APK ayrıştırılamadı (uyumsuz veya kısmi indirme)",
"beforeNewInstallsShareToAppVerifier": "Yeni Uygulamaları AppVerifier ile paylaşın (varsa)",
"appVerifierInstructionToast": "AppVerifier ile paylaşın, hazır olduğunuzda buraya dönün.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Uygulamayı Kaldır?", "one": "Uygulamayı Kaldır?",
"other": "Uygulamaları Kaldır?" "other": "Uygulamaları Kaldır?"

View File

@@ -28,13 +28,13 @@
"xIsTrackOnly": "{} - тільки відстежування", "xIsTrackOnly": "{} - тільки відстежування",
"source": "Джерело", "source": "Джерело",
"app": "застосунок", "app": "застосунок",
"appsFromSourceAreTrackOnly": "Додатки з цього джерела є лише для відстежування.", "appsFromSourceAreTrackOnly": "Застосунки з цього джерела є лише для відстежування.",
"youPickedTrackOnly": "Ви вибрали опцію лише для відстежування.", "youPickedTrackOnly": "Ви вибрали опцію лише для відстежування.",
"trackOnlyAppDescription": "Застосунок буде відстежуватися для оновлень, але Obtainium не зможе його завантажити або встановити.", "trackOnlyAppDescription": "Застосунок буде відстежуватися для оновлень, але Obtainium не зможе його завантажити або встановити.",
"cancelled": "Скасовано", "cancelled": "Скасовано",
"appAlreadyAdded": "Застосунок вже додано", "appAlreadyAdded": "Застосунок вже додано",
"alreadyUpToDateQuestion": "Застосунок вже оновлено?", "alreadyUpToDateQuestion": "Застосунок вже оновлено?",
"addApp": "Додати Застосунок", "addApp": "Додати",
"appSourceURL": "URL-адреса джерела застосунку", "appSourceURL": "URL-адреса джерела застосунку",
"error": "Помилка", "error": "Помилка",
"add": "Додати", "add": "Додати",
@@ -44,10 +44,10 @@
"supportedSources": "Підтримувані джерела", "supportedSources": "Підтримувані джерела",
"trackOnlyInBrackets": "(Тільки для відстеження)", "trackOnlyInBrackets": "(Тільки для відстеження)",
"searchableInBrackets": "(Можливий пошук)", "searchableInBrackets": "(Можливий пошук)",
"appsString": "Додатки", "appsString": "Застосунки",
"noApps": "Додатків немає", "noApps": "Застосунків немає",
"noAppsForFilter": "Додатків для фільтрації немає", "noAppsForFilter": "Застосунків для фільтрації немає",
"byX": "За {}", "byX": "Від {}",
"percentProgress": "Прогрес: {}%", "percentProgress": "Прогрес: {}%",
"pleaseWait": "Будь ласка, зачекайте", "pleaseWait": "Будь ласка, зачекайте",
"updateAvailable": "Доступно оновлення", "updateAvailable": "Доступно оновлення",
@@ -56,35 +56,35 @@
"selectAll": "Вибрати все", "selectAll": "Вибрати все",
"deselectX": "Скасувати вибір {}", "deselectX": "Скасувати вибір {}",
"xWillBeRemovedButRemainInstalled": "{} буде видалено з Obtainium, але залишиться встановленим на пристрої.", "xWillBeRemovedButRemainInstalled": "{} буде видалено з Obtainium, але залишиться встановленим на пристрої.",
"removeSelectedAppsQuestion": "Видалити вибрані додатки?", "removeSelectedAppsQuestion": "Видалити вибрані застосунки?",
"removeSelectedApps": "Видалити вибрані додатки", "removeSelectedApps": "Видалити вибрані застосунки",
"updateX": "Оновити {}", "updateX": "Оновити {}",
"installX": "Встановити {}", "installX": "Встановити {}",
"markXTrackOnlyAsUpdated": "Позначити {}\n(Тільки відстежування)\nяк оновлено", "markXTrackOnlyAsUpdated": "Позначити {}\n(Тільки відстежування)\nяк оновлено",
"changeX": "Змінити {}", "changeX": "Змінити {}",
"installUpdateApps": "Встановити/Оновити додатки", "installUpdateApps": "Встановити/Оновити застосунки",
"installUpdateSelectedApps": "Встановити/Оновити вибрані додатки", "installUpdateSelectedApps": "Встановити/Оновити вибрані застосунки",
"markXSelectedAppsAsUpdated": "Позначити {} вибрані додатки як оновлені?", "markXSelectedAppsAsUpdated": "Позначити {} вибрані застосунки як оновлені?",
"no": "Ні", "no": "Ні",
"yes": "Так", "yes": "Так",
"markSelectedAppsUpdated": "Позначити вибрані додатки як оновлені", "markSelectedAppsUpdated": "Позначити вибрані застосунки як оновлені",
"pinToTop": "Закріпити угорі", "pinToTop": "Закріпити угорі",
"unpinFromTop": "Відкріпити зверху", "unpinFromTop": "Відкріпити зверху",
"resetInstallStatusForSelectedAppsQuestion": "Скинути статус встановлення для вибраних додатків?", "resetInstallStatusForSelectedAppsQuestion": "Скинути статус встановлення для вибраних застосунків?",
"installStatusOfXWillBeResetExplanation": "Статус встановлення будь-яких вибраних додатків буде скинутий.\n\nЦе може допомогти, коли версія застосунку, відображена в Obtainium, є неправильною через невдалі оновлення або інші проблеми.", "installStatusOfXWillBeResetExplanation": "Статус встановлення будь-яких вибраних застосунків буде скинутий.\n\nЦе може допомогти, коли версія застосунку, відображена в Obtainium, є неправильною через невдалі оновлення або інші проблеми.",
"customLinkMessage": "Ці посилання працюють на пристроях з встановленим Obtainium", "customLinkMessage": "Ці посилання працюють на пристроях з встановленим Obtainium",
"shareAppConfigLinks": "Поділитися посиланнями на конфігурацію Застосунку як HTML", "shareAppConfigLinks": "Поділитися посиланнями на конфігурацію Застосунку як HTML",
"shareSelectedAppURLs": "Поділитися вибраними URL-адресами додатків", "shareSelectedAppURLs": "Поділитися вибраними URL-адресами застосунків",
"resetInstallStatus": "Скинути статус встановлення", "resetInstallStatus": "Скинути статус встановлення",
"more": "Більше", "more": "Більше",
"removeOutdatedFilter": "Видалити фільтр застарілих додатків", "removeOutdatedFilter": "Видалити фільтр застарілих застосунків",
"showOutdatedOnly": "Показати лише застарілі додатки", "showOutdatedOnly": "Показати лише застарілі застосунки",
"filter": "Фільтр", "filter": "Фільтр",
"filterApps": "Фільтрувати додатки", "filterApps": "Фільтрувати застосунки",
"appName": "Назва застосунку", "appName": "Назва застосунку",
"author": "Автор", "author": "Автор",
"upToDateApps": "Актуальні додатки", "upToDateApps": "Актуальні застосунки",
"nonInstalledApps": "Невстановлені додатки", "nonInstalledApps": "Невстановлені застосунки",
"importExport": "Імпорт/Експорт", "importExport": "Імпорт/Експорт",
"settings": "Налаштування", "settings": "Налаштування",
"exportedTo": "Експортовано в {}", "exportedTo": "Експортовано в {}",
@@ -94,14 +94,14 @@
"obtainiumImport": "Імпорт в Obtainium", "obtainiumImport": "Імпорт в Obtainium",
"importFromURLList": "Імпорт зі списку URL-адрес", "importFromURLList": "Імпорт зі списку URL-адрес",
"searchQuery": "Пошуковий запит", "searchQuery": "Пошуковий запит",
"appURLList": "Список URL-адрес додатків", "appURLList": "Список URL-адрес застосунків",
"line": "Лінія", "line": "Лінія",
"searchX": "Пошук {}", "searchX": "Пошук {}",
"noResults": "Результати відсутні", "noResults": "Результати відсутні",
"importX": "Імпорт {}", "importX": "Імпорт {}",
"importedAppsIdDisclaimer": "Імпортовані додатки можуть неправильно відображатися як \"Не встановлені\".\nДля виправлення цього перевстановіть їх через Obtainium.\nЦе не повинно вплинути на дані додатків.\n\nПов'язано лише з URL-адресами та імпортом від третіх сторін.", "importedAppsIdDisclaimer": "Імпортовані застосунки можуть неправильно відображатися як \"Не встановлені\".\nДля виправлення цього перевстановіть їх через Obtainium.\nЦе не повинно вплинути на дані застосунків.\n\nПов'язано лише з URL-адресами та імпортом від третіх сторін.",
"importErrors": "Помилки імпорту", "importErrors": "Помилки імпорту",
"importedXOfYApps": "Імпортовано {} з {} додатків.", "importedXOfYApps": "Імпортовано {} з {} застосунків.",
"followingURLsHadErrors": "Помилки в наступних URL-адресах:", "followingURLsHadErrors": "Помилки в наступних URL-адресах:",
"selectURL": "Вибрати URL", "selectURL": "Вибрати URL",
"selectURLs": "Вибрати URL-адреси", "selectURLs": "Вибрати URL-адреси",
@@ -110,19 +110,19 @@
"dark": "Темна", "dark": "Темна",
"light": "Світла", "light": "Світла",
"followSystem": "Дотримуватися системи", "followSystem": "Дотримуватися системи",
"useBlackTheme": "Використовувати чисто чорну темну тему", "useBlackTheme": "Використовувати чорну тему (Amoled)",
"appSortBy": "Сортувати додатки за", "appSortBy": "Сортувати застосунки за",
"authorName": "Автор/Назва", "authorName": "Автор/Назва",
"nameAuthor": "Назва/Автор", "nameAuthor": "Назва/Автор",
"asAdded": "За додаванням", "asAdded": "За додаванням",
"appSortOrder": "Порядок сортування додатків", "appSortOrder": "Порядок сортування застосунків",
"ascending": "За зростанням", "ascending": "За зростанням",
"descending": "За спаданням", "descending": "За спаданням",
"bgUpdateCheckInterval": "Інтервал перевірки оновлень у фоновому режимі", "bgUpdateCheckInterval": "Інтервал перевірки оновлень у фоновому режимі",
"neverManualOnly": "Ніколи - Тільки вручну", "neverManualOnly": "Ніколи - Тільки вручну",
"appearance": "Вигляд", "appearance": "Вигляд",
"showWebInAppView": "Показати джерело застосунку у вигляді веб-сторінки", "showWebInAppView": "Показати джерело застосунку у вигляді веб-сторінки",
"pinUpdates": "Закріпити оновлення у верхній частині вигляду додатків", "pinUpdates": "Закріпити оновлення у верхній частині вигляду застосунків",
"updates": "Оновлення", "updates": "Оновлення",
"sourceSpecific": "Певне джерело", "sourceSpecific": "Певне джерело",
"appSource": "Джерело застосунку", "appSource": "Джерело застосунку",
@@ -139,23 +139,23 @@
"warning": "Попередження", "warning": "Попередження",
"sourceIsXButPackageFromYPrompt": "Джерело застосунку - '{}' але пакет випуску походить з '{}'. Продовжити?", "sourceIsXButPackageFromYPrompt": "Джерело застосунку - '{}' але пакет випуску походить з '{}'. Продовжити?",
"updatesAvailable": "Доступні оновлення", "updatesAvailable": "Доступні оновлення",
"updatesAvailableNotifDescription": "Повідомляє користувача, що доступні оновлення для одного чи декількох додатків, які відстежує Obtainium", "updatesAvailableNotifDescription": "Повідомляє користувача, що доступні оновлення для одного чи декількох застосунків, які відстежує Obtainium",
"noNewUpdates": "Немає нових оновлень.", "noNewUpdates": "Немає нових оновлень.",
"xHasAnUpdate": "{} має оновлення.", "xHasAnUpdate": "{} має оновлення.",
"appsUpdated": "Додатки оновлено", "appsUpdated": "Застосунки оновлено",
"appsUpdatedNotifDescription": "Повідомляє користувача, що оновлення одного чи декількох додатків було застосовано в фоновому режимі", "appsUpdatedNotifDescription": "Повідомляє користувача, що оновлення одного чи декількох застосунків було застосовано в фоновому режимі",
"xWasUpdatedToY": "{} було оновлено до {}.", "xWasUpdatedToY": "{} було оновлено до {}.",
"errorCheckingUpdates": "Помилка перевірки оновлень", "errorCheckingUpdates": "Помилка перевірки оновлень",
"errorCheckingUpdatesNotifDescription": "Повідомлення, яке з'являється, коли перевірка оновлень в фоновому режимі завершується невдачею", "errorCheckingUpdatesNotifDescription": "Повідомлення, яке з'являється, коли перевірка оновлень в фоновому режимі завершується невдачею",
"appsRemoved": "Додатки видалено", "appsRemoved": "Застосунки видалено",
"appsRemovedNotifDescription": "Повідомляє користувача, що один чи декілька додатків були видалені через помилки при завантаженні", "appsRemovedNotifDescription": "Повідомляє користувача, що один чи декілька застосунків були видалені через помилки при завантаженні",
"xWasRemovedDueToErrorY": "{} було видалено через цю помилку: {}", "xWasRemovedDueToErrorY": "{} було видалено через цю помилку: {}",
"completeAppInstallation": "Завершення установки застосунку", "completeAppInstallation": "Завершення установки застосунку",
"obtainiumMustBeOpenToInstallApps": "Для встановлення додатків Obtainium має бути відкритий", "obtainiumMustBeOpenToInstallApps": "Для встановлення застосунків Obtainium має бути відкритий",
"completeAppInstallationNotifDescription": "Прохання користувача повернутися до Obtainium для завершення установки застосунку", "completeAppInstallationNotifDescription": "Прохання користувача повернутися до Obtainium для завершення установки застосунку",
"checkingForUpdates": "Перевірка оновлень", "checkingForUpdates": "Перевірка оновлень",
"checkingForUpdatesNotifDescription": "Тимчасове повідомлення, яке з'являється при перевірці оновлень", "checkingForUpdatesNotifDescription": "Тимчасове повідомлення, яке з'являється при перевірці оновлень",
"pleaseAllowInstallPerm": "Будь ласка, дозвольте Obtainium встановлювати додатки", "pleaseAllowInstallPerm": "Будь ласка, дозвольте Obtainium встановлювати застосунки",
"trackOnly": "Тільки відстеження", "trackOnly": "Тільки відстеження",
"errorWithHttpStatusCode": "Помилка {} HTTP-коду", "errorWithHttpStatusCode": "Помилка {} HTTP-коду",
"versionCorrectionDisabled": "Виправлення версії вимкнено (здається, плагін не працює)", "versionCorrectionDisabled": "Виправлення версії вимкнено (здається, плагін не працює)",
@@ -171,7 +171,7 @@
"appIdOrName": "Ідентифікатор або назва застосунку", "appIdOrName": "Ідентифікатор або назва застосунку",
"appId": "Ідентифікатор застосунку", "appId": "Ідентифікатор застосунку",
"appWithIdOrNameNotFound": "Застосунок з таким ідентифікатором або назвою не знайдено", "appWithIdOrNameNotFound": "Застосунок з таким ідентифікатором або назвою не знайдено",
"reposHaveMultipleApps": "Сховища можуть містити кілька додатків", "reposHaveMultipleApps": "Сховища можуть містити кілька застосунків",
"fdroidThirdPartyRepo": "F-Droid Стороннє сховище", "fdroidThirdPartyRepo": "F-Droid Стороннє сховище",
"steamMobile": "Мобільний Steam", "steamMobile": "Мобільний Steam",
"steamChat": "Чат Steam", "steamChat": "Чат Steam",
@@ -181,8 +181,11 @@
"markUpdated": "Позначити як оновлене", "markUpdated": "Позначити як оновлене",
"additionalOptions": "Додаткові опції", "additionalOptions": "Додаткові опції",
"disableVersionDetection": "Вимкнути визначення версії", "disableVersionDetection": "Вимкнути визначення версії",
"noVersionDetectionExplanation": "Цю опцію слід використовувати лише для додатків, де визначення версії працює неправильно.", "noVersionDetectionExplanation": "Цю опцію слід використовувати лише для застосунків, де визначення версії працює неправильно.",
"downloadingX": "Завантаження {}", "downloadingX": "Завантаження {}",
"downloadX": "Завантажити {}",
"downloadedX": "Завантажено {}",
"releaseAsset": "Звільнити актив",
"downloadNotifDescription": "Повідомляє користувача про прогрес завантаження застосунку", "downloadNotifDescription": "Повідомляє користувача про прогрес завантаження застосунку",
"noAPKFound": "APK не знайдено", "noAPKFound": "APK не знайдено",
"noVersionDetection": "Визначення версії відключено", "noVersionDetection": "Визначення версії відключено",
@@ -192,19 +195,19 @@
"noCategory": "Без категорії", "noCategory": "Без категорії",
"noCategories": "Немає категорій", "noCategories": "Немає категорій",
"deleteCategoriesQuestion": "Видалити категорії?", "deleteCategoriesQuestion": "Видалити категорії?",
"categoryDeleteWarning": "Усі додатки у видалених категоріях будуть переведені у некатегоризовані.", "categoryDeleteWarning": "Усі застосунки у видалених категоріях будуть переведені у некатегоризовані.",
"addCategory": "Додати категорію", "addCategory": "Додати категорію",
"label": "Мітка", "label": "Мітка",
"language": "Мова", "language": "Мова",
"copiedToClipboard": "Скопійовано в буфер обміну", "copiedToClipboard": "Скопійовано в буфер обміну",
"storagePermissionDenied": "Відмовлено у дозволі на доступ до сховища", "storagePermissionDenied": "Відмовлено у дозволі на доступ до сховища",
"selectedCategorizeWarning": "Це замінить будь-які існуючі налаштування категорій для вибраних додатків.", "selectedCategorizeWarning": "Це замінить будь-які існуючі налаштування категорій для вибраних застосунків.",
"filterAPKsByRegEx": "Фільтрувати APK за регулярним виразом", "filterAPKsByRegEx": "Фільтрувати APK за регулярним виразом",
"removeFromObtainium": "Видалити з Obtainium", "removeFromObtainium": "Видалити з Obtainium",
"uninstallFromDevice": "Видалити з пристрою", "uninstallFromDevice": "Видалити з пристрою",
"onlyWorksWithNonVersionDetectApps": "Працює лише з застосунками з вимкненим визначенням версії.", "onlyWorksWithNonVersionDetectApps": "Працює лише з застосунками з вимкненим визначенням версії.",
"releaseDateAsVersion": "Використовувати дату випуску як рядок версії", "releaseDateAsVersion": "Використовувати дату випуску як рядок версії",
"releaseDateAsVersionExplanation": "Цю опцію слід використовувати лише для додатків, де визначення версії працює неправильно, але є дата випуску.", "releaseDateAsVersionExplanation": "Цю опцію слід використовувати лише для застосунків, де визначення версії працює неправильно, але є дата випуску.",
"changes": "Зміни", "changes": "Зміни",
"releaseDate": "Дата випуску", "releaseDate": "Дата випуску",
"importFromURLsInFile": "Імпорт з URL-адрес у файлі (наприклад, OPML)", "importFromURLsInFile": "Імпорт з URL-адрес у файлі (наприклад, OPML)",
@@ -217,13 +220,13 @@
"dontShowAgain": "Не показувати це знову", "dontShowAgain": "Не показувати це знову",
"dontShowTrackOnlyWarnings": "Не показувати попередження про 'Тільки відстеження'", "dontShowTrackOnlyWarnings": "Не показувати попередження про 'Тільки відстеження'",
"dontShowAPKOriginWarnings": "Не показувати попередження про походження APK", "dontShowAPKOriginWarnings": "Не показувати попередження про походження APK",
"moveNonInstalledAppsToBottom": "Перемістити невстановлені додатки вниз у перегляді додатків", "moveNonInstalledAppsToBottom": "Перемістити невстановлені застосунки вниз у перегляді застосунків",
"gitlabPATLabel": "Особистий токен GitLab (Увімкнення пошуку та краще виявлення APK)", "gitlabPATLabel": "Особистий токен GitLab (Увімкнення пошуку та краще виявлення APK)",
"about": "Про програму", "about": "Про програму",
"requiresCredentialsInSettings": "{} потребує додаткових облікових даних (у налаштуваннях)", "requiresCredentialsInSettings": "{} потребує додаткових облікових даних (у налаштуваннях)",
"checkOnStart": "Перевірити наявність оновлень при запуску", "checkOnStart": "Перевірити наявність оновлень при запуску",
"tryInferAppIdFromCode": "Спробувати вивести ідентифікатор застосунку з вихідного коду", "tryInferAppIdFromCode": "Спробувати вивести ідентифікатор застосунку з вихідного коду",
"removeOnExternalUninstall": "Автоматично видаляти додатки, які було видалено зовнішнім чином", "removeOnExternalUninstall": "Автоматично видаляти застосунки, які було видалено зовнішнім чином",
"pickHighestVersionCode": "Автоматично вибрати APK з найвищим кодом версії", "pickHighestVersionCode": "Автоматично вибрати APK з найвищим кодом версії",
"checkUpdateOnDetailPage": "Перевіряти наявність оновлень при відкритті сторінки деталей застосунку", "checkUpdateOnDetailPage": "Перевіряти наявність оновлень при відкритті сторінки деталей застосунку",
"disablePageTransitions": "Вимкнути анімації переходів між сторінками", "disablePageTransitions": "Вимкнути анімації переходів між сторінками",
@@ -232,15 +235,14 @@
"addInfoBelow": "Додати цю інформацію нижче.", "addInfoBelow": "Додати цю інформацію нижче.",
"addInfoInSettings": "Додати цю інформацію у налаштуваннях.", "addInfoInSettings": "Додати цю інформацію у налаштуваннях.",
"githubSourceNote": "Лімітування швидкості GitHub можна уникнути, використовуючи ключ API.", "githubSourceNote": "Лімітування швидкості GitHub можна уникнути, використовуючи ключ API.",
"gitlabSourceNote": "Вилучення APK з GitLab може не працювати без ключа API.",
"sortByLastLinkSegment": "Сортувати лише за останнім сегментом посилання", "sortByLastLinkSegment": "Сортувати лише за останнім сегментом посилання",
"filterReleaseNotesByRegEx": "Фільтрувати примітки до релізу за регулярним виразом", "filterReleaseNotesByRegEx": "Фільтрувати примітки до релізу за регулярним виразом",
"customLinkFilterRegex": "Фільтр кастомного посилання на APK за регулярним виразом (за замовчуванням '.apk$')", "customLinkFilterRegex": "Фільтр кастомного посилання на APK за регулярним виразом (за замовчуванням '.apk$')",
"appsPossiblyUpdated": "Оновлення додатків спробовано", "appsPossiblyUpdated": "Спроб оновлення застосунків",
"appsPossiblyUpdatedNotifDescription": "Повідомляє користувача, що оновлення одного або декількох додатків можливо були застосовані в фоновому режимі", "appsPossiblyUpdatedNotifDescription": "Повідомляє користувача, що оновлення одного або декількох застосунків можливо були застосовані в фоновому режимі",
"xWasPossiblyUpdatedToY": "{} можливо було оновлено до {}.", "xWasPossiblyUpdatedToY": "{} можливо було оновлено до {}.",
"enableBackgroundUpdates": "Увімкнути оновлення в фоновому режимі", "enableBackgroundUpdates": "Увімкнути оновлення в фоновому режимі",
"backgroundUpdateReqsExplanation": "Оновлення в фоновому режимі може бути неможливим для всіх додатків.", "backgroundUpdateReqsExplanation": "Оновлення в фоновому режимі може бути неможливим для всіх застосунків.",
"backgroundUpdateLimitsExplanation": "Успіх фонової установки може бути визначений лише після відкриття Obtainium.", "backgroundUpdateLimitsExplanation": "Успіх фонової установки може бути визначений лише після відкриття Obtainium.",
"verifyLatestTag": "Перевірити тег 'latest'", "verifyLatestTag": "Перевірити тег 'latest'",
"intermediateLinkRegex": "Фільтр для 'Проміжного' Посилання для Відвідування", "intermediateLinkRegex": "Фільтр для 'Проміжного' Посилання для Відвідування",
@@ -269,14 +271,14 @@
"installing": "Встановлення", "installing": "Встановлення",
"skipUpdateNotifications": "Пропустити сповіщення про оновлення", "skipUpdateNotifications": "Пропустити сповіщення про оновлення",
"updatesAvailableNotifChannel": "Доступні оновлення", "updatesAvailableNotifChannel": "Доступні оновлення",
"appsUpdatedNotifChannel": "Додатки оновлені", "appsUpdatedNotifChannel": "Застосунки оновлені",
"appsPossiblyUpdatedNotifChannel": "Спроба оновлення додатків", "appsPossiblyUpdatedNotifChannel": "Спроба оновлення застосунків",
"errorCheckingUpdatesNotifChannel": "Помилка перевірки оновлень", "errorCheckingUpdatesNotifChannel": "Помилка перевірки оновлень",
"appsRemovedNotifChannel": "Додатки видалені", "appsRemovedNotifChannel": "Застосунки видалені",
"downloadingXNotifChannel": "Завантаження {}", "downloadingXNotifChannel": "Завантаження {}",
"completeAppInstallationNotifChannel": "Завершення встановлення застосунку", "completeAppInstallationNotifChannel": "Завершення встановлення застосунку",
"checkingForUpdatesNotifChannel": "Перевірка оновлень", "checkingForUpdatesNotifChannel": "Перевірка оновлень",
"onlyCheckInstalledOrTrackOnlyApps": "Перевіряти лише встановлені та додатки, які відстежуються для оновлень", "onlyCheckInstalledOrTrackOnlyApps": "Перевіряти лише встановлені та застосунки, які відстежуються для оновлень",
"supportFixedAPKURL": "Підтримка фіксованих посилань на APK", "supportFixedAPKURL": "Підтримка фіксованих посилань на APK",
"selectX": "Вибрати {}", "selectX": "Вибрати {}",
"parallelDownloads": "Дозволити паралельні завантаження", "parallelDownloads": "Дозволити паралельні завантаження",
@@ -300,9 +302,11 @@
"note": "Примітка", "note": "Примітка",
"selfHostedNote": "Випадаючий список \"{}\" може використовуватися для доступу до власних/призначених для самостійного використання екземплярів будь-якого джерела.", "selfHostedNote": "Випадаючий список \"{}\" може використовуватися для доступу до власних/призначених для самостійного використання екземплярів будь-якого джерела.",
"badDownload": "APK не вдалося розпарсити (несумісний або часткове завантаження)", "badDownload": "APK не вдалося розпарсити (несумісний або часткове завантаження)",
"beforeNewInstallsShareToAppVerifier": "Діліться новими додатками з AppVerifier (якщо доступно)",
"appVerifierInstructionToast": "Надішліть на AppVerifier, а потім поверніться сюди, коли будете готові.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Видалити Застосунок?", "one": "Видалити застосунок?",
"other": "Видалити додатки?" "other": "Видалити застосунки?"
}, },
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Забагато запитів (обмеження швидкості) - повторіть спробу через {} хвилину", "one": "Забагато запитів (обмеження швидкості) - повторіть спробу через {} хвилину",
@@ -313,44 +317,44 @@
"other": "Помилка перевірки оновлень у фоновому режимі - спробую знову через {} хвилин" "other": "Помилка перевірки оновлень у фоновому режимі - спробую знову через {} хвилин"
}, },
"bgCheckFoundUpdatesWillNotifyIfNeeded": { "bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Фонова перевірка оновлень знайшла {} оновлення - сповістить користувача, якщо це необхідно", "one": "Фонова перевірка оновлень знайшла {} оновлення - сповістити користувача, якщо це необхідно",
"other": "Фонова перевірка оновлень знайшла {} оновлень - сповістить користувача, якщо це необхідно" "other": "Фонова перевірка оновлень знайшла {} оновлень - сповістити користувача, якщо це необхідно"
}, },
"apps": { "apps": {
"one": "{} Застосунок", "one": "{} застосунок",
"other": "{} Додатки" "other": "{} застосунки"
}, },
"url": { "url": {
"one": "{} URL", "one": "{} URL-адреса",
"other": "{} URL-адреси" "other": "{} URL-адреси"
}, },
"minute": { "minute": {
"one": "{} Хвилина", "one": "{} хвилина",
"other": "{} Хвилин" "other": "{} хвилин"
}, },
"hour": { "hour": {
"one": "{} Година", "one": "{} година",
"other": "{} Годин" "other": "{} годин"
}, },
"day": { "day": {
"one": "{} День", "one": "{} день",
"other": "{} Днів" "other": "{} днів"
}, },
"clearedNLogsBeforeXAfterY": { "clearedNLogsBeforeXAfterY": {
"one": "Очищено {n} журнал (до = {before}, після = {after})", "one": "Очищено {n} журнал (до = {before}, після = {after})",
"other": "Очищено {n} журналів (до = {before}, після = {after})" "other": "Очищено {n} журналів (до = {before}, після = {after})"
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "{} і 1 інше Застосунок мають оновлення.", "one": "{} та ще 1 застосунок мають оновлення.",
"other": "{} і {} інших додатки мають оновлення." "other": "{} та ще {} застосунки мають оновлення."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} і 1 інше Застосунок було оновлено.", "one": "{} та ще 1 застосунок було оновлено.",
"other": "{} і {} інших додатків було оновлено." "other": "{} та ще {} застосунків було оновлено."
}, },
"xAndNMoreUpdatesPossiblyInstalled": { "xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} і 1 інше Застосунок можливо було оновлено.", "one": "{} та ще 1 застосунок можливо було оновлено.",
"other": "{} і {} інших додатків можливо було оновлено." "other": "{} та ще {} застосунків можливо було оновлено."
}, },
"apk": { "apk": {
"one": "{} APK", "one": "{} APK",

View File

@@ -11,7 +11,7 @@
"unexpectedError": "Lỗi không mong đợi", "unexpectedError": "Lỗi không mong đợi",
"ok": "OK", "ok": "OK",
"and": "và", "and": "và",
"githubPATLabel": "GitHub Token (Tăng tốc độ, giới hạn)", "githubPATLabel": "Token truy cập cá nhân GitHub (Cải thiện tốc độ giới hạn)",
"includePrereleases": "Bao gồm các bản phát hành trước", "includePrereleases": "Bao gồm các bản phát hành trước",
"fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn", "fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn",
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy", "filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
@@ -183,6 +183,9 @@
"disableVersionDetection": "Tắt tính năng phát hiện phiên bản", "disableVersionDetection": "Tắt tính năng phát hiện phiên bản",
"noVersionDetectionExplanation": "Chỉ nên sử dụng tùy chọn này cho Ứng dụng mà tính năng phát hiện phiên bản không hoạt động chính xác.", "noVersionDetectionExplanation": "Chỉ nên sử dụng tùy chọn này cho Ứng dụng mà tính năng phát hiện phiên bản không hoạt động chính xác.",
"downloadingX": "Đang tải xuống {}", "downloadingX": "Đang tải xuống {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Thông báo cho người dùng về tiến trình tải xuống Ứng dụng", "downloadNotifDescription": "Thông báo cho người dùng về tiến trình tải xuống Ứng dụng",
"noAPKFound": "Không tìm thấy APK", "noAPKFound": "Không tìm thấy APK",
"noVersionDetection": "Không phát hiện phiên bản", "noVersionDetection": "Không phát hiện phiên bản",
@@ -218,7 +221,7 @@
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ theo dõi'", "dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ theo dõi'",
"dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK", "dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK",
"moveNonInstalledAppsToBottom": "Chuyển Ứng dụng chưa được cài đặt xuống cuối danh sách", "moveNonInstalledAppsToBottom": "Chuyển Ứng dụng chưa được cài đặt xuống cuối danh sách",
"gitlabPATLabel": "GitLab Token", "gitlabPATLabel": "Token truy cập cá nhân GitLab",
"about": "Giới thiệu", "about": "Giới thiệu",
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Thiết đặt)", "requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Thiết đặt)",
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động", "checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
@@ -299,6 +302,8 @@
"note": "Ghi chú", "note": "Ghi chú",
"selfHostedNote": "Trình đơn thả xuống \"{}\" có thể được dùng để tiếp cận các phiên bản tự lưu trữ/tùy chỉnh của bất kỳ nguồn nào.", "selfHostedNote": "Trình đơn thả xuống \"{}\" có thể được dùng để tiếp cận các phiên bản tự lưu trữ/tùy chỉnh của bất kỳ nguồn nào.",
"badDownload": "Không thể phân tích cú pháp APK (tải xuống một phần hoặc không tương thích)", "badDownload": "Không thể phân tích cú pháp APK (tải xuống một phần hoặc không tương thích)",
"beforeNewInstallsShareToAppVerifier": "Chia sẻ ứng dụng mới với AppVerifier (nếu có)",
"appVerifierInstructionToast": "Chia sẻ lên AppVerifier, sau đó quay lại đây khi sẵn sàng.",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Gỡ ứng dụng?", "one": "Gỡ ứng dụng?",
"other": "Gỡ ứng dụng?" "other": "Gỡ ứng dụng?"

View File

@@ -183,6 +183,9 @@
"disableVersionDetection": "禁用版本检测", "disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。", "noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
"downloadingX": "正在下载“{}”", "downloadingX": "正在下载“{}”",
"downloadX": "下载 {}",
"downloadedX": "下载 {}",
"releaseAsset": "释放资产",
"downloadNotifDescription": "提示应用的下载进度", "downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件", "noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测", "noVersionDetection": "禁用版本检测",
@@ -299,6 +302,8 @@
"note": "备注", "note": "备注",
"selfHostedNote": "可以通过“{}”下拉菜单来指向任意来源的自托管/自定义实例。", "selfHostedNote": "可以通过“{}”下拉菜单来指向任意来源的自托管/自定义实例。",
"badDownload": "无法解析 APK 文件(不兼容或文件不完整)", "badDownload": "无法解析 APK 文件(不兼容或文件不完整)",
"beforeNewInstallsShareToAppVerifier": "与 AppVerifier 共享新应用程序(如有)",
"appVerifierInstructionToast": "分享到 AppVerifier准备就绪后返回此处。",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"

View File

@@ -0,0 +1,54 @@
<p>Obtainium позволяет вам устанавливать и обновлять приложения прямо с их объявлений о выпусках и получать уведомления о новых выпусках.</p>
<p>Для деталей читайте <a href="https://github.com/ImranR98/Obtainium/wiki">Вики</a></p>
<p>
<b>Поддерживаемые источники приложений:</b>
</p>
<ul>
<li>
<p>Свободное ПО - Общие:</p>
<ul>
<li>GitHub</li>
<li>GitLab</li>
<li>Codeberg</li>
<li>F-Droid</li>
<li>Third Party F-Droid Repos</li>
<li>IzzyOnDroid</li>
<li>SourceForge</li>
<li>SourceHut</li>
</ul>
</li>
<li>
<p>Другие - Общие:</p>
<ul>
<li>APKPure</li>
<li>Aptoide</li>
<li>Uptodowng</li>
<li>APKMirror (Track-Only)</li>
<li>Huawei AppGallery</li>
<li>Jenkins Jobs</li>
</ul>
</li>
<li>
<p>Свободное ПО - Для отдельных приложений:</p>
<ul>
<li>Mullvad</li>
<li>Signal</li>
<li>VLC</li>
</ul>
</li>
<li>
<p>Другие - Для отдельных приложений:</p>
<ul>
<li>WhatsApp</li>
<li>Telegram App</li>
<li>Neutron Code</li>
</ul>
</li>
<li><p>"HTML" (Подстраховка): Любой другой URL-адрес, который возвращает HTML-страницу со ссылками на APK-файлы.</p></li>
</ul>
<p>
<b>Ограничения:</b>
</p>
<p>
Для некоторых источников данные собираются с помощью веб-скрапинга и могут легко сломаться из-за изменений в дизайне веб-сайта. В таких случаях более надежные методы могут быть недоступны.
</p>

View File

@@ -0,0 +1 @@
Получайте обновления приложений прямо из источника

View File

@@ -271,17 +271,14 @@ class GitHub extends AppSource {
} }
} }
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) => List<MapEntry<String, String>> getReleaseAssetUrls(dynamic release) =>
(release['assets'] as List<dynamic>?) (release['assets'] as List<dynamic>?)?.map((e) {
?.map((e) { return (e['name'] != null) &&
return (e['name'] != null) && ((e['url'] ?? e['browser_download_url']) != null)
((e['url'] ?? e['browser_download_url']) != null) ? MapEntry(e['name'] as String,
? MapEntry(e['name'] as String, (e['url'] ?? e['browser_download_url']) as String)
(e['url'] ?? e['browser_download_url']) as String) : const MapEntry('', '');
: const MapEntry('', ''); }).toList() ??
})
.where((element) => element.key.toLowerCase().endsWith('.apk'))
.toList() ??
[]; [];
DateTime? getPublishDateFromRelease(dynamic rel) => DateTime? getPublishDateFromRelease(dynamic rel) =>
@@ -383,7 +380,11 @@ class GitHub extends AppSource {
.hasMatch(((releases[i]['body'] as String?) ?? '').trim())) { .hasMatch(((releases[i]['body'] as String?) ?? '').trim())) {
continue; continue;
} }
var apkUrls = getReleaseAPKUrls(releases[i]); var allAssetUrls = getReleaseAssetUrls(releases[i]);
List<MapEntry<String, String>> apkUrls = allAssetUrls
.where((element) => element.key.toLowerCase().endsWith('.apk'))
.toList();
apkUrls = filterApks(apkUrls, additionalSettings['apkFilterRegEx'], apkUrls = filterApks(apkUrls, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']); additionalSettings['invertAPKFilter']);
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) { if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
@@ -391,12 +392,25 @@ class GitHub extends AppSource {
} }
targetRelease = releases[i]; targetRelease = releases[i];
targetRelease['apkUrls'] = apkUrls; targetRelease['apkUrls'] = apkUrls;
targetRelease['version'] =
targetRelease['tag_name'] ?? targetRelease['name'];
if (targetRelease['tarball_url'] != null) {
allAssetUrls.add(MapEntry(
(targetRelease['version'] ?? 'source') + '.tar.gz',
targetRelease['tarball_url']));
}
if (targetRelease['zipball_url'] != null) {
allAssetUrls.add(MapEntry(
(targetRelease['version'] ?? 'source') + '.zip',
targetRelease['zipball_url']));
}
targetRelease['allAssetUrls'] = allAssetUrls;
break; break;
} }
if (targetRelease == null) { if (targetRelease == null) {
throw NoReleasesError(); throw NoReleasesError();
} }
String? version = targetRelease['tag_name'] ?? targetRelease['name']; String? version = targetRelease['version'];
DateTime? releaseDate = getReleaseDateFromRelease( DateTime? releaseDate = getReleaseDateFromRelease(
targetRelease, useLatestAssetDateAsReleaseDate); targetRelease, useLatestAssetDateAsReleaseDate);
if (version == null) { if (version == null) {
@@ -408,7 +422,9 @@ class GitHub extends AppSource {
targetRelease['apkUrls'] as List<MapEntry<String, String>>, targetRelease['apkUrls'] as List<MapEntry<String, String>>,
getAppNames(standardUrl), getAppNames(standardUrl),
releaseDate: releaseDate, releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog); changeLog: changeLog.isEmpty ? null : changeLog,
allAssetUrls:
targetRelease['allAssetUrls'] as List<MapEntry<String, String>>);
} else { } else {
if (onHttpErrorCode != null) { if (onHttpErrorCode != null) {
onHttpErrorCode(res); onHttpErrorCode(res);

View File

@@ -121,9 +121,11 @@ class GitLab extends AppSource {
String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {}); String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {});
String optionalAuth = (PAT != null) ? 'private_token=$PAT' : ''; String optionalAuth = (PAT != null) ? 'private_token=$PAT' : '';
bool trackOnly = additionalSettings['trackOnly'] == true;
// Request data from REST API // Request data from REST API
Response res = await sourceRequest( Response res = await sourceRequest(
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?$optionalAuth', 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/${trackOnly ? 'repository/tags' : 'releases'}?$optionalAuth',
additionalSettings); additionalSettings);
if (res.statusCode != 200) { if (res.statusCode != 200) {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
@@ -152,9 +154,8 @@ class GitLab extends AppSource {
var apkUrlsSet = apkUrlsFromAssets.toSet(); var apkUrlsSet = apkUrlsFromAssets.toSet();
apkUrlsSet.addAll(uploadedAPKsFromDescription); apkUrlsSet.addAll(uploadedAPKsFromDescription);
var releaseDateString = e['released_at'] ?? e['created_at']; var releaseDateString = e['released_at'] ?? e['created_at'];
DateTime? releaseDate = releaseDateString != null DateTime? releaseDate =
? DateTime.parse(releaseDateString) releaseDateString != null ? DateTime.parse(releaseDateString) : null;
: null;
return APKDetails( return APKDetails(
e['tag_name'] ?? e['name'], e['tag_name'] ?? e['name'],
getApkUrlsFromUrls(apkUrlsSet.toList()), getApkUrlsFromUrls(apkUrlsSet.toList()),
@@ -164,20 +165,31 @@ class GitLab extends AppSource {
if (apkDetailsList.isEmpty) { if (apkDetailsList.isEmpty) {
throw NoReleasesError(); throw NoReleasesError();
} }
var finalResult = apkDetailsList.first;
// Fallback procedure // Fallback procedure
bool fallbackToOlderReleases = bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true; additionalSettings['fallbackToOlderReleases'] == true;
if (fallbackToOlderReleases) { if (finalResult.apkUrls.isEmpty && fallbackToOlderReleases && !trackOnly) {
if (additionalSettings['trackOnly'] != true) { apkDetailsList =
apkDetailsList = apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList(); finalResult = apkDetailsList.first;
}
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
}
} }
return apkDetailsList.first; if (finalResult.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError();
}
finalResult.apkUrls = finalResult.apkUrls.map((apkUrl) {
if (RegExp('^$standardUrl/-/jobs/[0-9]+/artifacts/file/[^/]+\$')
.hasMatch(apkUrl.value)) {
return MapEntry(
apkUrl.key, apkUrl.value.replaceFirst('/file/', '/raw/'));
} else {
return apkUrl;
}
}).toList();
return finalResult;
} }
} }

View File

@@ -244,16 +244,17 @@ class HTML extends AppSource {
true) { true) {
var reg = RegExp(additionalSettings['customLinkFilterRegex']); var reg = RegExp(additionalSettings['customLinkFilterRegex']);
links = allLinks links = allLinks
.where((element) => .where((element) => reg.hasMatch(
reg.hasMatch(filterLinkByText ? element.value : element.key)) filterLinkByText ? element.value : Uri.decodeFull(element.key)))
.toList(); .toList();
} else { } else {
links = allLinks links = allLinks
.where((element) => .where((element) => Uri.parse(filterLinkByText
Uri.parse(filterLinkByText ? element.value : element.key) ? element.value
.path : Uri.decodeFull(element.key))
.toLowerCase() .path
.endsWith('.apk')) .toLowerCase()
.endsWith('.apk'))
.toList(); .toList();
} }
if (!skipSort) { if (!skipSort) {
@@ -315,7 +316,7 @@ class HTML extends AppSource {
additionalSettings['matchGroupToUse'] as String?, additionalSettings['matchGroupToUse'] as String?,
additionalSettings['versionExtractWholePage'] == true additionalSettings['versionExtractWholePage'] == true
? versionExtractionWholePageString ? versionExtractionWholePageString
: rel); : Uri.decodeFull(rel));
version ??= version ??=
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash' additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
? rel.hashCode.toString() ? rel.hashCode.toString()

View File

@@ -245,8 +245,8 @@ class _GeneratedFormState extends State<GeneratedForm> {
void someValueChanged({bool isBuilding = false, bool forceInvalid = 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 < formInputs.length; r++) {
for (int i = 0; i < widget.items[r].length; i++) { for (int i = 0; i < formInputs[r].length; i++) {
if (formInputs[r][i] is TextFormField) { if (formInputs[r][i] is TextFormField) {
valid = valid && validateTextField(formInputs[r][i] as TextFormField); valid = valid && validateTextField(formInputs[r][i] as TextFormField);
} }

View File

@@ -155,7 +155,8 @@ class AddAppPageState extends State<AddAppPage> {
// Only download the APK here if you need to for the package ID // Only download the APK here if you need to for the package ID
if (isTempId(app) && app.additionalSettings['trackOnly'] != true) { if (isTempId(app) && app.additionalSettings['trackOnly'] != true) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var apkUrl = await appsProvider.confirmApkUrl(app, context); var apkUrl =
await appsProvider.confirmAppFileUrl(app, context, false);
if (apkUrl == null) { if (apkUrl == null) {
throw ObtainiumError(tr('cancelled')); throw ObtainiumError(tr('cancelled'));
} }

View File

@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart'; import 'package:obtainium/main.dart';
import 'package:obtainium/pages/apps.dart';
import 'package:obtainium/pages/settings.dart'; import 'package:obtainium/pages/settings.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
@@ -108,6 +109,7 @@ class _AppPageState extends State<AppPage> {
infoLines = infoLines =
'$infoLines\n${app?.app.apkUrls.length == 1 ? app?.app.apkUrls[0].key : plural('apk', app?.app.apkUrls.length ?? 0)}'; '$infoLines\n${app?.app.apkUrls.length == 1 ? app?.app.apkUrls[0].key : plural('apk', app?.app.apkUrls.length ?? 0)}';
} }
var changeLogFn = app != null ? getChangeLogFn(context, app.app) : null;
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -125,13 +127,26 @@ class _AppPageState extends State<AppPage> {
.textTheme .textTheme
.bodyLarge! .bodyLarge!
.copyWith(fontWeight: FontWeight.bold)), .copyWith(fontWeight: FontWeight.bold)),
app?.app.releaseDate == null changeLogFn != null || app?.app.releaseDate != null
? const SizedBox.shrink() ? GestureDetector(
: Text( onTap: changeLogFn,
app!.app.releaseDate.toString(), child: Text(
textAlign: TextAlign.center, app?.app.releaseDate == null
style: Theme.of(context).textTheme.labelSmall, ? tr('changes')
), : app!.app.releaseDate.toString(),
textAlign: TextAlign.center,
style:
Theme.of(context).textTheme.labelSmall!.copyWith(
decoration: changeLogFn != null
? TextDecoration.underline
: null,
fontStyle: changeLogFn != null
? FontStyle.italic
: null,
),
),
)
: const SizedBox.shrink(),
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
@@ -143,6 +158,29 @@ class _AppPageState extends State<AppPage> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12), style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
), ),
if (app?.app.apkUrls.isNotEmpty == true ||
app?.app.otherAssetUrls.isNotEmpty == true)
GestureDetector(
onTap: app?.app == null || updating
? null
: () async {
try {
await appsProvider
.downloadAppAssets([app!.app.id], context);
} catch (e) {
showError(e, context);
}
},
child: Text(
tr('downloadX', args: [tr('releaseAsset').toLowerCase()]),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall!.copyWith(
decoration:
changeLogFn != null ? TextDecoration.underline : null,
fontStyle: changeLogFn != null ? FontStyle.italic : null,
),
),
),
const SizedBox( const SizedBox(
height: 48, height: 48,
), ),
@@ -361,6 +399,9 @@ class _AppPageState extends State<AppPage> {
!areDownloadsRunning !areDownloadsRunning
? () async { ? () async {
try { try {
var successMessage = app?.app.installedVersion == null
? tr('installed')
: tr('appsUpdated');
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
var res = await appsProvider.downloadAndInstallLatestApps( var res = await appsProvider.downloadAndInstallLatestApps(
app?.app.id != null ? [app!.app.id] : [], app?.app.id != null ? [app!.app.id] : [],
@@ -368,7 +409,7 @@ class _AppPageState extends State<AppPage> {
); );
if (res.isNotEmpty && !trackOnly) { if (res.isNotEmpty && !trackOnly) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
showMessage(tr('appsUpdated'), context); showMessage(successMessage, context);
} }
if (res.isNotEmpty && mounted) { if (res.isNotEmpty && mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@@ -26,6 +26,92 @@ class AppsPage extends StatefulWidget {
State<AppsPage> createState() => AppsPageState(); State<AppsPage> createState() => AppsPageState();
} }
showChangeLogDialog(BuildContext context, App app, String? changesUrl,
AppSource appSource, String changeLog) {
showDialog(
context: context,
builder: (BuildContext context) {
return GeneratedFormModal(
title: tr('changes'),
items: const [],
message: app.latestVersion,
additionalWidgets: [
changesUrl != null
? GestureDetector(
child: Text(
changesUrl,
style: const TextStyle(
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic),
),
onTap: () {
launchUrlString(changesUrl,
mode: LaunchMode.externalApplication);
},
)
: const SizedBox.shrink(),
changesUrl != null
? const SizedBox(
height: 16,
)
: const SizedBox.shrink(),
appSource.changeLogIfAnyIsMarkDown
? SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 350,
child: Markdown(
data: changeLog,
onTapLink: (text, href, title) {
if (href != null) {
launchUrlString(
href.startsWith('http://') ||
href.startsWith('https://')
? href
: '${Uri.parse(app.url).origin}/$href',
mode: LaunchMode.externalApplication);
}
},
extensionSet: md.ExtensionSet(
md.ExtensionSet.gitHubFlavored.blockSyntaxes,
[
md.EmojiSyntax(),
...md.ExtensionSet.gitHubFlavored.inlineSyntaxes
],
),
))
: Text(changeLog),
],
singleNullReturnButton: tr('ok'),
);
});
}
getChangeLogFn(BuildContext context, App app) {
AppSource appSource =
SourceProvider().getSource(app.url, overrideSource: app.overrideSource);
String? changesUrl = appSource.changeLogPageFromStandardUrl(app.url);
String? changeLog = app.changeLog;
if (changeLog?.split('\n').length == 1) {
if (RegExp(
'(http|ftp|https)://([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?')
.hasMatch(changeLog!)) {
if (changesUrl == null) {
changesUrl = changeLog;
changeLog = null;
}
}
}
return (changeLog == null && changesUrl == null)
? null
: () {
if (changeLog != null) {
showChangeLogDialog(context, app, changesUrl, appSource, changeLog);
} else {
launchUrlString(changesUrl!, mode: LaunchMode.externalApplication);
}
};
}
class AppsPageState extends State<AppsPage> { class AppsPageState extends State<AppsPage> {
AppsFilter filter = AppsFilter(); AppsFilter filter = AppsFilter();
final AppsFilter neutralFilter = AppsFilter(); final AppsFilter neutralFilter = AppsFilter();
@@ -262,66 +348,6 @@ class AppsPageState extends State<AppsPage> {
.where((a) => selectedAppIds.contains(a.id)) .where((a) => selectedAppIds.contains(a.id))
.toSet(); .toSet();
showChangeLogDialog(
String? changesUrl, AppSource appSource, String changeLog, int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return GeneratedFormModal(
title: tr('changes'),
items: const [],
message: listedApps[index].app.latestVersion,
additionalWidgets: [
changesUrl != null
? GestureDetector(
child: Text(
changesUrl,
style: const TextStyle(
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic),
),
onTap: () {
launchUrlString(changesUrl,
mode: LaunchMode.externalApplication);
},
)
: const SizedBox.shrink(),
changesUrl != null
? const SizedBox(
height: 16,
)
: const SizedBox.shrink(),
appSource.changeLogIfAnyIsMarkDown
? SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 350,
child: Markdown(
data: changeLog,
onTapLink: (text, href, title) {
if (href != null) {
launchUrlString(
href.startsWith('http://') ||
href.startsWith('https://')
? href
: '${Uri.parse(listedApps[index].app.url).origin}/$href',
mode: LaunchMode.externalApplication);
}
},
extensionSet: md.ExtensionSet(
md.ExtensionSet.gitHubFlavored.blockSyntaxes,
[
md.EmojiSyntax(),
...md.ExtensionSet.gitHubFlavored.inlineSyntaxes
],
),
))
: Text(changeLog),
],
singleNullReturnButton: tr('ok'),
);
});
}
getLoadingWidgets() { getLoadingWidgets() {
return [ return [
if (listedApps.isEmpty) if (listedApps.isEmpty)
@@ -351,35 +377,6 @@ class AppsPageState extends State<AppsPage> {
]; ];
} }
getChangeLogFn(int appIndex) {
AppSource appSource = SourceProvider().getSource(
listedApps[appIndex].app.url,
overrideSource: listedApps[appIndex].app.overrideSource);
String? changesUrl =
appSource.changeLogPageFromStandardUrl(listedApps[appIndex].app.url);
String? changeLog = listedApps[appIndex].app.changeLog;
if (changeLog?.split('\n').length == 1) {
if (RegExp(
'(http|ftp|https)://([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?')
.hasMatch(changeLog!)) {
if (changesUrl == null) {
changesUrl = changeLog;
changeLog = null;
}
}
}
return (changeLog == null && changesUrl == null)
? null
: () {
if (changeLog != null) {
showChangeLogDialog(changesUrl, appSource, changeLog, appIndex);
} else {
launchUrlString(changesUrl!,
mode: LaunchMode.externalApplication);
}
};
}
getUpdateButton(int appIndex) { getUpdateButton(int appIndex) {
return IconButton( return IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
@@ -444,7 +441,7 @@ class AppsPageState extends State<AppsPage> {
} }
getSingleAppHorizTile(int index) { getSingleAppHorizTile(int index) {
var showChangesFn = getChangeLogFn(index); var showChangesFn = getChangeLogFn(context, listedApps[index].app);
var hasUpdate = listedApps[index].app.installedVersion != null && var hasUpdate = listedApps[index].app.installedVersion != null &&
listedApps[index].app.installedVersion != listedApps[index].app.installedVersion !=
listedApps[index].app.latestVersion; listedApps[index].app.latestVersion;
@@ -857,69 +854,78 @@ class AppsPageState extends State<AppsPage> {
scrollable: true, scrollable: true,
content: Padding( content: Padding(
padding: const EdgeInsets.only(top: 6), padding: const EdgeInsets.only(top: 6),
child: Row( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
IconButton( TextButton(
onPressed: pinSelectedApps,
child: Text(selectedApps
.where((element) => element.pinned)
.isEmpty
? tr('pinToTop')
: tr('unpinFromTop'))),
const Divider(),
TextButton(
onPressed: () {
String urls = '';
for (var a in selectedApps) {
urls += '${a.url}\n';
}
urls = urls.substring(0, urls.length - 1);
Share.share(urls,
subject: 'Obtainium - ${tr('appsString')}');
Navigator.of(context).pop();
},
child: Text(tr('shareSelectedAppURLs'))),
const Divider(),
TextButton(
onPressed: selectedAppIds.isEmpty
? null
: () {
String urls =
'<p>${tr('customLinkMessage')}:</p>\n\n<ul>\n';
for (var a in selectedApps) {
urls +=
' <li><a href="obtainium://app/${Uri.encodeComponent(jsonEncode({
'id': a.id,
'url': a.url,
'author': a.author,
'name': a.name,
'preferredApkIndex':
a.preferredApkIndex,
'additionalSettings':
jsonEncode(a.additionalSettings)
}))}">${a.name}</a></li>\n';
}
urls +=
'</ul>\n\n<p><a href="$obtainiumUrl">${tr('about')}</a></p>';
Share.share(urls,
subject:
'Obtainium - ${tr('appsString')}');
},
child: Text(tr('shareAppConfigLinks'))),
const Divider(),
TextButton(
onPressed: () {
appsProvider
.downloadAppAssets(
selectedApps.map((e) => e.id).toList(),
globalNavigatorKey.currentContext ??
context)
.catchError((e) => showError(
e,
globalNavigatorKey.currentContext ??
context));
Navigator.of(context).pop();
},
child: Text(tr('downloadX',
args: [tr('releaseAsset').toLowerCase()]))),
const Divider(),
TextButton(
onPressed: appsProvider.areDownloadsRunning() onPressed: appsProvider.areDownloadsRunning()
? null ? null
: showMassMarkDialog, : showMassMarkDialog,
tooltip: tr('markSelectedAppsUpdated'), child: Text(tr('markSelectedAppsUpdated'))),
icon: const Icon(Icons.done)),
IconButton(
onPressed: pinSelectedApps,
tooltip: selectedApps
.where((element) => element.pinned)
.isEmpty
? tr('pinToTop')
: tr('unpinFromTop'),
icon: Icon(selectedApps
.where((element) => element.pinned)
.isEmpty
? Icons.bookmark_outline_rounded
: Icons.bookmark_remove_outlined),
),
IconButton(
onPressed: () {
String urls = '';
for (var a in selectedApps) {
urls += '${a.url}\n';
}
urls = urls.substring(0, urls.length - 1);
Share.share(urls,
subject: 'Obtainium - ${tr('appsString')}');
Navigator.of(context).pop();
},
tooltip: tr('shareSelectedAppURLs'),
icon: const Icon(Icons.share_rounded),
),
IconButton(
onPressed: selectedAppIds.isEmpty
? null
: () {
String urls =
'<p>${tr('customLinkMessage')}:</p>\n\n<ul>\n';
for (var a in selectedApps) {
urls +=
' <li><a href="obtainium://app/${Uri.encodeComponent(jsonEncode({
'id': a.id,
'url': a.url,
'author': a.author,
'name': a.name,
'preferredApkIndex':
a.preferredApkIndex,
'additionalSettings':
jsonEncode(a.additionalSettings)
}))}">${a.name}</a></li>\n';
}
urls +=
'</ul>\n\n<p><a href="$obtainiumUrl">${tr('about')}</a></p>';
Share.share(urls,
subject: 'Obtainium - ${tr('appsString')}');
},
tooltip: tr('shareAppConfigLinks'),
icon: const Icon(Icons.ios_share),
),
]), ]),
), ),
); );

View File

@@ -351,6 +351,22 @@ class _SettingsPageState extends State<SettingsPage> {
], ],
), ),
height16, height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child:
Text(tr('removeOnExternalUninstall'))),
Switch(
value: settingsProvider
.removeOnExternalUninstall,
onChanged: (value) {
settingsProvider
.removeOnExternalUninstall = value;
})
],
),
height16,
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -363,6 +379,43 @@ class _SettingsPageState extends State<SettingsPage> {
}) })
], ],
), ),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(tr(
'beforeNewInstallsShareToAppVerifier')),
GestureDetector(
onTap: () {
launchUrlString(
'https://github.com/soupslurpr/AppVerifier',
mode: LaunchMode
.externalApplication);
},
child: Text(
tr('about'),
style: const TextStyle(
decoration:
TextDecoration.underline,
fontSize: 12),
)),
],
)),
Switch(
value: settingsProvider
.beforeNewInstallsShareToAppVerifier,
onChanged: (value) {
settingsProvider
.beforeNewInstallsShareToAppVerifier =
value;
})
],
),
installMethodDropdown, installMethodDropdown,
height32, height32,
Text( Text(
@@ -474,22 +527,6 @@ class _SettingsPageState extends State<SettingsPage> {
], ],
), ),
height16, height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child:
Text(tr('removeOnExternalUninstall'))),
Switch(
value: settingsProvider
.removeOnExternalUninstall,
onChanged: (value) {
settingsProvider
.removeOnExternalUninstall = value;
})
],
),
height16,
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View File

@@ -5,6 +5,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
@@ -31,6 +32,7 @@ import 'package:obtainium/providers/source_provider.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart'; import 'package:android_intent_plus/android_intent.dart';
import 'package:flutter_archive/flutter_archive.dart'; import 'package:flutter_archive/flutter_archive.dart';
import 'package:share_plus/share_plus.dart';
import 'package:shared_storage/shared_storage.dart' as saf; import 'package:shared_storage/shared_storage.dart' as saf;
import 'native_provider.dart'; import 'native_provider.dart';
@@ -202,14 +204,18 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
Future<File> downloadFile( Future<File> downloadFile(
String url, String fileNameNoExt, Function? onProgress, String destDir, String url, String fileNameNoExt, Function? onProgress, String destDir,
{bool useExisting = true, Map<String, String>? headers}) async { {bool useExisting = true, Map<String, String>? headers}) async {
// Send the initial request but cancel it as soon as you have the headers
var reqHeaders = headers ?? {};
var req = Request('GET', Uri.parse(url)); var req = Request('GET', Uri.parse(url));
if (headers != null) { req.headers.addAll(reqHeaders);
req.headers.addAll(headers);
}
var client = http.Client(); var client = http.Client();
StreamedResponse response = await client.send(req); StreamedResponse response = await client.send(req);
String ext = var resHeaders = response.headers;
response.headers['content-disposition']?.split('.').last ?? 'apk';
// Use the headers to decide what the file extension is, and
// whether it supports partial downloads (range request), and
// what the total size of the file is (if provided)
String ext = resHeaders['content-disposition']?.split('.').last ?? 'apk';
if (ext.endsWith('"') || ext.endsWith("other")) { if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1); ext = ext.substring(0, ext.length - 1);
} }
@@ -217,41 +223,106 @@ Future<File> downloadFile(
ext = 'apk'; ext = 'apk';
} }
File downloadedFile = File('$destDir/$fileNameNoExt.$ext'); File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
if (!(downloadedFile.existsSync() && useExisting)) {
File tempDownloadedFile = File('${downloadedFile.path}.part'); bool rangeFeatureEnabled = false;
if (tempDownloadedFile.existsSync()) { if (resHeaders['accept-ranges']?.isNotEmpty == true) {
tempDownloadedFile.deleteSync(recursive: true); rangeFeatureEnabled =
} resHeaders['accept-ranges']?.trim().toLowerCase() == 'bytes';
var length = response.contentLength; }
var received = 0;
double? progress; // If you have an existing file that is usable,
var sink = tempDownloadedFile.openWrite(); // decide whether you can use it (either return full or resume partial)
await response.stream.map((s) { var fullContentLength = response.contentLength;
received += s.length; if (useExisting && downloadedFile.existsSync()) {
progress = (length != null ? received / length * 100 : 30); var length = downloadedFile.lengthSync();
if (onProgress != null) { if (fullContentLength == null || !rangeFeatureEnabled) {
onProgress(progress); // If there is no content length reported, assume it the existing file is fully downloaded
// Also if the range feature is not supported, don't trust the content length if any (#1542)
client.close();
return downloadedFile;
} else {
// Check if resume needed/possible
if (length == fullContentLength) {
client.close();
return downloadedFile;
} }
return s; if (length > fullContentLength) {
}).pipe(sink); useExisting = false;
await sink.close(); }
progress = null; }
}
// Download to a '.temp' file (to distinguish btn. complete/incomplete files)
File tempDownloadedFile = File('${downloadedFile.path}.part');
// If the range feature is not available (or you need to start a ranged req from 0),
// complete the already-started request, else cancel it and start a ranged request,
// and open the file for writing in the appropriate mode
var targetFileLength = useExisting && tempDownloadedFile.existsSync()
? tempDownloadedFile.lengthSync()
: null;
int rangeStart = targetFileLength ?? 0;
IOSink? sink;
if (rangeFeatureEnabled && fullContentLength != null && rangeStart > 0) {
client.close();
client = http.Client();
req = Request('GET', Uri.parse(url));
req.headers.addAll(reqHeaders);
req.headers.addAll({'range': 'bytes=$rangeStart-${fullContentLength - 1}'});
response = await client.send(req);
sink = tempDownloadedFile.openWrite(mode: FileMode.writeOnlyAppend);
} else if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.deleteSync(recursive: true);
}
sink ??= tempDownloadedFile.openWrite(mode: FileMode.writeOnly);
// Perform the download
var received = 0;
double? progress;
if (rangeStart > 0 && fullContentLength != null) {
received = rangeStart;
}
await response.stream.map((s) {
received += s.length;
progress =
(fullContentLength != null ? (received / fullContentLength) * 100 : 30);
if (onProgress != null) { if (onProgress != null) {
onProgress(progress); onProgress(progress);
} }
if (response.statusCode != 200) { return s;
tempDownloadedFile.deleteSync(recursive: true); }).pipe(sink);
throw response.reasonPhrase ?? tr('unexpectedError'); await sink.close();
} progress = null;
if (tempDownloadedFile.existsSync()) { if (onProgress != null) {
tempDownloadedFile.renameSync(downloadedFile.path); onProgress(progress);
}
} else {
client.close();
} }
if (response.statusCode < 200 || response.statusCode > 299) {
tempDownloadedFile.deleteSync(recursive: true);
throw response.reasonPhrase ?? tr('unexpectedError');
}
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.renameSync(downloadedFile.path);
}
client.close();
return downloadedFile; return downloadedFile;
} }
Future<Map<String, String>> getHeaders(String url,
{Map<String, String>? headers}) async {
var req = http.Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
var client = http.Client();
var response = await client.send(req);
if (response.statusCode < 200 || response.statusCode > 299) {
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
}
var returnHeaders = response.headers;
client.close();
return returnHeaders;
}
Future<PackageInfo?> getInstalledInfo(String? packageName, Future<PackageInfo?> getInstalledInfo(String? packageName,
{bool printErr = true}) async { {bool printErr = true}) async {
if (packageName != null) { if (packageName != null) {
@@ -287,7 +358,7 @@ class AppsProvider with ChangeNotifier {
foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundStream = FGBGEvents.stream.asBroadcastStream();
foregroundSubscription = foregroundStream?.listen((event) async { foregroundSubscription = foregroundStream?.listen((event) async {
isForeground = event == FGBGType.foreground; isForeground = event == FGBGType.foreground;
if (isForeground) await loadApps(); if (isForeground) loadApps();
}); });
() async { () async {
await settingsProvider.initializeSettings(); await settingsProvider.initializeSettings();
@@ -319,30 +390,26 @@ class AppsProvider with ChangeNotifier {
}(); }();
} }
Future<File> handleAPKIDChange(App app, PackageInfo? newInfo, Future<File> handleAPKIDChange(App app, PackageInfo newInfo,
File downloadedFile, String downloadUrl) async { File downloadedFile, String downloadUrl) async {
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed // If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
// The former case should be handled (give the App its real ID), the latter is a security issue // The former case should be handled (give the App its real ID), the latter is a security issue
var isTempIdBool = isTempId(app); var isTempIdBool = isTempId(app);
if (newInfo != null) { if (app.id != newInfo.packageName) {
if (app.id != newInfo.packageName) { if (apps[app.id] != null && !isTempIdBool && !app.allowIdChange) {
if (apps[app.id] != null && !isTempIdBool && !app.allowIdChange) { throw IDChangedError(newInfo.packageName!);
throw IDChangedError(newInfo.packageName!); }
} var idChangeWasAllowed = app.allowIdChange;
var idChangeWasAllowed = app.allowIdChange; app.allowIdChange = false;
app.allowIdChange = false; var originalAppId = app.id;
var originalAppId = app.id; app.id = newInfo.packageName!;
app.id = newInfo.packageName!; downloadedFile = downloadedFile.renameSync(
downloadedFile = downloadedFile.renameSync( '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}');
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}'); if (apps[originalAppId] != null) {
if (apps[originalAppId] != null) { await removeApps([originalAppId]);
await removeApps([originalAppId]); await saveApps([app],
await saveApps([app], onlyIfExists: !isTempIdBool && !idChangeWasAllowed);
onlyIfExists: !isTempIdBool && !idChangeWasAllowed);
}
} }
} else if (isTempIdBool) {
throw ObtainiumError('Could not get ID from APK');
} }
return downloadedFile; return downloadedFile;
} }
@@ -406,6 +473,10 @@ class AppsProvider with ChangeNotifier {
newInfo = newInfo =
await pm.getPackageArchiveInfo(archiveFilePath: apks.first.path); await pm.getPackageArchiveInfo(archiveFilePath: apks.first.path);
} }
if (newInfo == null) {
downloadedFile.delete();
throw ObtainiumError('Could not get ID from APK');
}
downloadedFile = downloadedFile =
await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl); await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
// Delete older versions of the file if any // Delete older versions of the file if any
@@ -493,13 +564,14 @@ class AppsProvider with ChangeNotifier {
zipFile: File(filePath), destinationDir: Directory(destinationPath)); zipFile: File(filePath), destinationDir: Directory(destinationPath));
} }
Future<void> installXApkDir(DownloadedXApkDir dir, Future<bool> installXApkDir(
DownloadedXApkDir dir, BuildContext? firstTimeWithContext,
{bool needsBGWorkaround = false}) async { {bool needsBGWorkaround = false}) async {
// We don't know which APKs in an XAPK are supported by the user's device // We don't know which APKs in an XAPK are supported by the user's device
// So we try installing all of them and assume success if at least one installed // So we try installing all of them and assume success if at least one installed
// If 0 APKs installed, throw the first install error encountered // If 0 APKs installed, throw the first install error encountered
var somethingInstalled = false;
try { try {
var somethingInstalled = false;
MultiAppMultiError errors = MultiAppMultiError(); MultiAppMultiError errors = MultiAppMultiError();
for (var file in dir.extracted for (var file in dir.extracted
.listSync(recursive: true, followLinks: false) .listSync(recursive: true, followLinks: false)
@@ -507,7 +579,8 @@ class AppsProvider with ChangeNotifier {
if (file.path.toLowerCase().endsWith('.apk')) { if (file.path.toLowerCase().endsWith('.apk')) {
try { try {
somethingInstalled = somethingInstalled || somethingInstalled = somethingInstalled ||
await installApk(DownloadedApk(dir.appId, file), await installApk(
DownloadedApk(dir.appId, file), firstTimeWithContext,
needsBGWorkaround: needsBGWorkaround); needsBGWorkaround: needsBGWorkaround);
} catch (e) { } catch (e) {
logs.add( logs.add(
@@ -526,10 +599,22 @@ class AppsProvider with ChangeNotifier {
} finally { } finally {
dir.extracted.delete(recursive: true); dir.extracted.delete(recursive: true);
} }
return somethingInstalled;
} }
Future<bool> installApk(DownloadedApk file, Future<bool> installApk(
DownloadedApk file, BuildContext? firstTimeWithContext,
{bool needsBGWorkaround = false}) async { {bool needsBGWorkaround = false}) async {
if (firstTimeWithContext != null &&
settingsProvider.beforeNewInstallsShareToAppVerifier &&
(await getInstalledInfo('dev.soupslurpr.appverifier')) != null) {
XFile f = XFile.fromData(file.file.readAsBytesSync(),
mimeType: 'application/vnd.android.package-archive');
Fluttertoast.showToast(
msg: tr('appVerifierInstructionToast'),
toastLength: Toast.LENGTH_LONG);
await Share.shareXFiles([f]);
}
var newInfo = var newInfo =
await pm.getPackageArchiveInfo(archiveFilePath: file.file.path); await pm.getPackageArchiveInfo(archiveFilePath: file.file.path);
if (newInfo == null) { if (newInfo == null) {
@@ -577,7 +662,13 @@ class AppsProvider with ChangeNotifier {
} }
bool installed = false; bool installed = false;
if (code != null && code != 0 && code != 3) { if (code != null && code != 0 && code != 3) {
throw InstallError(code); try {
file.file.deleteSync(recursive: true);
} catch (e) {
//
} finally {
throw InstallError(code);
}
} else if (code == 0) { } else if (code == 0) {
installed = true; installed = true;
apps[file.appId]!.app.installedVersion = apps[file.appId]!.app.installedVersion =
@@ -612,23 +703,28 @@ class AppsProvider with ChangeNotifier {
await intent.launch(); await intent.launch();
} }
Future<MapEntry<String, String>?> confirmApkUrl( Future<MapEntry<String, String>?> confirmAppFileUrl(
App app, BuildContext? context) async { App app, BuildContext? context, bool pickAnyAsset) async {
var urlsToSelectFrom = app.apkUrls;
if (pickAnyAsset) {
urlsToSelectFrom = [...urlsToSelectFrom, ...app.otherAssetUrls];
}
// If the App has more than one APK, the user should pick one (if context provided) // If the App has more than one APK, the user should pick one (if context provided)
MapEntry<String, String>? apkUrl = MapEntry<String, String>? appFileUrl = urlsToSelectFrom[
app.apkUrls[app.preferredApkIndex >= 0 ? app.preferredApkIndex : 0]; app.preferredApkIndex >= 0 ? app.preferredApkIndex : 0];
// get device supported architecture // get device supported architecture
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis; List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
if (app.apkUrls.length > 1 && context != null) { if (urlsToSelectFrom.length > 1 && context != null) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
apkUrl = await showDialog( appFileUrl = await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return APKPicker( return AppFilePicker(
app: app, app: app,
initVal: apkUrl, initVal: appFileUrl,
archs: archs, archs: archs,
pickAnyAsset: pickAnyAsset,
); );
}); });
} }
@@ -638,8 +734,8 @@ class AppsProvider with ChangeNotifier {
} }
// If the picked APK comes from an origin different from the source, get user confirmation (if context provided) // If the picked APK comes from an origin different from the source, get user confirmation (if context provided)
if (apkUrl != null && if (appFileUrl != null &&
getHost(apkUrl.value) != getHost(app.url) && getHost(appFileUrl.value) != getHost(app.url) &&
context != null) { context != null) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
if (!(settingsProvider.hideAPKOriginWarning) && if (!(settingsProvider.hideAPKOriginWarning) &&
@@ -648,13 +744,13 @@ class AppsProvider with ChangeNotifier {
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return APKOriginWarningDialog( return APKOriginWarningDialog(
sourceUrl: app.url, apkUrl: apkUrl!.value); sourceUrl: app.url, apkUrl: appFileUrl!.value);
}) != }) !=
true) { true) {
apkUrl = null; appFileUrl = null;
} }
} }
return apkUrl; return appFileUrl;
} }
// Given a list of AppIds, uses stored info about the apps to download APKs and install them // Given a list of AppIds, uses stored info about the apps to download APKs and install them
@@ -681,7 +777,7 @@ class AppsProvider with ChangeNotifier {
var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true; var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true;
if (!trackOnly) { if (!trackOnly) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
apkUrl = await confirmApkUrl(apps[id]!.app, context); apkUrl = await confirmAppFileUrl(apps[id]!.app, context, false);
} }
if (apkUrl != null) { if (apkUrl != null) {
int urlInd = apps[id]! int urlInd = apps[id]!
@@ -758,17 +854,24 @@ class AppsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
if (!skipInstalls) { if (!skipInstalls) {
bool sayInstalled = true;
var contextIfNewInstall =
apps[id]?.installedInfo == null ? context : null;
if (downloadedFile != null) { if (downloadedFile != null) {
if (willBeSilent && context == null) { if (willBeSilent && context == null) {
installApk(downloadedFile, needsBGWorkaround: true); installApk(downloadedFile, contextIfNewInstall,
needsBGWorkaround: true);
} else { } else {
await installApk(downloadedFile); sayInstalled =
await installApk(downloadedFile, contextIfNewInstall);
} }
} else { } else {
if (willBeSilent && context == null) { if (willBeSilent && context == null) {
installXApkDir(downloadedDir!, needsBGWorkaround: true); installXApkDir(downloadedDir!, contextIfNewInstall,
needsBGWorkaround: true);
} else { } else {
await installXApkDir(downloadedDir!); sayInstalled =
await installXApkDir(downloadedDir!, contextIfNewInstall);
} }
} }
if (willBeSilent && context == null) { if (willBeSilent && context == null) {
@@ -776,7 +879,9 @@ class AppsProvider with ChangeNotifier {
[apps[id]!.app], [apps[id]!.app],
id: id.hashCode)); id: id.hashCode));
} }
installedIds.add(id); if (sayInstalled) {
installedIds.add(id);
}
} }
} finally { } finally {
apps[id]?.downloadProgress = null; apps[id]?.downloadProgress = null;
@@ -809,6 +914,85 @@ class AppsProvider with ChangeNotifier {
return installedIds; return installedIds;
} }
Future<List<String>> downloadAppAssets(
List<String> appIds, BuildContext context,
{bool forceParallelDownloads = false}) async {
NotificationsProvider notificationsProvider =
context.read<NotificationsProvider>();
List<MapEntry<MapEntry<String, String>, App>> filesToDownload = [];
for (var id in appIds) {
if (apps[id] == null) {
throw ObtainiumError(tr('appNotFound'));
}
MapEntry<String, String>? fileUrl;
if (apps[id]!.app.apkUrls.isNotEmpty ||
apps[id]!.app.otherAssetUrls.isNotEmpty) {
// ignore: use_build_context_synchronously
fileUrl = await confirmAppFileUrl(apps[id]!.app, context, true);
}
if (fileUrl != null) {
filesToDownload.add(MapEntry(fileUrl, apps[id]!.app));
}
}
// Prepare to download+install Apps
MultiAppMultiError errors = MultiAppMultiError();
List<String> downloadedIds = [];
Future<void> downloadFn(MapEntry<String, String> fileUrl, App app) async {
try {
var exportDir = await settingsProvider.getExportDir();
String downloadPath = '/storage/emulated/0/Download';
bool downloadsAccessible = false;
try {
downloadsAccessible = Directory(downloadPath).existsSync();
} catch (e) {
//
}
if (!downloadsAccessible && exportDir != null) {
downloadPath = exportDir.path;
}
await downloadFile(
fileUrl.value,
fileUrl.key
.split('.')
.reversed
.toList()
.sublist(1)
.reversed
.join('.'), (double? progress) {
notificationsProvider
.notify(DownloadNotification(fileUrl.key, progress?.ceil() ?? 0));
}, downloadPath,
headers: await SourceProvider()
.getSource(app.url, overrideSource: app.overrideSource)
.getRequestHeaders(app.additionalSettings,
forAPKDownload:
fileUrl.key.endsWith('.apk') ? true : false),
useExisting: false);
notificationsProvider
.notify(DownloadedNotification(fileUrl.key, fileUrl.value));
} catch (e) {
errors.add(fileUrl.key, e);
} finally {
notificationsProvider.cancel(DownloadNotification(fileUrl.key, 0).id);
}
}
if (forceParallelDownloads || !settingsProvider.parallelDownloads) {
for (var urlWithApp in filesToDownload) {
await downloadFn(urlWithApp.key, urlWithApp.value);
}
} else {
await Future.wait(filesToDownload
.map((urlWithApp) => downloadFn(urlWithApp.key, urlWithApp.value)));
}
if (errors.idsByErrorString.isNotEmpty) {
throw errors;
}
return downloadedIds;
}
Future<Directory> getAppsDir() async { Future<Directory> getAppsDir() async {
Directory appsDir = Directory appsDir =
Directory('${(await getExternalStorageDirectory())!.path}/app_data'); Directory('${(await getExternalStorageDirectory())!.path}/app_data');
@@ -1380,38 +1564,49 @@ class AppsProvider with ChangeNotifier {
} }
} }
class APKPicker extends StatefulWidget { class AppFilePicker extends StatefulWidget {
const APKPicker({super.key, required this.app, this.initVal, this.archs}); const AppFilePicker(
{super.key,
required this.app,
this.initVal,
this.archs,
this.pickAnyAsset = false});
final App app; final App app;
final MapEntry<String, String>? initVal; final MapEntry<String, String>? initVal;
final List<String>? archs; final List<String>? archs;
final bool pickAnyAsset;
@override @override
State<APKPicker> createState() => _APKPickerState(); State<AppFilePicker> createState() => _AppFilePickerState();
} }
class _APKPickerState extends State<APKPicker> { class _AppFilePickerState extends State<AppFilePicker> {
MapEntry<String, String>? apkUrl; MapEntry<String, String>? fileUrl;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
apkUrl ??= widget.initVal; fileUrl ??= widget.initVal;
var urlsToSelectFrom = widget.app.apkUrls;
if (widget.pickAnyAsset) {
urlsToSelectFrom = [...urlsToSelectFrom, ...widget.app.otherAssetUrls];
}
return AlertDialog( return AlertDialog(
scrollable: true, scrollable: true,
title: Text(tr('pickAnAPK')), title: Text(widget.pickAnyAsset
? tr('selectX', args: [tr('releaseAsset').toLowerCase()])
: tr('pickAnAPK')),
content: Column(children: [ content: Column(children: [
Text(tr('appHasMoreThanOnePackage', args: [widget.app.finalName])), Text(tr('appHasMoreThanOnePackage', args: [widget.app.finalName])),
const SizedBox(height: 16), const SizedBox(height: 16),
...widget.app.apkUrls.map( ...urlsToSelectFrom.map(
(u) => RadioListTile<String>( (u) => RadioListTile<String>(
title: Text(u.key), title: Text(u.key),
value: u.value, value: u.value,
groupValue: apkUrl!.value, groupValue: fileUrl!.value,
onChanged: (String? val) { onChanged: (String? val) {
setState(() { setState(() {
apkUrl = fileUrl = urlsToSelectFrom.where((e) => e.value == val).first;
widget.app.apkUrls.where((e) => e.value == val).first;
}); });
}), }),
), ),
@@ -1438,7 +1633,7 @@ class _APKPickerState extends State<APKPicker> {
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
Navigator.of(context).pop(apkUrl); Navigator.of(context).pop(fileUrl);
}, },
child: Text(tr('continue'))) child: Text(tr('continue')))
], ],

View File

@@ -120,6 +120,18 @@ class DownloadNotification extends ObtainiumNotification {
progPercent: progPercent); progPercent: progPercent);
} }
class DownloadedNotification extends ObtainiumNotification {
DownloadedNotification(String fileName, String downloadUrl)
: super(
downloadUrl.hashCode,
tr('downloadedX', args: [fileName]),
'',
'FILE_DOWNLOADED',
tr('downloadedXNotifChannel', args: [tr('app')]),
tr('downloadedX', args: [tr('app')]),
Importance.defaultImportance);
}
final completeInstallationNotification = ObtainiumNotification( final completeInstallationNotification = ObtainiumNotification(
1, 1,
tr('completeAppInstallation'), tr('completeAppInstallation'),

View File

@@ -479,4 +479,13 @@ class SettingsProvider with ChangeNotifier {
prefs?.setStringList('searchDeselected', list); prefs?.setStringList('searchDeselected', list);
notifyListeners(); notifyListeners();
} }
bool get beforeNewInstallsShareToAppVerifier {
return prefs?.getBool('beforeNewInstallsShareToAppVerifier') ?? true;
}
set beforeNewInstallsShareToAppVerifier(bool val) {
prefs?.setBool('beforeNewInstallsShareToAppVerifier', val);
notifyListeners();
}
} }

View File

@@ -47,9 +47,10 @@ class APKDetails {
late AppNames names; late AppNames names;
late DateTime? releaseDate; late DateTime? releaseDate;
late String? changeLog; late String? changeLog;
late List<MapEntry<String, String>> allAssetUrls;
APKDetails(this.version, this.apkUrls, this.names, APKDetails(this.version, this.apkUrls, this.names,
{this.releaseDate, this.changeLog}); {this.releaseDate, this.changeLog, this.allAssetUrls = const []});
} }
stringMapListTo2DList(List<MapEntry<String, String>> mapList) => stringMapListTo2DList(List<MapEntry<String, String>> mapList) =>
@@ -223,6 +224,7 @@ class App {
String? installedVersion; String? installedVersion;
late String latestVersion; late String latestVersion;
List<MapEntry<String, String>> apkUrls = []; List<MapEntry<String, String>> apkUrls = [];
List<MapEntry<String, String>> otherAssetUrls = [];
late int preferredApkIndex; late int preferredApkIndex;
late Map<String, dynamic> additionalSettings; late Map<String, dynamic> additionalSettings;
late DateTime? lastUpdateCheck; late DateTime? lastUpdateCheck;
@@ -248,7 +250,8 @@ class App {
this.releaseDate, this.releaseDate,
this.changeLog, this.changeLog,
this.overrideSource, this.overrideSource,
this.allowIdChange = false}); this.allowIdChange = false,
this.otherAssetUrls = const []});
@override @override
String toString() { String toString() {
@@ -280,41 +283,44 @@ class App {
changeLog: changeLog, changeLog: changeLog,
releaseDate: releaseDate, releaseDate: releaseDate,
overrideSource: overrideSource, overrideSource: overrideSource,
allowIdChange: allowIdChange); allowIdChange: allowIdChange,
otherAssetUrls: otherAssetUrls);
factory App.fromJson(Map<String, dynamic> json) { factory App.fromJson(Map<String, dynamic> json) {
json = appJSONCompatibilityModifiers(json); json = appJSONCompatibilityModifiers(json);
return App( return App(
json['id'] as String, json['id'] as String,
json['url'] as String, json['url'] as String,
json['author'] as String, json['author'] as String,
json['name'] as String, json['name'] as String,
json['installedVersion'] == null json['installedVersion'] == null
? null ? null
: json['installedVersion'] as String, : json['installedVersion'] as String,
(json['latestVersion'] ?? tr('unknown')) as String, (json['latestVersion'] ?? tr('unknown')) as String,
assumed2DlistToStringMapList(jsonDecode( assumed2DlistToStringMapList(
(json['apkUrls'] ?? '[["placeholder", "placeholder"]]'))), jsonDecode((json['apkUrls'] ?? '[["placeholder", "placeholder"]]'))),
(json['preferredApkIndex'] ?? -1) as int, (json['preferredApkIndex'] ?? -1) as int,
jsonDecode(json['additionalSettings']) as Map<String, dynamic>, jsonDecode(json['additionalSettings']) as Map<String, dynamic>,
json['lastUpdateCheck'] == null json['lastUpdateCheck'] == null
? null ? null
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
json['pinned'] ?? false, json['pinned'] ?? false,
categories: json['categories'] != null categories: json['categories'] != null
? (json['categories'] as List<dynamic>) ? (json['categories'] as List<dynamic>)
.map((e) => e.toString()) .map((e) => e.toString())
.toList() .toList()
: json['category'] != null : json['category'] != null
? [json['category'] as String] ? [json['category'] as String]
: [], : [],
releaseDate: json['releaseDate'] == null releaseDate: json['releaseDate'] == null
? null ? null
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']), : DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
changeLog: changeLog: json['changeLog'] == null ? null : json['changeLog'] as String,
json['changeLog'] == null ? null : json['changeLog'] as String, overrideSource: json['overrideSource'],
overrideSource: json['overrideSource'], allowIdChange: json['allowIdChange'] ?? false,
allowIdChange: json['allowIdChange'] ?? false); otherAssetUrls: assumed2DlistToStringMapList(
jsonDecode((json['otherAssetUrls'] ?? '[]'))),
);
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@@ -325,6 +331,7 @@ class App {
'installedVersion': installedVersion, 'installedVersion': installedVersion,
'latestVersion': latestVersion, 'latestVersion': latestVersion,
'apkUrls': jsonEncode(stringMapListTo2DList(apkUrls)), 'apkUrls': jsonEncode(stringMapListTo2DList(apkUrls)),
'otherAssetUrls': jsonEncode(stringMapListTo2DList(otherAssetUrls)),
'preferredApkIndex': preferredApkIndex, 'preferredApkIndex': preferredApkIndex,
'additionalSettings': jsonEncode(additionalSettings), 'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, 'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
@@ -736,7 +743,6 @@ class SourceProvider {
FDroid(), FDroid(),
FDroidRepo(), FDroidRepo(),
IzzyOnDroid(), IzzyOnDroid(),
SourceForge(),
SourceHut(), SourceHut(),
APKPure(), APKPure(),
Aptoide(), Aptoide(),
@@ -893,8 +899,10 @@ class SourceProvider {
allowIdChange: currentApp?.allowIdChange ?? allowIdChange: currentApp?.allowIdChange ??
trackOnly || trackOnly ||
(source.appIdInferIsOptional && (source.appIdInferIsOptional &&
inferAppIdIfOptional) // Optional ID inferring may be incorrect - allow correction on first install inferAppIdIfOptional), // Optional ID inferring may be incorrect - allow correction on first install
); otherAssetUrls: apk.allAssetUrls
.where((a) => apk.apkUrls.indexWhere((p) => a.key == p.key) < 0)
.toList());
return source.endOfGetAppChanges(finalApp); return source.endOfGetAppChanges(finalApp);
} }

View File

@@ -5,10 +5,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: android_intent_plus name: android_intent_plus
sha256: e1c62bb41c90e15083b7fb84dc327fe90396cc9c1445b55ff1082144fabfb4d9 sha256: "2bfdbee8d65e7c26f88b66f0a91f2863da4d3596d8a658b4162c8de5cf04b074"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.3" version: "5.0.2"
android_package_installer: android_package_installer:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -38,10 +38,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: app_links name: app_links
sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4" sha256: "42dc15aecf2618ace4ffb74a2e58a50e45cd1b9f2c17c8f0cafe4c297f08c815"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.1" version: "4.0.1"
archive: archive:
dependency: transitive dependency: transitive
description: description:
@@ -54,10 +54,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -70,10 +70,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: background_fetch name: background_fetch
sha256: eb3af263d390d7e68ecb90f2ae984d2bfd96dceb4c7b4f72418dd5383b49de0a sha256: dbffec0317ccdef6e2014cb543e147f52441e29c4fcb53dfd23558c4d92ddece
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.4" version: "1.3.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -126,18 +126,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity_plus name: connectivity_plus
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0" sha256: ebe15d94de9dd7c31dc2ac54e42780acdf3384b1497c69290c9f3c5b0279fc57
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.2" version: "6.0.2"
connectivity_plus_platform_interface: connectivity_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: connectivity_plus_platform_interface name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a sha256: b6a56efe1e6675be240de39107281d4034b64ac23438026355b4234042a35adb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.4" version: "2.0.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@@ -190,10 +190,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.1.2" version: "10.1.0"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -254,10 +254,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: caa6bc229eab3e32eb2f37b53a5f9d22a6981474afd210c512a7546c1e1a04f6 sha256: d1d0ac3966b36dc3e66eeefb40280c17feb87fa2099c6e22e6a1fc959327bd03
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.0" version: "8.0.0+1"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@@ -275,10 +275,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_archive name: flutter_archive
sha256: "004132780d382df5171589ab793e2efc9c3eef570fe72d78b4ccfbfbe52762ae" sha256: "5ca235f304c12bf468979235f400f79846d204169d715939e39197106f5fc970"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.3"
flutter_fgbg: flutter_fgbg:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -299,18 +299,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
flutter_local_notifications: flutter_local_notifications:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: "55b9b229307a10974b26296ff29f2e132256ba4bd74266939118eaefa941cb00" sha256: a701df4866f9a38bb8e4450a54c143bbeeb0ce2381e7df5a36e1006f3b43bb28
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "16.3.3" version: "17.0.1"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
@@ -336,18 +336,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db sha256: "04c4722cc36ec5af38acc38ece70d22d3c2123c61305d555750a091517bbe504"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.21" version: "0.6.23"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.17" version: "2.0.19"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -362,10 +362,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: fluttertoast name: fluttertoast
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1 sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.2.4" version: "8.2.5"
gtk: gtk:
dependency: transitive dependency: transitive
description: description:
@@ -426,10 +426,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.7" version: "0.7.1"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
@@ -538,18 +538,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.4"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@@ -586,10 +586,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: permission_handler name: permission_handler
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44" sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.3.0" version: "11.3.1"
permission_handler_android: permission_handler_android:
dependency: transitive dependency: transitive
description: description:
@@ -602,10 +602,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_apple name: permission_handler_apple
sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.4.0" version: "9.4.4"
permission_handler_html: permission_handler_html:
dependency: transitive dependency: transitive
description: description:
@@ -618,10 +618,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_platform_interface name: permission_handler_platform_interface
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78" sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.0" version: "4.2.1"
permission_handler_windows: permission_handler_windows:
dependency: transitive dependency: transitive
description: description:
@@ -658,10 +658,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.4" version: "3.8.0"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -674,34 +674,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900" sha256: fb5319f3aab4c5dda5ebb92dca978179ba21f8c783ee4380910ef4c1c6824f51
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.2" version: "8.0.3"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.1" version: "3.4.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@@ -775,18 +775,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite name: sqflite
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 sha256: "5ce2e1a15e822c3b4bfb5400455775e421da7098eed8adc8f26298ada7c9308c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.3"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.3" version: "2.5.4"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@@ -855,18 +855,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.5" version: "6.2.6"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.0" version: "6.3.1"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@@ -903,10 +903,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.1"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@@ -919,10 +919,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: uuid name: uuid
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.3" version: "4.4.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -959,10 +959,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0" sha256: f038ee2fae73b509dde1bc9d2c5a50ca92054282de17631a9a3d515883740934
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.15.0" version: "3.16.0"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -975,26 +975,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86" sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.12.0" version: "3.13.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.3.0" version: "5.4.0"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
name: win32_registry name: win32_registry
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.5+2255 # When changing this, update the tag in main() accordingly version: 1.1.4+2261
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
@@ -38,7 +38,7 @@ dependencies:
cupertino_icons: ^1.0.5 cupertino_icons: ^1.0.5
path_provider: ^2.0.11 path_provider: ^2.0.11
flutter_fgbg: ^0.3.0 # Try removing reliance on this flutter_fgbg: ^0.3.0 # Try removing reliance on this
flutter_local_notifications: ^16.1.0 flutter_local_notifications: ^17.0.0
provider: ^6.0.3 provider: ^6.0.3
http: ^1.0.0 http: ^1.0.0
webview_flutter: ^4.0.0 webview_flutter: ^4.0.0
@@ -48,25 +48,25 @@ dependencies:
url_launcher: ^6.1.5 url_launcher: ^6.1.5
permission_handler: ^11.0.0 permission_handler: ^11.0.0
fluttertoast: ^8.0.9 fluttertoast: ^8.0.9
device_info_plus: ^9.0.0 device_info_plus: ^10.0.1
file_picker: ^6.0.0 file_picker: ^8.0.0+1
animations: ^2.0.4 animations: ^2.0.4
android_package_installer: android_package_installer:
git: git:
url: https://github.com/ImranR98/android_package_installer url: https://github.com/ImranR98/android_package_installer
ref: main ref: main
android_package_manager: ^0.7.0 android_package_manager: ^0.7.0
share_plus: ^7.0.0 share_plus: ^8.0.2
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: ^5.0.1
flutter_markdown: ^0.6.14 flutter_markdown: ^0.6.14
flutter_archive: ^6.0.0 flutter_archive: ^6.0.0
hsluv: ^1.1.3 hsluv: ^1.1.3
connectivity_plus: ^5.0.0 connectivity_plus: ^6.0.1
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: ^4.0.0
background_fetch: ^1.2.1 background_fetch: ^1.2.1
dev_dependencies: dev_dependencies: