mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Compare commits
64 Commits
v0.15.2-be
...
v0.15.7-be
Author | SHA1 | Date | |
---|---|---|---|
e8580dc1d5 | |||
daffff7eb0 | |||
751fda5e37 | |||
1e38abc500 | |||
8e7137815b | |||
4226ee453f | |||
568a110443 | |||
7fd7a6ca8d | |||
3092c854ff | |||
a9566f4b23 | |||
7a5aa3c11d | |||
f2454bb028 | |||
2c1023a6fd | |||
9187ac6684 | |||
9640e43bc5 | |||
09b24b36f2 | |||
06a761c0d0 | |||
6ff7d7fcd7 | |||
0f27c79303 | |||
9961ad081f | |||
02056b4f78 | |||
0492588c25 | |||
ad8463ac8b | |||
7843de240a | |||
0037a08017 | |||
92ba432992 | |||
3a521f4014 | |||
e8bbc9799c | |||
96a48b4813 | |||
355450e82d | |||
99307c5b0c | |||
ace8be6514 | |||
1b538931b1 | |||
7ccd08ee2b | |||
104beed594 | |||
e8df594edb | |||
6e922a84f8 | |||
b74bf86277 | |||
6cbe50c9ef | |||
14fcca040b | |||
7bb545cff9 | |||
f52b1a246c | |||
273dfd89cc | |||
52d72d287b | |||
e8d8fcbe69 | |||
30c6c1afd3 | |||
c8c8093b3a | |||
c970b2cf2e | |||
0815283d31 | |||
55ee2d93b8 | |||
c9e17ed42b | |||
76a91b7fe0 | |||
f9b3169b6a | |||
4f42b5a3ee | |||
5d9645eaff | |||
c032808d82 | |||
282c94266e | |||
e2f7d52bee | |||
6317f0162a | |||
436a6310d7 | |||
0f74195255 | |||
5ba33786ab | |||
70fcfc1753 | |||
1fc8ee6fee |
@ -1,12 +1,14 @@
|
||||
#  Obtainium
|
||||
|
||||
[](https://techforpalestine.org/learn-more)
|
||||
|
||||
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.
|
||||
|
||||
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
|
||||
|
||||
Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
|
||||
Read the Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
|
||||
|
||||
Currently supported App sources:
|
||||
- Open Source - General:
|
||||
@ -30,8 +32,8 @@ Currently supported App sources:
|
||||
- [Signal](https://signal.org/)
|
||||
- [VLC](https://videolan.org/)
|
||||
- Other - App-Specific:
|
||||
- [WhatsApp](https://whatsapp.com)
|
||||
- [Telegram App](https://telegram.org)
|
||||
- [Steam Mobile Apps](https://store.steampowered.com/mobile)
|
||||
- [Neutron Code](https://neutroncode.com)
|
||||
- "HTML" (Fallback): Any other URL that returns an HTML page with links to APK files
|
||||
|
||||
|
@ -96,19 +96,15 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
def shizuku_version = '13.1.5'
|
||||
implementation "dev.rikka.shizuku:api:$shizuku_version"
|
||||
implementation "dev.rikka.shizuku:provider:$shizuku_version"
|
||||
|
||||
def hidden_api_version = '4.1.0'
|
||||
// DO NOT UPDATE Hidden API without updating the Android tools
|
||||
// and do not update Android tools without updating the whole Flutter
|
||||
// (also in android/build.gradle)
|
||||
def hidden_api_version = '4.3.1'
|
||||
implementation "dev.rikka.tools.refine:runtime:$hidden_api_version"
|
||||
implementation "dev.rikka.hidden:compat:$hidden_api_version"
|
||||
compileOnly "dev.rikka.hidden:stub:$hidden_api_version"
|
||||
implementation "org.lsposed.hiddenapibypass:hiddenapibypass:4.3"
|
||||
|
||||
implementation "com.github.topjohnwu.libsu:core:5.2.2"
|
||||
}
|
||||
|
@ -5,7 +5,8 @@
|
||||
android:label="Obtainium"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@ -44,21 +45,6 @@
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<service
|
||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false" />
|
||||
<receiver
|
||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
|
||||
android:exported="false" />
|
||||
<receiver
|
||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="dev.imranr.obtainium"
|
||||
|
@ -0,0 +1,44 @@
|
||||
package dev.imranr.obtainium
|
||||
|
||||
import android.util.Xml
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
class DefaultSystemFont {
|
||||
fun get(): String {
|
||||
return try {
|
||||
val file = File("/system/etc/fonts.xml")
|
||||
val fileStream = FileInputStream(file)
|
||||
parseFontsFileStream(fileStream)
|
||||
} catch (e: Exception) {
|
||||
e.message ?: "Unknown fonts.xml parsing exception"
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseFontsFileStream(fileStream: FileInputStream): String {
|
||||
fileStream.use { stream ->
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(stream, null)
|
||||
parser.nextTag()
|
||||
return parseFonts(parser)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseFonts(parser: XmlPullParser): String {
|
||||
while (!((parser.next() == XmlPullParser.END_TAG) && (parser.name == "family"))) {
|
||||
if ((parser.eventType == XmlPullParser.START_TAG) && (parser.name == "font")
|
||||
&& (parser.getAttributeValue(null, "style") == "normal")
|
||||
&& (parser.getAttributeValue(null, "weight") == "400")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
parser.next()
|
||||
val fontFile = parser.text.trim()
|
||||
if (fontFile == "") {
|
||||
throw NoSuchFieldException("The font filename couldn't be found in fonts.xml")
|
||||
}
|
||||
return "/system/fonts/$fontFile"
|
||||
}
|
||||
}
|
@ -22,12 +22,13 @@ import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private var installersChannel: MethodChannel? = null
|
||||
private var nativeChannel: MethodChannel? = null
|
||||
private val SHIZUKU_PERMISSION_REQUEST_CODE = (10..200).random()
|
||||
|
||||
private fun shizukuCheckPermission(result: Result) {
|
||||
@ -51,7 +52,7 @@ class MainActivity: FlutterActivity() {
|
||||
requestCode: Int, grantResult: Int ->
|
||||
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
|
||||
val res = if (grantResult == PackageManager.PERMISSION_GRANTED) 1 else 0
|
||||
installersChannel!!.invokeMethod("resPermShizuku", mapOf("res" to res))
|
||||
nativeChannel!!.invokeMethod("resPermShizuku", mapOf("res" to res))
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +77,8 @@ class MainActivity: FlutterActivity() {
|
||||
val params =
|
||||
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
var installFlags: Int = PackageInstallerUtils.getInstallFlags(params)
|
||||
installFlags = installFlags or 0x00000004 // PackageManager.INSTALL_ALLOW_TEST
|
||||
installFlags = installFlags or (0x00000002/*PackageManager.INSTALL_REPLACE_EXISTING*/
|
||||
or 0x00000004 /*PackageManager.INSTALL_ALLOW_TEST*/)
|
||||
PackageInstallerUtils.setInstallFlags(params, installFlags)
|
||||
val sessionId = packageInstaller.createSession(params)
|
||||
val iSession = IPackageInstallerSession.Stub.asInterface(
|
||||
@ -136,7 +138,7 @@ class MainActivity: FlutterActivity() {
|
||||
}
|
||||
|
||||
private fun rootInstallApk(apkFilePath: String, result: Result) {
|
||||
Shell.sh("pm install -R -t " + apkFilePath).submit { out ->
|
||||
Shell.sh("pm install -r -t " + apkFilePath).submit { out ->
|
||||
val builder = StringBuilder()
|
||||
for (data in out.getOut()) { builder.append(data) }
|
||||
result.success(builder.toString().endsWith("Success"))
|
||||
@ -145,12 +147,18 @@ class MainActivity: FlutterActivity() {
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
HiddenApiBypass.addHiddenApiExemptions("")
|
||||
}
|
||||
Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener)
|
||||
installersChannel = MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger, "installers")
|
||||
installersChannel!!.setMethodCallHandler {
|
||||
nativeChannel = MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger, "native")
|
||||
nativeChannel!!.setMethodCallHandler {
|
||||
call, result ->
|
||||
if (call.method == "checkPermissionShizuku") {
|
||||
if (call.method == "getSystemFont") {
|
||||
val res = DefaultSystemFont().get()
|
||||
result.success(res)
|
||||
} else if (call.method == "checkPermissionShizuku") {
|
||||
shizukuCheckPermission(result)
|
||||
} else if (call.method == "checkPermissionRoot") {
|
||||
rootCheckPermission(result)
|
||||
|
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
ext.kotlin_version = '1.8.10'
|
||||
ext {
|
||||
compileSdkVersion = 34 // or latest
|
||||
targetSdkVersion = 34 // or latest
|
||||
@ -11,9 +11,9 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
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.1.0' // Do not update!
|
||||
classpath "dev.rikka.tools.refine:gradle-plugin:4.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Uvezi greške",
|
||||
"importedXOfYApps": "{} od {} aplikacija uvezeno.",
|
||||
"followingURLsHadErrors": "Sljedeći URL-ovi su imali greške:",
|
||||
"okay": "Dobro",
|
||||
"selectURL": "Odaberite URL",
|
||||
"selectURLs": "Odaberite URL-ove",
|
||||
"pick": "Odaberi",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "Podržite fiksne APK URL-ove",
|
||||
"selectX": "Izaberite {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "Želite li ukloniti aplikaciju?",
|
||||
"other": "Želite li ukloniti aplikacije?"
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Chyba importu",
|
||||
"importedXOfYApps": "{}importováno z {} aplikací.",
|
||||
"followingURLsHadErrors": "U následujících adres došlo k chybám:",
|
||||
"okay": "Okay",
|
||||
"selectURL": "Vybrat adresu",
|
||||
"selectURLs": "Select adresy",
|
||||
"pick": "Vybrat",
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Importfehler",
|
||||
"importedXOfYApps": "{} von {} Apps importiert.",
|
||||
"followingURLsHadErrors": "Bei folgenden URLs traten Fehler auf:",
|
||||
"okay": "Okay",
|
||||
"selectURL": "URL auswählen",
|
||||
"selectURLs": "URLs auswählen",
|
||||
"pick": "Auswählen",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "neuere Version anhand der ersten dreißig Zahlen der Checksumme der APK URL erraten, wenn anderweitig nicht unterstützt",
|
||||
"selectX": "Wähle {}",
|
||||
"parallelDownloads": "Erlaube parallele Downloads",
|
||||
"installMethod": "Installationsmethode",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku läuft nicht",
|
||||
"removeAppQuestion": {
|
||||
"one": "App entfernen?",
|
||||
"other": "Apps entfernen?"
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Import Errors",
|
||||
"importedXOfYApps": "{} of {} Apps imported.",
|
||||
"followingURLsHadErrors": "The following URLs had errors:",
|
||||
"okay": "Okay",
|
||||
"selectURL": "Select URL",
|
||||
"selectURLs": "Select URLs",
|
||||
"pick": "Pick",
|
||||
@ -287,7 +286,9 @@
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
|
||||
"useSystemFont": "Use the system font",
|
||||
"systemFontError": "Error loading the system font: {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remove App?",
|
||||
"other": "Remove Apps?"
|
||||
|
@ -8,13 +8,13 @@
|
||||
"functionNotImplemented": "Esta clase no ha implementado esta función",
|
||||
"placeholder": "Espacio reservado",
|
||||
"someErrors": "Han ocurrido algunos errores",
|
||||
"unexpectedError": "Error Inesperado",
|
||||
"unexpectedError": "Error inesperado",
|
||||
"ok": "OK",
|
||||
"and": "y",
|
||||
"githubPATLabel": "Token Github de Acceso Personal\n(Reduce tiempos de espera)",
|
||||
"githubPATLabel": "Token GitHub de acceso personal\n(reduce tiempos de espera)",
|
||||
"includePrereleases": "Incluir versiones preliminares",
|
||||
"fallbackToOlderReleases": "Retroceder a versiones previas",
|
||||
"filterReleaseTitlesByRegEx": "Filtrar por título de version",
|
||||
"filterReleaseTitlesByRegEx": "Filtrar por título de versión",
|
||||
"invalidRegEx": "Expresión inválida",
|
||||
"noDescription": "Sin descripción",
|
||||
"cancel": "Cancelar",
|
||||
@ -25,11 +25,11 @@
|
||||
"githubStarredRepos": "Repositorios favoritos GitHub",
|
||||
"uname": "Nombre de usuario",
|
||||
"wrongArgNum": "Número de argumentos provistos inválido",
|
||||
"xIsTrackOnly": "{} es de 'Solo Seguimiento'",
|
||||
"xIsTrackOnly": "{} es de 'Sólo seguimiento'",
|
||||
"source": "Origen",
|
||||
"app": "Aplicación",
|
||||
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
|
||||
"youPickedTrackOnly": "Debe seleccionar la opción de 'Solo Seguimiento'.",
|
||||
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'solo seguimiento'.",
|
||||
"youPickedTrackOnly": "Debe seleccionar la opción de 'solo seguimiento'.",
|
||||
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargarla o actalizarla.",
|
||||
"cancelled": "Cancelado",
|
||||
"appAlreadyAdded": "Aplicación ya añadida",
|
||||
@ -42,7 +42,7 @@
|
||||
"search": "Buscar",
|
||||
"additionalOptsFor": "Opciones Adicionales para {}",
|
||||
"supportedSources": "Fuentes Soportadas",
|
||||
"trackOnlyInBrackets": "(Solo Seguimiento)",
|
||||
"trackOnlyInBrackets": "(Solo seguimiento)",
|
||||
"searchableInBrackets": "(soporta búsqueda)",
|
||||
"appsString": "Aplicaciones",
|
||||
"noApps": "Sin Aplicaciones",
|
||||
@ -61,27 +61,27 @@
|
||||
"removeSelectedApps": "Eliminar Aplicaciones Seleccionadas",
|
||||
"updateX": "Actualizar {}",
|
||||
"installX": "Instalar {}",
|
||||
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo Seguimiento)\ncomo Actualizada",
|
||||
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo seguimiento)\ncomo actualizada",
|
||||
"changeX": "Cambiar {}",
|
||||
"installUpdateApps": "Instalar/Actualizar Aplicaciones",
|
||||
"installUpdateSelectedApps": "Instalar/Actualizar Aplicaciones Seleccionadas",
|
||||
"markXSelectedAppsAsUpdated": "¿Marcar {} Aplicaciones Seleccionadas como Actualizadas?",
|
||||
"installUpdateApps": "Instalar/Actualizar aplicaciones",
|
||||
"installUpdateSelectedApps": "Instalar/Actualizar aplicaciones seleccionadas",
|
||||
"markXSelectedAppsAsUpdated": "¿Marcar {} aplicaciones seleccionadas como actualizadas?",
|
||||
"no": "No",
|
||||
"yes": "Sí",
|
||||
"markSelectedAppsUpdated": "Marcar Aplicaciones Seleccionadas como Actualizadas",
|
||||
"markSelectedAppsUpdated": "Marcar aplicaciones seleccionadas como actualizadas",
|
||||
"pinToTop": "Fijar arriba",
|
||||
"unpinFromTop": "Desfijar de arriba",
|
||||
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar Estado de Instalación para las Aplicaciones Seleccionadas?",
|
||||
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar estado de instalación para las aplicaciones seleccionadas?",
|
||||
"installStatusOfXWillBeResetExplanation": "El estado de instalación de las aplicaciones seleccionadas será restaurado.\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.",
|
||||
"shareSelectedAppURLs": "Compartir URLs de las Aplicaciones Seleccionadas",
|
||||
"shareSelectedAppURLs": "Compartir URLs de las aplicaciones seleccionadas",
|
||||
"resetInstallStatus": "Restaurar Estado de Instalación",
|
||||
"more": "Más",
|
||||
"removeOutdatedFilter": "Elimiar Filtro de Aplicaciones Desactualizado",
|
||||
"showOutdatedOnly": "Mostrar solo Aplicaciones Desactualizadas",
|
||||
"removeOutdatedFilter": "Elimiar filtro de aplicaciones desactualizado",
|
||||
"showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas",
|
||||
"filter": "Filtrar",
|
||||
"filterActive": "Filtrar *",
|
||||
"filterApps": "Filtrar Actualizaciones",
|
||||
"appName": "Nombre de la Aplicación",
|
||||
"appName": "Nombre de la aplicación",
|
||||
"author": "Autor",
|
||||
"upToDateApps": "Aplicaciones Actualizadas",
|
||||
"nonInstalledApps": "Aplicaciones No Instaladas",
|
||||
@ -103,14 +103,13 @@
|
||||
"importErrors": "Errores de Importación",
|
||||
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
|
||||
"followingURLsHadErrors": "Las siguientes URLs han tenido problemas:",
|
||||
"okay": "Aceptar",
|
||||
"selectURL": "Seleccionar URL",
|
||||
"selectURLs": "Seleccionar URLs",
|
||||
"pick": "Escoger",
|
||||
"theme": "Tema",
|
||||
"dark": "Oscuro",
|
||||
"light": "Claro",
|
||||
"followSystem": "Seguir al Sistema",
|
||||
"followSystem": "Seguir al sistema",
|
||||
"obtainium": "Obtainium",
|
||||
"materialYou": "Material You",
|
||||
"useBlackTheme": "Negro puro en tema Oscuro",
|
||||
@ -121,16 +120,16 @@
|
||||
"appSortOrder": "Orden de Clasificación",
|
||||
"ascending": "Ascendente",
|
||||
"descending": "Descendente",
|
||||
"bgUpdateCheckInterval": "Intervalo de Comprobación de Actualizaciones en Segundo Plano",
|
||||
"neverManualOnly": "Nunca - Solo Manual",
|
||||
"bgUpdateCheckInterval": "Comprobación actualizaciones en segundo plano",
|
||||
"neverManualOnly": "Nunca, solo manual",
|
||||
"appearance": "Apariencia",
|
||||
"showWebInAppView": "Mostrar Vista de la Web de Origen",
|
||||
"pinUpdates": "Fijar Actualizaciones en la Parte Superior de la Vista de Aplicaciones",
|
||||
"showWebInAppView": "Mostrar vista de la web de origen",
|
||||
"pinUpdates": "Fijar Actualizaciones al principio",
|
||||
"updates": "Actualizaciones",
|
||||
"sourceSpecific": "Fuente Específica",
|
||||
"appSource": "Fuente de la Aplicación",
|
||||
"appSource": "Obtainium en GitHub",
|
||||
"noLogs": "Sin Logs",
|
||||
"appLogs": "Logs de la Aplicación",
|
||||
"appLogs": "Logs",
|
||||
"close": "Cerrar",
|
||||
"share": "Compartir",
|
||||
"appNotFound": "Aplicación no encontrada",
|
||||
@ -214,30 +213,30 @@
|
||||
"importFromURLsInFile": "Importar URLs desde archivo (como OPML)",
|
||||
"versionDetection": "Detección de Versiones",
|
||||
"standardVersionDetection": "Por versión",
|
||||
"groupByCategory": "Agrupar por Categoría",
|
||||
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador, si es posible",
|
||||
"groupByCategory": "Agrupar por categoría",
|
||||
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador (si es posible)",
|
||||
"overrideSource": "Sobrescribir Fuente",
|
||||
"dontShowAgain": "No mostrar de nuevo",
|
||||
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
|
||||
"dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APKs",
|
||||
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas en la Parte Inferior de la Vista de Aplicaciones",
|
||||
"gitlabPATLabel": "Token GitLab de Acceso Personal\n(Habilita la Búsqueda y Mejor Detección de APKs)",
|
||||
"dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps en 'solo seguimiento'",
|
||||
"dontShowAPKOriginWarnings": "No mostrar avisos sobre las fuentes de las APKs",
|
||||
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas al final",
|
||||
"gitlabPATLabel": "Token GitLab de acceso personal\n(habilita la búsqueda y mejor detección de APKs)",
|
||||
"about": "Acerca",
|
||||
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en Ajustes)",
|
||||
"checkOnStart": "Comprobar actualizaciones durante el inicio",
|
||||
"tryInferAppIdFromCode": "Intentar deducir la ID de la APP por el código fuente",
|
||||
"removeOnExternalUninstall": "Auto eliminar Apps desinstaladas externamente",
|
||||
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en ajustes)",
|
||||
"checkOnStart": "Comprobar actualizaciones al inicio",
|
||||
"tryInferAppIdFromCode": "Intentar deducir la ID de la app por el código fuente",
|
||||
"removeOnExternalUninstall": "Auto eliminar apps desinstaladas externamente",
|
||||
"pickHighestVersionCode": "Auto selección versión superior del código APK",
|
||||
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la App",
|
||||
"disablePageTransitions": "Deshabilitar animaciones de transición de la página",
|
||||
"reversePageTransitions": "Invertir animaciones de transición de la página",
|
||||
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la app",
|
||||
"disablePageTransitions": "Deshabilitar animaciones de transición",
|
||||
"reversePageTransitions": "Invertir animaciones de transición",
|
||||
"minStarCount": "Número Mínimo de Estrellas",
|
||||
"addInfoBelow": "Añadir esta información debajo.",
|
||||
"addInfoInSettings": "Puede añadir esta información en Ajustes.",
|
||||
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con una clave API.",
|
||||
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin una clave API.",
|
||||
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||
"filterReleaseNotesByRegEx": "Filtrar por notas de nersión (release notes)",
|
||||
"filterReleaseNotesByRegEx": "Filtrar por notas de versión (release notes)",
|
||||
"customLinkFilterRegex": "Filtro personalizado de Enlace APK (por defecto '.apk$')",
|
||||
"appsPossiblyUpdated": "Actualización de Apps intentada",
|
||||
"appsPossiblyUpdatedNotifDescription": "Notifica al usuario que las actualizaciones en segundo plano podrían haberse realizado para una o más aplicaciones",
|
||||
@ -245,7 +244,7 @@
|
||||
"enableBackgroundUpdates": "Habilitar actualizaciones en segundo plano",
|
||||
"backgroundUpdateReqsExplanation": "Las actualizaciones en segundo plano pueden no estar disponibles para todas las aplicaciones.",
|
||||
"backgroundUpdateLimitsExplanation": "El éxito de las instalaciones en segundo plano solo se puede comprobar con Obtainium abierto.",
|
||||
"verifyLatestTag": "Comprueba la etiqueta 'Latest'",
|
||||
"verifyLatestTag": "Comprobar la etiqueta 'Latest'",
|
||||
"intermediateLinkRegex": "Filtrar por enlace 'intermedio' para visitar primero",
|
||||
"filterByLinkText": "Filter links by link text",
|
||||
"intermediateLinkNotFound": "Enlace intermedio no encontrado",
|
||||
@ -253,10 +252,10 @@
|
||||
"exemptFromBackgroundUpdates": "Exenta de actualizciones en segundo plano (si están habilitadas)",
|
||||
"bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi",
|
||||
"autoSelectHighestVersionCode": "Auto Selección de la versionCode APK superior",
|
||||
"versionExtractionRegEx": "Versión de Extracción de RegEx",
|
||||
"matchGroupToUse": "Coincidir en Grupo a Usar",
|
||||
"versionExtractionRegEx": "Versión de extracción regex",
|
||||
"matchGroupToUse": "Grupo a usar para versión de extracción regex",
|
||||
"highlightTouchTargets": "Resaltar objetivos menos obvios",
|
||||
"pickExportDir": "Selecciona el Directorio para Exportar",
|
||||
"pickExportDir": "Directorio para Exportar",
|
||||
"autoExportOnChanges": "Auto Exportar cuando haya cambios",
|
||||
"includeSettings": "Incluir ajustes",
|
||||
"filterVersionsByRegEx": "Filtrar por Versiones",
|
||||
@ -268,21 +267,26 @@
|
||||
"debugMenu": "Menu Depurar",
|
||||
"bgTaskStarted": "Iniciada tarea en segundo plano - revisa los logs.",
|
||||
"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",
|
||||
"skipUpdateNotifications": "Omitir de notificaciones sobre actualizaciones",
|
||||
"updatesAvailableNotifChannel": "Actualizaciones Disponibles",
|
||||
"appsUpdatedNotifChannel": "Aplicaciones Actualizadas",
|
||||
"appsPossiblyUpdatedNotifChannel": "Se ha Intentado Actualizar la Aplicación",
|
||||
"errorCheckingUpdatesNotifChannel": "Error Buscando Actualizaciones",
|
||||
"appsRemovedNotifChannel": "Aplicaciones Eliminadas",
|
||||
"skipUpdateNotifications": "No notificar sobre actualizaciones",
|
||||
"updatesAvailableNotifChannel": "Actualizaciones disponibles",
|
||||
"appsUpdatedNotifChannel": "Aplicaciones actualizadas",
|
||||
"appsPossiblyUpdatedNotifChannel": "Se ha intentado actualizar la aplicación",
|
||||
"errorCheckingUpdatesNotifChannel": "Error buscando actualizaciones",
|
||||
"appsRemovedNotifChannel": "Aplicaciones eliminadas",
|
||||
"downloadingXNotifChannel": "Descargando {}",
|
||||
"completeAppInstallationNotifChannel": "Instalación Completa de la Aplicación",
|
||||
"checkingForUpdatesNotifChannel": "Buscando Actualizaciones",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Comprobar actualizaciones solo para apps instaladas y en seguimiento",
|
||||
"completeAppInstallationNotifChannel": "Instalación completada",
|
||||
"checkingForUpdatesNotifChannel": "Buscando actualizaciones",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Comprobar actualizaciones solo para apps instaladas o en seguimiento",
|
||||
"supportFixedAPKURL": "Soporte para URLs fijas de APK",
|
||||
"selectX": "Selecciona {}",
|
||||
"parallelDownloads": "Permitir descargas paralelas",
|
||||
"installMethod": "Método de instalación",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku no está operativo",
|
||||
"removeAppQuestion": {
|
||||
"one": "¿Eliminar Aplicación?",
|
||||
"other": "¿Eliminar Aplicaciones?"
|
||||
@ -308,16 +312,16 @@
|
||||
"other": "{} URLs"
|
||||
},
|
||||
"minute": {
|
||||
"one": "{} Minuto",
|
||||
"other": "{} Minutos"
|
||||
"one": "{} minuto",
|
||||
"other": "{} minutos"
|
||||
},
|
||||
"hour": {
|
||||
"one": "{} Hora",
|
||||
"other": "{} Horas"
|
||||
"one": "{} hora",
|
||||
"other": "{} horas"
|
||||
},
|
||||
"day": {
|
||||
"one": "{} Día",
|
||||
"other": "{} Días"
|
||||
"one": "{} día",
|
||||
"other": "{} días"
|
||||
},
|
||||
"clearedNLogsBeforeXAfterY": {
|
||||
"one": "Eliminado {n} log (previo a = {before}, posterior a = {after})",
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "خطاهای وارد کردن",
|
||||
"importedXOfYApps": "{} از {} برنامه وارد شد.",
|
||||
"followingURLsHadErrors": "آدرس های اینترنتی زیر دارای خطا بودند:",
|
||||
"okay": "باشه",
|
||||
"selectURL": "آدرس اینترنتی انتخاب شده",
|
||||
"selectURLs": "آدرس های اینترنتی انتخاب شده",
|
||||
"pick": "انتخاب",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "پشتیبانی از URL های APK ثابت",
|
||||
"selectX": "انتخاب کنید {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "برنامه حذف شود؟",
|
||||
"other": "برنامه ها حذف شوند؟"
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Erreurs d'importation",
|
||||
"importedXOfYApps": "{} sur {} applications importées.",
|
||||
"followingURLsHadErrors": "Les URL suivantes comportaient des erreurs :",
|
||||
"okay": "Okay",
|
||||
"selectURL": "Sélectionnez l'URL",
|
||||
"selectURLs": "Sélectionnez les URLs",
|
||||
"pick": "Prendre",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "Supprimer l'application ?",
|
||||
"other": "Supprimer les applications ?"
|
||||
@ -335,4 +339,4 @@
|
||||
"one": "{} and 1 more app may have been updated.",
|
||||
"other": "{} and {} more apps may have been updated."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Importálási hibák",
|
||||
"importedXOfYApps": "{}/{} app importálva.",
|
||||
"followingURLsHadErrors": "A következő URL-ek hibákat tartalmaztak:",
|
||||
"okay": "Oké",
|
||||
"selectURL": "Válassza ki az URL-t",
|
||||
"selectURLs": "Kiválasztott URL-ek",
|
||||
"pick": "Válasszon",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "Támogatja a rögzített APK URL-eket",
|
||||
"selectX": "Kiválaszt {}",
|
||||
"parallelDownloads": "Párhuzamos letöltéseket enged",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "Eltávolítja az alkalmazást?",
|
||||
"other": "Eltávolítja az alkalmazást?"
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Errori di importazione",
|
||||
"importedXOfYApps": "{} app di {} importate.",
|
||||
"followingURLsHadErrors": "I seguenti URL contengono errori:",
|
||||
"okay": "Va bene",
|
||||
"selectURL": "Seleziona l'URL",
|
||||
"selectURLs": "Seleziona gli URL",
|
||||
"pick": "Seleziona",
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "インポートエラー",
|
||||
"importedXOfYApps": "{} / {} アプリをインポートしました",
|
||||
"followingURLsHadErrors": "以下のURLでエラーが発生しました:",
|
||||
"okay": "OK",
|
||||
"selectURL": "URLを選択",
|
||||
"selectURLs": "URLを選択",
|
||||
"pick": "選択",
|
||||
@ -236,7 +235,7 @@
|
||||
"addInfoInSettings": "設定でこの情報を追加してください。",
|
||||
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
|
||||
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
|
||||
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||
"sortByLastLinkSegment": "リンクの最後のセグメントのみでソートする",
|
||||
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
|
||||
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
|
||||
"appsPossiblyUpdated": "アプリのアップデートを試行",
|
||||
@ -246,10 +245,10 @@
|
||||
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
|
||||
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
|
||||
"verifyLatestTag": "'latest'タグを確認する",
|
||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||
"filterByLinkText": "Filter links by link text",
|
||||
"intermediateLinkRegex": "訪問する「中間」リンクのフィルター",
|
||||
"filterByLinkText": "テキストでリンクをフィルタリングする",
|
||||
"intermediateLinkNotFound": "中間リンクが見つかりませんでした",
|
||||
"intermediateLink": "Intermediate link",
|
||||
"intermediateLink": "中間リンク",
|
||||
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
|
||||
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする",
|
||||
"autoSelectHighestVersionCode": "最も高いバージョンコードのAPKを自動で選択する",
|
||||
@ -263,8 +262,8 @@
|
||||
"trySelectingSuggestedVersionCode": "提案されたバージョンコードのAPKを選択する",
|
||||
"dontSortReleasesList": "APIからのリリース順を保持する",
|
||||
"reverseSort": "逆順ソート",
|
||||
"takeFirstLink": "Take first link",
|
||||
"skipSort": "Skip sorting",
|
||||
"takeFirstLink": "最初のリンクを取得する",
|
||||
"skipSort": "ソートをスキップする",
|
||||
"debugMenu": "デバッグメニュー",
|
||||
"bgTaskStarted": "バックグラウンドタスクが開始されました - ログを確認してください。",
|
||||
"runBgCheckNow": "今すぐバックグラウンドでのアップデート確認を開始する",
|
||||
@ -280,9 +279,14 @@
|
||||
"completeAppInstallationNotifChannel": "アプリのインストールを完了する",
|
||||
"checkingForUpdatesNotifChannel": "アップデートを確認中",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "インストール済みのアプリと「追跡のみ」のアプリのアップデートのみを確認する",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"supportFixedAPKURL": "固定されたAPKのURLをサポートする",
|
||||
"selectX": "{} 選択",
|
||||
"parallelDownloads": "並行ダウンロードを許可する",
|
||||
"installMethod": "インストール方法",
|
||||
"normal": "通常",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizukuが起動していません",
|
||||
"removeAppQuestion": {
|
||||
"one": "アプリを削除しますか?",
|
||||
"other": "アプリを削除しますか?"
|
||||
@ -335,4 +339,4 @@
|
||||
"one": "{} とさらに 1 個のアプリがアップデートされた可能性があります。",
|
||||
"other": "{} とさらに {} 個のアプリがアップデートされた可能性があります。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Import foutmeldingen",
|
||||
"importedXOfYApps": "{} van {} apps geïmporteerd.",
|
||||
"followingURLsHadErrors": "De volgende URL's bevatten fouten:",
|
||||
"okay": "Ok",
|
||||
"selectURL": "Selecteer URL",
|
||||
"selectURLs": "Selecteer URL's",
|
||||
"pick": "Kies",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "App verwijderen?",
|
||||
"other": "Apps verwijderen?"
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Błędy importowania",
|
||||
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
|
||||
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
|
||||
"okay": "Okej",
|
||||
"selectURL": "Wybierz adres URL",
|
||||
"selectURLs": "Wybierz adresy URL",
|
||||
"pick": "Wybierz",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "Obsługuj stałe adresy URL APK",
|
||||
"selectX": "Wybierz {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "Usunąć aplikację?",
|
||||
"few": "Usunąć aplikacje?",
|
||||
@ -361,4 +365,4 @@
|
||||
"many": "{} i {} innych apek mogło zostać zaktualizowanych.",
|
||||
"other": "{} i {} inne apki mogły zostać zaktualizowane."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
{
|
||||
"invalidURLForSource": "URL {} inválida",
|
||||
"noReleaseFound": "Não foi possivel encontrar uma versão adequada",
|
||||
"noVersionFound": "Não foi possivel encontrar uma versão lançada",
|
||||
"noReleaseFound": "Não foi possível encontrar uma versão adequada",
|
||||
"noVersionFound": "Não foi possível encontrar uma versão",
|
||||
"urlMatchesNoSource": "URL não corresponde a uma fonte conhecida",
|
||||
"cantInstallOlderVersion": "Não pode instalar uma versão anterior de um App",
|
||||
"appIdMismatch": "ID do pacote baixado não é igual ao ID do App instalado",
|
||||
"cantInstallOlderVersion": "Não é permitido instalar uma versão anterior de um aplicativo",
|
||||
"appIdMismatch": "ID do pacote baixado não é igual ao ID do aplicativo instalado",
|
||||
"functionNotImplemented": "Esta classe não implementou essa função",
|
||||
"placeholder": "Espaço Reservado",
|
||||
"someErrors": "Alguns Erros Ocorreram",
|
||||
"unexpectedError": "Erro Inesperado",
|
||||
"ok": "Ok",
|
||||
"placeholder": "Espaço reservado",
|
||||
"someErrors": "Alguns erros ocorreram",
|
||||
"unexpectedError": "Erro inesperado",
|
||||
"ok": "OK",
|
||||
"and": "e",
|
||||
"githubPATLabel": "Token de Acceso Pessoal do GitHub (Reduz tempos de espera)",
|
||||
"githubPATLabel": "Token de acesso pessoal do GitHub (Reduz tempos de espera)",
|
||||
"includePrereleases": "Incluir pré-lançamentos",
|
||||
"fallbackToOlderReleases": "Retornar para versões anteriores",
|
||||
"filterReleaseTitlesByRegEx": "Filtrar Titulos de Versões por Expressão Regular",
|
||||
"invalidRegEx": "Expressão Regular Inválida",
|
||||
"filterReleaseTitlesByRegEx": "Filtrar títulos de versões por expressão regular",
|
||||
"invalidRegEx": "Expressão regular inválida",
|
||||
"noDescription": "Sem descrição",
|
||||
"cancel": "Cancelar",
|
||||
"continue": "Continuar",
|
||||
@ -25,66 +25,66 @@
|
||||
"githubStarredRepos": "Favoritados no GitHub",
|
||||
"uname": "Nome de usuário",
|
||||
"wrongArgNum": "Número de argumentos errado",
|
||||
"xIsTrackOnly": "{} é 'Apenas Seguir'",
|
||||
"xIsTrackOnly": "{} é 'Apenas monitorar'",
|
||||
"source": "Fonte",
|
||||
"app": "App",
|
||||
"appsFromSourceAreTrackOnly": "Os apps desta fonte são 'Apenas Seguir'.",
|
||||
"youPickedTrackOnly": "Você selecionou a opção 'Apenas Seguir'.",
|
||||
"trackOnlyAppDescription": "Esse App vai ser seguido por atualizações, mais o Obtainium não poderá baixa-lo ou instala-lo.",
|
||||
"app": "Aplicativo",
|
||||
"appsFromSourceAreTrackOnly": "Os aplicativos desta fonte são 'Apenas monitorar'.",
|
||||
"youPickedTrackOnly": "Você selecionou a opção 'Apenas monitorar'.",
|
||||
"trackOnlyAppDescription": "As atualizações desse aplicativo serão monitoradas, mas o Obtainium não poderá baixá-lo ou instalá-lo.",
|
||||
"cancelled": "Cancelado",
|
||||
"appAlreadyAdded": "App já adicionado",
|
||||
"alreadyUpToDateQuestion": "App já atualizado?",
|
||||
"addApp": "Adicionar App",
|
||||
"appSourceURL": "URL de origem do App",
|
||||
"appAlreadyAdded": "Aplicativo já adicionado",
|
||||
"alreadyUpToDateQuestion": "Aplicativo já foi atualizado?",
|
||||
"addApp": "Adicionar aplicativo",
|
||||
"appSourceURL": "URL de origem do aplicativo",
|
||||
"error": "Erro",
|
||||
"add": "Adicionar",
|
||||
"searchSomeSourcesLabel": "Procurar (Apenas Algumas Fontes)",
|
||||
"searchSomeSourcesLabel": "Procurar (Apenas algumas fontes)",
|
||||
"search": "Procurar",
|
||||
"additionalOptsFor": "Opções Adicionais para {}",
|
||||
"supportedSources": "Fontes Compatíveis",
|
||||
"trackOnlyInBrackets": "(Apenas Seguir)",
|
||||
"additionalOptsFor": "Opções adicionais para {}",
|
||||
"supportedSources": "Fontes compatíveis",
|
||||
"trackOnlyInBrackets": "(Apenas monitorar)",
|
||||
"searchableInBrackets": "(Pesquisável)",
|
||||
"appsString": "Apps",
|
||||
"noApps": "Sem Apps",
|
||||
"noAppsForFilter": "Sem Apps para Filtrar",
|
||||
"appsString": "Aplicativos",
|
||||
"noApps": "Não há aplicativos",
|
||||
"noAppsForFilter": "Sem aplicativos para filtrar",
|
||||
"byX": "Por {}",
|
||||
"percentProgress": "Progresso: {}%",
|
||||
"pleaseWait": "Por Favor Espere",
|
||||
"updateAvailable": "Atualização Disponível",
|
||||
"pleaseWait": "Por favor, espere",
|
||||
"updateAvailable": "Atualização disponível",
|
||||
"estimateInBracketsShort": "(Aprox.)",
|
||||
"notInstalled": "Não Instalado",
|
||||
"notInstalled": "Não instalado",
|
||||
"estimateInBrackets": "(Aproximado)",
|
||||
"selectAll": "Selecionar All",
|
||||
"selectAll": "Selecionar todos",
|
||||
"deselectX": "Deselecionar {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} sera removido do Obtainium mais permanecerá instalado no dispositivo.",
|
||||
"removeSelectedAppsQuestion": "Remover Apps Selecionados?",
|
||||
"removeSelectedApps": "Remover Apps Selecionados",
|
||||
"xWillBeRemovedButRemainInstalled": "{} será removido do Obtainium mais permanecerá instalado no dispositivo.",
|
||||
"removeSelectedAppsQuestion": "Remover aplicativos selecionados?",
|
||||
"removeSelectedApps": "Remover aplicativos selecionados",
|
||||
"updateX": "Atualizar {}",
|
||||
"installX": "Instalar {}",
|
||||
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas Seguir)\ncomo Atualizado",
|
||||
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas monitorar)\ncomo Atualizado",
|
||||
"changeX": "Mudar {}",
|
||||
"installUpdateApps": "Instalar/Atualizar Apps",
|
||||
"installUpdateSelectedApps": "Instalar/Atualizar Apps Selecionados",
|
||||
"markXSelectedAppsAsUpdated": "Marcar {} Apps Delecionados como Atualizados?",
|
||||
"installUpdateApps": "Instalar/Atualizar aplicativos",
|
||||
"installUpdateSelectedApps": "Instalar/Atualizar aplicativos selecionados",
|
||||
"markXSelectedAppsAsUpdated": "Marcar {} aplicativos selecionados como atualizados?",
|
||||
"no": "Não",
|
||||
"yes": "Sim",
|
||||
"markSelectedAppsUpdated": "Marcar Apps Selecionados como Atualizados",
|
||||
"markSelectedAppsUpdated": "Marcar aplicativos selecionados como Atualizados",
|
||||
"pinToTop": "Fixar no topo",
|
||||
"unpinFromTop": "Desafixar do topo",
|
||||
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar Status de Instalação para Apps Seleciondos?",
|
||||
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer app selecionado sera reiniciado.\n\nIsso pode ajudar quando uma versão de um App mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
|
||||
"shareSelectedAppURLs": "Compartilhar URLs de Apps Selecionados",
|
||||
"resetInstallStatus": "Reiniciar Status de Instalação",
|
||||
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar status de instalação para aplicativos selecionados?",
|
||||
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer aplicativo selecionado será reiniciado.\n\nIsso pode ajudar quando uma versão de um aplicativo mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
|
||||
"shareSelectedAppURLs": "Compartilhar URLs de aplicativos selecionados",
|
||||
"resetInstallStatus": "Reiniciar status de Iistalação",
|
||||
"more": "Mais",
|
||||
"removeOutdatedFilter": "Remover Filtro de Apps Desatualizados",
|
||||
"showOutdatedOnly": "Mostrar Apenas Apps Desatualizados",
|
||||
"removeOutdatedFilter": "Remover filtro de aplicativos desatualizados",
|
||||
"showOutdatedOnly": "Mostrar apenas aplicativos desatualizados",
|
||||
"filter": "Filtro",
|
||||
"filterActive": "Filtro *",
|
||||
"filterApps": "Filtrar Apps",
|
||||
"appName": "Nome do App",
|
||||
"filterApps": "Filtrar aplicativos",
|
||||
"appName": "Nome do aplicativo",
|
||||
"author": "Autor",
|
||||
"upToDateApps": "Apps Atualizados",
|
||||
"nonInstalledApps": "Apps Não Instalados",
|
||||
"upToDateApps": "Aplicativos tualizados",
|
||||
"nonInstalledApps": "Aplicativos não instalados",
|
||||
"importExport": "Importar/Exportar",
|
||||
"settings": "Configurações",
|
||||
"exportedTo": "Exportado para {}",
|
||||
@ -92,143 +92,142 @@
|
||||
"invalidInput": "Input Inválido",
|
||||
"importedX": "Importado {}",
|
||||
"obtainiumImport": "Importar Obtainium",
|
||||
"importFromURLList": "Importar de Lista de URLs",
|
||||
"importFromURLList": "Importar de lista de URLs",
|
||||
"searchQuery": "Pesquisa",
|
||||
"appURLList": "Lista de URLs de Apps",
|
||||
"appURLList": "Lista de URLs de aplicativos",
|
||||
"line": "Linha",
|
||||
"searchX": "Pesquisa {}",
|
||||
"noResults": "Nenhum resultado encontrado",
|
||||
"importX": "Importar {}",
|
||||
"importedAppsIdDisclaimer": "Apps Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do App.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
|
||||
"importErrors": "Erros de Importação",
|
||||
"importedXOfYApps": "{} de {} Apps importados.",
|
||||
"importedAppsIdDisclaimer": "Aplicativos Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do aplicativo.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
|
||||
"importErrors": "Erros de importação",
|
||||
"importedXOfYApps": "{} de {} aplicativos importados.",
|
||||
"followingURLsHadErrors": "As seguintes URLs apresentaram erros:",
|
||||
"okay": "Ok",
|
||||
"selectURL": "Selecionar URL",
|
||||
"selectURLs": "Selecionar URLs",
|
||||
"pick": "Escolher",
|
||||
"theme": "Tema",
|
||||
"dark": "Escuro",
|
||||
"light": "Claro",
|
||||
"followSystem": "Seguir o Sistema",
|
||||
"followSystem": "Seguir o sistema",
|
||||
"obtainium": "Obtainium",
|
||||
"materialYou": "Material You",
|
||||
"useBlackTheme": "Usar tema preto completamente escuro",
|
||||
"appSortBy": "Classificar App por",
|
||||
"appSortBy": "Classificar aplicativo por",
|
||||
"authorName": "Autor/Nome",
|
||||
"nameAuthor": "Nome/Autor",
|
||||
"asAdded": "Como Adicionado",
|
||||
"appSortOrder": "Ordem de classificação de Apps",
|
||||
"asAdded": "Como adicionado",
|
||||
"appSortOrder": "Ordem de classificação de aplicativos",
|
||||
"ascending": "Ascendente",
|
||||
"descending": "Descendente",
|
||||
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo plano",
|
||||
"neverManualOnly": "Nunca - Apenas Manual",
|
||||
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo-plano",
|
||||
"neverManualOnly": "Nunca - apenas manual",
|
||||
"appearance": "Aparência",
|
||||
"showWebInAppView": "Mostrar páginas da internet em App view",
|
||||
"pinUpdates": "Fixar atualizações no topo da visão de Apps",
|
||||
"showWebInAppView": "Mostrar página da internet em informações do aplicativo",
|
||||
"pinUpdates": "Fixar atualizações no topo da janela de aplicativos",
|
||||
"updates": "Atualizações",
|
||||
"sourceSpecific": "Específico a fonte",
|
||||
"appSource": "Fonte do App",
|
||||
"noLogs": "Sem Logs",
|
||||
"appLogs": "Logs do App",
|
||||
"appSource": "Fonte do aplicativo",
|
||||
"noLogs": "Sem logs",
|
||||
"appLogs": "Logs do aplicativo",
|
||||
"close": "Fechar",
|
||||
"share": "Compartilhar",
|
||||
"appNotFound": "App não encontrado",
|
||||
"appNotFound": "Aplicativo não encontrado",
|
||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||
"pickAnAPK": "Selecionar um APK",
|
||||
"appHasMoreThanOnePackage": "{} tem mais de um pacote:",
|
||||
"deviceSupportsXArch": "Seu dispositivo suporta a arquitetura de CPU {}.",
|
||||
"deviceSupportsFollowingArchs": "Seu dispositivo suporta as seguintes arquiteturas de CPU:",
|
||||
"warning": "Aviso",
|
||||
"sourceIsXButPackageFromYPrompt": "A Fonte do App é '{}' mais o pacote lançado vem de '{}'. Continuar?",
|
||||
"updatesAvailable": "Atualizações Disponíveis",
|
||||
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações estão disponíveis um ou mais Apps seguidos pelo Obtainium",
|
||||
"sourceIsXButPackageFromYPrompt": "A fonte do aplicativo é '{}' mas a origem do pacote é '{}'. Continuar?",
|
||||
"updatesAvailable": "Atualizações disponíveis",
|
||||
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações de um ou mais aplicativos monitorados pelo Obtainium estão disponíveis",
|
||||
"noNewUpdates": "Sem novas atualizações.",
|
||||
"xHasAnUpdate": "{} tem uma atualização.",
|
||||
"appsUpdated": "Apps Atualizados",
|
||||
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações para um ou mais Apps foram aplicadas em segundo plano",
|
||||
"appsUpdated": "Aplicativos atualizados",
|
||||
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações foram aplicadas em segundo-plano para um ou mais aplicativos ",
|
||||
"xWasUpdatedToY": "{} foi atualizado para {}.",
|
||||
"errorCheckingUpdates": "Erro ao Procurar por Atualizações",
|
||||
"errorCheckingUpdatesNotifDescription": "Uma notificação que mostra quando a checagem por atualizações em segundo plano falha",
|
||||
"appsRemoved": "Apps Removidos",
|
||||
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais Apps foram removidos devido a erros ao carregá-los",
|
||||
"errorCheckingUpdates": "Erro ao procurar por atualizações",
|
||||
"errorCheckingUpdatesNotifDescription": "Uma notificação que mostra quando a checagem por atualizações em segundo-plano falha",
|
||||
"appsRemoved": "Aplicativos removidos",
|
||||
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais aplicativos foram removidos devido a erros de carregamento",
|
||||
"xWasRemovedDueToErrorY": "{} foi removido devido a este erro: {}",
|
||||
"completeAppInstallation": "Instalação completa do App",
|
||||
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar Apps",
|
||||
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um App",
|
||||
"checkingForUpdates": "Checando por Atualizações",
|
||||
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando checando por atualizações",
|
||||
"pleaseAllowInstallPerm": "Por favor, permita o Obtainium instalar Apps",
|
||||
"trackOnly": "Apenas Seguir",
|
||||
"completeAppInstallation": "Instalação do aplicativo completa",
|
||||
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar os aplicativos",
|
||||
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um aplicativo",
|
||||
"checkingForUpdates": "Verificando atualizações",
|
||||
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando o Obtainium está verificando se há atualizações",
|
||||
"pleaseAllowInstallPerm": "Por favor, permita que o Obtainium possa instalar aplicativos",
|
||||
"trackOnly": "Apenas monitorar",
|
||||
"errorWithHttpStatusCode": "Erro {}",
|
||||
"versionCorrectionDisabled": "Correção de versão desativada (plugin parece não funcionar)",
|
||||
"unknown": "Desconhecido",
|
||||
"none": "Nenhum",
|
||||
"never": "Nunca",
|
||||
"latestVersionX": "Última versão: {}",
|
||||
"installedVersionX": "Versão Instalada: {}",
|
||||
"lastUpdateCheckX": "Última Checagem por Atualização: {}",
|
||||
"installedVersionX": "Versão instalada: {}",
|
||||
"lastUpdateCheckX": "Última verificação de atualizações: {}",
|
||||
"remove": "Remover",
|
||||
"yesMarkUpdated": "Sim, Marcar como Atualizado",
|
||||
"yesMarkUpdated": "Sim, marcar como Atualizado",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "ID do App ou Nome",
|
||||
"appId": "ID do App",
|
||||
"appWithIdOrNameNotFound": "Nenhum App foi encontrado com esse ID ou nome",
|
||||
"reposHaveMultipleApps": "Repositórios podem conter multiplos Apps",
|
||||
"appIdOrName": "ID do aplicativo ou nome",
|
||||
"appId": "ID do aplicativo",
|
||||
"appWithIdOrNameNotFound": "Nenhum aplicativo foi encontrado com esse ID ou nome",
|
||||
"reposHaveMultipleApps": "Repositórios podem conter multiplos aplicativos",
|
||||
"fdroidThirdPartyRepo": "Repositórios de terceiros F-Droid",
|
||||
"steam": "Steam",
|
||||
"steamMobile": "Steam Mobile",
|
||||
"steamChat": "Steam Chat",
|
||||
"install": "Instalar",
|
||||
"markInstalled": "Marcar Instalado",
|
||||
"markInstalled": "Marcar instalado",
|
||||
"update": "Atualizar",
|
||||
"markUpdated": "Marcar Atualizado",
|
||||
"additionalOptions": "Opções Adicionais",
|
||||
"disableVersionDetection": "Desativar Detecção de Versão",
|
||||
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por Apps onde detecção de versão não funciona corretamente.",
|
||||
"markUpdated": "Marcar como atualizado",
|
||||
"additionalOptions": "Opções adicionais",
|
||||
"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.",
|
||||
"downloadingX": "Baixando {}",
|
||||
"downloadNotifDescription": "Notifica o usuário do progresso ao baixar um App",
|
||||
"downloadNotifDescription": "Notifica o usuário o progresso do download de um aplicativo",
|
||||
"noAPKFound": "APK não encontrado",
|
||||
"noVersionDetection": "Sem Detecção de versão",
|
||||
"noVersionDetection": "Sem detecção de versão",
|
||||
"categorize": "Categorizar",
|
||||
"categories": "Categorias",
|
||||
"category": "Categoria",
|
||||
"noCategory": "Sem Categoria",
|
||||
"noCategories": "Sem Categoria",
|
||||
"deleteCategoriesQuestion": "Deletar Categorias?",
|
||||
"categoryDeleteWarning": "Todos os Apps em categorias removidas serão descategorizados.",
|
||||
"addCategory": "Adicionar Categoria",
|
||||
"noCategory": "Sem categoria",
|
||||
"noCategories": "Sem categoria",
|
||||
"deleteCategoriesQuestion": "Deletar categorias?",
|
||||
"categoryDeleteWarning": "Todos os aplicativos em categorias removidas serão descategorizados.",
|
||||
"addCategory": "Adicionar categoria",
|
||||
"label": "Etiqueta",
|
||||
"language": "Linguagem",
|
||||
"copiedToClipboard": "Copiado para a área de transferência",
|
||||
"storagePermissionDenied": "Permição ao armazenamento negada",
|
||||
"selectedCategorizeWarning": "Isso vai substituir qualquer confirução de categoria para os Apps selecionados.",
|
||||
"filterAPKsByRegEx": "Filtrar APKs por Expressão Regular",
|
||||
"storagePermissionDenied": "Permissão de armazenamento negada",
|
||||
"selectedCategorizeWarning": "Isso vai substituir qualquer configuração de categoria para os aplicativos selecionados.",
|
||||
"filterAPKsByRegEx": "Filtrar APKs por expressão regular",
|
||||
"removeFromObtainium": "Remover do Obtainium",
|
||||
"uninstallFromDevice": "Desinstalar do dispositivo",
|
||||
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para Apps com detecção de versão desativada.",
|
||||
"releaseDateAsVersion": "Usar Data de Lançamento como Versão",
|
||||
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para aplicativos com detecção de versão desativada.",
|
||||
"releaseDateAsVersion": "Usar data de lançamento como versão",
|
||||
"releaseDateAsVersionExplanation": "Esta opção só deve ser usada para aplicativos onde a detecção de versão não funciona corretamente, mas há uma data de lançamento disponível.",
|
||||
"changes": "Mudanças",
|
||||
"releaseDate": "Data de Lançamento",
|
||||
"importFromURLsInFile": "Importar de URLs em Arquivo (como OPML)",
|
||||
"releaseDate": "Data de lançamento",
|
||||
"importFromURLsInFile": "Importar de URLs em arquivo (como OPML)",
|
||||
"versionDetection": "Detecção de Versão",
|
||||
"standardVersionDetection": "Detecção de versão padrão",
|
||||
"groupByCategory": "Agroupar por Categoria",
|
||||
"groupByCategory": "Agroupar por categoria",
|
||||
"autoApkFilterByArch": "Tente filtrar APKs por arquitetura de CPU, se possível",
|
||||
"overrideSource": "Substituir Fonte",
|
||||
"overrideSource": "Substituir fonte",
|
||||
"dontShowAgain": "Não mostrar isso novamente",
|
||||
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Seguir'",
|
||||
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Monitorar'",
|
||||
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
|
||||
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
|
||||
"moveNonInstalledAppsToBottom": "Mover aplicativos não instalados para o fundo da lista de aplicativos",
|
||||
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
|
||||
"about": "Sobre",
|
||||
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
|
||||
"checkOnStart": "Checar por atualizações ao iniciar ",
|
||||
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
|
||||
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
|
||||
"tryInferAppIdFromCode": "Tente inferir o ID do aplicativo pelo código-fonte",
|
||||
"removeOnExternalUninstall": "Remover automaticamente aplicativos desinstalados externamente",
|
||||
"pickHighestVersionCode": "Auto-selecionar o maior numero de versão do APK",
|
||||
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a pagina de detalhes de um App",
|
||||
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a página de detalhes de um aplicativo",
|
||||
"disablePageTransitions": "Desativar animações de transição de pagina",
|
||||
"reversePageTransitions": "Reverter animações de transição de pagina",
|
||||
"minStarCount": "Contagem Minima de Estrelas",
|
||||
@ -239,19 +238,19 @@
|
||||
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
|
||||
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
|
||||
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
|
||||
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais Apps foram potencialmente aplicadas em segundo plano",
|
||||
"appsPossiblyUpdated": "Tentativas de atualização de aplicativos",
|
||||
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais aplicativos foram potencialmente aplicadas em segundo-plano",
|
||||
"xWasPossiblyUpdatedToY": "{} pode ter sido atualizado para {}.",
|
||||
"enableBackgroundUpdates": "Ativar atualizações em segundo plano",
|
||||
"backgroundUpdateReqsExplanation": "Atualizações em segundo plano podem não ser possíveis para todos os Apps.",
|
||||
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
|
||||
"enableBackgroundUpdates": "Ativar atualizações em segundo-plano",
|
||||
"backgroundUpdateReqsExplanation": "Atualizações em segundo-plano podem não ser possíveis para todos os aplicativos.",
|
||||
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo-plano só pode ser determinado quando o Obtainium é aberto.",
|
||||
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
|
||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||
"filterByLinkText": "Filter links by link text",
|
||||
"intermediateLinkNotFound": "Link intermediário não encontrado",
|
||||
"intermediateLink": "Intermediate link",
|
||||
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
|
||||
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
|
||||
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo-plano (se ativadas)",
|
||||
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo-plano quando não estiver em WiFi",
|
||||
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
|
||||
"versionExtractionRegEx": "RegEx para Extração de Versão",
|
||||
"matchGroupToUse": "Grupo de Seleção para Usar",
|
||||
@ -266,18 +265,18 @@
|
||||
"takeFirstLink": "Take first link",
|
||||
"skipSort": "Skip sorting",
|
||||
"debugMenu": "Menu Debug",
|
||||
"bgTaskStarted": "Tarefa em segundo plano iniciada - verifique os logs.",
|
||||
"runBgCheckNow": "Execute a verificação de atualização em segundo plano agora",
|
||||
"bgTaskStarted": "Tarefa em segundo-plano iniciada - verifique os logs.",
|
||||
"runBgCheckNow": "Execute a verificação de atualização em segundo-plano agora",
|
||||
"versionExtractWholePage": "Aplicar Regex de Extração de Versão à Página Inteira",
|
||||
"installing": "Instalando",
|
||||
"skipUpdateNotifications": "Pular notificações de update",
|
||||
"updatesAvailableNotifChannel": "Atualizações Disponíveis",
|
||||
"appsUpdatedNotifChannel": "Apps Atualizados",
|
||||
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de Apps",
|
||||
"appsUpdatedNotifChannel": "Aplicativos Atualizados",
|
||||
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de aplicativos",
|
||||
"errorCheckingUpdatesNotifChannel": "Erro ao Procurar por Atualizações",
|
||||
"appsRemovedNotifChannel": "Apps Removidos",
|
||||
"appsRemovedNotifChannel": "Aplicativos Removidos",
|
||||
"downloadingXNotifChannel": "Baixando {}",
|
||||
"completeAppInstallationNotifChannel": "Instalação completa do App",
|
||||
"completeAppInstallationNotifChannel": "Instalação completa do aplicativo",
|
||||
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Apenas checar apps instalados e 'Apenas Seguir' por updates",
|
||||
"supportFixedAPKURL": "Suporte APK com URLs fixas",
|
||||
@ -287,26 +286,26 @@
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku não esta rodando",
|
||||
"shizukuBinderNotFound": "Shizuku não está rodando",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remover App?",
|
||||
"other": "Remover Apps?"
|
||||
"one": "Remover aplicativo?",
|
||||
"other": "Remover aplicativos?"
|
||||
},
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Muitas solicitações (taxa limitada) - tente novamente em {} minuto",
|
||||
"other": "Muitas solicitações (taxa limitada) - tente novamente em {} minutos"
|
||||
},
|
||||
"bgUpdateGotErrorRetryInMinutes": {
|
||||
"one": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minuto",
|
||||
"other": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minutos"
|
||||
"one": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minuto",
|
||||
"other": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minutos"
|
||||
},
|
||||
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||
"one": "A verificação de atualizações em segundo plano encontrou {} atualização, o usuário sera notificado caso necessário",
|
||||
"other": "A verificação de atualizações em segundo plano encontrou {} atualizações, o usuário sera notificado caso necessário"
|
||||
"one": "A verificação de atualizações em segundo-plano encontrou {} atualização, o usuário sera notificado caso necessário",
|
||||
"other": "A verificação de atualizações em segundo-plano encontrou {} atualizações, o usuário sera notificado caso necessário"
|
||||
},
|
||||
"apps": {
|
||||
"one": "{} App",
|
||||
"other": "{} Apps"
|
||||
"one": "{} Aplicativo",
|
||||
"other": "{} Aplicativos"
|
||||
},
|
||||
"url": {
|
||||
"one": "{} URL",
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Ошибка импорта",
|
||||
"importedXOfYApps": "Импортировано приложений: {} из {}",
|
||||
"followingURLsHadErrors": "При импорте следующие URL-адреса содержали ошибки:",
|
||||
"okay": "Ok",
|
||||
"selectURL": "Выбрать URL-адрес",
|
||||
"selectURLs": "Выбрать URL-адреса",
|
||||
"pick": "Выбрать",
|
||||
@ -287,7 +286,9 @@
|
||||
"normal": "Нормальный",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Суперпользователь",
|
||||
"shizukuBinderNotFound": "Shizuku не запущен",
|
||||
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
|
||||
"useSystemFont": "Использовать системный шрифт",
|
||||
"systemFontError": "Ошибка загрузки системного шрифта: {}",
|
||||
"removeAppQuestion": {
|
||||
"one": "Удалить приложение?",
|
||||
"other": "Удалить приложения?"
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "Importfel",
|
||||
"importedXOfYApps": "{} av {} Appar importerade.",
|
||||
"followingURLsHadErrors": "Följande URL:er hade fel:",
|
||||
"okay": "Okej",
|
||||
"selectURL": "Välj URL",
|
||||
"selectURLs": "Välj URL:er",
|
||||
"pick": "Välj",
|
||||
@ -269,6 +268,11 @@
|
||||
"bgTaskStarted": "Background task started - check logs.",
|
||||
"runBgCheckNow": "Kör Bakgrundsuppdateringskoll Nu",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "Ta Bort App?",
|
||||
"other": "Ta Bort Appar?"
|
||||
|
@ -103,7 +103,6 @@
|
||||
"importErrors": "İçe Aktarma Hataları",
|
||||
"importedXOfYApps": "{}'den {} Uygulama İçe Aktarıldı.",
|
||||
"followingURLsHadErrors": "Aşağıdaki URL'lerde hatalar oluştu:",
|
||||
"okay": "Tamam",
|
||||
"selectURL": "URL Seç",
|
||||
"selectURLs": "URL'leri Seç",
|
||||
"pick": "Seç",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "Uygulamayı Kaldır?",
|
||||
"other": "Uygulamaları Kaldır?"
|
||||
@ -335,4 +339,4 @@
|
||||
"one": "{} ve 1 diğer uygulama muhtemelen güncellendi.",
|
||||
"other": "{} ve {} daha fazla uygulama muhtemelen güncellendi."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
"placeholder": "Giữ chỗ",
|
||||
"someErrors": "Đã xảy ra một số lỗi",
|
||||
"unexpectedError": "Lỗi không mong đợi",
|
||||
"ok": "Ôkê",
|
||||
"ok": "OK",
|
||||
"and": "và",
|
||||
"githubPATLabel": "Mã thông báo truy cập cá nhân GitHub (Tăng tốc độ giới hạn)",
|
||||
"includePrereleases": "Bao gồm các bản phát hành trước",
|
||||
@ -17,7 +17,7 @@
|
||||
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
|
||||
"invalidRegEx": "Biểu thức chính quy không hợp lệ",
|
||||
"noDescription": "Không có mô tả",
|
||||
"cancel": "Hủy bỏ",
|
||||
"cancel": "Hủy",
|
||||
"continue": "Tiếp tục",
|
||||
"requiredInBrackets": "(Yêu cầu)",
|
||||
"dropdownNoOptsError": "LỖI: TẢI XUỐNG PHẢI CÓ ÍT NHẤT MỘT LỰA CHỌN",
|
||||
@ -103,7 +103,6 @@
|
||||
"importErrors": "Lỗi nhập",
|
||||
"importedXOfYApps": "{} trong số {} Ứng dụng đã được nhập.",
|
||||
"followingURLsHadErrors": "Các URL sau có lỗi:",
|
||||
"okay": "Ôkê",
|
||||
"selectURL": "Chọn URL",
|
||||
"selectURLs": "Chọn URL",
|
||||
"pick": "Chọn",
|
||||
@ -113,17 +112,17 @@
|
||||
"followSystem": "Theo hệ thống",
|
||||
"obtainium": "Obtainium",
|
||||
"materialYou": "Material You",
|
||||
"useBlackTheme": "Sử dụng chủ đề tối màu đen thuần túy",
|
||||
"appSortBy": "Sắp xếp ứng dụng theo",
|
||||
"authorName": "Tác giả/Tên",
|
||||
"nameAuthor": "Tên/Tác giả",
|
||||
"useBlackTheme": "Nền đen",
|
||||
"appSortBy": "Sắp xếp ứng dụng",
|
||||
"authorName": "Tác giả",
|
||||
"nameAuthor": "Tên",
|
||||
"asAdded": "Như đã thêm",
|
||||
"appSortOrder": "Thứ tự sắp xếp ứng dụng",
|
||||
"appSortOrder": "Thứ tự sắp xếp",
|
||||
"ascending": "Tăng dần",
|
||||
"descending": "Giảm dần",
|
||||
"bgUpdateCheckInterval": "Khoảng thời gian kiểm tra cập nhật nền",
|
||||
"neverManualOnly": "Không bao giờ - Chỉ thủ công",
|
||||
"appearance": "Vẻ ngoài",
|
||||
"appearance": "Hiển thị",
|
||||
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem Ứng dụng",
|
||||
"pinUpdates": "Ghim nội dung cập nhật lên đầu chế độ xem Ứng dụng",
|
||||
"updates": "Cập nhật",
|
||||
@ -229,7 +228,7 @@
|
||||
"removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",
|
||||
"pickHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
|
||||
"checkUpdateOnDetailPage": "Kiểm tra các bản cập nhật khi mở trang chi tiết Ứng dụng",
|
||||
"disablePageTransitions": "Tắt hoạt ảnh chuyển trang",
|
||||
"disablePageTransitions": "Tắt hiệu ứng chuyển trang",
|
||||
"reversePageTransitions": "Hoạt ảnh chuyển đổi trang đảo ngược",
|
||||
"minStarCount": "Số lượng sao tối thiểu",
|
||||
"addInfoBelow": "Thêm thông tin này vào bên dưới.",
|
||||
@ -283,6 +282,11 @@
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"parallelDownloads": "Allow parallel downloads",
|
||||
"installMethod": "Installation method",
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku chưa khởi động",
|
||||
"removeAppQuestion":{
|
||||
"one": "Gỡ ứng dụng?",
|
||||
"other": "Gỡ ứng dụng?"
|
||||
|
@ -22,11 +22,11 @@
|
||||
"requiredInBrackets": "(必填)",
|
||||
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
|
||||
"colour": "配色",
|
||||
"githubStarredRepos": "GitHub 已星标仓库",
|
||||
"githubStarredRepos": "已星标的 GitHub 仓库",
|
||||
"uname": "用户名",
|
||||
"wrongArgNum": "参数数量错误",
|
||||
"xIsTrackOnly": "{}为“仅追踪”模式",
|
||||
"source": "源代码",
|
||||
"xIsTrackOnly": "“{}”为“仅追踪”模式",
|
||||
"source": "来源",
|
||||
"app": "应用",
|
||||
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
|
||||
"youPickedTrackOnly": "您选择了“仅追踪”。",
|
||||
@ -56,12 +56,12 @@
|
||||
"estimateInBrackets": "(推测)",
|
||||
"selectAll": "全选",
|
||||
"deselectX": "取消选择 {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
||||
"xWillBeRemovedButRemainInstalled": "“{}”将从 Obtainium 中删除,但仍安装在您的设备中。",
|
||||
"removeSelectedAppsQuestion": "是否删除选中的应用?",
|
||||
"removeSelectedApps": "删除选中的应用",
|
||||
"updateX": "更新 {}",
|
||||
"installX": "安装 {}",
|
||||
"markXTrackOnlyAsUpdated": "将 {}\n(仅追踪)\n标记为已更新",
|
||||
"markXTrackOnlyAsUpdated": "将“{}”\n(仅追踪)\n标记为已更新",
|
||||
"changeX": "更改 {}",
|
||||
"installUpdateApps": "安装/更新应用",
|
||||
"installUpdateSelectedApps": "安装/更新选中的应用",
|
||||
@ -88,22 +88,21 @@
|
||||
"importExport": "导入/导出",
|
||||
"settings": "设置",
|
||||
"exportedTo": "已导出至 {}",
|
||||
"obtainiumExport": "Obtainium 导出",
|
||||
"obtainiumExport": "导出 Obtainium",
|
||||
"invalidInput": "无效的输入",
|
||||
"importedX": "已导入 {}",
|
||||
"obtainiumImport": "Obtainium 导入",
|
||||
"obtainiumImport": "导入 Obtainium",
|
||||
"importFromURLList": "从 URL 列表导入",
|
||||
"searchQuery": "搜索查询",
|
||||
"appURLList": "应用 URL 列表",
|
||||
"line": "行",
|
||||
"searchX": "搜索 {}",
|
||||
"searchX": "搜索{}",
|
||||
"noResults": "无结果",
|
||||
"importX": "导入 {}",
|
||||
"importX": "导入{}",
|
||||
"importedAppsIdDisclaimer": "导入的应用可能会错误地显示为“未安装”状态。\n请通过 Obtainium 重新安装这些应用来解决此问题。",
|
||||
"importErrors": "导入错误",
|
||||
"importedXOfYApps": "已导入 {} 中的 {} 个应用。",
|
||||
"followingURLsHadErrors": "下列 URL 存在错误:",
|
||||
"okay": "好的",
|
||||
"selectURL": "选择 URL",
|
||||
"selectURLs": "选择 URL",
|
||||
"pick": "选择",
|
||||
@ -124,7 +123,7 @@
|
||||
"bgUpdateCheckInterval": "后台更新检查间隔",
|
||||
"neverManualOnly": "手动",
|
||||
"appearance": "外观",
|
||||
"showWebInAppView": "在应用详情页显示来源网页",
|
||||
"showWebInAppView": "应用详情页显示来源网页",
|
||||
"pinUpdates": "将待更新应用置顶",
|
||||
"updates": "更新",
|
||||
"sourceSpecific": "来源",
|
||||
@ -136,7 +135,7 @@
|
||||
"appNotFound": "未找到应用",
|
||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||
"pickAnAPK": "选择一个 APK 文件",
|
||||
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
|
||||
"appHasMoreThanOnePackage": "“{}”有多个架构可用:",
|
||||
"deviceSupportsXArch": "您的设备支持 {} 架构。",
|
||||
"deviceSupportsFollowingArchs": "您的设备支持下列架构:",
|
||||
"warning": "警告",
|
||||
@ -144,15 +143,15 @@
|
||||
"updatesAvailable": "更新可用",
|
||||
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发送通知",
|
||||
"noNewUpdates": "全部应用已是最新。",
|
||||
"xHasAnUpdate": "{} 可以更新了。",
|
||||
"xHasAnUpdate": "“{}”可以更新了。",
|
||||
"appsUpdated": "应用已更新",
|
||||
"appsUpdatedNotifDescription": "当应用在后台安装更新时发送通知",
|
||||
"xWasUpdatedToY": "{} 已更新至 {}。",
|
||||
"xWasUpdatedToY": "“{}”已更新至 {}。",
|
||||
"errorCheckingUpdates": "检查更新出错",
|
||||
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
|
||||
"appsRemoved": "应用已删除",
|
||||
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发送通知",
|
||||
"xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}",
|
||||
"xWasRemovedDueToErrorY": "“{}”由于以下错误被删除:{}",
|
||||
"completeAppInstallation": "完成应用安装",
|
||||
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
|
||||
"completeAppInstallationNotifDescription": "提示返回 Obtainium 以完成应用的安装",
|
||||
@ -160,7 +159,7 @@
|
||||
"checkingForUpdatesNotifDescription": "检查更新时短暂显示的通知",
|
||||
"pleaseAllowInstallPerm": "请授予 Obtainium 安装应用的权限",
|
||||
"trackOnly": "仅追踪",
|
||||
"errorWithHttpStatusCode": "错误 {}",
|
||||
"errorWithHttpStatusCode": "{} 错误",
|
||||
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
|
||||
"unknown": "未知",
|
||||
"none": "无",
|
||||
@ -186,11 +185,11 @@
|
||||
"additionalOptions": "附加选项",
|
||||
"disableVersionDetection": "禁用版本检测",
|
||||
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
|
||||
"downloadingX": "正在下载{}",
|
||||
"downloadingX": "正在下载“{}”",
|
||||
"downloadNotifDescription": "提示应用的下载进度",
|
||||
"noAPKFound": "未找到 APK 文件",
|
||||
"noVersionDetection": "禁用版本检测",
|
||||
"categorize": "分类",
|
||||
"categorize": "归类",
|
||||
"categories": "类别",
|
||||
"category": "类别",
|
||||
"noCategory": "无类别",
|
||||
@ -218,17 +217,17 @@
|
||||
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
|
||||
"overrideSource": "覆盖来源",
|
||||
"dontShowAgain": "不再显示",
|
||||
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
|
||||
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
|
||||
"dontShowTrackOnlyWarnings": "忽略“仅追踪”模式警告",
|
||||
"dontShowAPKOriginWarnings": "忽略 APK 文件来源警告",
|
||||
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
||||
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
|
||||
"about": "相关文档",
|
||||
"requiresCredentialsInSettings": "{}:此功能需要额外的凭据(在“设置”中添加)",
|
||||
"checkOnStart": "启动时进行一次检查",
|
||||
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
|
||||
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
|
||||
"removeOnExternalUninstall": "自动删除列表中已卸载的应用",
|
||||
"pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
||||
"checkUpdateOnDetailPage": "打开应用详情页时检查更新",
|
||||
"checkUpdateOnDetailPage": "打开应用详情页时进行检查",
|
||||
"disablePageTransitions": "禁用页面过渡动画效果",
|
||||
"reversePageTransitions": "反转页面过渡动画效果",
|
||||
"minStarCount": "最小星标数",
|
||||
@ -276,18 +275,20 @@
|
||||
"appsPossiblyUpdatedNotifChannel": "已尝试更新应用",
|
||||
"errorCheckingUpdatesNotifChannel": "检查更新出错",
|
||||
"appsRemovedNotifChannel": "应用已删除",
|
||||
"downloadingXNotifChannel": "正在下载{}",
|
||||
"downloadingXNotifChannel": "正在下载“{}”",
|
||||
"completeAppInstallationNotifChannel": "完成应用安装",
|
||||
"checkingForUpdatesNotifChannel": "正在检查更新",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "只检查已安装和“仅追踪”的应用",
|
||||
"supportFixedAPKURL": "支持固定的 APK 文件链接",
|
||||
"selectX": "选择 {}",
|
||||
"selectX": "选择{}",
|
||||
"parallelDownloads": "启用并行下载",
|
||||
"installMethod": "安装方式",
|
||||
"normal": "常规",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku 服务未运行",
|
||||
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
|
||||
"useSystemFont": "使用系统字体",
|
||||
"systemFontError": "加载系统字体出错:{}",
|
||||
"removeAppQuestion": {
|
||||
"one": "是否删除应用?",
|
||||
"other": "是否删除应用?"
|
||||
@ -329,15 +330,15 @@
|
||||
"other": "清除了 {n} 个日志({before} 之前,{after} 之后)"
|
||||
},
|
||||
"xAndNMoreUpdatesAvailable": {
|
||||
"one": "{} 和另外 1 个应用可以更新了。",
|
||||
"other": "{} 和另外 {} 个应用可以更新了。"
|
||||
"one": "“{}”和另外 1 个应用可以更新了。",
|
||||
"other": "“{}”和另外 {} 个应用可以更新了。"
|
||||
},
|
||||
"xAndNMoreUpdatesInstalled": {
|
||||
"one": "{} 和另外 1 个应用已更新。",
|
||||
"other": "{} 和另外 {} 个应用已更新。"
|
||||
"other": "“{}”和另外 {} 个应用已更新。"
|
||||
},
|
||||
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||
"one": "{} 和另外 1 个应用已尝试更新。",
|
||||
"other": "{} 和另外 {} 个应用已尝试更新。"
|
||||
"other": "“{}”和另外 {} 个应用已尝试更新。"
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ class APKCombo extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/+[^/]+/+[^/]+');
|
||||
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+');
|
||||
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -34,7 +34,7 @@ class APKPure extends AppSource {
|
||||
url = 'https://$host${Uri.parse(url).path}';
|
||||
}
|
||||
RegExp standardUrlRegExA =
|
||||
RegExp('^https?://$host/+[^/]+/+[^/]+(/+[^/]+)?');
|
||||
RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+(/+[^/]+)?');
|
||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -16,7 +16,7 @@ class Codeberg extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
||||
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -38,13 +38,14 @@ class FDroid extends AppSource {
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegExB =
|
||||
RegExp('^https?://$host/+[^/]+/+packages/+[^/]+');
|
||||
RegExp('^https?://(www\\.)?$host/+[^/]+/+packages/+[^/]+');
|
||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
||||
if (match != null) {
|
||||
url =
|
||||
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
|
||||
}
|
||||
RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+');
|
||||
RegExp standardUrlRegExA =
|
||||
RegExp('^https?://(www\\.)?$host/+packages/+[^/]+');
|
||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
@ -84,17 +85,30 @@ class FDroid extends AppSource {
|
||||
try {
|
||||
var res = await sourceRequest(
|
||||
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml');
|
||||
String author = res.body
|
||||
.split('\n')
|
||||
var lines = res.body.split('\n');
|
||||
String author = lines
|
||||
.where((l) => l.startsWith('AuthorName: '))
|
||||
.first
|
||||
.split(': ')
|
||||
.sublist(1)
|
||||
.join(': ');
|
||||
details.names.author = author;
|
||||
var changelogUrls = lines.where((l) => l.startsWith('Changelog: '));
|
||||
if (changelogUrls.isNotEmpty) {
|
||||
details.changeLog = changelogUrls.first;
|
||||
details.changeLog = (await sourceRequest(details.changeLog!
|
||||
.split(': ')
|
||||
.sublist(1)
|
||||
.join(': ')
|
||||
.replaceFirst('/blob/', '/raw/')))
|
||||
.body;
|
||||
}
|
||||
} catch (e) {
|
||||
// Fail silently
|
||||
}
|
||||
if ((details.changeLog?.length ?? 0) > 1000) {
|
||||
details.changeLog = '${details.changeLog!.substring(0, 2048)}...';
|
||||
}
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ class GitHub extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
||||
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
@ -346,6 +346,11 @@ class GitHub extends AppSource {
|
||||
continue;
|
||||
}
|
||||
var apkUrls = getReleaseAPKUrls(releases[i]);
|
||||
if (additionalSettings['apkFilterRegEx'] != null) {
|
||||
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
||||
apkUrls =
|
||||
apkUrls.where((element) => reg.hasMatch(element.key)).toList();
|
||||
}
|
||||
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
|
||||
continue;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class GitLab extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
||||
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
@ -18,13 +17,17 @@ String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
||||
.split('/')
|
||||
.where((element) => element.trim().isNotEmpty)
|
||||
.toList();
|
||||
String absoluteUrl;
|
||||
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
|
||||
return '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
|
||||
absoluteUrl = '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
|
||||
} else if (ambiguousUrl.split('/').where((e) => e.isNotEmpty).length == 1) {
|
||||
return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
|
||||
absoluteUrl =
|
||||
'${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
|
||||
} else {
|
||||
return '${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - (currPathSegments.last.contains('.') ? 1 : 0)).join('/')}/$ambiguousUrl';
|
||||
absoluteUrl =
|
||||
'${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - (currPathSegments.last.contains('.') ? 1 : 0)).join('/')}/$ambiguousUrl';
|
||||
}
|
||||
return Uri.parse(absoluteUrl).toString();
|
||||
}
|
||||
|
||||
int compareAlphaNumeric(String a, String b) {
|
||||
@ -100,28 +103,6 @@ class HTML extends AppSource {
|
||||
}
|
||||
])
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('versionExtractionRegEx',
|
||||
label: tr('versionExtractionRegEx'),
|
||||
required: false,
|
||||
additionalValidators: [(value) => regExValidator(value)]),
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('matchGroupToUse',
|
||||
label: tr('matchGroupToUse'),
|
||||
required: false,
|
||||
hint: '0',
|
||||
textInputType: const TextInputType.numberWithOptions(),
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
if (value?.isEmpty == true) {
|
||||
value = null;
|
||||
}
|
||||
value ??= '0';
|
||||
return intValidator(value);
|
||||
}
|
||||
])
|
||||
],
|
||||
[
|
||||
GeneratedFormSwitch('versionExtractWholePage',
|
||||
label: tr('versionExtractWholePage'))
|
||||
@ -195,6 +176,8 @@ class HTML extends AppSource {
|
||||
? element.text
|
||||
: (element.attributes['href'] ?? '').split('/').last))
|
||||
.where((element) => element.key.isNotEmpty)
|
||||
.map((e) =>
|
||||
MapEntry(ensureAbsoluteUrl(e.key, res.request!.url), e.value))
|
||||
.toList();
|
||||
if (allLinks.isEmpty) {
|
||||
allLinks = RegExp(
|
||||
@ -242,9 +225,14 @@ class HTML extends AppSource {
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
var currentUrl = standardUrl;
|
||||
for (int i = 0;
|
||||
i < (additionalSettings['intermediateLink']?.length ?? 0);
|
||||
i++) {
|
||||
if (additionalSettings['intermediateLink']?.isNotEmpty != true) {
|
||||
additionalSettings['intermediateLink'] = [];
|
||||
}
|
||||
additionalSettings['intermediateLink'] =
|
||||
additionalSettings['intermediateLink']
|
||||
.where((l) => l['customLinkFilterRegex'].isNotEmpty == true)
|
||||
.toList();
|
||||
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
|
||||
var intLinks = await grabLinksCommon(await sourceRequest(currentUrl),
|
||||
additionalSettings['intermediateLink'][i]);
|
||||
if (intLinks.isEmpty) {
|
||||
@ -270,27 +258,12 @@ class HTML extends AppSource {
|
||||
if (additionalSettings['supportFixedAPKURL'] != true) {
|
||||
version = rel.hashCode.toString();
|
||||
}
|
||||
var versionExtractionRegEx =
|
||||
additionalSettings['versionExtractionRegEx'] as String?;
|
||||
if (versionExtractionRegEx?.isNotEmpty == true) {
|
||||
var match = RegExp(versionExtractionRegEx!).allMatches(
|
||||
additionalSettings['versionExtractWholePage'] == true
|
||||
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
|
||||
: rel);
|
||||
if (match.isEmpty) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
String matchGroupString =
|
||||
(additionalSettings['matchGroupToUse'] as String).trim();
|
||||
if (matchGroupString.isEmpty) {
|
||||
matchGroupString = "0";
|
||||
}
|
||||
version = match.last.group(int.parse(matchGroupString));
|
||||
if (version?.isEmpty == true) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
}
|
||||
rel = ensureAbsoluteUrl(rel, uri);
|
||||
version = extractVersion(
|
||||
additionalSettings['versionExtractionRegEx'] as String?,
|
||||
additionalSettings['matchGroupToUse'] as String?,
|
||||
additionalSettings['versionExtractWholePage'] == true
|
||||
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
|
||||
: rel);
|
||||
version ??= (await checkDownloadHash(rel)).toString();
|
||||
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||
AppNames(uri.host, tr('app')));
|
||||
|
@ -13,7 +13,7 @@ class HuaweiAppGallery extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/app/[^/]+');
|
||||
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/app/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -11,7 +11,7 @@ class Mullvad extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host');
|
||||
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -10,7 +10,8 @@ class NeutronCode extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+');
|
||||
RegExp standardUrlRegEx =
|
||||
RegExp('^https?://(www\\.)?$host/downloads/file/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -10,13 +10,14 @@ class SourceForge extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegExB = RegExp('^https?://$host/p/[^/]+');
|
||||
RegExp standardUrlRegExB = RegExp('^https?://(www\\.)?$host/p/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
||||
if (match != null) {
|
||||
url =
|
||||
'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}';
|
||||
}
|
||||
RegExp standardUrlRegExA = RegExp('^https?://$host/projects/[^/]+');
|
||||
RegExp standardUrlRegExA =
|
||||
RegExp('^https?://(www\\.)?$host/projects/[^/]+');
|
||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -20,7 +20,7 @@ class SourceHut extends AppSource {
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
||||
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
|
@ -6,6 +6,8 @@ import 'package:obtainium/providers/source_provider.dart';
|
||||
class WhatsApp extends AppSource {
|
||||
WhatsApp() {
|
||||
host = 'whatsapp.com';
|
||||
overrideVersionDetectionFormDefault('noVersionDetection',
|
||||
disableStandard: true, disableRelDate: true);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -123,7 +123,7 @@ class GeneratedFormSubForm extends GeneratedFormItem {
|
||||
final List<List<GeneratedFormItem>> items;
|
||||
|
||||
GeneratedFormSubForm(super.key, this.items,
|
||||
{super.label, super.belowWidgets, super.defaultValue});
|
||||
{super.label, super.belowWidgets, super.defaultValue = const []});
|
||||
|
||||
@override
|
||||
ensureType(val) {
|
||||
|
@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:easy_localization/src/localization.dart';
|
||||
|
||||
const String currentVersion = '0.15.2';
|
||||
const String currentVersion = '0.15.7';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
@ -36,7 +36,7 @@ List<MapEntry<Locale, String>> supportedLocales = const [
|
||||
MapEntry(Locale('fr'), 'Français'),
|
||||
MapEntry(Locale('es'), 'Español'),
|
||||
MapEntry(Locale('pl'), 'Polski'),
|
||||
MapEntry(Locale('ru'), 'Русский язык'),
|
||||
MapEntry(Locale('ru'), 'Русский'),
|
||||
MapEntry(Locale('bs'), 'Bosanski'),
|
||||
MapEntry(Locale('pt'), 'Brasileiro'),
|
||||
MapEntry(Locale('cs'), 'Česky'),
|
||||
@ -152,7 +152,6 @@ class _ObtainiumState extends State<Obtainium> {
|
||||
requiresStorageNotLow: false,
|
||||
requiresDeviceIdle: false,
|
||||
requiredNetworkType: NetworkType.ANY), (String taskId) async {
|
||||
// We don't want periodic tasks in the foreground - ignore
|
||||
await bgUpdateCheck(taskId, null);
|
||||
BackgroundFetch.finish(taskId);
|
||||
}, (String taskId) async {
|
||||
@ -237,13 +236,15 @@ class _ObtainiumState extends State<Obtainium> {
|
||||
colorScheme: settingsProvider.theme == ThemeSettings.dark
|
||||
? darkColorScheme
|
||||
: lightColorScheme,
|
||||
fontFamily: 'Metropolis'),
|
||||
fontFamily:
|
||||
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: settingsProvider.theme == ThemeSettings.light
|
||||
? lightColorScheme
|
||||
: darkColorScheme,
|
||||
fontFamily: 'Metropolis'),
|
||||
fontFamily:
|
||||
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
|
||||
home: Shortcuts(shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
|
||||
}, child: const HomePage()));
|
||||
|
@ -286,10 +286,14 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
selectedByDefault: true,
|
||||
onlyOneSelectionAllowed: false,
|
||||
titlesAreLinks: false,
|
||||
deselectThese: settingsProvider.searchDeselected,
|
||||
);
|
||||
}) ??
|
||||
[];
|
||||
if (searchSources.isNotEmpty) {
|
||||
settingsProvider.searchDeselected = sourceStrings.keys
|
||||
.where((s) => !searchSources.contains(s))
|
||||
.toList();
|
||||
var results = await Future.wait(sourceProvider.sources
|
||||
.where((e) => searchSources.contains(e.name))
|
||||
.map((e) async {
|
||||
@ -306,7 +310,6 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
}
|
||||
}));
|
||||
|
||||
// .then((results) async {
|
||||
// Interleave results instead of simple reduce
|
||||
Map<String, List<String>> res = {};
|
||||
var si = 0;
|
||||
|
@ -5,6 +5,7 @@ import 'package:app_links/app_links.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/pages/add_app.dart';
|
||||
import 'package:obtainium/pages/apps.dart';
|
||||
@ -76,14 +77,39 @@ class _HomePageState extends State<HomePage> {
|
||||
try {
|
||||
if (action == 'add') {
|
||||
await goToAddApp(data);
|
||||
} else if (action == 'app') {
|
||||
await context
|
||||
.read<AppsProvider>()
|
||||
.import('{ "apps": [${Uri.decodeComponent(data)}] }');
|
||||
} else if (action == 'apps') {
|
||||
await context
|
||||
.read<AppsProvider>()
|
||||
.import('{ "apps": ${Uri.decodeComponent(data)} }');
|
||||
} else if (action == 'app' || action == 'apps') {
|
||||
var dataStr = Uri.decodeComponent(data);
|
||||
if (await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: tr('importX', args: [
|
||||
action == 'app' ? tr('app') : tr('appsString')
|
||||
]),
|
||||
items: const [],
|
||||
additionalWidgets: [
|
||||
ExpansionTile(
|
||||
title: const Text('Raw JSON'),
|
||||
children: [
|
||||
Text(
|
||||
dataStr,
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}) !=
|
||||
null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
var result = await context.read<AppsProvider>().import(
|
||||
action == 'app'
|
||||
? '{ "apps": [$dataStr] }'
|
||||
: '{ "apps": $dataStr }');
|
||||
// ignore: use_build_context_synchronously
|
||||
showMessage(
|
||||
tr('importedX', args: [plural('apps', result.key)]), context);
|
||||
}
|
||||
} else {
|
||||
throw ObtainiumError(tr('unknown'));
|
||||
}
|
||||
|
@ -590,7 +590,7 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(null);
|
||||
},
|
||||
child: Text(tr('okay')))
|
||||
child: Text(tr('ok')))
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -604,11 +604,13 @@ class SelectionModal extends StatefulWidget {
|
||||
this.selectedByDefault = true,
|
||||
this.onlyOneSelectionAllowed = false,
|
||||
this.titlesAreLinks = true,
|
||||
this.title});
|
||||
this.title,
|
||||
this.deselectThese = const []});
|
||||
|
||||
String? title;
|
||||
Map<String, List<String>> entries;
|
||||
bool selectedByDefault;
|
||||
List<String> deselectThese;
|
||||
bool onlyOneSelectionAllowed;
|
||||
bool titlesAreLinks;
|
||||
|
||||
@ -622,9 +624,13 @@ class _SelectionModalState extends State<SelectionModal> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
for (var url in widget.entries.entries) {
|
||||
entrySelections.putIfAbsent(url,
|
||||
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
|
||||
for (var entry in widget.entries.entries) {
|
||||
entrySelections.putIfAbsent(
|
||||
entry,
|
||||
() =>
|
||||
widget.selectedByDefault &&
|
||||
!widget.onlyOneSelectionAllowed &&
|
||||
!widget.deselectThese.contains(entry.key));
|
||||
}
|
||||
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
|
||||
selectOnlyOne(widget.entries.entries.first.key);
|
||||
|
@ -7,6 +7,7 @@ import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/main.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/logs_provider.dart';
|
||||
import 'package:obtainium/providers/native_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -350,8 +351,6 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
],
|
||||
),
|
||||
height16,
|
||||
installMethodDropdown,
|
||||
height16,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -364,6 +363,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
})
|
||||
],
|
||||
),
|
||||
installMethodDropdown,
|
||||
height32,
|
||||
Text(
|
||||
tr('sourceSpecific'),
|
||||
@ -408,6 +408,30 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
height16,
|
||||
localeDropdown,
|
||||
height16,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(child: Text(tr('useSystemFont'))),
|
||||
Switch(
|
||||
value: settingsProvider.useSystemFont,
|
||||
onChanged: (useSystemFont) {
|
||||
if (useSystemFont) {
|
||||
NativeFeatures.loadSystemFont().then((fontLoadRes) {
|
||||
if (fontLoadRes == 'ok') {
|
||||
settingsProvider.useSystemFont = true;
|
||||
} else {
|
||||
showError(ObtainiumError(
|
||||
tr('systemFontError', args: [fontLoadRes])
|
||||
), context);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
settingsProvider.useSystemFont = false;
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
height16,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
@ -32,7 +32,7 @@ import 'package:http/http.dart';
|
||||
import 'package:android_intent_plus/android_intent.dart';
|
||||
import 'package:flutter_archive/flutter_archive.dart';
|
||||
import 'package:shared_storage/shared_storage.dart' as saf;
|
||||
import 'installers_provider.dart';
|
||||
import 'native_provider.dart';
|
||||
|
||||
final pm = AndroidPackageManager();
|
||||
|
||||
@ -522,14 +522,15 @@ class AppsProvider with ChangeNotifier {
|
||||
code = await AndroidPackageInstaller.installApk(
|
||||
apkFilePath: file.file.path);
|
||||
case InstallMethodSettings.shizuku:
|
||||
code = (await Installers.installWithShizuku(
|
||||
code = (await NativeFeatures.installWithShizuku(
|
||||
apkFileUri: file.file.uri.toString()))
|
||||
? 0
|
||||
: 1;
|
||||
case InstallMethodSettings.root:
|
||||
code = (await Installers.installWithRoot(apkFilePath: file.file.path))
|
||||
? 0
|
||||
: 1;
|
||||
code =
|
||||
(await NativeFeatures.installWithRoot(apkFilePath: file.file.path))
|
||||
? 0
|
||||
: 1;
|
||||
}
|
||||
bool installed = false;
|
||||
if (code != null && code != 0 && code != 3) {
|
||||
@ -694,14 +695,14 @@ class AppsProvider with ChangeNotifier {
|
||||
throw ObtainiumError(tr('cancelled'));
|
||||
}
|
||||
case InstallMethodSettings.shizuku:
|
||||
int code = await Installers.checkPermissionShizuku();
|
||||
int code = await NativeFeatures.checkPermissionShizuku();
|
||||
if (code == -1) {
|
||||
throw ObtainiumError(tr('shizukuBinderNotFound'));
|
||||
} else if (code == 0) {
|
||||
throw ObtainiumError(tr('cancelled'));
|
||||
}
|
||||
case InstallMethodSettings.root:
|
||||
if (!(await Installers.checkPermissionRoot())) {
|
||||
if (!(await NativeFeatures.checkPermissionRoot())) {
|
||||
throw ObtainiumError(tr('cancelled'));
|
||||
}
|
||||
}
|
||||
@ -1300,6 +1301,9 @@ class AppsProvider with ChangeNotifier {
|
||||
settingsProvider.prefs?.setInt(key, value);
|
||||
} else if (value is bool) {
|
||||
settingsProvider.prefs?.setBool(key, value);
|
||||
} else if (value is List) {
|
||||
settingsProvider.prefs
|
||||
?.setStringList(key, value.map((e) => e as String).toList());
|
||||
} else {
|
||||
settingsProvider.prefs?.setString(key, value as String);
|
||||
}
|
||||
|
@ -1,12 +1,26 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class Installers {
|
||||
static const MethodChannel _channel = MethodChannel('installers');
|
||||
class NativeFeatures {
|
||||
static const MethodChannel _channel = MethodChannel('native');
|
||||
static bool _systemFontLoaded = false;
|
||||
static bool _callbacksApplied = false;
|
||||
static int _resPermShizuku = -2; // not set
|
||||
|
||||
static Future waitWhile(bool Function() test,
|
||||
static Future<ByteData> _readFileBytes(String path) async {
|
||||
var file = File(path);
|
||||
var bytes = await file.readAsBytes();
|
||||
return ByteData.view(bytes.buffer);
|
||||
}
|
||||
|
||||
static Future _handleCalls(MethodCall call) async {
|
||||
if (call.method == 'resPermShizuku') {
|
||||
_resPermShizuku = call.arguments['res'];
|
||||
}
|
||||
}
|
||||
|
||||
static Future _waitWhile(bool Function() test,
|
||||
[Duration pollInterval = const Duration(milliseconds: 250)]) {
|
||||
var completer = Completer();
|
||||
check() {
|
||||
@ -20,20 +34,25 @@ class Installers {
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
static Future handleCalls(MethodCall call) async {
|
||||
if (call.method == 'resPermShizuku') {
|
||||
_resPermShizuku = call.arguments['res'];
|
||||
}
|
||||
static Future<String> loadSystemFont() async {
|
||||
if (_systemFontLoaded) { return "ok"; }
|
||||
var getFontRes = await _channel.invokeMethod('getSystemFont');
|
||||
if (getFontRes[0] != '/') { return getFontRes; } // Error
|
||||
var fontLoader = FontLoader('SystemFont');
|
||||
fontLoader.addFont(_readFileBytes(getFontRes));
|
||||
await fontLoader.load();
|
||||
_systemFontLoaded = true;
|
||||
return "ok";
|
||||
}
|
||||
|
||||
static Future<int> checkPermissionShizuku() async {
|
||||
if (!_callbacksApplied) {
|
||||
_channel.setMethodCallHandler(handleCalls);
|
||||
_channel.setMethodCallHandler(_handleCalls);
|
||||
_callbacksApplied = true;
|
||||
}
|
||||
int res = await _channel.invokeMethod('checkPermissionShizuku');
|
||||
if(res == -2) {
|
||||
await waitWhile(() => _resPermShizuku == -2);
|
||||
if (res == -2) {
|
||||
await _waitWhile(() => _resPermShizuku == -2);
|
||||
res = _resPermShizuku;
|
||||
_resPermShizuku = -2;
|
||||
}
|
@ -51,6 +51,15 @@ class SettingsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get useSystemFont {
|
||||
return prefs?.getBool('useSystemFont') ?? false;
|
||||
}
|
||||
|
||||
set useSystemFont(bool useSystemFont) {
|
||||
prefs?.setBool('useSystemFont', useSystemFont);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
InstallMethodSettings get installMethod {
|
||||
return InstallMethodSettings.values[
|
||||
prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
|
||||
@ -446,4 +455,13 @@ class SettingsProvider with ChangeNotifier {
|
||||
prefs?.setBool('parallelDownloads', val);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<String> get searchDeselected {
|
||||
return prefs?.getStringList('searchDeselected') ?? [];
|
||||
}
|
||||
|
||||
set searchDeselected(List<String> list) {
|
||||
prefs?.setStringList('searchDeselected', list);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +163,24 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
||||
return e['customLinkFilterRegex']?.isNotEmpty == true;
|
||||
}).toList();
|
||||
}
|
||||
// Steam source apps should be converted to HTML (#1244)
|
||||
var legacySteamSourceApps = SteamMobile().apks.keys;
|
||||
if (legacySteamSourceApps.contains(additionalSettings['app'] ?? '')) {
|
||||
json['url'] = '${json['url']}/mobile';
|
||||
var replacementAdditionalSettings = getDefaultValuesFromFormItems(
|
||||
HTML().combinedAppSpecificSettingFormItems);
|
||||
for (var s in replacementAdditionalSettings.keys) {
|
||||
if (additionalSettings.containsKey(s)) {
|
||||
replacementAdditionalSettings[s] = additionalSettings[s];
|
||||
}
|
||||
}
|
||||
replacementAdditionalSettings['customLinkFilterRegex'] =
|
||||
'/${additionalSettings['app']}-(([0-9]+\\.?){1,})\\.apk';
|
||||
replacementAdditionalSettings['versionExtractionRegEx'] =
|
||||
replacementAdditionalSettings['customLinkFilterRegex'];
|
||||
replacementAdditionalSettings['matchGroupToUse'] = '\$1';
|
||||
additionalSettings = replacementAdditionalSettings;
|
||||
}
|
||||
}
|
||||
json['additionalSettings'] = jsonEncode(additionalSettings);
|
||||
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
||||
@ -444,6 +462,16 @@ abstract class AppSource {
|
||||
label: tr('trackOnly'),
|
||||
)
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('versionExtractionRegEx',
|
||||
label: tr('versionExtractionRegEx'),
|
||||
required: false,
|
||||
additionalValidators: [(value) => regExValidator(value)]),
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('matchGroupToUse',
|
||||
label: tr('matchGroupToUse'), required: false, hint: '\$0')
|
||||
],
|
||||
[
|
||||
GeneratedFormDropdown(
|
||||
'versionDetection',
|
||||
@ -580,6 +608,57 @@ bool isTempId(App app) {
|
||||
return RegExp('^[0-9]+\$').hasMatch(app.id);
|
||||
}
|
||||
|
||||
replaceMatchGroupsInString(RegExpMatch match, String matchGroupString) {
|
||||
if (RegExp('^\\d+\$').hasMatch(matchGroupString)) {
|
||||
matchGroupString = '\$$matchGroupString';
|
||||
}
|
||||
// Regular expression to match numbers in the input string
|
||||
final numberRegex = RegExp(r'\$\d+');
|
||||
// Extract all numbers from the input string
|
||||
final numbers = numberRegex.allMatches(matchGroupString);
|
||||
if (numbers.isEmpty) {
|
||||
// If no numbers found, return the original string
|
||||
return null;
|
||||
}
|
||||
// Replace numbers with corresponding match groups
|
||||
var outputString = matchGroupString;
|
||||
for (final numberMatch in numbers) {
|
||||
final number = numberMatch.group(0)!;
|
||||
final matchGroup = match.group(int.parse(number.substring(1))) ?? '';
|
||||
// Check if the number is preceded by a single backslash
|
||||
final isEscaped = outputString.contains('\\$number');
|
||||
// Replace the number with the corresponding match group
|
||||
if (!isEscaped) {
|
||||
outputString = outputString.replaceAll(number, matchGroup);
|
||||
} else {
|
||||
outputString = outputString.replaceAll('\\$number', number);
|
||||
}
|
||||
}
|
||||
return outputString;
|
||||
}
|
||||
|
||||
String? extractVersion(String? versionExtractionRegEx, String? matchGroupString,
|
||||
String stringToCheck) {
|
||||
if (versionExtractionRegEx?.isNotEmpty == true) {
|
||||
String? version = stringToCheck;
|
||||
var match = RegExp(versionExtractionRegEx!).allMatches(version);
|
||||
if (match.isEmpty) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
matchGroupString = matchGroupString?.trim() ?? '';
|
||||
if (matchGroupString.isEmpty) {
|
||||
matchGroupString = "0";
|
||||
}
|
||||
version = replaceMatchGroupsInString(match.last, matchGroupString);
|
||||
if (version?.isNotEmpty != true) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
return version!;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceProvider {
|
||||
// Add more source classes here so they are available via the service
|
||||
List<AppSource> get sources => [
|
||||
@ -597,13 +676,11 @@ class SourceProvider {
|
||||
APKMirror(),
|
||||
HuaweiAppGallery(),
|
||||
Jenkins(),
|
||||
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
|
||||
Mullvad(),
|
||||
Signal(),
|
||||
VLC(),
|
||||
WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
|
||||
WhatsApp(),
|
||||
TelegramApp(),
|
||||
SteamMobile(),
|
||||
NeutronCode(),
|
||||
HTML() // This should ALWAYS be the last option as they are tried in order
|
||||
];
|
||||
@ -679,6 +756,18 @@ class SourceProvider {
|
||||
String standardUrl = source.standardizeUrl(url);
|
||||
APKDetails apk =
|
||||
await source.getLatestAPKDetails(standardUrl, additionalSettings);
|
||||
|
||||
if (source.runtimeType != HTML().runtimeType) {
|
||||
// HTML does it separately
|
||||
String? extractedVersion = extractVersion(
|
||||
additionalSettings['versionExtractionRegEx'] as String?,
|
||||
additionalSettings['matchGroupToUse'] as String?,
|
||||
apk.version);
|
||||
if (extractedVersion != null) {
|
||||
apk.version = extractedVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
|
||||
apk.releaseDate != null) {
|
||||
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
|
||||
|
56
pubspec.lock
56
pubspec.lock
@ -30,10 +30,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: animations
|
||||
sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5"
|
||||
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.10"
|
||||
version: "2.0.11"
|
||||
app_links:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -46,10 +46,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b"
|
||||
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.9"
|
||||
version: "3.4.10"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -328,10 +328,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196"
|
||||
sha256: "30088ce826b5b9cfbf9e8bece34c716c8a59fa54461dcae1e4ac01a94639e762"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.18+2"
|
||||
version: "0.6.18+3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -610,18 +610,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "3.1.4"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.7"
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -831,18 +831,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def"
|
||||
sha256: c0766a55ab42cefaa728cabc951e82919ab41a3a4fee0aaa96176ca82da8cc51
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.0"
|
||||
version: "6.2.1"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3
|
||||
sha256: "46b81e3109cbb2d6b81702ad3077540789a3e74e22795eb9f0b7d494dbaa72ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.1"
|
||||
version: "6.2.2"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -863,18 +863,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50"
|
||||
sha256: "4aca1e060978e19b2998ee28503f40b5ba6226819c2b5e3e4d1821e8ccd92198"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9"
|
||||
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.2.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -911,18 +911,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: "42393b4492e629aa3a88618530a4a00de8bb46e50e7b3993fedbfdc5352f0dbf"
|
||||
sha256: "60e23976834e995c404c0b21d3b9db37ecd77d3303ef74f8b8d7a7b19947fc04"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.2"
|
||||
version: "4.4.3"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: b54c89fe14a6d26a2a46e24880da0441cdd2bf1f6d01a5b3e1d39558feb1de0b
|
||||
sha256: "161af93c2abaf94ef2192bffb53a3658b2d721a3bf99b69aa1e47814ee18cc96"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.13.1"
|
||||
version: "3.13.2"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -935,18 +935,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: eebfabfa8a115b535b52031b8b26f7a4b58ceceab378bc9db8762b0fb46f7b5d
|
||||
sha256: "02d8f3ebbc842704b2b662377b3ee11c0f8f1bbaa8eab6398262f40049819160"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.10.0"
|
||||
version: "3.10.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
|
||||
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "5.2.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -959,10 +959,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.15.2+238 # When changing this, update the tag in main() accordingly
|
||||
version: 0.15.7+243 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
Reference in New Issue
Block a user