Compare commits

..

1 Commits

Author SHA1 Message Date
Imran Remtulla
ab43856b90 attempted switch to dio (still using http for partial dnwloads/headers) 2024-03-17 01:51:53 -04:00
75 changed files with 2027 additions and 2418 deletions

View File

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

View File

@@ -4,13 +4,11 @@
Get Android App Updates Directly From the Source.
Obtainium allows you to install and update apps directly from their releases pages, and receive notifications when new releases are made available.
Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.
More info:
- [Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
- [AppVerifier](https://github.com/soupslurpr/AppVerifier) - App verification tool (recommended, integrates with Obtainium)
- [apps.obtainium.imranr.dev](https://apps.obtainium.imranr.dev/) - Crowdsourced app configurations
- [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0) - Original motivation for this app
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
Read the Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
Currently supported App sources:
- Open Source - General:
@@ -20,6 +18,7 @@ Currently supported App sources:
- [F-Droid](https://f-droid.org/)
- Third Party F-Droid Repos
- [IzzyOnDroid](https://android.izzysoft.de/)
- [SourceForge](https://sourceforge.net/)
- [SourceHut](https://git.sr.ht/)
- Other - General:
- [APKPure](https://apkpure.net/)
@@ -29,23 +28,15 @@ Currently supported App sources:
- [Huawei AppGallery](https://appgallery.huawei.com/)
- Jenkins Jobs
- Open Source - App-Specific:
- [Mullvad](https://mullvad.net/en/)
- [Signal](https://signal.org/)
- [VLC](https://videolan.org/)
- Other - App-Specific:
- [WhatsApp](https://whatsapp.com)
- [Telegram App](https://telegram.org)
- [Neutron Code](https://neutroncode.com)
- Direct APK Link
- "HTML" (Fallback): Any other URL that returns an HTML page with links to APK files
## Finding App Configurations
You can find crowdsourced app configurations at [apps.obtainium.imranr.dev](https://apps.obtainium.imranr.dev).
If you can't find the configuration for an app you want, feel free to leave a request on the [discussions page](https://github.com/ImranR98/apps.obtainium.imranr.dev/discussions/new?category=app-requests).
Or, contribute some configurations to the website by creating a PR at [this repo](https://github.com/ImranR98/apps.obtainium.imranr.dev).
## Installation
[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png"

View File

@@ -1,9 +1,3 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
@@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) {
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
@@ -22,6 +21,11 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'dev.rikka.tools.refine'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
@@ -29,8 +33,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
namespace "dev.imranr.obtainium"
compileSdk flutter.compileSdkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
@@ -51,7 +54,7 @@ android {
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 24
targetSdkVersion flutter.targetSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@@ -92,6 +95,20 @@ repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
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.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"
}
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->

View File

@@ -6,8 +6,7 @@
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true"
android:localeConfig="@xml/locales_config">
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
@@ -48,7 +47,7 @@
android:value="2" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
android:authorities="dev.imranr.obtainium"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
@@ -73,4 +72,4 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />\
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest>
</manifest>

View File

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

View File

@@ -1,5 +1,179 @@
package dev.imranr.obtainium
import android.content.Intent
import android.content.IntentSender
import android.content.pm.IPackageInstaller
import android.content.pm.IPackageInstallerSession
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Process
import androidx.annotation.NonNull
import com.topjohnwu.superuser.Shell
import dev.imranr.obtainium.util.IIntentSenderAdaptor
import dev.imranr.obtainium.util.IntentSenderUtils
import dev.imranr.obtainium.util.PackageInstallerUtils
import dev.imranr.obtainium.util.ShizukuSystemServerApi
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
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()
class MainActivity: FlutterActivity() {
private var nativeChannel: MethodChannel? = null
private val SHIZUKU_PERMISSION_REQUEST_CODE = (10..200).random()
private fun shizukuCheckPermission(result: Result) {
try {
if (Shizuku.isPreV11()) { // Unsupported
result.success(-1)
} else if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
result.success(1)
} else if (Shizuku.shouldShowRequestPermissionRationale()) { // Deny and don't ask again
result.success(0)
} else {
Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE)
result.success(-2)
}
} catch (_: Exception) { // If shizuku not running
result.success(-1)
}
}
private val shizukuRequestPermissionResultListener = OnRequestPermissionResultListener {
requestCode: Int, grantResult: Int ->
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
val res = if (grantResult == PackageManager.PERMISSION_GRANTED) 1 else 0
nativeChannel!!.invokeMethod("resPermShizuku", mapOf("res" to res))
}
}
private fun shizukuInstallApk(apkFileUri: String, result: Result) {
val uri = Uri.parse(apkFileUri)
var res = false
var session: PackageInstaller.Session? = null
try {
val iPackageInstaller: IPackageInstaller =
ShizukuSystemServerApi.PackageManager_getPackageInstaller()
val isRoot = Shizuku.getUid() == 0
// The reason for use "com.android.shell" as installer package under adb
// is that getMySessions will check installer package's owner
val installerPackageName = if (isRoot) packageName else "com.android.shell"
var installerAttributionTag: String? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
installerAttributionTag = attributionTag
}
val userId = if (isRoot) Process.myUserHandle().hashCode() else 0
val packageInstaller = PackageInstallerUtils.createPackageInstaller(
iPackageInstaller, installerPackageName, installerAttributionTag, userId)
val params =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
var installFlags: Int = PackageInstallerUtils.getInstallFlags(params)
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(
ShizukuBinderWrapper(iPackageInstaller.openSession(sessionId).asBinder()))
session = PackageInstallerUtils.createSession(iSession)
val inputStream = contentResolver.openInputStream(uri)
val openedSession = session.openWrite("apk.apk", 0, -1)
val buffer = ByteArray(8192)
var length: Int
try {
while (inputStream!!.read(buffer).also { length = it } > 0) {
openedSession.write(buffer, 0, length)
openedSession.flush()
session.fsync(openedSession)
}
} finally {
try {
inputStream!!.close()
openedSession.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
val results = arrayOf<Intent?>(null)
val countDownLatch = CountDownLatch(1)
val intentSender: IntentSender =
IntentSenderUtils.newInstance(object : IIntentSenderAdaptor() {
override fun send(intent: Intent?) {
results[0] = intent
countDownLatch.countDown()
}
})
session.commit(intentSender)
countDownLatch.await()
res = results[0]!!.getIntExtra(
PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) == 0
} catch (_: Exception) {
res = false
} finally {
if (session != null) {
try {
session.close()
} catch (_: Exception) {
res = false
}
}
}
result.success(res)
}
private fun rootCheckPermission(result: Result) {
Shell.getShell(Shell.GetShellCallback(
fun(shell: Shell) {
result.success(shell.isRoot)
}
))
}
private fun rootInstallApk(apkFilePath: String, result: Result) {
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"))
}
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
HiddenApiBypass.addHiddenApiExemptions("")
}
Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener)
nativeChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, "native")
nativeChannel!!.setMethodCallHandler {
call, result ->
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)
} else if (call.method == "installWithShizuku") {
val apkFileUri: String? = call.argument("apkFileUri")
shizukuInstallApk(apkFileUri!!, result)
} else if (call.method == "installWithRoot") {
val apkFilePath: String? = call.argument("apkFilePath")
rootInstallApk(apkFilePath!!, result)
}
}
}
override fun onDestroy() {
super.onDestroy()
Shizuku.removeRequestPermissionResultListener(shizukuRequestPermissionResultListener)
}
}

View File

@@ -0,0 +1,37 @@
package dev.imranr.obtainium.util;
import android.annotation.SuppressLint;
import android.app.Application;
import android.os.Build;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ApplicationUtils {
private static Application application;
public static Application getApplication() {
return application;
}
public static void setApplication(Application application) {
ApplicationUtils.application = application;
}
public static String getProcessName() {
if (Build.VERSION.SDK_INT >= 28)
return Application.getProcessName();
else {
try {
@SuppressLint("PrivateApi")
Class<?> activityThread = Class.forName("android.app.ActivityThread");
@SuppressLint("DiscouragedPrivateApi")
Method method = activityThread.getDeclaredMethod("currentProcessName");
return (String) method.invoke(null);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,23 @@
package dev.imranr.obtainium.util;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
public abstract class IIntentSenderAdaptor extends IIntentSender.Stub {
public abstract void send(Intent intent);
@Override
public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
send(intent);
return 0;
}
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
send(intent);
}
}

View File

@@ -0,0 +1,14 @@
package dev.imranr.obtainium.util;
import android.content.IIntentSender;
import android.content.IntentSender;
import java.lang.reflect.InvocationTargetException;
public class IntentSenderUtils {
public static IntentSender newInstance(IIntentSender binder) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//noinspection JavaReflectionMemberAccess
return IntentSender.class.getConstructor(IIntentSender.class).newInstance(binder);
}
}

View File

@@ -0,0 +1,41 @@
package dev.imranr.obtainium.util;
import android.content.Context;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageInstallerSession;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Build;
import java.lang.reflect.InvocationTargetException;
@SuppressWarnings({"JavaReflectionMemberAccess"})
public class PackageInstallerUtils {
public static PackageInstaller createPackageInstaller(IPackageInstaller installer, String installerPackageName, String installerAttributionTag, int userId) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, String.class, int.class)
.newInstance(installer, installerPackageName, installerAttributionTag, userId);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, int.class)
.newInstance(installer, installerPackageName, userId);
} else {
return PackageInstaller.class.getConstructor(Context.class, PackageManager.class, IPackageInstaller.class, String.class, int.class)
.newInstance(ApplicationUtils.getApplication(), ApplicationUtils.getApplication().getPackageManager(), installer, installerPackageName, userId);
}
}
public static PackageInstaller.Session createSession(IPackageInstallerSession session) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return PackageInstaller.Session.class.getConstructor(IPackageInstallerSession.class)
.newInstance(session);
}
public static int getInstallFlags(PackageInstaller.SessionParams params) throws NoSuchFieldException, IllegalAccessException {
return (int) PackageInstaller.SessionParams.class.getDeclaredField("installFlags").get(params);
}
public static void setInstallFlags(PackageInstaller.SessionParams params, int newValue) throws NoSuchFieldException, IllegalAccessException {
PackageInstaller.SessionParams.class.getDeclaredField("installFlags").set(params, newValue);
}
}

View File

@@ -0,0 +1,68 @@
package dev.imranr.obtainium.util;
import android.content.Context;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.IUserManager;
import android.os.RemoteException;
import java.util.List;
import rikka.shizuku.ShizukuBinderWrapper;
import rikka.shizuku.SystemServiceHelper;
public class ShizukuSystemServerApi {
private static final Singleton<IPackageManager> PACKAGE_MANAGER = new Singleton<IPackageManager>() {
@Override
protected IPackageManager create() {
return IPackageManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")));
}
};
private static final Singleton<IUserManager> USER_MANAGER = new Singleton<IUserManager>() {
@Override
protected IUserManager create() {
return IUserManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService(Context.USER_SERVICE)));
}
};
public static IPackageInstaller PackageManager_getPackageInstaller() throws RemoteException {
IPackageInstaller packageInstaller = PACKAGE_MANAGER.get().getPackageInstaller();
return IPackageInstaller.Stub.asInterface(new ShizukuBinderWrapper(packageInstaller.asBinder()));
}
public static List<UserInfo> UserManager_getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated) throws RemoteException {
if (Build.VERSION.SDK_INT >= 30) {
return USER_MANAGER.get().getUsers(excludePartial, excludeDying, excludePreCreated);
} else {
try {
return USER_MANAGER.get().getUsers(excludeDying);
} catch (NoSuchFieldError e) {
return USER_MANAGER.get().getUsers(excludePartial, excludeDying, excludePreCreated);
}
}
}
// method 2: use transactRemote directly
/*public static List<UserInfo> UserManager_getUsers(boolean excludeDying) {
Parcel data = SystemServiceHelper.obtainParcel(Context.USER_SERVICE, "android.os.IUserManager", "getUsers");
Parcel reply = Parcel.obtain();
data.writeInt(excludeDying ? 1 : 0);
List<UserInfo> res = null;
try {
ShizukuService.transactRemote(data, reply, 0);
reply.readException();
res = reply.createTypedArrayList(UserInfo.CREATOR);
} catch (RemoteException e) {
Log.e("ShizukuSample", "UserManager#getUsers", e);
} finally {
data.recycle();
reply.recycle();
}
return res;
}*/
}

View File

@@ -0,0 +1,17 @@
package dev.imranr.obtainium.util;
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="bs"/>
<locale android:name="cs"/>
<locale android:name="de"/>
<locale android:name="en"/>
<locale android:name="es"/>
<locale android:name="fa"/>
<locale android:name="fr"/>
<locale android:name="hu"/>
<locale android:name="it"/>
<locale android:name="ja"/>
<locale android:name="nl"/>
<locale android:name="pl"/>
<locale android:name="pt"/>
<locale android:name="ru"/>
<locale android:name="sv"/>
<locale android:name="tr"/>
<locale android:name="uk"/>
<locale android:name="vi"/>
<locale android:name="zh"/>
</locale-config>

View File

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

View File

@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx2048M
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

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

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(obavezno)",
"dropdownNoOptsError": "GREŠKA: PADAJUĆI MENI MORA IMATI NAJMANJE JEDNU OPCIJU",
"colour": "Boja",
"standard": "Standard",
"custom": "Custom",
"useMaterialYou": "Use Material You",
"githubStarredRepos": "GitHub repo-i sa zvjezdicom",
"uname": "Korisničko ime",
"wrongArgNum": "Naveden je pogrešan broj argumenata",
@@ -113,7 +110,6 @@
"dark": "Tamna",
"light": "Svijetla",
"followSystem": "Pratite sistem",
"followSystemThemeExplanation": "Following system theme is possible only by using third-party applications",
"useBlackTheme": "Koristite čisto crnu tamnu temu",
"appSortBy": "Aplikacije sortirane po",
"authorName": "Autor/Ime",
@@ -147,10 +143,8 @@
"noNewUpdates": "Nema novih ažuriranja.",
"xHasAnUpdate": "{} ima ažuriranje.",
"appsUpdated": "Aplikacije su ažurirane",
"appsNotUpdated": "Failed to update applications",
"appsUpdatedNotifDescription": "Obavještava korisnika da su u pozadini primijenjena ažuriranja na jednu ili više aplikacija",
"xWasUpdatedToY": "{} je ažuriran na {}.",
"xWasNotUpdatedToY": "Failed to update {} to {}.",
"errorCheckingUpdates": "Greška pri provjeri ažuriranja",
"errorCheckingUpdatesNotifDescription": "Obavijest koja se prikazuje kada provjera sigurnosnog ažuriranja ne uspije",
"appsRemoved": "Aplikacije su uklonjene",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Onemogući detekciju verzije",
"noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.",
"downloadingX": "Preuzimanje {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije",
"noAPKFound": "APK nije pronađen",
"noVersionDetection": "Nema detekcije verzije",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Podržite fiksne APK URL-ove",
"selectX": "Izaberite {}",
"parallelDownloads": "Dozvoli paralelna preuzimanja",
"useShizuku": "Use Shizuku or Sui to install",
"installMethod": "Način instalacije",
"normal": "normalno",
"root": "korijen",
"shizukuBinderNotFound": "Shizuku is not running",
"shizukuOld": "Old Shizuku version (<11) - update it",
"shizukuOldAndroidWithADB": "Shizuku running on Android < 8.1 with ADB - update Android or use Sui instead",
"shizukuPretendToBeGooglePlay": "Set Google Play as the installation source (if Shizuku is used)",
"useSystemFont": "Koristite sistemski font",
"systemFontError": "Greška pri učitavanju sistemskog fonta: {}",
"useVersionCodeAsOSVersion": "Koristite kod verzije aplikacije kao verziju koju je otkrio OS",
"requestHeader": "Zaglavlje zahtjeva",
"useLatestAssetDateAsReleaseDate": "Koristite najnovije otpremanje materijala kao datum izdavanja",
@@ -308,10 +299,6 @@
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)",
"beforeNewInstallsShareToAppVerifier": "Share new Apps with AppVerifier (if available)",
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
"wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"removeAppQuestion": {
"one": "Želite li ukloniti aplikaciju?",
"other": "Želite li ukloniti aplikacije?"
@@ -360,10 +347,6 @@
"one": "{} i još 1 aplikacija je ažurirana.",
"other": "{} i još {} aplikacija je ažurirano."
},
"xAndNMoreUpdatesFailed": {
"one": "Failed to update {} and 1 more app.",
"other": "Failed to update {} and {} more apps."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} i još jedna aplikacija je vjerovatno ažurirana.",
"other": "{} i još {} aplikacija su vjerovatno ažurirane."

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(Požadované)",
"dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST",
"colour": "Barva",
"standard": "Standardní",
"custom": "Vlastní",
"useMaterialYou": "Použijte materiál, který jste",
"githubStarredRepos": "GitHub označená hvězdičkou",
"uname": "Uživatelské jméno",
"wrongArgNum": "Nesprávný počet zadaných argumentů",
@@ -113,7 +110,6 @@
"dark": "Tmavé",
"light": "Světlé",
"followSystem": "Jako systém",
"followSystemThemeExplanation": "Sledování motivu systému je možné pouze pomocí aplikací třetích stran.",
"useBlackTheme": "Použít čistě černé tmavé téma",
"appSortBy": "Seřadit podle",
"authorName": "Autor/Jméno",
@@ -147,10 +143,8 @@
"noNewUpdates": "Žádné nové aktualizace.",
"xHasAnUpdate": "{} má aktualizaci.",
"appsUpdated": "Aplikace aktualizovány",
"appsNotUpdated": "Nepodařilo se aktualizovat aplikace",
"appsUpdatedNotifDescription": "Upozornit, že byly provedeny aktualizace jedné nebo více aplikací na pozadí",
"xWasUpdatedToY": "{} byla aktualizována na {}",
"xWasNotUpdatedToY": "Nepodařilo se aktualizovat {} na {}.",
"errorCheckingUpdates": "Chyba kontroly aktualizací",
"errorCheckingUpdatesNotifDescription": "Zobrazit oznámení při neúspěšné kontrole aktualizací na pozadí",
"appsRemoved": "Odstraněné aplikace",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Deaktivovat detekci verze",
"noVersionDetectionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně.",
"downloadingX": "Stáhnout {}",
"downloadX": "Stáhnout {}",
"downloadedX": "Staženo {}",
"releaseAsset": "Vydání aktiva",
"downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace",
"noAPKFound": "Žádná APK nebyla nalezena",
"noVersionDetection": "Žádná detekce verze",
@@ -227,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'",
"dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK",
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
"gitlabPATLabel": "Osobní přístupový token GitLab",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "O",
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
"checkOnStart": "Zkontrolovat jednou při spuštění",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Odhadnout novější verzi na základě prvních třiceti číslic kontrolního součtu adresy URL APK, pokud není podporována jinak",
"selectX": "Vybrat {}",
"parallelDownloads": "Povolit souběžné stahování",
"useShizuku": "K instalaci použijte Shizuku nebo Sui",
"installMethod": "Metoda instalace",
"normal": "Normální",
"root": "Správce",
"shizukuBinderNotFound": "Shizuku neběží",
"shizukuOld": "Stará verze Shizuku (<11) - aktualizujte ji",
"shizukuOldAndroidWithADB": "Shizuku běží na Androidu < 8.1 s ADB - aktualizujte Android nebo místo toho použijte Sui",
"shizukuPretendToBeGooglePlay": "Nastavení Google Play jako zdroje instalace (pokud se používá Shizuku)",
"useSystemFont": "Použít systémové písmo",
"systemFontError": "Chyba při načítání systémového písma: {}",
"useVersionCodeAsOSVersion": "Použít kód verze aplikace jako verzi zjištěnou OS",
"requestHeader": "Hlavička požadavku",
"useLatestAssetDateAsReleaseDate": "Použít poslední nahrané dílo jako datum vydání",
@@ -308,10 +299,6 @@
"note": "Poznámka",
"selfHostedNote": "Rozbalovací seznam \"{}\" lze použít k dosažení vlastních/obvyklých instancí libovolného zdroje.",
"badDownload": "APK nelze analyzovat (nekompatibilní nebo částečné stažení)",
"beforeNewInstallsShareToAppVerifier": "Sdílení nových aplikací s aplikací AppVerifier (pokud je k dispozici)",
"appVerifierInstructionToast": "Sdílejte do aplikace AppVerifier a po dokončení se sem vraťte.",
"wiki": "Nápověda/Wiki",
"crowdsourcedConfigsLabel": "Konfigurace aplikací s využitím crowdsourcingu (použití na vlastní nebezpečí)",
"removeAppQuestion": {
"one": "Odstranit Apku?",
"other": "Odstranit Apky?"
@@ -360,10 +347,6 @@
"one": "{} a 1 další aplikace mají aktualizace.",
"other": "{} a {} další aplikace byly aktualizovány."
},
"xAndNMoreUpdatesFailed": {
"one": "Nepodařilo se aktualizovat {} a 1 další aplikaci.",
"other": "Nepodařilo se aktualizovat {} a {} další aplikace."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} a 1 další aplikace možno aktualizovat",
"other": "{} a {} další aplikace mohou být aktualizovány."

View File

@@ -19,12 +19,9 @@
"noDescription": "Keine Beschreibung",
"cancel": "Abbrechen",
"continue": "Weiter",
"requiredInBrackets": "(wird benötigt)",
"requiredInBrackets": "(Benötigt)",
"dropdownNoOptsError": "FEHLER: DROPDOWN MUSS MINDESTENS EINE OPTION HABEN",
"colour": "Farbe",
"standard": "Standard",
"custom": "Benutzerdefiniert",
"useMaterialYou": "Verwende Material You",
"githubStarredRepos": "GitHub Starred Repos",
"uname": "Benutzername",
"wrongArgNum": "Falsche Anzahl von Argumenten (Parametern) übermittelt",
@@ -113,7 +110,6 @@
"dark": "Dunkel",
"light": "Hell",
"followSystem": "System folgen",
"followSystemThemeExplanation": "Das Folgen des Systemthemes ist unter Android <10 nur mit Hilfe von Drittanbieteranwendungen möglich",
"useBlackTheme": "Verwende Pure Black Dark Theme",
"appSortBy": "App sortieren nach",
"authorName": "Autor/Name",
@@ -147,10 +143,8 @@
"noNewUpdates": "Keine neuen Aktualisierungen.",
"xHasAnUpdate": "{} hat eine Aktualisierung.",
"appsUpdated": "Apps aktualisiert",
"appsNotUpdated": "Aktualisierung der Anwendungen fehlgeschlagen",
"appsUpdatedNotifDescription": "Benachrichtigt den Benutzer, dass Aktualisierungen für eine oder mehrere Apps im Hintergrund durchgeführt wurden",
"xWasUpdatedToY": "{} wurde auf {} aktualisiert.",
"xWasNotUpdatedToY": "Die Aktualisierung von {} auf {} ist fehlgeschlagen.",
"errorCheckingUpdates": "Fehler beim Prüfen auf Aktualisierungen",
"errorCheckingUpdatesNotifDescription": "Eine Benachrichtigung, die angezeigt wird, wenn die Prüfung der Hintergrundaktualisierung fehlschlägt",
"appsRemoved": "Apps entfernt",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Versionsermittlung deaktivieren",
"noVersionDetectionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert.",
"downloadingX": "Lade {} herunter",
"downloadX": "{} herunterladen",
"downloadedX": "{} heruntergeladen",
"releaseAsset": "release Asset",
"downloadNotifDescription": "Benachrichtigt den Nutzer über den Fortschritt beim Herunterladen einer App",
"noAPKFound": "Keine APK gefunden",
"noVersionDetection": "Keine Versionserkennung",
@@ -288,12 +279,12 @@
"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",
"useShizuku": "Verwenden Sie Shizuku oder Sui zur Installation",
"installMethod": "Installationsmethode",
"normal": "Normal",
"root": "Root",
"shizukuBinderNotFound": "Kompatibler Shizukudienst wurde nicht gefunden",
"shizukuOld": "Alte Shizuku-Version (<11) - aktualisieren Sie sie",
"shizukuOldAndroidWithADB": "Shizuku läuft auf Android < 8.1 mit ADB - aktualisieren Sie Android oder verwenden Sie stattdessen Sui",
"shizukuPretendToBeGooglePlay": "Google Play als Installationsquelle festlegen (wenn Shizuku verwendet wird)",
"useSystemFont": "Verwende die Systemschriftart",
"systemFontError": "Fehler beim Laden der Systemschriftart: {}",
"useVersionCodeAsOSVersion": "Verwende die Appversion als erkannte Version vom Betriebssystem",
"requestHeader": "Request Header",
"useLatestAssetDateAsReleaseDate": "Den letzten Asset-Upload als Veröffentlichungsdatum verwenden",
@@ -308,10 +299,6 @@
"note": "Hinweis",
"selfHostedNote": "Das „{}“-Dropdown-Menü kann verwendet werden, um selbst gehostete/angepasste Instanzen einer beliebigen Quelle zu erreichen.",
"badDownload": "Die APK konnte nicht geparst werden (inkompatibler oder teilweiser Download)",
"beforeNewInstallsShareToAppVerifier": "Neue Apps mit AppVerifier teilen (falls verfügbar)",
"appVerifierInstructionToast": "Geben Sie die Daten an AppVerifier weiter und kehren Sie dann hierher zurück, wenn Sie fertig sind.",
"wiki": "Hilfe/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (Verwendung auf eigene Gefahr)",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "Apps entfernen?"
@@ -360,10 +347,6 @@
"one": "{} und 1 weitere Anwendung wurden aktualisiert.",
"other": "{} und {} weitere Anwendungen wurden aktualisiert."
},
"xAndNMoreUpdatesFailed": {
"one": "Aktualisierung fehlgeschlagen {} und 1 weitere Anwendung.",
"other": "Die Aktualisierung von {} und {} weiteren Anwendungen ist fehlgeschlagen."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} und 1 weitere Anwendung wurden möglicherweise aktualisiert.",
"other": "{} und {} weitere Anwendungen wurden möglicherweise aktualisiert."

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(Required)",
"dropdownNoOptsError": "ERROR: DROPDOWN MUST HAVE AT LEAST ONE OPT",
"colour": "Colour",
"standard": "Standard",
"custom": "Custom",
"useMaterialYou": "Use Material You",
"githubStarredRepos": "GitHub Starred Repos",
"uname": "Username",
"wrongArgNum": "Wrong number of arguments provided",
@@ -113,7 +110,6 @@
"dark": "Dark",
"light": "Light",
"followSystem": "Follow System",
"followSystemThemeExplanation": "Following system theme is possible only by using third-party applications",
"useBlackTheme": "Use pure black dark theme",
"appSortBy": "App Sort By",
"authorName": "Author/Name",
@@ -147,10 +143,8 @@
"noNewUpdates": "No new updates.",
"xHasAnUpdate": "{} has an update.",
"appsUpdated": "Apps Updated",
"appsNotUpdated": "Failed to update applications",
"appsUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were applied in the background",
"xWasUpdatedToY": "{} was updated to {}.",
"xWasNotUpdatedToY": "Failed to update {} to {}.",
"errorCheckingUpdates": "Error Checking for Updates",
"errorCheckingUpdatesNotifDescription": "A notification that shows when background update checking fails",
"appsRemoved": "Apps Removed",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Disable Version Detection",
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
"downloadingX": "Downloading {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Notifies the user of the progress in downloading an App",
"noAPKFound": "No APK found",
"noVersionDetection": "No version detection",
@@ -259,7 +250,7 @@
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version String Extraction RegEx",
"matchGroupToUse": "Match Group to Use for Version String Extraction RegEx",
"matchGroupToUse": "Match Group to Use for Version String Extraction Regex",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"useShizuku": "Use Shizuku or Sui to install",
"shizukuBinderNotFound": "Shizuku service not running",
"shizukuOld": "Old Shizuku version (<11) - update it",
"shizukuOldAndroidWithADB": "Shizuku running on Android < 8.1 with ADB - update Android or use Sui instead",
"shizukuPretendToBeGooglePlay": "Set Google Play as the installation source (if Shizuku is used)",
"installMethod": "Installation method",
"normal": "Normal",
"root": "Root",
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
"useSystemFont": "Use the system font",
"systemFontError": "Error loading the system font: {}",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"useLatestAssetDateAsReleaseDate": "Use latest asset upload as release date",
@@ -308,10 +299,6 @@
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)",
"beforeNewInstallsShareToAppVerifier": "Share new Apps with AppVerifier (if available)",
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
"wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"
@@ -360,10 +347,6 @@
"one": "{} and 1 more app was updated.",
"other": "{} and {} more apps were updated."
},
"xAndNMoreUpdatesFailed": {
"one": "Failed to update {} and 1 more app.",
"other": "Failed to update {} and {} more apps."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."

View File

@@ -1,15 +1,15 @@
{
"invalidURLForSource": "El URL de la aplicación {} no es válido",
"noReleaseFound": "No se ha encontrado una versión válida",
"invalidURLForSource": "URL de la aplicación {} no es válida",
"noReleaseFound": "No se ha podido encontrar una versión válida",
"noVersionFound": "No se ha podido determinar la versión",
"urlMatchesNoSource": "El URL no coincide con ninguna fuente conocida",
"urlMatchesNoSource": "La URL no coincide con ninguna fuente conocida",
"cantInstallOlderVersion": "No se puede instalar una versión previa de la aplicación",
"appIdMismatch": "El ID del paquete descargado no coincide con el ID de la aplicación instalada",
"appIdMismatch": "La ID del paquete descargado no coincide con la ID de la aplicación instalada",
"functionNotImplemented": "Esta clase no ha implementado esta función",
"placeholder": "Espacio reservado",
"someErrors": "Han ocurrido algunos errores",
"unexpectedError": "Error inesperado",
"ok": "Aceptar",
"ok": "OK",
"and": "y",
"githubPATLabel": "Token de acceso personal a GitHub\n(reduce tiempos de espera)",
"includePrereleases": "Incluir versiones preliminares",
@@ -22,90 +22,87 @@
"requiredInBrackets": "(Requerido)",
"dropdownNoOptsError": "ERROR: EL DESPLEGABLE DEBE TENER AL MENOS UNA OPCIÓN",
"colour": "Color",
"standard": "Estándar",
"custom": "A medida",
"useMaterialYou": "Use 'Material You'",
"githubStarredRepos": "Repositorios favoritos en GitHub",
"uname": "Nombre de usuario",
"wrongArgNum": "Número de argumentos provistos inválido",
"xIsTrackOnly": "{} es de 'sólo seguimiento'",
"source": "Origen",
"app": "Aplicación",
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son solo para seguimiento.",
"youPickedTrackOnly": "Debe seleccionar la opción de 'solo para 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 descargar o actualizarla.",
"cancelled": "Cancelado",
"appAlreadyAdded": "Aplicación añadida anteriormente",
"alreadyUpToDateQuestion": "¿Aplicación actualizada previamente?",
"addApp": "Añadir aplicación",
"addApp": "Añadir Aplicación",
"appSourceURL": "URL de origen de la aplicación",
"error": "Error",
"add": "Añadir",
"searchSomeSourcesLabel": "Buscar (solo algunas fuentes)",
"search": "Buscar",
"additionalOptsFor": "Opciones adicionales para {}",
"supportedSources": "Fuentes admitidas",
"trackOnlyInBrackets": "(Solo para seguimiento)",
"searchableInBrackets": "(permite búsqueda)",
"additionalOptsFor": "Opciones Adicionales para {}",
"supportedSources": "Fuentes Soportadas",
"trackOnlyInBrackets": "(Solo seguimiento)",
"searchableInBrackets": "(soporta búsqueda)",
"appsString": "Aplicaciones",
"noApps": "Sin Aplicaciones",
"noAppsForFilter": "Sin aplicaciones para filtrar",
"byX": "por: {}",
"percentProgress": "Progreso: {} %",
"pleaseWait": "Espere...",
"updateAvailable": "Actualización disponible",
"notInstalled": "No instalado",
"percentProgress": "Progreso: {}%",
"pleaseWait": "Por favor, espere",
"updateAvailable": "Actualización Disponible",
"notInstalled": "No Instalado",
"pseudoVersion": "pseudoversión",
"selectAll": "Seleccionar todo",
"selectAll": "Seleccionar Todo",
"deselectX": "Deseleccionar {}",
"xWillBeRemovedButRemainInstalled": "{} se elimina de Obtainium pero continuará instalada en el dispositivo.",
"xWillBeRemovedButRemainInstalled": "{} se eliminada de Obtainium pero continuará instalada en el dispositivo.",
"removeSelectedAppsQuestion": "¿Eliminar aplicaciones seleccionadas?",
"removeSelectedApps": "Eliminar aplicaciones seleccionadas",
"removeSelectedApps": "Eliminar Aplicaciones Seleccionadas",
"updateX": "Actualizar {}",
"installX": "Instalar {}",
"markXTrackOnlyAsUpdated": "Marcar {}\n(solo para seguimiento)\ncomo actualizada",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo seguimiento)\ncomo actualizada",
"changeX": "Cambiar {}",
"installUpdateApps": "Instalar/actualizar aplicaciones",
"installUpdateSelectedApps": "Instalar/actualizar aplicaciones seleccionadas",
"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",
"pinToTop": "Anclar al principio",
"unpinFromTop": "Desanclar del principio",
"pinToTop": "Fijar arriba",
"unpinFromTop": "Desfijar de arriba",
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar estado de instalación para las aplicaciones seleccionadas?",
"installStatusOfXWillBeResetExplanation": "Se restaurará el estado de instalación de las aplicaciones seleccionadas.\n\nEsto puede ser de útil cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
"customLinkMessage": "Estos enlaces funcionan en dispositivos con Obtainium instalado",
"shareAppConfigLinks": "Compartir la configuración de la aplicación como enlace HTML",
"shareSelectedAppURLs": "Compartir URL de las aplicaciones seleccionadas",
"shareSelectedAppURLs": "Compartir URLs de las aplicaciones seleccionadas",
"resetInstallStatus": "Restaurar estado de instalación",
"more": "Más",
"removeOutdatedFilter": "Eliminar filtro de aplicaciones desactualizado",
"removeOutdatedFilter": "Elimiar filtro de aplicaciones desactualizado",
"showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas",
"filter": "Filtrar",
"filterApps": "Filtrar actualizaciones",
"filterApps": "Filtrar Actualizaciones",
"appName": "Nombre de la aplicación",
"author": "Autor",
"upToDateApps": "Aplicaciones actualizadas",
"nonInstalledApps": "Aplicaciones no instaladas",
"importExport": "Importar/exportar",
"upToDateApps": "Aplicaciones Actualizadas",
"nonInstalledApps": "Aplicaciones No Instaladas",
"importExport": "Importar/Exportar",
"settings": "Ajustes",
"exportedTo": "Exportado a {}",
"obtainiumExport": "Exportar Obtainium",
"invalidInput": "Entrada no válida",
"invalidInput": "Input incorrecto",
"importedX": "Importado {}",
"obtainiumImport": "Importar Obtainium",
"importFromURLList": "Importar desde lista de URL",
"searchQuery": "Término de búsqueda",
"appURLList": "Lista de URL de aplicaciones",
"importFromURLList": "Importar desde lista de URLs",
"searchQuery": "Consulta de Búsqueda",
"appURLList": "Lista de URLs de Aplicaciones",
"line": "Línea",
"searchX": "Buscar {}",
"noResults": "No se ha encontrado ningún resultado",
"noResults": "Resultados no encontrados",
"importX": "Importar desde {}",
"importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como «No instalada».\nPara solucionarlo, reinstálelas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a los URL y a los métodos de importación mediante terceros.",
"importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como \"No Instalada\".\nPara solucionarlo, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
"importErrors": "Errores de Importación",
"importedXOfYApps": "{} de {} aplicaciones importadas.",
"followingURLsHadErrors": "Los URL siguientes han tenido problemas:",
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
"followingURLsHadErrors": "Las siguientes URLs han tenido problemas:",
"selectURL": "Seleccionar URL",
"selectURLs": "Seleccionar URLs",
"pick": "Escoger",
@@ -113,10 +110,9 @@
"dark": "Oscuro",
"light": "Claro",
"followSystem": "Seguir al sistema",
"followSystemThemeExplanation": "Seguir el tema del sistema sólo es posible utilizando aplicaciones de terceros",
"useBlackTheme": "Negro puro en tema oscuro",
"appSortBy": "Ordenar aplicaciones por",
"authorName": "Autor/nombre",
"useBlackTheme": "Negro puro en tema Oscuro",
"appSortBy": "Ordenar Apps Por",
"authorName": "Autor/Nombre",
"nameAuthor": "Nombre/Autor",
"asAdded": "Según se Añadieron",
"appSortOrder": "Orden de Clasificación",
@@ -126,42 +122,40 @@
"neverManualOnly": "Nunca, solo manual",
"appearance": "Apariencia",
"showWebInAppView": "Mostrar vista de la web de origen",
"pinUpdates": "Anclar actualizaciones al principio",
"pinUpdates": "Fijar actualizaciones al principio",
"updates": "Actualizaciones",
"sourceSpecific": "Fuente específica",
"sourceSpecific": "Fuente Específica",
"appSource": "Obtainium en GitHub",
"noLogs": "Ningún registro",
"appLogs": "Registros",
"noLogs": "Sin Logs",
"appLogs": "Logs",
"close": "Cerrar",
"share": "Compartir",
"appNotFound": "Aplicación no encontrada",
"obtainiumExportHyphenatedLowercase": "obtainium-exportación",
"pickAnAPK": "Seleccione una APK",
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
"deviceSupportsXArch": "Su dispositivo admite las siguientes arquitecturas de procesador: {}.",
"deviceSupportsFollowingArchs": "Su dispositivo admite las siguientes arquitecturas de procesador:",
"deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.",
"deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:",
"warning": "Aviso",
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es «{}» pero el paquete de la actualización viene de «{}». ¿Desea continuar?",
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
"updatesAvailable": "Actualizaciones Disponibles",
"updatesAvailableNotifDescription": "Notifica al usuario de que hay actualizaciones para una o más aplicaciones monitoreadas por Obtainium",
"noNewUpdates": "No hay nuevas actualizaciones.",
"xHasAnUpdate": "{} tiene una actualización.",
"appsUpdated": "Aplicaciones actualizadas",
"appsNotUpdated": "Error al actualizar las aplicaciones",
"appsUpdatedNotifDescription": "Notifica al usuario de que una o más aplicaciones han sido actualizadas en segundo plano",
"xWasUpdatedToY": "{} ha sido actualizada a {}.",
"xWasNotUpdatedToY": "Error al actualizar {} a {}.",
"errorCheckingUpdates": "Error al buscar actualizaciones",
"errorCheckingUpdates": "Error buscando ectualizaciones",
"errorCheckingUpdatesNotifDescription": "Una notificación que muestra cuándo la comprobación de actualizaciones en segundo plano falla",
"appsRemoved": "Aplicaciones eliminadas",
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
"completeAppInstallation": "Instalación completa de la aplicación",
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierto para instalar aplicaciones",
"completeAppInstallationNotifDescription": "Le pide volver a Obtainium para terminar de instalar una aplicación",
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación",
"checkingForUpdates": "Buscando actualizaciones...",
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
"pleaseAllowInstallPerm": "Permita que Obtainium instale aplicaciones",
"pleaseAllowInstallPerm": "Por favor, permita que Obtainium instale aplicaciones",
"trackOnly": "Solo para seguimiento",
"errorWithHttpStatusCode": "Error {}",
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
@@ -176,11 +170,11 @@
"fdroid": "Repositorio oficial F-Droid",
"appIdOrName": "ID o Nombre de la Aplicación",
"appId": "ID de la Aplicación",
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con ese ID o nombre",
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
"fdroidThirdPartyRepo": "Repositorio de terceros F-Droid",
"steamMobile": "Steam para móviles",
"steamChat": "Chat de Steam",
"fdroidThirdPartyRepo": "Repositorio de tercera parte F-Droid",
"steamMobile": "Móvil de vapor",
"steamChat": "Chat de vapor",
"install": "Instalar",
"markInstalled": "Marcar como instalada",
"update": "Actualizar",
@@ -189,11 +183,8 @@
"disableVersionDetection": "Desactivar la detección de versiones",
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda que no funcionar correctamente.",
"downloadingX": "Descargando {}",
"downloadX": "Descargar {}",
"downloadedX": "Descargado {}",
"releaseAsset": "Recurso publicado",
"downloadNotifDescription": "Notifica al usuario del progreso de descarga de una aplicación",
"noAPKFound": "No se ha encontrado el paquete de instalación APK",
"noAPKFound": "No se encontró el paquete de instalación APK",
"noVersionDetection": "Sin detección de versiones",
"categorize": "Catogorizar",
"categories": "Categorías",
@@ -201,14 +192,14 @@
"noCategory": "Sin categoría",
"noCategories": "Sin categorías",
"deleteCategoriesQuestion": "¿Eliminar categorías?",
"categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas se marcarán como «Sin categoría».",
"categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas serán marcadas como 'Sin categoría'.",
"addCategory": "Añadir categoría",
"label": "Nombre",
"language": "Idioma",
"copiedToClipboard": "Copiado en el portapapeles",
"copiedToClipboard": "Copiado al portapapeles",
"storagePermissionDenied": "Permiso de almacenamiento rechazado",
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaciones seleccionadas.",
"filterAPKsByRegEx": "Filtrar por APK",
"filterAPKsByRegEx": "Filtrar por APKs",
"removeFromObtainium": "Eliminar de Obtainium",
"uninstallFromDevice": "Desinstalar del dispositivo",
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
@@ -221,17 +212,17 @@
"versionDetection": "Detección de versiones",
"standardVersionDetection": "Por versión",
"groupByCategory": "Agrupar por categoría",
"autoApkFilterByArch": "Filtrar APK por arquitectura del procesador (si es posible)",
"overrideSource": "Forzar desde la fuente",
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador (si es posible)",
"overrideSource": "Sobrescribir Fuente",
"dontShowAgain": "No mostrar de nuevo",
"dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps 'solo para seguimiento",
"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",
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas al final",
"gitlabPATLabel": "Token de acceso personal a GitLab",
"about": "Acerca",
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en ajustes)",
"checkOnStart": "Comprobar actualizaciones al inicio",
"tryInferAppIdFromCode": "Intentar deducir el ID de la app por el código fuente",
"tryInferAppIdFromCode": "Intentar deducir la ID de la app por el código fuente",
"removeOnExternalUninstall": "Auto eliminar apps desinstaladas externamente",
"pickHighestVersionCode": "Auto selección de versión superior del paquete APK",
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la app",
@@ -271,7 +262,7 @@
"takeFirstLink": "Usar primer enlace",
"skipSort": "Omitir orden",
"debugMenu": "Menu Depurar",
"bgTaskStarted": "Iniciada tarea en segundo plano; revise los registros.",
"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",
"installing": "Instalando",
@@ -286,47 +277,43 @@
"checkingForUpdatesNotifChannel": "Buscando actualizaciones",
"onlyCheckInstalledOrTrackOnlyApps": "Comprobar actualizaciones solo para apps instaladas o en seguimiento",
"supportFixedAPKURL": "Soporte para URLs fijas de APK",
"selectX": "Elija {}",
"selectX": "Selecciona {}",
"parallelDownloads": "Permitir descargas paralelas",
"useShizuku": "Use Shizuku o Sui para instalar",
"installMethod": "Método de instalación",
"normal": "Normal",
"root": "Raíz",
"shizukuBinderNotFound": "Shizuku no funciona",
"shizukuOld": "Versión antigua de Shizuku (<11) - actualícela",
"shizukuOldAndroidWithADB": "Shizuku corriendo en Android < 8.1 con ADB - actualiza Android o usa Sui en su lugar",
"shizukuPretendToBeGooglePlay": "Establecer Google Play como fuente de instalación (si se usa Shizuku)",
"useSystemFont": "Usar fuente del sistema",
"useSystemFont": "Usar la fuente de impresión del sistema",
"systemFontError": "Error al cargar la fuente de impresión del sistema: {}",
"useVersionCodeAsOSVersion": "Usar la versión de la aplicación como versión detectada por el sistema operativo",
"requestHeader": "Encabezado de solicitud",
"useLatestAssetDateAsReleaseDate": "Usar la última carga del recurso como fecha de lanzamiento",
"useLatestAssetDateAsReleaseDate": "Usar la última carga de recursos como fecha de lanzamiento",
"defaultPseudoVersioningMethod": "Método de pseudoversionado predeterminado",
"partialAPKHash": "Hash de APK parcial",
"APKLinkHash": "Hash de enlace APK",
"directAPKLink": "Enlace APK directo",
"pseudoVersionInUse": "Se está usando una pseudoversión",
"pseudoVersionInUse": "Se está utilizando una pseudoversión",
"installed": "Instalado",
"latest": "Versión más reciente",
"invertRegEx": "Invertir expresión regular",
"note": "Nota",
"selfHostedNote": "El desplegable «{}» puede usarse para acceder a instancias autoalojadas/personalizadas de cualquier fuente.",
"selfHostedNote": "El desplegable \"{}\" puede utilizarse para acceder a instancias autoalojadas/personalizadas de cualquier fuente.",
"badDownload": "No se ha podido analizar el APK (incompatible o descarga parcial)",
"beforeNewInstallsShareToAppVerifier": "Compartir nuevas aplicaciones con AppVerifier (si está disponible)",
"appVerifierInstructionToast": "Comparta con AppVerifier y vuelva aquí cuando esté listo.",
"wiki": "Ayuda/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (uso bajo su propia responsabilidad)",
"removeAppQuestion": {
"one": "¿Eliminar aplicación?",
"other": "¿Eliminar aplicaciones?"
"one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Muchas peticiones (limitado); pruebe de nuevo en {} minuto",
"other": "Muchas peticiones (limitado); pruebe de nuevo en {} minutos"
"one": "Muchas peticiones (limitado) - prueba de nuevo en {} minuto",
"other": "Muchas peticiones (limitado) - prueba de nuevo en {} minutos"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}; se volverá a probar en {} minuto",
"other": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}; se volverá a probar en {} minutos"
"one": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}, se volverá a probar en {} minuto",
"other": "La comprobación de actualizaciones en segundo plano se ha encontrado un {}, se volverá a probar en {} minutos"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualización; se le notificará si es necesario",
"other": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualizaciones; se le notificará si es necesario"
"one": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualización - se notificará al usuario si es necesario",
"other": "La comprobación de actualizaciones en segundo plano ha encontrado {} actualizaciones - se notificará al usuario si es necesario"
},
"apps": {
"one": "{} Aplicación",
@@ -349,20 +336,16 @@
"other": "{} días"
},
"clearedNLogsBeforeXAfterY": {
"one": "Eliminado {n} registro (previo a = {before}, posterior a = {after})",
"other": "Eliminados {n} registros (previos a = {before}, posteriores a = {after})"
"one": "Eliminado {n} log (previo a = {before}, posterior a = {after})",
"other": "Eliminados {n} logs (previos a = {before}, posteriores a = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} y 1 aplicación más tiene actualizaciones.",
"other": "{} y {} aplicaciones más tienen actualizaciones."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} y 1 aplicación más se han actualizado.",
"other": "{} y {} aplicaciones más se han actualizado."
},
"xAndNMoreUpdatesFailed": {
"one": "Error al actualizar {} y 1 aplicación más.",
"other": "No se han podido actualizar {} y {} aplicaciones más."
"one": "{} y 1 aplicación más han sido actualizadas.",
"other": "{} y {} aplicaciones más han sido actualizadas."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} y 1 aplicación más podría haber sido actualizada.",

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(ضروری)",
"dropdownNoOptsError": "خطا: کشویی باید حداقل یک گزینه داشته باشد",
"colour": "رنگ",
"standard": "Standard",
"custom": "Custom",
"useMaterialYou": "Use Material You",
"githubStarredRepos": "مخازن ستاره دار گیتهاب",
"uname": "نام کاربری",
"wrongArgNum": "تعداد آرگومان های ارائه شده اشتباه است",
@@ -113,7 +110,6 @@
"dark": "تاریک",
"light": "روشن",
"followSystem": "هماهنگ با سیستم",
"followSystemThemeExplanation": "Following system theme is possible only by using third-party applications",
"useBlackTheme": "استفاده از تم تیره سیاه خالص",
"appSortBy": "مرتب سازی برنامه بر اساس",
"authorName": "سازنده/اسم",
@@ -147,10 +143,8 @@
"noNewUpdates": "به روز رسانی جدیدی وجود ندارد.",
"xHasAnUpdate": "{} یک به روز رسانی دارد.",
"appsUpdated": "برنامه ها به روز شدند",
"appsNotUpdated": "Failed to update applications",
"appsUpdatedNotifDescription": "به کاربر اطلاع می دهد که به روز رسانی یک یا چند برنامه در پس زمینه اعمال شده است",
"xWasUpdatedToY": "{} به {} به روز شد.",
"xWasNotUpdatedToY": "Failed to update {} to {}.",
"errorCheckingUpdates": "خطا در بررسی به‌روزرسانی‌ها",
"errorCheckingUpdatesNotifDescription": "اعلانی که وقتی بررسی به‌روزرسانی پس‌زمینه ناموفق است نشان می‌دهد",
"appsRemoved": "برنامه ها حذف شدند",
@@ -189,9 +183,6 @@
"disableVersionDetection": "غیرفعال کردن تشخیص نسخه",
"noVersionDetectionExplanation": "این گزینه فقط باید برای برنامه هایی استفاده شود که تشخیص نسخه به درستی کار نمی کند.",
"downloadingX": "در حال دانلود {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "کاربر را از پیشرفت دانلود یک برنامه مطلع می کند",
"noAPKFound": "APK پیدا نشد فایل",
"noVersionDetection": "بدون تشخیص نسخه",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "پشتیبانی از URL های APK ثابت",
"selectX": "انتخاب کنید {}",
"parallelDownloads": "اجازه دانلود موازی",
"useShizuku": "Use Shizuku or Sui to install",
"installMethod": "روش نصب",
"normal": "طبیعی",
"root": "ریشه",
"shizukuBinderNotFound": "Shizuku در حال اجرا نیست",
"shizukuOld": "Old Shizuku version (<11) - update it",
"shizukuOldAndroidWithADB": "Shizuku running on Android < 8.1 with ADB - update Android or use Sui instead",
"shizukuPretendToBeGooglePlay": "Set Google Play as the installation source (if Shizuku is used)",
"useSystemFont": "استفاده از فونت سیستم",
"systemFontError": "خطا در بارگیری فونت سیستم: {}",
"useVersionCodeAsOSVersion": "استفاده کد نسخه برنامه به جای نسخه شناسایی شده توسط سیستم عامل استفاده کنید",
"requestHeader": "درخواست سطر بالایی",
"useLatestAssetDateAsReleaseDate": "استفاده از آخرین بارگذاری دارایی به عنوان تاریخ انتشار",
@@ -308,10 +299,6 @@
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)",
"beforeNewInstallsShareToAppVerifier": "Share new Apps with AppVerifier (if available)",
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
"wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"removeAppQuestion": {
"one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟"
@@ -360,10 +347,6 @@
"one": "{} و 1 برنامه دیگر به روز شدند.",
"other": "{} و {} برنامه دیگر به روز شدند."
},
"xAndNMoreUpdatesFailed": {
"one": "Failed to update {} and 1 more app.",
"other": "Failed to update {} and {} more apps."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} و 1 برنامه دیگر ممکن است به روز شده باشند.",
"other": "ممکن است {} و {} برنامه های دیگر به روز شده باشند."

View File

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

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(Kötelező)",
"dropdownNoOptsError": "HIBA: A LEDOBÁST LEGALÁBB EGY OPCIÓHOZ KELL RENDELNI",
"colour": "Szín",
"standard": "Standard",
"custom": "Custom",
"useMaterialYou": "Használja az Ön által használt anyagot",
"githubStarredRepos": "GitHub Csillagos Repo-k",
"uname": "Felh.név",
"wrongArgNum": "Rossz számú argumentumot adott meg",
@@ -113,7 +110,6 @@
"dark": "Sötét",
"light": "Világos",
"followSystem": "Rendszer szerint",
"followSystemThemeExplanation": "A következő rendszer téma csak harmadik féltől származó alkalmazások használatával lehetséges",
"useBlackTheme": "Használjon teljesen fekete sötét témát",
"appSortBy": "App rendezés...",
"authorName": "Szerző/Név",
@@ -147,10 +143,8 @@
"noNewUpdates": "Nincsenek új frissítések.",
"xHasAnUpdate": "A(z) {} frissítést kapott.",
"appsUpdated": "Alkalmazások frissítve",
"appsNotUpdated": "Nem sikerült frissíteni az alkalmazásokat",
"appsUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy/több app frissítése megtörtént a háttérben",
"xWasUpdatedToY": "{} frissítve a következőre: {}.",
"xWasNotUpdatedToY": "A {} frissítése a {}-ra nem sikerült.",
"errorCheckingUpdates": "Hiba a frissítések keresésekor",
"errorCheckingUpdatesNotifDescription": "Értesítés, amely akkor jelenik meg, ha a háttérbeli frissítések ellenőrzése sikertelen",
"appsRemoved": "Alkalmazások eltávolítva",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Verzió érzékelés letiltása",
"noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.",
"downloadingX": "{} letöltés",
"downloadX": "Letöltés {}",
"downloadedX": "Letöltés {}",
"releaseAsset": "Kiadási tartalom",
"downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról",
"noAPKFound": "Nem található APK",
"noVersionDetection": "Nincs verzió érzékelés",
@@ -227,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
"dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket",
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
"gitlabPATLabel": "GitLab személyes hozzáférési token",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Rólunk",
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
"checkOnStart": "Egyszer az alkalmazás indításakor is",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Támogatja a rögzített APK URL-eket",
"selectX": "Kiválaszt {}",
"parallelDownloads": "Párhuzamos letöltéseket enged",
"useShizuku": "Használja Shizuku vagy Sui telepítéséhez",
"installMethod": "Telepítési mód",
"normal": "Normál",
"root": "Root",
"shizukuBinderNotFound": "A Shizuku nem fut",
"shizukuOld": "Régi Shizuku verzió (<11) - frissítsd!",
"shizukuOldAndroidWithADB": "Shizuku fut Android < 8.1 ADB-vel - frissítse az Androidot vagy használja a Sui-t helyette",
"shizukuPretendToBeGooglePlay": "Állítsa be a Google Play-t telepítési forrásként (ha Shizuku-t használ)",
"useSystemFont": "Használja a rendszer betűtípusát",
"systemFontError": "Hiba a rendszer betűtípusának betöltésekor: {}",
"useVersionCodeAsOSVersion": "Az app verziókód használata a rendszer által észlelt verzióként",
"requestHeader": "Kérelem fejléc",
"useLatestAssetDateAsReleaseDate": "Használja a legújabb tartalomfeltöltést megjelenési dátumként",
@@ -308,10 +299,6 @@
"note": "Megjegyzés:",
"selfHostedNote": "A \"{}\" legördülő menü használható bármely forrás saját üzemeltetésű/egyéni példányainak eléréséhez.",
"badDownload": "Az APK-t nem lehetett elemezni (inkompatibilis vagy részleges letöltés)",
"beforeNewInstallsShareToAppVerifier": "Új alkalmazások megosztása az AppVerifierrel (ha elérhető)",
"appVerifierInstructionToast": "Ossza meg az AppVerifierrel, majd térjen vissza ide, ha kész.",
"wiki": "Súgó/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (használat saját felelősségre)",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazásokat?"
@@ -360,10 +347,6 @@
"one": "A(z) {} és 1 további alkalmazás frissítve.",
"other": "{} és {} további alkalmazás frissítve."
},
"xAndNMoreUpdatesFailed": {
"one": "Nem sikerült frissíteni {} és még 1 alkalmazást.",
"other": "Nem sikerült frissíteni {} és {} további alkalmazásokat."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} és 1 további alkalmazás is frissült.",
"other": "{} és {} további alkalmazás is frissült."

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(richiesto)",
"dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE",
"colour": "Colore",
"standard": "Standard",
"custom": "Personalizzato",
"useMaterialYou": "Utilizzate il materiale che avete a disposizione",
"githubStarredRepos": "repository stellati da GitHub",
"uname": "Nome utente",
"wrongArgNum": "Numero di argomenti forniti errato",
@@ -113,7 +110,6 @@
"dark": "Scuro",
"light": "Chiaro",
"followSystem": "Segui il sistema",
"followSystemThemeExplanation": "È possibile seguire il tema di sistema solo utilizzando applicazioni di terze parti.",
"useBlackTheme": "Usa il tema nero puro",
"appSortBy": "App ordinate per",
"authorName": "Autore/Nome",
@@ -147,10 +143,8 @@
"noNewUpdates": "Nessun nuovo aggiornamento.",
"xHasAnUpdate": "Aggiornamento disponibile per {}",
"appsUpdated": "App aggiornate",
"appsNotUpdated": "Impossibile aggiornare le applicazioni",
"appsUpdatedNotifDescription": "Notifica all'utente che una o più app sono state aggiornate in secondo piano",
"xWasUpdatedToY": "{} è stato aggiornato alla {}.",
"xWasNotUpdatedToY": "Impossibile aggiornare {} a {}.",
"errorCheckingUpdates": "Controllo degli errori per gli aggiornamenti",
"errorCheckingUpdatesNotifDescription": "Una notifica che mostra quando il controllo degli aggiornamenti in secondo piano fallisce",
"appsRemoved": "App rimosse",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Disattiva il rilevamento della versione",
"noVersionDetectionExplanation": "Questa opzione dovrebbe essere usata solo per le app la cui versione non viene rilevata correttamente.",
"downloadingX": "Scaricamento di {} in corso",
"downloadX": "Scarica {}",
"downloadedX": "Scaricato {}",
"releaseAsset": "Rilascio Asset",
"downloadNotifDescription": "Notifica all'utente lo stato di avanzamento del download di un'app",
"noAPKFound": "Nessun APK trovato",
"noVersionDetection": "Disattiva rilevamento di versione",
@@ -227,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'",
"dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK",
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
"gitlabPATLabel": "GitLab Token di accesso personale",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Informazioni",
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
"checkOnStart": "Controlla una volta all'avvio",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Supporta URL fissi di APK",
"selectX": "Seleziona {}",
"parallelDownloads": "Permetti download paralleli",
"useShizuku": "Utilizzare Shizuku o Sui per installare",
"installMethod": "Metodo d'installazione",
"normal": "Normale",
"root": "Root",
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
"shizukuOld": "Vecchia versione di Shizuku (<11) - aggiornarla",
"shizukuOldAndroidWithADB": "Shizuku funziona su Android < 8.1 con ADB - aggiornare Android o utilizzare Sui al suo posto",
"shizukuPretendToBeGooglePlay": "Impostare Google Play come fonte di installazione (se si usa Shizuku)",
"useSystemFont": "Usa i caratteri di sistema",
"systemFontError": "Errore durante il caricamento dei caratteri di sistema: {}",
"useVersionCodeAsOSVersion": "Usa il codice versione dell'app come versione rilevata dal sistema operativo",
"requestHeader": "Intestazione della richiesta",
"useLatestAssetDateAsReleaseDate": "Usa l'ultimo caricamento della risorsa come data di rilascio",
@@ -308,10 +299,6 @@
"note": "Nota",
"selfHostedNote": "Il menu a tendina \"{}\" può essere usato per raggiungere istanze autogestite/personali di qualsiasi fonte.",
"badDownload": "Non è stato possibile analizzare l'APK (download incompatibile o parziale).",
"beforeNewInstallsShareToAppVerifier": "Condividere le nuove applicazioni con AppVerifier (se disponibile)",
"appVerifierInstructionToast": "Condividete con AppVerifier, quindi tornate qui quando siete pronti.",
"wiki": "Aiuto/Wiki",
"crowdsourcedConfigsLabel": "Configurazioni di app in crowdsourcing (uso a proprio rischio)",
"removeAppQuestion": {
"one": "Rimuovere l'app?",
"other": "Rimuovere le app?"
@@ -360,10 +347,6 @@
"one": "{} e un'altra app sono state aggiornate.",
"other": "{} e altre {} app sono state aggiornate."
},
"xAndNMoreUpdatesFailed": {
"one": "Non è riuscito ad aggiornare {} e altre 1 app.",
"other": "Non è riuscito ad aggiornare {} e {} altre applicazioni."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} e un'altra app potrebbero essere state aggiornate.",
"other": "{} e altre {} app potrebbero essere state aggiornate."

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(必須)",
"dropdownNoOptsError": "エラー: ドロップダウンには、少なくとも1つのオプションが必要です",
"colour": "カラー",
"standard": "スタンダード",
"custom": "カスタム",
"useMaterialYou": "Material Youを使用する",
"githubStarredRepos": "Githubでスターしたリポジトリ",
"uname": "ユーザー名",
"wrongArgNum": "提供する引数の数が間違っています",
@@ -113,7 +110,6 @@
"dark": "ダーク",
"light": "ライト",
"followSystem": "システムに従う",
"followSystemThemeExplanation": "以下のシステムテーマは、サードパーティのアプリケーションを使用することによってのみ可能です。",
"useBlackTheme": "ピュアブラックダークテーマを使用する",
"appSortBy": "アプリの並び方",
"authorName": "作者名/アプリ名",
@@ -147,10 +143,8 @@
"noNewUpdates": "新しいアップデートはありません",
"xHasAnUpdate": "{} のアップデートが利用可能です。",
"appsUpdated": "アプリをアップデートしました",
"appsNotUpdated": "アプリケーションの更新に失敗",
"appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する",
"xWasUpdatedToY": "{} が {} にアップデートされました",
"xWasNotUpdatedToY": "への更新に失敗しました。",
"errorCheckingUpdates": "アップデート確認中のエラー",
"errorCheckingUpdatesNotifDescription": "バックグラウンドでのアップデート確認に失敗した際に表示される通知",
"appsRemoved": "削除されたアプリ",
@@ -189,9 +183,6 @@
"disableVersionDetection": "バージョン検出を無効にする",
"noVersionDetectionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリにのみ使用する必要があります。",
"downloadingX": "{} をダウンロード中",
"downloadX": "ダウンロード",
"downloadedX": "ダウンロード",
"releaseAsset": "リリース資産",
"downloadNotifDescription": "アプリのダウンロード状況を通知する",
"noAPKFound": "APKが見つかりません",
"noVersionDetection": "バージョン検出を行わない",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "固定されたAPKのURLをサポートする",
"selectX": "{} 選択",
"parallelDownloads": "並行ダウンロードを許可する",
"useShizuku": "ShizukuまたはSuiを使用してインストールする",
"installMethod": "インストール方法",
"normal": "通常",
"root": "Root",
"shizukuBinderNotFound": "Shizukuが起動していません",
"shizukuOld": "古いShizukuのバージョン (<11) - アップデートしてください",
"shizukuOldAndroidWithADB": "ShizukuがAndroid 8.1未満でADBを使用して動作しています - Androidをアップデートするか、代わりにSuiを使用してください",
"shizukuPretendToBeGooglePlay": "Google Playをインストール元として設定するShizukuを使用する場合",
"useSystemFont": "システムフォントを使用する",
"systemFontError": "システムフォントの読み込みエラー: {}",
"useVersionCodeAsOSVersion": "アプリのバージョンコードをOSで検出されたバージョンとして使用する",
"requestHeader": "リクエストヘッダー",
"useLatestAssetDateAsReleaseDate": "最新のアセットアップロードをリリース日として使用する",
@@ -308,10 +299,6 @@
"note": "注",
"selfHostedNote": "ドロップダウン\"{}\"を使用すると、あらゆるソースのセルフホスト/カスタムインスタンスにアクセスできます。",
"badDownload": "APK を解析できませんでした(互換性がないか、部分的にダウンロードされています)。",
"beforeNewInstallsShareToAppVerifier": "AppVerifierで新しいアプリを共有する利用可能な場合",
"appVerifierInstructionToast": "AppVerifierに共有し、準備ができたらここに戻ってください。",
"wiki": "ヘルプ/ウィキ",
"crowdsourcedConfigsLabel": "クラウドソーシングによるアプリの設定(利用は自己責任で)",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"
@@ -360,10 +347,6 @@
"one": "{} とさらに {} 個のアプリがアップデートされました。",
"other": "{} とさらに {} 個のアプリがアップデートされました。"
},
"xAndNMoreUpdatesFailed": {
"one": "更新に失敗しました。",
"other": "アプリのアップデートに失敗しました。"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} とさらに 1 個のアプリがアップデートされた可能性があります。",
"other": "{} とさらに {} 個のアプリがアップデートされた可能性があります。"

View File

@@ -1,135 +1,131 @@
{
"invalidURLForSource": "Ongeldige app-URL voor {}",
"noReleaseFound": "Geen geschikte release gevonden",
"noVersionFound": "Geen versie kunnen bepalen",
"invalidURLForSource": "Geen valide {} app URL",
"noReleaseFound": "Kan geen geschikte release vinden",
"noVersionFound": "Kan de versie niet bepalen",
"urlMatchesNoSource": "URL komt niet overeen met bekende bron",
"cantInstallOlderVersion": "Kan geen oudere versie van de app installeren",
"appIdMismatch": "Gedownload pakket-ID komt niet overeen met de bestaande app-ID",
"functionNotImplemented": "Deze klasse heeft deze functie niet geïmplementeerd.",
"placeholder": "Dummy",
"appIdMismatch": "Gedownloade pakket-ID komt niet overeen met de bestaande app-ID",
"functionNotImplemented": "Deze class heeft deze functie niet geïmplementeerd.",
"placeholder": "Plaatshouder",
"someErrors": "Er zijn enkele fouten opgetreden",
"unexpectedError": "Onverwachte fout",
"ok": "Oké",
"ok": "Ok",
"and": "en",
"githubPATLabel": "GitHub Personal Access Token\n(Verhoogt limiet aantal verzoeken)",
"includePrereleases": "Inclusief pre-releases",
"includePrereleases": "Bevat prereleases",
"fallbackToOlderReleases": "Terugvallen op oudere releases",
"filterReleaseTitlesByRegEx": "Release-titels filteren met reguliere expressies.",
"filterReleaseTitlesByRegEx": "Filter release-titels met reguliere expressies.",
"invalidRegEx": "Ongeldige reguliere expressie",
"noDescription": "Geen omschrijving",
"cancel": "Annuleren",
"continue": "Doorgaan",
"cancel": "Annuleer",
"continue": "Ga verder",
"requiredInBrackets": "(Verplicht)",
"dropdownNoOptsError": "FOUTMELDING: UITKLAPMENU MOET TENMINSTE EEN OPT HEBBEN",
"dropdownNoOptsError": "FOUTMELDING: DROPDOWN MOET TENMINSTE ÉÉN OPT HEBBEN",
"colour": "Kleur",
"standard": "Standaard",
"custom": "Aangepast",
"useMaterialYou": "Material You gebruiken",
"githubStarredRepos": "GitHub-repo's met ster",
"uname": "Gebruikersnaam",
"wrongArgNum": "Incorrect aantal argumenten.",
"xIsTrackOnly": "{} is 'Alleen volgen'",
"wrongArgNum": "Onjuist aantal argumenten verstrekt.",
"xIsTrackOnly": "{} is alleen tracken",
"source": "Bron",
"app": "App",
"appsFromSourceAreTrackOnly": "Apps van deze bron zijn 'Alleen volgen'.",
"youPickedTrackOnly": "De optie 'Alleen volgen' is geselecteerd.",
"appsFromSourceAreTrackOnly": "Apps van deze bron zijn 'Track-Only'.",
"youPickedTrackOnly": "Je hebt de 'Track-Only' optie geselecteerd.",
"trackOnlyAppDescription": "De app zal worden gevolgd voor updates, maar Obtainium zal niet in staat zijn om deze te downloaden of te installeren.",
"cancelled": "Geannuleerd",
"appAlreadyAdded": "App reeds toegevoegd",
"alreadyUpToDateQuestion": "App al bijgewerkt?",
"appAlreadyAdded": "App al toegevoegd",
"alreadyUpToDateQuestion": "Is de app al up-to-date?",
"addApp": "App toevoegen",
"appSourceURL": "App-bron URL",
"appSourceURL": "App bron URL",
"error": "Foutmelding",
"add": "Toevoegen",
"searchSomeSourcesLabel": "Zoeken (sommige bronnen)",
"searchSomeSourcesLabel": "Zoeken (Alleen sommige bronnen)",
"search": "Zoeken",
"additionalOptsFor": "Aanvullende opties voor {}",
"supportedSources": "Ondersteunde bronnen",
"trackOnlyInBrackets": "(Alleen volgen)",
"trackOnlyInBrackets": "(Alleen track)",
"searchableInBrackets": "(Doorzoekbaar)",
"appsString": "Apps",
"noApps": "Geen Apps",
"noAppsForFilter": "Geen Apps voor filter",
"byX": "Door {}",
"percentProgress": "Voortgang: {}%",
"percentProgress": "Vooruitgang: {}%",
"pleaseWait": "Even geduld",
"updateAvailable": "Update beschikbaar",
"notInstalled": "Niet geinstalleerd",
"pseudoVersion": "pseudo-versie",
"selectAll": "Alles selecteren",
"deselectX": "Selectie opheffen {}",
"xWillBeRemovedButRemainInstalled": "{} zal worden gewist uit Obtainium, maar blijft geïnstalleerd op het apparaat.",
"removeSelectedAppsQuestion": "Geselecteerde apps verwijderen?",
"selectAll": "Selecteer alles",
"deselectX": "Deselecteer {}",
"xWillBeRemovedButRemainInstalled": "{} zal worden verwijderd uit Obtainium, maar blijft geïnstalleerd op het apparaat.",
"removeSelectedAppsQuestion": "Geselecteerde apps verwijderen??",
"removeSelectedApps": "Geselecteerde apps verwijderen",
"updateX": "{} bijwerken",
"installX": "{} installeren",
"markXTrackOnlyAsUpdated": "{}\n(Alleen volgen)\nmarkeren als bijgewerkt",
"changeX": "{} wijzigen",
"installUpdateApps": "Apps installeren/bijwerken",
"installUpdateSelectedApps": "Geselecteerde apps installeren/bijwerken",
"markXSelectedAppsAsUpdated": "{} geselecteerde apps markeren als bijgewerkt?",
"updateX": "Update {}",
"installX": "Installeer {}",
"markXTrackOnlyAsUpdated": "Markeer {}\n(Track-Only)\nals up-to-date",
"changeX": "Verander {}",
"installUpdateApps": "Installeer/Update apps",
"installUpdateSelectedApps": "Installeer/Update geselecteerde apps",
"markXSelectedAppsAsUpdated": "{} geselecteerde apps markeren als up-to-date?",
"no": "Nee",
"yes": "Ja",
"markSelectedAppsUpdated": "Geselecteerde apps markeren als bijgewerkt",
"pinToTop": "Bovenaan plaatsen",
"unpinFromTop": "Bovenaan wegnemen",
"resetInstallStatusForSelectedAppsQuestion": "Installatiestatus herstellen voor geselecteerde apps?",
"installStatusOfXWillBeResetExplanation": "De installatiestatus van alle geselecteerde apps zal worden hersteld.\n\nDit kan helpen wanneer de versie van de app die in Obtainium wordt weergegeven onjuist is vanwege mislukte updates of andere problemen.",
"customLinkMessage": "Deze koppelingen werken op apparaten waarop Obtainium is geïnstalleerd",
"markSelectedAppsUpdated": "Markeer geselecteerde aps als up-to-date",
"pinToTop": "Vastzetten aan de bovenkant",
"unpinFromTop": "Losmaken van de bovenkant",
"resetInstallStatusForSelectedAppsQuestion": "Installatiestatus resetten voor geselecteerde apps?",
"installStatusOfXWillBeResetExplanation": "De installatiestatus van alle geselecteerde apps zal worden gereset.\n\nDit kan helpen wanneer de versie van de app die in Obtainium wordt weergegeven onjuist is vanwege mislukte updates of andere problemen.",
"customLinkMessage": "Deze links werken op apparaten waarop Obtainium is geïnstalleerd",
"shareAppConfigLinks": "App-configuratie delen als HTML-link",
"shareSelectedAppURLs": "Geselecteerde app-URL's delen",
"resetInstallStatus": "Installatiestatus herstellen",
"shareSelectedAppURLs": "Deel geselecteerde app URL's",
"resetInstallStatus": "Reset installatiestatus",
"more": "Meer",
"removeOutdatedFilter": "Verouderde apps-filter verwijderen",
"showOutdatedOnly": "Alleen verouderde apps weergeven",
"filter": "Filteren",
"filterApps": "Apps filteren",
"appName": "App-naam",
"removeOutdatedFilter": "Verwijder out-of-date app filter",
"showOutdatedOnly": "Toon alleen out-of-date apps",
"filter": "Filter",
"filterApps": "Filter apps",
"appName": "App naam",
"author": "Auteur",
"upToDateApps": "Bijgewerkte apps",
"upToDateApps": "Up-to-date apps",
"nonInstalledApps": "Niet-geïnstalleerde apps",
"importExport": "Importeren/exporteren",
"importExport": "Importeren/Exporteren",
"settings": "Instellingen",
"exportedTo": "Geëxporteerd naar {}",
"obtainiumExport": "Obtainium export",
"invalidInput": "Ongeldige invoer",
"importedX": "{} geïmporteerd",
"importedX": "Geïmporteerd {}",
"obtainiumImport": "Obtainium import",
"importFromURLList": "Importeren van URL-lijsten",
"importFromURLList": "Importeer van URL-lijsten",
"searchQuery": "Zoekopdracht",
"appURLList": "App URL-lijst",
"line": "Regel",
"searchX": "{} zoeken",
"line": "Lijn",
"searchX": "Zoek {}",
"noResults": "Geen resultaten gevonden",
"importX": "{} importeren",
"importedAppsIdDisclaimer": "Geïmporteerde apps kunnen mogelijk onjuist worden weergegeven als \"Niet geïnstalleerd\".\nOm dit op te lossen, installeer deze opnieuw via Obtainium.\nDit zou geen invloed moeten hebben op app-gegevens.\n\nDit heeft alleen invloed op URL- en importmethoden van derden.",
"importErrors": "Fouten bij het importeren",
"importX": "Importeer {}",
"importedAppsIdDisclaimer": "Geïmporteerde apps kunnen mogelijk onjuist worden weergegeven als \"Niet geïnstalleerd\".\nOm dit op te lossen, herinstalleer ze via Obtainium.\nDit zou geen invloed moeten hebben op app-gegevens.\n\nDit heeft alleen invloed op URL- en importmethoden van derden.",
"importErrors": "Import foutmeldingen",
"importedXOfYApps": "{} van {} apps geïmporteerd.",
"followingURLsHadErrors": "De volgende URL's bevatten fouten:",
"selectURL": "URL selecteren",
"selectURLs": "URL's selecteren",
"pick": "Kiezen",
"selectURL": "Selecteer URL",
"selectURLs": "Selecteer URL's",
"pick": "Kies",
"theme": "Thema",
"dark": "Donker",
"light": "Licht",
"followSystem": "Systeem volgen",
"followSystemThemeExplanation": "Het volgen van het systeemthema is alleen mogelijk met applicaties van derden",
"useBlackTheme": "Zwart thema gebruiken",
"appSortBy": "Sortering",
"followSystem": "Volg systeem",
"useBlackTheme": "Gebruik zwart thema",
"appSortBy": "App sorteren op",
"authorName": "Auteur/Naam",
"nameAuthor": "Naam/Auteur",
"asAdded": "Datum toegevoegd",
"appSortOrder": "Volgorde",
"asAdded": "Zoals toegevoegd",
"appSortOrder": "App sorteervolgorde",
"ascending": "Oplopend",
"descending": "Aflopend",
"bgUpdateCheckInterval": "Frequentie voor achtergrond-updatecontrole",
"bgUpdateCheckInterval": "Frequentie voor achtergrondupdatecontrole",
"neverManualOnly": "Nooit - Alleen handmatig",
"appearance": "Weergave",
"showWebInAppView": "Bron-webpagina weergeven in app-weergave",
"pinUpdates": "Updates bovenaan plaatsen in de apps-weergave",
"showWebInAppView": "Toon de bronwebpagina in app-weergave",
"pinUpdates": "Updates bovenaan in de apps-weergave vastpinnen",
"updates": "Updates",
"sourceSpecific": "Bron-specifiek",
"appSource": "App-bron",
"appSource": "App bron",
"noLogs": "Geen logs",
"appLogs": "App logs",
"close": "Sluiten",
@@ -137,32 +133,30 @@
"appNotFound": "App niet gevonden",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Kies een APK",
"appHasMoreThanOnePackage": "{} biedt verschillende pakketten:",
"deviceSupportsXArch": "Dit apparaat ondersteunt de {} CPU-architectuur.",
"deviceSupportsFollowingArchs": "Dit apparaat ondersteunt de volgende CPU-architecturen:",
"appHasMoreThanOnePackage": "{} heeft meer dan één package:",
"deviceSupportsXArch": "Jouw apparaat support de {} CPU-architectuur.",
"deviceSupportsFollowingArchs": "Je apparaat ondersteunt de volgende CPU-architecturen:",
"warning": "Waarschuwing",
"sourceIsXButPackageFromYPrompt": "De app-bron is '{}' maar het release-pakket komt van '{}'. Doorgaan?",
"sourceIsXButPackageFromYPrompt": "De appbron is '{}' maar de release package komt van '{}'. Doorgaan?",
"updatesAvailable": "Updates beschikbaar",
"updatesAvailableNotifDescription": "Stelt de gebruiker op de hoogte dat er updates beschikbaar zijn voor een of meer apps die worden bijgehouden door Obtainium.",
"updatesAvailableNotifDescription": "Stelt de gebruiker op de hoogte dat er updates beschikbaar zijn voor één of meer apps die worden bijgehouden door Obtainium.",
"noNewUpdates": "Geen nieuwe updates.",
"xHasAnUpdate": "{} heeft een update.",
"appsUpdated": "Apps bijgewerkt",
"appsNotUpdated": "Applicaties konden niet worden bijgewerkt",
"appsUpdatedNotifDescription": "Stelt de gebruiker op de hoogte dat updates voor één of meer apps in de achtergrond zijn toegepast.",
"xWasUpdatedToY": "{} is bijgewerkt naar {}.",
"xWasNotUpdatedToY": "Het bijwerken van {} naar {} is mislukt.",
"errorCheckingUpdates": "Fout bij het controleren op updates",
"errorCheckingUpdatesNotifDescription": "Een melding die verschijnt wanneer de achtergrondcontrole op updates mislukt",
"errorCheckingUpdatesNotifDescription": "Een melding die verschijnt wanneer het controleren op updates in de achtergrond mislukt",
"appsRemoved": "Apps verwijderd",
"appsRemovedNotifDescription": "Stelt de gebruiker op de hoogte dat een of meer apps zijn verwijderd vanwege fouten tijdens het laden",
"xWasRemovedDueToErrorY": "{} is verwijderd vanwege de fout: {}",
"completeAppInstallation": "App-installatie voltooien",
"appsRemovedNotifDescription": "Stelt de gebruiker op de hoogte dat één of meer apps zijn verwijderd vanwege fouten tijdens het laden ervan",
"xWasRemovedDueToErrorY": "{} is verwijderd vanwege deze foutmelding: {}",
"completeAppInstallation": "Complete app installatie",
"obtainiumMustBeOpenToInstallApps": "Obtainium moet geopend zijn om apps te installeren",
"completeAppInstallationNotifDescription": "Vraagt de gebruiker om terug te keren naar Obtainium om de installatie van een app af te ronden",
"checkingForUpdates": "Controleren op updates",
"checkingForUpdatesNotifDescription": "Tijdelijke melding die verschijnt tijdens het controleren op updates",
"pleaseAllowInstallPerm": "Toestaan dat Obtainium apps installeert",
"trackOnly": "'Alleen volgen'",
"pleaseAllowInstallPerm": "Sta Obtainium toe om apps te installeren",
"trackOnly": "Alleen track",
"errorWithHttpStatusCode": "Foutmelding {}",
"versionCorrectionDisabled": "Versiecorrectie uitgeschakeld (de plug-in lijkt niet te werken)",
"unknown": "Onbekend",
@@ -172,26 +166,23 @@
"installedVersionX": "Geïnstalleerde versie: {}",
"lastUpdateCheckX": "Laatste updatecontrole: {}",
"remove": "Verwijderen",
"yesMarkUpdated": "Ja, markeren als bijgewerkt",
"fdroid": "F-Droid (Officieel)",
"appIdOrName": "App-ID of naam",
"yesMarkUpdated": "Ja, markeer als bijgewerkt",
"fdroid": "F-Droid-ambtenaar",
"appIdOrName": "App ID of naam",
"appId": "App-ID",
"appWithIdOrNameNotFound": "Er is geen app gevonden met dat ID of die naam",
"appWithIdOrNameNotFound": "Er werd geen app gevonden met dat ID of die naam",
"reposHaveMultipleApps": "Repositories kunnen meerdere apps bevatten",
"fdroidThirdPartyRepo": "F-Droid Repository voor derden",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"fdroidThirdPartyRepo": "F-Droid Repository van derden",
"steamMobile": "Stoommobiel",
"steamChat": "Steamchat",
"install": "Installeren",
"markInstalled": "Als geïnstalleerd markeren",
"update": "Bijwerken",
"markUpdated": "Als bijgewerkt markeren",
"markInstalled": "Als geïnstalleerd markere",
"update": "Update",
"markUpdated": "Markeren als bijgewerkt",
"additionalOptions": "Aanvullende opties",
"disableVersionDetection": "Versieherkenning uitschakelen",
"noVersionDetectionExplanation": "Deze optie moet alleen worden gebruikt voor apps waar versieherkenning niet correct werkt.",
"downloadingX": "{} downloaden",
"downloadX": "Downloaden",
"downloadedX": "{} gedownload",
"releaseAsset": "Release Activa",
"downloadingX": "Downloaden {}",
"downloadNotifDescription": "Stelt de gebruiker op de hoogte van de voortgang bij het downloaden van een app",
"noAPKFound": "Geen APK gevonden",
"noVersionDetection": "Geen versieherkenning",
@@ -203,100 +194,100 @@
"deleteCategoriesQuestion": "Categorieën verwijderen?",
"categoryDeleteWarning": "Alle apps in verwijderde categorieën worden teruggezet naar 'ongecategoriseerd'.",
"addCategory": "Categorie toevoegen",
"label": "Label",
"label": "Etiket",
"language": "Taal",
"copiedToClipboard": "Gekopieerd naar klembord",
"storagePermissionDenied": "Toegang tot opslag geweigerd",
"selectedCategorizeWarning": "Dit zal eventuele bestaande categorie-instellingen voor de geselecteerde apps vervangen.",
"filterAPKsByRegEx": "APK's flteren met reguliere expressie",
"removeFromObtainium": "Uit Obtainium verwijderen",
"uninstallFromDevice": "Van apparaat verwijderen",
"filterAPKsByRegEx": "Filter APK's op reguliere expressie",
"removeFromObtainium": "Verwijder van Obtainium",
"uninstallFromDevice": "Verwijder van apparaat",
"onlyWorksWithNonVersionDetectApps": "Werkt alleen voor apps waarbij versieherkenning is uitgeschakeld.",
"releaseDateAsVersion": "Releasedatum als versie gebruiken",
"releaseDateAsVersion": "Gebruik de releasedatum als versie",
"releaseDateAsVersionExplanation": "Deze optie moet alleen worden gebruikt voor apps waar versieherkenning niet correct werkt, maar waar wel een releasedatum beschikbaar is.",
"changes": "Aanpassingen",
"changes": "Veranderingen",
"releaseDate": "Releasedatum",
"importFromURLsInFile": "Importeren vanaf URL's in een bestand (zoals OPML)",
"versionDetectionExplanation": "Versiereeks afstemmen met versie gedetecteerd door besturingssysteem",
"versionDetection": "Versieherkenning",
"standardVersionDetection": "Standaard versieherkenning",
"groupByCategory": "Groeperen op categorie",
"autoApkFilterByArch": "Probeer APK's te filteren op CPU-architectuur, indien mogelijk",
"groupByCategory": "Groepeer op categorie",
"autoApkFilterByArch": "Poging om APK's te filteren op CPU-architectuur indien mogelijk",
"overrideSource": "Bron overschrijven",
"dontShowAgain": "Laat dit niet meer zien",
"dontShowTrackOnlyWarnings": "Geen waarschuwingen weergeven voor 'Alleen volgen'",
"dontShowAPKOriginWarnings": "Geen waarschuwingen weergeven voor APK-herkomst",
"moveNonInstalledAppsToBottom": "Niet-geïnstalleerde apps onderaan de apps-lijst plaatsen",
"gitlabPATLabel": "GitLab persoonlijk toegangskenmerk",
"dontShowTrackOnlyWarnings": "Geen waarschuwingen voor 'Track-Only' weergeven",
"dontShowAPKOriginWarnings": "APK-herkomstwaarschuwingen niet weergeven",
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Over",
"requiresCredentialsInSettings": "{} vereist aanvullende referenties (in Instellingen)",
"checkOnStart": "Bij opstarten op updates controleren",
"requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
"checkOnStart": "Controleren op updates bij opstarten",
"tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode",
"removeOnExternalUninstall": "Extern verwijderde apps automatisch verwijderen",
"pickHighestVersionCode": "De APK met de hoogste versiecode automatisch selecteren",
"removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen",
"pickHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
"checkUpdateOnDetailPage": "Controleren op updates bij het openen van een app-detailpagina",
"disablePageTransitions": "Overgangsanimaties tussen pagina's uitschakelen",
"reversePageTransitions": "Overgangsanimaties tussen pagina's herstellen",
"minStarCount": "Minimum Github Stars",
"addInfoBelow": "Deze informatie hieronder toevoegen.",
"addInfoInSettings": "Deze informatie toevoegen in de instellingen.",
"disablePageTransitions": "Schakel overgangsanimaties tussen pagina's uit",
"reversePageTransitions": "Omgekeerde overgangsanimaties tussen pagina's",
"minStarCount": "Minimale Github Stars",
"addInfoBelow": "Voeg deze informatie hieronder toe.",
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
"sortByLastLinkSegment": "Alleen sorteren op het laatste segment van de link",
"filterReleaseNotesByRegEx": "Release-opmerkingen fiteren met een reguliere expressie.",
"customLinkFilterRegex": "Aangepaste APK-links filteren met een reguliere expressie (Standaard '.apk$').",
"appsPossiblyUpdated": "Pogingen tot app-updates",
"sortByLastLinkSegment": "Sorteren op alleen het laatste segment van de link",
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",
"appsPossiblyUpdated": "Poging tot app-updates",
"appsPossiblyUpdatedNotifDescription": "Stelt de gebruiker op de hoogte dat updates voor één of meer apps mogelijk in de achtergrond zijn toegepast",
"xWasPossiblyUpdatedToY": "{} kan bijgewerkt zijn naar {}.",
"enableBackgroundUpdates": "Achtergrond-updates inschakelen",
"backgroundUpdateReqsExplanation": "Achtergrond-updates zijn niet voor alle apps mogelijk.",
"xWasPossiblyUpdatedToY": "{} mogelijk bijgewerkt naar {}.",
"enableBackgroundUpdates": "Achtergrondupdates inschakelen",
"backgroundUpdateReqsExplanation": "Achtergrondupdates zijn mogelijk niet voor alle apps mogelijk.",
"backgroundUpdateLimitsExplanation": "Het succes van een installatie in de achtergrond kan alleen worden bepaald wanneer Obtainium is geopend.",
"verifyLatestTag": "Het label 'Laatste' verifiëren",
"intermediateLinkRegex": "Filteren op een 'Intermediaire' link om te bezoeken",
"verifyLatestTag": "Verifieer de 'Laatste'-tag",
"intermediateLinkRegex": "Filter voor een 'Intermediaire' link om te bezoeken",
"filterByLinkText": "Links filteren op linktekst",
"intermediateLinkNotFound": "Intermediaire link niet gevonden",
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
"intermediateLink": "Intermediaire link",
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrond-updates (indien ingeschakeld)",
"bgUpdatesOnWiFiOnly": "Achtergrond-updates uitschakelen wanneer niet verbonden met WiFi",
"autoSelectHighestVersionCode": "De APK met de hoogste versiecode automatisch selecteren",
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrondupdates (indien ingeschakeld)",
"bgUpdatesOnWiFiOnly": "Achtergrondupdates uitschakelen wanneer niet verbonden met WiFi",
"autoSelectHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
"versionExtractionRegEx": "Reguliere expressie voor versie-extractie",
"matchGroupToUse": "Overeenkomende groep om te gebruiken voor de reguliere expressie voor versie-extractie",
"highlightTouchTargets": "Minder voor de hand liggende aanraakdoelen markeren.",
"highlightTouchTargets": "Markeer minder voor de hand liggende aanraakdoelen.",
"pickExportDir": "Kies de exportmap",
"autoExportOnChanges": "Bij wijzigingen automatisch exporteren",
"autoExportOnChanges": "Automatisch exporteren bij wijzigingen",
"includeSettings": "Instellingen opnemen",
"filterVersionsByRegEx": "Versies met een reguliere expressie filteren",
"filterVersionsByRegEx": "Filter versies met een reguliere expressie",
"trySelectingSuggestedVersionCode": "Probeer de voorgestelde versiecode APK te selecteren",
"dontSortReleasesList": "Volgorde van releases behouden vanuit de API",
"reverseSort": "Omgekeerde sortering",
"reverseSort": "Sortering omkeren",
"takeFirstLink": "Neem de eerste link",
"skipSort": "Sortering overslaan",
"debugMenu": "Debug-menu",
"skipSort": "Sorteren overslaan",
"debugMenu": "Debug menu",
"bgTaskStarted": "Achtergrondtaak gestart - controleer de logs.",
"runBgCheckNow": "Nu een achtergrond-updatecontrole uitvoeren",
"runBgCheckNow": "Voer nu een achtergrondupdatecontrole uit",
"versionExtractWholePage": "De reguliere expressie voor versie-extractie toepassen op de hele pagina",
"installing": "Installeren",
"skipUpdateNotifications": "Updatemeldingen overslaan",
"updatesAvailableNotifChannel": "Updates beschikbaar",
"appsUpdatedNotifChannel": "Apps bijgewerkt",
"appsPossiblyUpdatedNotifChannel": "Pogingen tot app-updates",
"appsPossiblyUpdatedNotifChannel": "Poging tot app-updates",
"errorCheckingUpdatesNotifChannel": "Foutcontrole bij het zoeken naar updates",
"appsRemovedNotifChannel": "Apps verwijderd",
"downloadingXNotifChannel": "{} downloaden",
"completeAppInstallationNotifChannel": "App-installatie voltooien",
"completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
"checkingForUpdatesNotifChannel": "Controleren op updates",
"onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde apps en 'Alleen volgen' controleren op updates",
"supportFixedAPKURL": "Vaste APK-URL's ondersteunen",
"selectX": "{} selecteren",
"onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
"supportFixedAPKURL": "Ondersteuning vaste APK URL's",
"selectX": "Selecteer {}",
"parallelDownloads": "Parallelle downloads toestaan",
"useShizuku": "Shizuku of Sui gebruiken om te installeren",
"shizukuBinderNotFound": "Shizuku is niet actief",
"shizukuOld": "Verouderde Shizuku-versie (<11) - bijwerken",
"shizukuOldAndroidWithADB": "Shizuku draait op Android < 8.1 met ADB - update Android of gebruik in plaats daarvan Sui",
"shizukuPretendToBeGooglePlay": "Google Play instellen als installatiebron (bij Shizuku)",
"useSystemFont": "Systeemlettertype gebruiken",
"useVersionCodeAsOSVersion": "App versiecode gebruiken als door OS gedetecteerde versie",
"installMethod": "Installatiemethode",
"normal": "Normaal",
"root": "Wortel",
"shizukuBinderNotFound": "Shizuku draait niet",
"useSystemFont": "Gebruik het systeemlettertype",
"systemFontError": "Fout bij het laden van het systeemlettertype: {}",
"useVersionCodeAsOSVersion": "Gebruik app versieCode als door OS gedetecteerde versie",
"requestHeader": "Verzoekkoptekst",
"useLatestAssetDateAsReleaseDate": "Laatste upload als releasedatum gebruiken",
"useLatestAssetDateAsReleaseDate": "Gebruik laatste upload als releasedatum",
"defaultPseudoVersioningMethod": "Standaard pseudo-versiebeheermethode",
"partialAPKHash": "Gedeeltelijke APK-hash",
"APKLinkHash": "APK-link-hash",
@@ -308,25 +299,21 @@
"note": "Opmerking",
"selfHostedNote": "De \"{}\" dropdown kan gebruikt worden om zelf gehoste/aangepaste instanties van elke bron te bereiken.",
"badDownload": "De APK kon niet worden verwerkt (incompatibele of gedeeltelijke download)",
"beforeNewInstallsShareToAppVerifier": "Nieuwe Apps delen met AppVerifier (indien beschikbaar)",
"appVerifierInstructionToast": "Deel het met AppVerifier en keer daarna hier terug.",
"wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App-configuraties (gebruik op eigen risico)",
"removeAppQuestion": {
"one": "App verwijderen?",
"other": "Apps verwijderen?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Te veel verzoeken (aantal beperkt) - opnieuw proberen over {} minuut",
"other": "Te veel verzoeken (aantal beperkt) - opnieuw proberen over {} minuten"
"one": "Te veel verzoeken (aantal beperkt) - probeer het opnieuw in {} minuut",
"other": "Te veel verzoeken (aantal beperkt) - probeer het opnieuw in {} minuten"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "Achtergrond-updatecontrole heeft een {}, zal een nieuwe controle plannen over {} minuut",
"other": "Achtergrond-updatecontrole heeft een {}, zal een nieuwe controle plannen over {} minuten"
"one": "Achtergrondupdatecontrole heeft een {}, zal een hercontrole plannen over {} minuut",
"other": "Achtergrondupdatecontrole heeft een {}, zal een hercontrole plannen over {} minuten"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Achtergrond-updatecontrole heeft {} update gevonden - zal de gebruiker op de hoogte stellen indien nodig",
"other": "Achtergrond-updatecontrole heeft {} updates gevonden - zal de gebruiker op de hoogte stellen indien nodig"
"one": "Achtergrondupdatecontrole heeft {} update gevonden - zal de gebruiker op de hoogte stellen indien nodig",
"other": "Achtergrondupdatecontrole heeft {} updates gevonden - zal de gebruiker op de hoogte stellen indien nodig"
},
"apps": {
"one": "{} app",
@@ -354,19 +341,15 @@
},
"xAndNMoreUpdatesAvailable": {
"one": "{} en nog 1 app hebben updates.",
"other": "{} en nog {} apps hebben updates."
"other": "{} en {} meer apps hebben updates."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} en nog 1 app is bijgewerkt.",
"other": "{} en nog {} apps zijn bijgewerkt."
},
"xAndNMoreUpdatesFailed": {
"one": "Bijwerken mislukt voor {} en nog 1 app.",
"other": "Bijwerken mislukt voor {} en nog {} apps."
"other": "{} en {} meer apps zijn bijgewerkt."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} en nog 1 app zijn mogelijk bijgewerkt.",
"other": "{} en nog {} apps zijn mogelijk bijgwerkt."
"other": "{} en {} meer apps zijn mogelijk bijgwerkt."
},
"apk": {
"one": "{} APK",

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(Wymagane)",
"dropdownNoOptsError": "BŁĄD: LISTA ROZWIJANA MUSI MIEĆ CO NAJMNIEJ JEDNĄ OPCJĘ",
"colour": "Kolor",
"standard": "Standard",
"custom": "Niestandardowe",
"useMaterialYou": "Używaj materiałów",
"githubStarredRepos": "Repozytoria GitHub oznaczone gwiazdką",
"uname": "Nazwa użytkownika",
"wrongArgNum": "Nieprawidłowa liczba podanych argumentów",
@@ -113,7 +110,6 @@
"dark": "Ciemny",
"light": "Jasny",
"followSystem": "Zgodny z systemem",
"followSystemThemeExplanation": "Podążanie za motywem systemowym jest możliwe tylko przy użyciu aplikacji firm trzecich",
"useBlackTheme": "Użyj czarnego motywu",
"appSortBy": "Sortuj aplikacje według",
"authorName": "Autor/Nazwa",
@@ -147,10 +143,8 @@
"noNewUpdates": "Brak nowych aktualizacji.",
"xHasAnUpdate": "{} ma aktualizację.",
"appsUpdated": "Zaktualizowano aplikacje",
"appsNotUpdated": "Nie udało się zaktualizować aplikacji",
"appsUpdatedNotifDescription": "Informuje, gdy co najmniej jedna aplikacja została zaktualizowana w tle",
"xWasUpdatedToY": "{} zaktualizowano do {}.",
"xWasNotUpdatedToY": "Nie udało się zaktualizować {} do {}.",
"errorCheckingUpdates": "Błąd sprawdzania aktualizacji",
"errorCheckingUpdatesNotifDescription": "Jest wyświetlane, gdy sprawdzanie aktualizacji w tle nie powiedzie się",
"appsRemoved": "Usunięte aplikacje",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Wyłącz wykrywanie wersji",
"noVersionDetectionExplanation": "Opcja ta powinna być używana tylko w przypadku aplikacji, w których wykrywanie wersji nie działa poprawnie.",
"downloadingX": "Pobieranie {}",
"downloadX": "Pobierz {}",
"downloadedX": "Pobrano {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Informuje o postępach w pobieraniu aplikacji",
"noAPKFound": "Nie znaleziono pakietu APK",
"noVersionDetection": "Bez wykrywania wersji",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Obsługuj stałe adresy URL APK",
"selectX": "Wybierz {}",
"parallelDownloads": "Zezwól na pobieranie równoległe",
"useShizuku": "Użyj Shizuku lub Sui, aby zainstalować",
"installMethod": "Metoda instalacji",
"normal": "Normalna",
"root": "Źródło",
"shizukuBinderNotFound": "Shizuku is not running",
"shizukuOld": "Stara wersja Shizuku (<11) - zaktualizuj ją",
"shizukuOldAndroidWithADB": "Shizuku działa na Androidzie < 8.1 z ADB - zaktualizuj Androida lub użyj zamiast tego Sui",
"shizukuPretendToBeGooglePlay": "Ustaw Google Play jako źródło instalacji (jeśli używana jest aplikacja Shizuku).",
"useSystemFont": "Użyj czcionki systemowej",
"systemFontError": "Błąd podczas ładowania czcionki systemowej: {}",
"useVersionCodeAsOSVersion": "Użyj kodu wersji aplikacji jako wersji wykrytej przez system operacyjny",
"requestHeader": "Nagłówek żądania",
"useLatestAssetDateAsReleaseDate": "Użyj najnowszego przesłanego zasobu jako daty wydania",
@@ -308,10 +299,6 @@
"note": "Uwaga",
"selfHostedNote": "Lista rozwijana \"{}\" może być używana do uzyskiwania dostępu do samodzielnie hostowanych / niestandardowych instancji dowolnego źródła.",
"badDownload": "Nie można przeanalizować pliku APK (niekompatybilny lub częściowo pobrany).",
"beforeNewInstallsShareToAppVerifier": "Udostępnianie nowych aplikacji za pomocą AppVerifier (jeśli dostępne)",
"appVerifierInstructionToast": "Udostępnij w AppVerifier, a następnie wróć tutaj, gdy będziesz gotowy.",
"wiki": "Pomoc/Wiki",
"crowdsourcedConfigsLabel": "Konfiguracje aplikacji pochodzące z crowdsourcingu (korzystanie na własne ryzyko)",
"removeAppQuestion": {
"one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?",
@@ -384,10 +371,6 @@
"many": "{} i {} innych apek zostało zaktualizowanych.",
"other": "{} i {} inne apki zostały zaktualizowane."
},
"xAndNMoreUpdatesFailed": {
"one": "Nie udało się zaktualizować {} i 1 innej aplikacji.",
"other": "Nie udało się zaktualizować {} i {} więcej aplikacji."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} i 1 inna apka mogły zostać zaktualizowane.",
"few": "{} i {} inne apki mogły zostać zaktualizowane.",

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(Necessário)",
"dropdownNoOptsError": "ERRO: O DROPDOWN DEVE TER PELO MENOS UMA OPÇÃO",
"colour": "Cor",
"standard": "Padrão",
"custom": "Personalizado",
"useMaterialYou": "Utilizar o material que",
"githubStarredRepos": "repositórios favoritos no GitHub",
"uname": "Nome de usuário",
"wrongArgNum": "Número de argumentos errado",
@@ -113,7 +110,6 @@
"dark": "Escuro",
"light": "Claro",
"followSystem": "Padrão do sistema",
"followSystemThemeExplanation": "O tema do sistema seguinte só é possível através da utilização de aplicações de terceiros",
"useBlackTheme": "Usar tema preto AMOLED",
"appSortBy": "Classificar aplicativo por",
"authorName": "Autor/Nome",
@@ -147,10 +143,8 @@
"noNewUpdates": "Sem novas atualizações.",
"xHasAnUpdate": "{} tem uma atualização.",
"appsUpdated": "Aplicativos atualizados",
"appsNotUpdated": "Falha na atualização das aplicações",
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações foram aplicadas em segundo-plano para um ou mais aplicativos ",
"xWasUpdatedToY": "{} foi atualizado para {}.",
"xWasNotUpdatedToY": "Falha ao atualizar {} 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": "Aplicativos removidos",
@@ -189,9 +183,6 @@
"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 {}",
"downloadX": "Descarregar {}",
"downloadedX": "Descarregado {}",
"releaseAsset": "Libertação de activos",
"downloadNotifDescription": "Notifica o usuário o progresso do download de um aplicativo",
"noAPKFound": "APK não encontrado",
"noVersionDetection": "Sem detecção de versão",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Suporte a APK com URLs fixas",
"selectX": "Selecionar {}",
"parallelDownloads": "Permitir downloads paralelos",
"useShizuku": "Utilizar Shizuku ou Sui para instalar",
"installMethod": "Método de instalação",
"normal": "Normal",
"root": "Root",
"shizukuBinderNotFound": "O Shizuku não está rodando",
"shizukuOld": "Versão antiga do Shizuku (<11) - atualizar",
"shizukuOldAndroidWithADB": "Shizuku a funcionar no Android < 8.1 com ADB - atualizar o Android ou utilizar o Sui",
"shizukuPretendToBeGooglePlay": "Definir o Google Play como fonte de instalação (se for utilizado o Shizuku)",
"useSystemFont": "Usar fonte padrão do sistema",
"systemFontError": "Erro ao carregar a fonte do sistema: {}",
"useVersionCodeAsOSVersion": "Usar versionCode do aplicativo como versão detectada pelo sistema operacional",
"requestHeader": "Requisitar cabeçalho",
"useLatestAssetDateAsReleaseDate": "Use o último upload de recursos como data de lançamento",
@@ -308,10 +299,6 @@
"note": "Nota",
"selfHostedNote": "O menu suspenso \"{}\" pode ser usado para acessar instâncias auto-hospedadas/personalizadas de qualquer fonte.",
"badDownload": "Não foi possível analisar o APK (transferência incompatível ou parcial)",
"beforeNewInstallsShareToAppVerifier": "Partilhar novas aplicações com o AppVerifier (se disponível)",
"appVerifierInstructionToast": "Partilhe com o AppVerifier e, em seguida, regresse aqui quando estiver pronto.",
"wiki": "Ajuda/Wiki",
"crowdsourcedConfigsLabel": "Configurações de aplicações de crowdsourcing (utilização por sua conta e risco)",
"removeAppQuestion": {
"one": "Remover aplicativo?",
"other": "Remover aplicativos?"
@@ -360,10 +347,6 @@
"one": "{} e um outro aplicativo foram atualizado.",
"other": "{} e {} outros aplicativos foram atualizados."
},
"xAndNMoreUpdatesFailed": {
"one": "Falha ao atualizar {} e mais 1 aplicação.",
"other": "Falha ao atualizar {} e {} mais aplicações."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} e um outro aplicativo podem ter sido atualizados.",
"other": "{} e {} outros aplicativos podem ter sido atualizados."

View File

@@ -9,7 +9,7 @@
"placeholder": "Заполнитель",
"someErrors": "Возникли некоторые ошибки",
"unexpectedError": "Неожиданная ошибка",
"ok": "Ок",
"ok": "Ok",
"and": "и",
"githubPATLabel": "Персональный токен доступа GitHub\n(увеличивает лимит запросов)",
"includePrereleases": "Включить предварительные релизы",
@@ -22,9 +22,6 @@
"requiredInBrackets": "(обязательно)",
"dropdownNoOptsError": "Ошибка: в выпадающем списке должна быть выбрана хотя бы одна настройка",
"colour": "Цвет",
"standard": "Стандартный",
"custom": "Индивидуальный",
"useMaterialYou": "Использовать Material You",
"githubStarredRepos": "Избранные репозитории GitHub",
"uname": "Имя пользователя",
"wrongArgNum": "Неправильное количество предоставленных аргументов",
@@ -113,7 +110,6 @@
"dark": "Тёмная",
"light": "Светлая",
"followSystem": "Системная",
"followSystemThemeExplanation": "Следование системной теме возможно только при использовании сторонних приложений",
"useBlackTheme": "Использовать чёрную тему",
"appSortBy": "Сортировка приложений",
"authorName": "Автор/Название",
@@ -135,7 +131,7 @@
"close": "Закрыть",
"share": "Поделиться",
"appNotFound": "Приложение не найдено",
"obtainiumExportHyphenatedLowercase": "экспорт-obtainium",
"obtainiumExportHyphenatedLowercase": "получение-экспорт",
"pickAnAPK": "Выберите APK-файл",
"appHasMoreThanOnePackage": "{} имеет более одного пакета:",
"deviceSupportsXArch": "Ваше устройство поддерживает архитектуру процессора {}",
@@ -147,10 +143,8 @@
"noNewUpdates": "Нет новых обновлений",
"xHasAnUpdate": "{} есть обновление",
"appsUpdated": "Приложения обновлены",
"appsNotUpdated": "Не удалось обновить приложения",
"appsUpdatedNotifDescription": "Уведомляет об обновлении одного или нескольких приложений в фоновом режиме",
"xWasUpdatedToY": "{} была обновлена до версии {}",
"xWasNotUpdatedToY": "Не удалось обновить {} до версии {}",
"errorCheckingUpdates": "Ошибка при проверке обновлений",
"errorCheckingUpdatesNotifDescription": "Уведомление о завершении проверки обновлений в фоновом режиме с ошибкой",
"appsRemoved": "Приложение удалено",
@@ -179,8 +173,8 @@
"appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено",
"reposHaveMultipleApps": "В хранилище несколько приложений",
"fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
"steamMobile": "Приложение Steam",
"steamChat": "Steam Chat",
"steamMobile": "Стим Мобайл",
"steamChat": "Стим-чат",
"install": "Установить",
"markInstalled": "Пометить как установленное",
"update": "Обновить",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Отключить обнаружение версии",
"noVersionDetectionExplanation": "Эта настройка должна использоваться только для приложений, где обнаружение версии не работает корректно",
"downloadingX": "Загрузка {}",
"downloadX": "Скачать {}",
"downloadedX": "Загружено {}",
"releaseAsset": "Релизный объект",
"downloadNotifDescription": "Уведомляет пользователя о прогрессе загрузки приложения",
"noAPKFound": "APK не найден",
"noVersionDetection": "Обнаружение версий отключено",
@@ -254,7 +245,7 @@
"intermediateLinkRegex": "Фильтр для \"промежуточной\" ссылки для посещения",
"filterByLinkText": "Фильтрация ссылок по тексту ссылки",
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
"intermediateLink": "Промежуточная ссылка",
"intermediateLink": "Промежуточное звено",
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Поддержка фиксированных URL-адресов APK",
"selectX": "Выбрать {}",
"parallelDownloads": "Разрешить параллельные загрузки",
"useShizuku": "Использовать Shizuku или Sui для установки",
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден, возможно он не запущен",
"shizukuOld": "Устаревшая версия Shizuku (<11), обновите",
"shizukuOldAndroidWithADB": "Shizuku работает на Android < 8.1 с ADB, обновите Android или используйте Sui",
"shizukuPretendToBeGooglePlay": "Указать Google Play как источник установки (если используется Shizuku)",
"installMethod": "Метод установки",
"normal": "Нормальный",
"root": "Суперпользователь",
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
"useSystemFont": "Использовать системный шрифт",
"systemFontError": "Ошибка загрузки системного шрифта: {}",
"useVersionCodeAsOSVersion": "Использовать код версии приложения как версию, обнаруженную ОС",
"requestHeader": "Заголовок запроса",
"useLatestAssetDateAsReleaseDate": "Использовать последнюю загрузку ресурса в качестве даты выпуска",
@@ -308,10 +299,6 @@
"note": "Примечание",
"selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.",
"badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)",
"beforeNewInstallsShareToAppVerifier": "Поделитесь новыми приложениями с AppVerifier (если доступно)",
"appVerifierInstructionToast": "Поделитесь с AppVerifier, а затем вернитесь сюда, когда будете готовы.",
"wiki": "Помощь/Вики",
"crowdsourcedConfigsLabel": "Конфигурации приложений на основе краудсорсинга (используйте на свой страх и риск)",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"
@@ -360,16 +347,12 @@
"one": "{} и ещё 1 приложение были обновлены",
"other": "{} и ещё {} приложений были обновлены"
},
"xAndNMoreUpdatesFailed": {
"one": "Не удалось обновить {} и ещё 1 приложение",
"other": "Не удалось обновить {} и ещё {} приложений"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} и ещё 1 приложение могли быть обновлены",
"other": "{} и ещё {} приложений могли быть обновлены"
},
"apk": {
"one": "{} APK",
"other": "{} APKи"
"other": "{} APKs"
}
}

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(Kräver)",
"dropdownNoOptsError": "FEL: DROPDOWN MÅSTE HA MINST ETT OPT",
"colour": "Färg",
"standard": "Standard",
"custom": "Anpassad",
"useMaterialYou": "Använd material Du",
"githubStarredRepos": "GitHub Stjärnmärkta Förråd",
"uname": "Användarnamn",
"wrongArgNum": "Fel antal argument har angetts",
@@ -113,7 +110,6 @@
"dark": "Mörkt",
"light": "Ljust",
"followSystem": "Följ System",
"followSystemThemeExplanation": "Följande systemtema är endast möjligt med hjälp av tredjepartsapplikationer",
"useBlackTheme": "Använd svart tema",
"appSortBy": "Sortera Appar via",
"authorName": "Utvecklare/Namn",
@@ -147,10 +143,8 @@
"noNewUpdates": "Inga nya uppdateringar.",
"xHasAnUpdate": "{} har en uppdatering.",
"appsUpdated": "Appar Uppdaterade",
"appsNotUpdated": "Misslyckades med att uppdatera applikationer",
"appsUpdatedNotifDescription": "Meddelar användaren att uppdateringar av en eller flera appar har tillämpats i bakgrunden",
"xWasUpdatedToY": "{} uppdaterades till {}.",
"xWasNotUpdatedToY": "Det gick inte att uppdatera {} till {}.",
"errorCheckingUpdates": "Fel vid uppdateringskoll",
"errorCheckingUpdatesNotifDescription": "En aviserings som visar när bakgrundsuppdateringarkollar misslyckas",
"appsRemoved": "Appar borttagna",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Inaktivera versionsdetektering",
"noVersionDetectionExplanation": "Det här alternativet bör endast användas för appar där versionsidentifiering inte fungerar korrekt.",
"downloadingX": "Laddar ner {}",
"downloadX": "Ladda ner {}",
"downloadedX": "Nedladdad {}",
"releaseAsset": "Frigör tillgång",
"downloadNotifDescription": "Meddelar användaren om framstegen med att ladda ner en app",
"noAPKFound": "Ingen APK funnen",
"noVersionDetection": "Ingen versiondetektering",
@@ -227,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Visa inte 'Följ-Endast' varningar",
"dontShowAPKOriginWarnings": "Visa inte APK-ursprung varningar",
"moveNonInstalledAppsToBottom": "Flytta icke-installerade appar till botten av appvyn",
"gitlabPATLabel": "Personligt åtkomsttoken för GitLab",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Om",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Kolla efter uppdateringar vid start",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Stöd fasta APK-webbadresser",
"selectX": "Välj {}",
"parallelDownloads": "Tillåt parallella nedladdningar",
"useShizuku": "Använd Shizuku eller Sui för att installera",
"installMethod": "Installationsmetod",
"normal": "Vanligt",
"root": "Rot",
"shizukuBinderNotFound": "Shizuku is not running",
"shizukuOld": "Gammal Shizuku-version (<11) - uppdatera den",
"shizukuOldAndroidWithADB": "Shizuku körs på Android < 8.1 med ADB - uppdatera Android eller använd Sui istället",
"shizukuPretendToBeGooglePlay": "Ange Google Play som installationskälla (om Shizuku används)",
"useSystemFont": "Använd systemteckensnittet",
"systemFontError": "Fel vid laddning av systemteckensnittet: {}",
"useVersionCodeAsOSVersion": "Använd appversionskoden som OS-upptäckt version",
"requestHeader": "Rubrik för begäran",
"useLatestAssetDateAsReleaseDate": "Använd senaste tillgångsuppladdning som releasedatum",
@@ -308,10 +299,6 @@
"note": "Anmärkning",
"selfHostedNote": "Rullgardinsmenyn \"{}\" kan användas för att nå självhostade/anpassade instanser av valfri källa.",
"badDownload": "APK kunde inte analyseras (inkompatibel eller partiell nedladdning)",
"beforeNewInstallsShareToAppVerifier": "Dela nya appar med AppVerifier (om tillgängligt)",
"appVerifierInstructionToast": "Dela till AppVerifier och återvänd sedan hit när du är klar.",
"wiki": "Hjälp/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourcade appkonfigurationer (använd på egen risk)",
"removeAppQuestion": {
"one": "Ta Bort App?",
"other": "Ta Bort Appar?"
@@ -360,10 +347,6 @@
"one": "{} och 1 till app uppdaterades.",
"other": "{} och {} appar till uppdaterades."
},
"xAndNMoreUpdatesFailed": {
"one": "Misslyckades med att uppdatera {} och ytterligare 1 app.",
"other": "Det gick inte att uppdatera {} och {} fler appar."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} och 1 till app kan ha uppdaterats.",
"other": "{} och {} appar till kan ha uppdaterats."

View File

@@ -22,9 +22,6 @@
"requiredInBrackets": "(Gerekli)",
"dropdownNoOptsError": "HATA: DİPLOMADA EN AZ BİR SEÇENEK OLMALI",
"colour": "Renk",
"standard": "Standart",
"custom": "Özel",
"useMaterialYou": "Sizin Malzemenizi Kullanın",
"githubStarredRepos": "GitHub'a Yıldızlı Depolar",
"uname": "Kullanıcı Adı",
"wrongArgNum": "Hatalı argüman sayısı sağlandı",
@@ -113,7 +110,6 @@
"dark": "Koyu",
"light": "Aydınlık",
"followSystem": "Sistemi Takip Et",
"followSystemThemeExplanation": "Sistem temasını takip etmek yalnızca üçüncü taraf uygulamaları kullanarak mümkündür",
"useBlackTheme": "Saf siyah koyu temasını kullan",
"appSortBy": "Uygulama Sıralama Ölçütü",
"authorName": "Yazar/Ad",
@@ -147,10 +143,8 @@
"noNewUpdates": "Yeni güncelleme yok.",
"xHasAnUpdate": "{} güncelleme alıyor.",
"appsUpdated": "Uygulamalar Güncellendi",
"appsNotUpdated": "Uygulamalar güncellenemedi",
"appsUpdatedNotifDescription": "Kullanıcıya bir veya daha fazla uygulamanın arka planda güncellendiğine dair bilgi verir",
"xWasUpdatedToY": "{} şu sürüme güncellendi: {}.",
"xWasNotUpdatedToY": "{} öğesi {} olarak güncellenemedi.",
"errorCheckingUpdates": "Güncellemeler Kontrol Edilirken Hata Oluştu",
"errorCheckingUpdatesNotifDescription": "Arka planda güncelleme kontrolü sırasında hata oluştuğunda görünen bir bildirim",
"appsRemoved": "Uygulamalar Kaldırıldı",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Sürüm Algılama Devre Dışı",
"noVersionDetectionExplanation": "Bu seçenek, sürüm algılamanın doğru çalışmadığı uygulamalar için kullanılmalıdır.",
"downloadingX": "{} İndiriliyor",
"downloadX": "İndir {}",
"downloadedX": "İndirildi {}",
"releaseAsset": "Varlık Serbest Bırakma",
"downloadNotifDescription": "Bir uygulamanın indirme sürecinde ilerlemeyi bildiren bir bildirim",
"noAPKFound": "APK bulunamadı",
"noVersionDetection": "Sürüm Algılanamıyor",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Sabit APK URL'lerini destekleyin",
"selectX": "Seçme {}",
"parallelDownloads": "Paralel indirmelere izin ver",
"useShizuku": "Yüklemek için Shizuku veya Sui'yi kullanın",
"installMethod": "Kurulum yöntemi",
"normal": "Normal",
"root": "Kök",
"shizukuBinderNotFound": "Shizuku is not running",
"shizukuOld": "Eski Shizuku sürümü (<11) - güncelleyin",
"shizukuOldAndroidWithADB": "Shizuku ADB ile Android < 8.1 üzerinde çalışıyor - Android'i güncelleyin veya bunun yerine Sui kullanın",
"shizukuPretendToBeGooglePlay": "Google Play'i yükleme kaynağı olarak ayarlayın (Shizuku kullanılıyorsa)",
"useSystemFont": "Sistem yazı tipini kullan",
"systemFontError": "Sistem yazı tipi yüklenirken hata oluştu: {}",
"useVersionCodeAsOSVersion": "Uygulama versionCode'unu işletim sistemi tarafından algılanan sürüm olarak kullan",
"requestHeader": "Başlık talep et",
"useLatestAssetDateAsReleaseDate": "Yayın tarihi olarak en son öğe yüklemesini kullan",
@@ -308,10 +299,6 @@
"note": "Not",
"selfHostedNote": "\"{}\" açılır menüsü, herhangi bir kaynağın kendi kendine barındırılan/özel örneklerine ulaşmak için kullanılabilir.",
"badDownload": "APK ayrıştırılamadı (uyumsuz veya kısmi indirme)",
"beforeNewInstallsShareToAppVerifier": "Yeni Uygulamaları AppVerifier ile paylaşın (varsa)",
"appVerifierInstructionToast": "AppVerifier ile paylaşın, hazır olduğunuzda buraya dönün.",
"wiki": "Yardım/Wiki",
"crowdsourcedConfigsLabel": "Kitle Kaynaklı Uygulama Yapılandırmaları (riski size ait olmak üzere kullanın)",
"removeAppQuestion": {
"one": "Uygulamayı Kaldır?",
"other": "Uygulamaları Kaldır?"
@@ -360,10 +347,6 @@
"one": "{} ve 1 diğer uygulama güncellendi.",
"other": "{} ve {} daha fazla uygulama güncellendi."
},
"xAndNMoreUpdatesFailed": {
"one": "{} ve 1 uygulama daha güncellenemedi.",
"other": "{} ve {} daha fazla uygulama güncellenemedi."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} ve 1 diğer uygulama muhtemelen güncellendi.",
"other": "{} ve {} daha fazla uygulama muhtemelen güncellendi."

View File

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

View File

@@ -11,7 +11,7 @@
"unexpectedError": "Lỗi không mong đợi",
"ok": "OK",
"and": "và",
"githubPATLabel": "Token truy cập cá nhân GitHub (Cải thiện tốc độ giới hạn)",
"githubPATLabel": "GitHub Token (Tăng tốc độ, giới hạn)",
"includePrereleases": "Bao gồm các bản phát hành trước",
"fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn",
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
@@ -22,9 +22,6 @@
"requiredInBrackets": "(Yêu cầu)",
"dropdownNoOptsError": "LỖI: TẢI XUỐNG PHẢI CÓ ÍT NHẤT MỘT LỰA CHỌN",
"colour": "Màu sắc",
"standard": "Standard",
"custom": "Custom",
"useMaterialYou": "Use Material You",
"githubStarredRepos": "Kho lưu trữ có gắn dấu sao GitHub",
"uname": "Tên người dùng",
"wrongArgNum": "Số lượng đối số được cung cấp sai",
@@ -113,7 +110,6 @@
"dark": "Tối",
"light": "Sáng",
"followSystem": "Theo hệ thống",
"followSystemThemeExplanation": "Following system theme is possible only by using third-party applications",
"useBlackTheme": "Nền đen",
"appSortBy": "Sắp xếp ứng dụng",
"authorName": "Tác giả",
@@ -147,10 +143,8 @@
"noNewUpdates": "Không có bản cập nhật mới.",
"xHasAnUpdate": "{} có bản cập nhật.",
"appsUpdated": "Ứng dụng đã cập nhật ",
"appsNotUpdated": "Failed to update applications",
"appsUpdatedNotifDescription": "Thông báo cho người dùng rằng các bản cập nhật cho một hoặc nhiều Ứng dụng đã được áp dụng trong nền",
"xWasUpdatedToY": "{} đã được cập nhật thành {}.",
"xWasNotUpdatedToY": "Failed to update {} to {}.",
"errorCheckingUpdates": "Lỗi kiểm tra bản cập nhật",
"errorCheckingUpdatesNotifDescription": "Thông báo hiển thị khi kiểm tra cập nhật nền không thành công",
"appsRemoved": "Ứng dụng đã loại bỏ",
@@ -189,9 +183,6 @@
"disableVersionDetection": "Tắt tính năng phát hiện phiên bản",
"noVersionDetectionExplanation": "Chỉ nên sử dụng tùy chọn này cho Ứng dụng mà tính năng phát hiện phiên bản không hoạt động chính xác.",
"downloadingX": "Đang tải xuống {}",
"downloadX": "Download {}",
"downloadedX": "Downloaded {}",
"releaseAsset": "Release Asset",
"downloadNotifDescription": "Thông báo cho người dùng về tiến trình tải xuống Ứng dụng",
"noAPKFound": "Không tìm thấy APK",
"noVersionDetection": "Không phát hiện phiên bản",
@@ -227,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ theo dõi'",
"dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK",
"moveNonInstalledAppsToBottom": "Chuyển Ứng dụng chưa được cài đặt xuống cuối danh sách",
"gitlabPATLabel": "Token truy cập cá nhân GitLab",
"gitlabPATLabel": "GitLab Token",
"about": "Giới thiệu",
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Thiết đặt)",
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
@@ -288,12 +279,12 @@
"supportFixedAPKURL": "Hỗ trợ URL APK cố định",
"selectX": "Lựa chọn {}",
"parallelDownloads": "Cho phép tải đa luồng",
"useShizuku": "Use Shizuku or Sui to install",
"installMethod": "Phương thức cài đặt",
"normal": "Mặc định",
"root": "Root",
"shizukuBinderNotFound": "Shizuku chưa khởi động",
"shizukuOld": "Old Shizuku version (<11) - update it",
"shizukuOldAndroidWithADB": "Shizuku running on Android < 8.1 with ADB - update Android or use Sui instead",
"shizukuPretendToBeGooglePlay": "Set Google Play as the installation source (if Shizuku is used)",
"useSystemFont": "Sử dụng phông chữ hệ thống",
"systemFontError": "Lỗi tải phông chữ hệ thống: {}",
"useVersionCodeAsOSVersion": "Sử dụng Mã phiên bản ứng dụng làm phiên bản do hệ điều hành phát hiện",
"requestHeader": "Tiêu đề yêu cầu",
"useLatestAssetDateAsReleaseDate": "Sử dụng nội dung tải lên mới nhất làm ngày phát hành",
@@ -308,10 +299,6 @@
"note": "Ghi chú",
"selfHostedNote": "Trình đơn thả xuống \"{}\" có thể được dùng để tiếp cận các phiên bản tự lưu trữ/tùy chỉnh của bất kỳ nguồn nào.",
"badDownload": "Không thể phân tích cú pháp APK (tải xuống một phần hoặc không tương thích)",
"beforeNewInstallsShareToAppVerifier": "Chia sẻ ứng dụng mới với AppVerifier (nếu có)",
"appVerifierInstructionToast": "Chia sẻ lên AppVerifier, sau đó quay lại đây khi sẵn sàng.",
"wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"removeAppQuestion": {
"one": "Gỡ ứng dụng?",
"other": "Gỡ ứng dụng?"
@@ -360,10 +347,6 @@
"one": "{} và 1 ứng dụng khác đã được cập nhật.",
"other": "{} và {} ứng dụng khác đã được cập nhật."
},
"xAndNMoreUpdatesFailed": {
"one": "Failed to update {} and 1 more app.",
"other": "Failed to update {} and {} more apps."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} và 1 ứng dụng khác có thể đã được cập nhật.",
"other": "{} và {} ứng dụng khác có thể đã được cập nhật."

View File

@@ -13,8 +13,8 @@
"and": "和",
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
"includePrereleases": "包含预发行版",
"fallbackToOlderReleases": "将过往的发行版作为备选",
"filterReleaseTitlesByRegEx": "筛选发行标题正则表达式",
"fallbackToOlderReleases": "将发行版作为备选",
"filterReleaseTitlesByRegEx": "筛选发行标题正则表达式",
"invalidRegEx": "无效的正则表达式",
"noDescription": "无描述",
"cancel": "取消",
@@ -22,9 +22,6 @@
"requiredInBrackets": "(必填)",
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
"colour": "配色",
"standard": "标准",
"custom": "定制",
"useMaterialYou": "使用 Material You",
"githubStarredRepos": "已星标的 GitHub 仓库",
"uname": "用户名",
"wrongArgNum": "参数数量错误",
@@ -45,8 +42,8 @@
"search": "搜索",
"additionalOptsFor": "{} 的更多选项",
"supportedSources": "支持的来源",
"trackOnlyInBrackets": "仅追踪",
"searchableInBrackets": "可搜索",
"trackOnlyInBrackets": "(仅追踪)",
"searchableInBrackets": "(可搜索)",
"appsString": "应用列表",
"noApps": "无应用",
"noAppsForFilter": "没有符合条件的应用",
@@ -75,8 +72,8 @@
"unpinFromTop": "取消置顶",
"resetInstallStatusForSelectedAppsQuestion": "是否重置选中应用的安装状态?",
"installStatusOfXWillBeResetExplanation": "选中应用的安装状态将会被重置。\n\n当更新安装失败或其他问题导致 Obtainium 中的应用版本显示错误时,可以尝试通过此方法解决。",
"customLinkMessage": "分享链接适用于安装 Obtainium 的设备",
"shareAppConfigLinks": "通过链接分享应用配置",
"customLinkMessage": "这些链接适用于安装了 Gettingium 的设备",
"shareAppConfigLinks": "将应用程序配置共享为 HTML 链接",
"shareSelectedAppURLs": "分享选中应用的 URL",
"resetInstallStatus": "重置安装状态",
"more": "更多",
@@ -113,7 +110,6 @@
"dark": "深色",
"light": "浅色",
"followSystem": "跟随系统",
"followSystemThemeExplanation": "跟随系统主题仅在使用第三方应用时有效",
"useBlackTheme": "使用纯黑深色主题",
"appSortBy": "排序依据",
"authorName": "作者 / 应用名称",
@@ -125,7 +121,7 @@
"bgUpdateCheckInterval": "后台更新检查间隔",
"neverManualOnly": "手动",
"appearance": "外观",
"showWebInAppView": "应用详情页显示来源网站内容",
"showWebInAppView": "应用详情页显示来源网",
"pinUpdates": "将待更新应用置顶",
"updates": "更新",
"sourceSpecific": "来源",
@@ -135,7 +131,7 @@
"close": "关闭",
"share": "分享",
"appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "获取出口",
"pickAnAPK": "选择一个 APK 文件",
"appHasMoreThanOnePackage": "“{}”有多个架构可用:",
"deviceSupportsXArch": "您的设备支持 {} 架构。",
@@ -147,10 +143,8 @@
"noNewUpdates": "全部应用已是最新。",
"xHasAnUpdate": "“{}”可以更新了。",
"appsUpdated": "应用已更新",
"appsNotUpdated": "更新应用失败",
"appsUpdatedNotifDescription": "当应用在后台安装更新时发送通知",
"xWasUpdatedToY": "“{}”已更新至 {}。",
"xWasNotUpdatedToY": "未能将 {} 更新为 {}。",
"errorCheckingUpdates": "检查更新出错",
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
"appsRemoved": "应用已删除",
@@ -179,8 +173,8 @@
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
"reposHaveMultipleApps": "存储库中可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方存储库",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "蒸汽手机",
"steamChat": "蒸汽聊天",
"install": "安装",
"markInstalled": "标记为已安装",
"update": "更新",
@@ -189,9 +183,6 @@
"disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
"downloadingX": "正在下载“{}”",
"downloadX": "下载 {}",
"downloadedX": "下载 {}",
"releaseAsset": "发行版附件",
"downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测",
@@ -201,14 +192,14 @@
"noCategory": "无类别",
"noCategories": "无类别",
"deleteCategoriesQuestion": "是否删除选中的类别?",
"categoryDeleteWarning": "被删除类别的应用将恢复为未分类状态。",
"categoryDeleteWarning": "被删除类别的应用将恢复为未分类状态。",
"addCategory": "添加类别",
"label": "标签",
"language": "语言",
"copiedToClipboard": "已复制至剪贴板",
"storagePermissionDenied": "已拒绝授予存储权限",
"selectedCategorizeWarning": "这将覆盖选中应用当前的类别设置。",
"filterAPKsByRegEx": "筛选 APK 文件正则表达式",
"filterAPKsByRegEx": "筛选 APK 文件正则表达式",
"removeFromObtainium": "从 Obtainium 中删除",
"uninstallFromDevice": "从设备中卸载",
"onlyWorksWithNonVersionDetectApps": "仅适用于禁用版本检测的应用。",
@@ -242,29 +233,29 @@
"addInfoInSettings": "在“设置”中添加此凭据。",
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
"sortByLastLinkSegment": "仅根据链接的末尾部分进行筛选",
"filterReleaseNotesByRegEx": "筛选发行说明正则表达式",
"filterReleaseNotesByRegEx": "筛选发行说明正则表达式",
"customLinkFilterRegex": "筛选自定义来源的 APK 文件链接\n正则表达式默认匹配模式为“.apk$”)",
"appsPossiblyUpdated": "已尝试更新应用",
"appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知",
"xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。",
"enableBackgroundUpdates": "启用全局后台更新",
"enableBackgroundUpdates": "启用后台更新",
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
"verifyLatestTag": "验证“Latest”标签",
"intermediateLinkRegex": "筛选中转链接正则表达式",
"intermediateLinkRegex": "筛选中转链接正则表达式",
"filterByLinkText": "根据链接文本进行筛选",
"intermediateLinkNotFound": "未找到中转链接",
"intermediateLink": "中转链接",
"exemptFromBackgroundUpdates": "禁用后台更新(仅此应用生效,即使已启用全局后台更新",
"exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用",
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
"autoSelectHighestVersionCode": "自动选择内部版本号最高的 APK 文件",
"versionExtractionRegEx": "提取版本号正则表达式",
"matchGroupToUse": "从上述匹配结果中引用的捕获组",
"highlightTouchTargets": "突出展示不明显的可交互区域",
"versionExtractionRegEx": "版本号提取规则(正则表达式",
"matchGroupToUse": "引用的捕获组",
"highlightTouchTargets": "突出展示不明显的触摸区域",
"pickExportDir": "选择导出文件夹",
"autoExportOnChanges": "数据变更时自动导出",
"includeSettings": "同时导出应用设置",
"filterVersionsByRegEx": "筛选版本号正则表达式",
"filterVersionsByRegEx": "筛选版本号正则表达式",
"trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件",
"dontSortReleasesList": "保持来自 API 的发行顺序",
"reverseSort": "反转排序",
@@ -288,15 +279,15 @@
"supportFixedAPKURL": "支持固定的 APK 文件链接",
"selectX": "选择{}",
"parallelDownloads": "启用并行下载",
"useShizuku": "使用 Shizuku 或 Sui 安装",
"installMethod": "安装方式",
"normal": "常规",
"root": "root",
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
"shizukuOld": "Shizuku 版本过低(<11- 请更新",
"shizukuOldAndroidWithADB": "正在低版本 Android<8.1)系统中以 ADB 模式运行 Shizuku - 请更新 Android 系统版本或使用 Sui 代替",
"shizukuPretendToBeGooglePlay": "将安装来源伪装为 Google Play需要使用 Shizuku",
"useSystemFont": "使用系统字体",
"systemFontError": "加载系统字体出错:{}",
"useVersionCodeAsOSVersion": "使用内部版本号代替应用定义的版本号",
"requestHeader": "请求标头",
"useLatestAssetDateAsReleaseDate": "使用最文件上传时间作为发行日期",
"useLatestAssetDateAsReleaseDate": "使用最文件上传时间作为发行日期",
"defaultPseudoVersioningMethod": "默认虚拟版本方案",
"partialAPKHash": "APK 文件散列值片段",
"APKLinkHash": "APK 文件链接散列值",
@@ -308,21 +299,17 @@
"note": "备注",
"selfHostedNote": "可以通过“{}”下拉菜单来指向任意来源的自托管/自定义实例。",
"badDownload": "无法解析 APK 文件(不兼容或文件不完整)",
"beforeNewInstallsShareToAppVerifier": "通过 AppVerifier 校验新应用(如果可用)",
"appVerifierInstructionToast": "分享至 AppVerifier完成后返回此处。",
"wiki": "帮助/Wiki",
"crowdsourcedConfigsLabel": "众包应用程序配置(使用风险自负)",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "API 请求过于频繁(速率限制)- 在 {} 分钟后重试",
"other": "API 请求过于频繁(速率限制)- 在 {} 分钟后重试"
"one": "API 请求过于频繁(速率限制)- 在 {} 分钟后重试",
"other": "API 请求过于频繁(速率限制)- 在 {} 分钟后重试"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "后台更新检查遇到了“{}”问题,于 {} 分钟后重试",
"other": "后台更新检查遇到了“{}”问题,于 {} 分钟后重试"
"one": "后台更新检查遇到了“{}”问题,预定于 {} 分钟后重试",
"other": "后台更新检查遇到了“{}”问题,预定于 {} 分钟后重试"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "后台检查发现 {} 个应用更新 - 如有需要将发送通知",
@@ -360,10 +347,6 @@
"one": "{} 和另外 1 个应用已更新。",
"other": "“{}”和另外 {} 个应用已更新。"
},
"xAndNMoreUpdatesFailed": {
"one": "{} 和另外 1 个应用更新失败。",
"other": "{} 和另外 {} 个应用更新失败。"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} 和另外 1 个应用已尝试更新。",
"other": "“{}”和另外 {} 个应用已尝试更新。"

View File

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

View File

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

View File

@@ -45,7 +45,7 @@ class APKCombo extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
return html
.querySelectorAll('#variants-tab > div > ul > li')
.map((e) {
@@ -96,7 +96,7 @@ class APKCombo extends AppSource {
if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres);
}
var res = parse(preres.body);
var res = parse(preres.data);
String? version = res.querySelector('div.version')?.text.trim();
if (version == null) {
throw NoVersionError();

View File

@@ -1,8 +1,8 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -62,7 +62,7 @@ class APKMirror extends AppSource {
: null;
Response res = await sourceRequest('$standardUrl/feed', additionalSettings);
if (res.statusCode == 200) {
var items = parse(res.body).querySelectorAll('item');
var items = parse(res.data).querySelectorAll('item');
dynamic targetRelease;
for (int i = 0; i < items.length; i++) {
if (!fallbackToOlderReleases && i > 0) break;

View File

@@ -61,8 +61,8 @@ class APKPure extends AppSource {
var res = await sourceRequest('$standardUrl/download', additionalSettings);
var resChangelog = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
var html = parse(res.body);
var htmlChangelog = parse(resChangelog.body);
var html = parse(res.data);
var htmlChangelog = parse(resChangelog.data);
String? version = html.querySelector('span.info-sdk span')?.text.trim();
if (version == null) {
throw NoVersionError();

View File

@@ -38,10 +38,10 @@ class Aptoide extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.body);
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.data);
String? id;
if (idMatch != null) {
id = res.body.substring(idMatch.start + 12, idMatch.end);
id = res.data.substring(idMatch.start + 12, idMatch.end);
} else {
throw NoReleasesError();
}
@@ -50,7 +50,7 @@ class Aptoide extends AppSource {
if (res2.statusCode != 200) {
throw getObtainiumHttpError(res);
}
return jsonDecode(res2.body)?['nodes']?['meta']?['data'];
return jsonDecode(res2.data)?['nodes']?['meta']?['data'];
}
@override

View File

@@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/components/generated_form.dart';
@@ -82,7 +82,7 @@ class FDroid extends AppSource {
var res = await sourceRequest(
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml',
additionalSettings);
var lines = res.body.split('\n');
var lines = res.data.split('\n');
var authorLines = lines.where((l) => l.startsWith('AuthorName: '));
if (authorLines.isNotEmpty) {
details.names.author =
@@ -112,7 +112,7 @@ class FDroid extends AppSource {
details.changeLog = (await sourceRequest(
details.changeLog!.replaceFirst('/blob/', '/raw/'),
additionalSettings))
.body;
.data;
}
}
} catch (e) {
@@ -132,7 +132,7 @@ class FDroid extends AppSource {
'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {});
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
parse(res.body).querySelectorAll('.package-header').forEach((e) {
parse(res.data).querySelectorAll('.package-header').forEach((e) {
String? url = e.attributes['href'];
if (url != null) {
try {
@@ -172,7 +172,7 @@ class FDroid extends AppSource {
? additionalSettings['apkFilterRegEx']
: null;
if (res.statusCode == 200) {
var response = jsonDecode(res.body);
var response = jsonDecode(res.data);
List<dynamic> releases = response['packages'] ?? [];
if (apkFilterRegEx != null) {
releases = releases.where((rel) {

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -61,9 +61,10 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
url = removeQueryParamsFromUrl(standardizeUrl(url));
var res = await sourceRequestWithURLVariants(url, {});
var ress = await sourceRequestWithURLVariants(url, {});
var res = ress.value;
if (res.statusCode == 200) {
var body = parse(res.body);
var body = parse(res.data);
Map<String, List<String>> results = {};
body.querySelectorAll('application').toList().forEach((app) {
String appId = app.attributes['id']!;
@@ -74,7 +75,7 @@ class FDroidRepo extends AppSource {
appName.contains(query) ||
appDesc.contains(query)) {
results[
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [
'${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [
appName,
appDesc
];
@@ -107,24 +108,24 @@ class FDroidRepo extends AppSource {
return app;
}
Future<Response> sourceRequestWithURLVariants(
Future<MapEntry<String, Response>> sourceRequestWithURLVariants(
String url,
Map<String, dynamic> additionalSettings,
) async {
var res = await sourceRequest(
'$url${url.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
var finalUrl = '$url${url.endsWith('/index.xml') ? '' : '/index.xml'}';
var res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode != 200) {
var base = url.endsWith('/index.xml')
? url.split('/').reversed.toList().sublist(1).reversed.join('/')
: url;
res = await sourceRequest('$base/repo/index.xml', additionalSettings);
finalUrl = '$base/repo/index.xml';
res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode != 200) {
res = await sourceRequest(
'$base/fdroid/repo/index.xml', additionalSettings);
finalUrl = '$base/fdroid/repo/index.xml';
res = await sourceRequest(finalUrl, additionalSettings);
}
}
return res;
return MapEntry(finalUrl, res);
}
@override
@@ -142,10 +143,11 @@ class FDroidRepo extends AppSource {
if (appIdOrName == null) {
throw NoReleasesError();
}
var res =
var ress =
await sourceRequestWithURLVariants(standardUrl, additionalSettings);
var res = ress.value;
if (res.statusCode == 200) {
var body = parse(res.body);
var body = parse(res.data);
var foundApps = body.querySelectorAll('application').where((element) {
return element.attributes['id'] == appIdOrName;
}).toList();
@@ -193,7 +195,7 @@ class FDroidRepo extends AppSource {
}
List<String> apkUrls = latestVersionReleases
.map((e) =>
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}')
'${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}')
.toList();
return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(authorName, appName),

View File

@@ -1,8 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
@@ -112,12 +112,12 @@ class GitHub extends AppSource {
];
for (var path in possibleBuildGradleLocations) {
try {
var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path',
additionalSettings);
var finalUrl =
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path';
var res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode == 200) {
try {
var body = jsonDecode(res.body);
var body = jsonDecode(res.data);
var trimmedLines = utf8
.decode(base64
.decode(body['content'].toString().split('\n').join('')))
@@ -143,7 +143,7 @@ class GitHub extends AppSource {
}
} catch (err) {
LogsProvider().add(
'Error parsing build.gradle from ${res.request!.url.toString()}: ${err.toString()}');
'Error parsing build.gradle from $finalUrl: ${err.toString()}');
}
}
} catch (err) {
@@ -256,11 +256,11 @@ class GitHub extends AppSource {
}
throw getObtainiumHttpError(res);
}
latestRelease = jsonDecode(res.body);
latestRelease = jsonDecode(res.data);
}
Response res = await sourceRequest(requestUrl, additionalSettings);
if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>;
var releases = jsonDecode(res.data) as List<dynamic>;
if (latestRelease != null) {
var latestTag = latestRelease['tag_name'] ?? latestRelease['name'];
if (releases
@@ -271,15 +271,17 @@ class GitHub extends AppSource {
}
}
List<MapEntry<String, String>> getReleaseAssetUrls(dynamic release) =>
(release['assets'] as List<dynamic>?)?.map((e) {
var url = !e['name'].toString().toLowerCase().endsWith('.apk')
? (e['browser_download_url'] ?? e['url'])
: (e['url'] ?? e['browser_download_url']);
return (e['name'] != null) && (url != null)
? MapEntry(e['name'] as String, url as String)
: const MapEntry('', '');
}).toList() ??
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
(release['assets'] as List<dynamic>?)
?.map((e) {
return (e['name'] != null) &&
((e['url'] ?? e['browser_download_url']) != null)
? MapEntry(e['name'] as String,
(e['url'] ?? e['browser_download_url']) as String)
: const MapEntry('', '');
})
.where((element) => element.key.toLowerCase().endsWith('.apk'))
.toList() ??
[];
DateTime? getPublishDateFromRelease(dynamic rel) =>
@@ -381,11 +383,7 @@ class GitHub extends AppSource {
.hasMatch(((releases[i]['body'] as String?) ?? '').trim())) {
continue;
}
var allAssetUrls = getReleaseAssetUrls(releases[i]);
List<MapEntry<String, String>> apkUrls = allAssetUrls
.where((element) => element.key.toLowerCase().endsWith('.apk'))
.toList();
var apkUrls = getReleaseAPKUrls(releases[i]);
apkUrls = filterApks(apkUrls, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
@@ -393,25 +391,12 @@ class GitHub extends AppSource {
}
targetRelease = releases[i];
targetRelease['apkUrls'] = apkUrls;
targetRelease['version'] =
targetRelease['tag_name'] ?? targetRelease['name'];
if (targetRelease['tarball_url'] != null) {
allAssetUrls.add(MapEntry(
(targetRelease['version'] ?? 'source') + '.tar.gz',
targetRelease['tarball_url']));
}
if (targetRelease['zipball_url'] != null) {
allAssetUrls.add(MapEntry(
(targetRelease['version'] ?? 'source') + '.zip',
targetRelease['zipball_url']));
}
targetRelease['allAssetUrls'] = allAssetUrls;
break;
}
if (targetRelease == null) {
throw NoReleasesError();
}
String? version = targetRelease['version'];
String? version = targetRelease['tag_name'] ?? targetRelease['name'];
DateTime? releaseDate = getReleaseDateFromRelease(
targetRelease, useLatestAssetDateAsReleaseDate);
if (version == null) {
@@ -423,9 +408,7 @@ class GitHub extends AppSource {
targetRelease['apkUrls'] as List<MapEntry<String, String>>,
getAppNames(standardUrl),
releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog,
allAssetUrls:
targetRelease['allAssetUrls'] as List<MapEntry<String, String>>);
changeLog: changeLog.isEmpty ? null : changeLog);
} else {
if (onHttpErrorCode != null) {
onHttpErrorCode(res);
@@ -483,7 +466,7 @@ class GitHub extends AppSource {
? int.parse(querySettings['minStarCount'])
: 0;
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
for (var e in (jsonDecode(res.data)[rootProp] as List<dynamic>)) {
if ((e['stargazers_count'] ?? e['stars_count'] ?? 0) >= minStarCount) {
urlsWithDescriptions.addAll({
e['html_url'] as String: [
@@ -517,11 +500,13 @@ class GitHub extends AppSource {
}
rateLimitErrorCheck(Response res) {
if (res.headers['x-ratelimit-remaining'] == '0') {
String? rateLimitHeader;
if (res.headers.map['x-ratelimit-remaining']?.isNotEmpty == true) {
rateLimitHeader = res.headers.map['x-ratelimit-remaining']![0];
}
if (rateLimitHeader == '0') {
throw RateLimitError(
(int.parse(res.headers['x-ratelimit-reset'] ?? '1800000000') /
60000000)
.round());
(int.parse(rateLimitHeader ?? '1800000000') / 60000000).round());
}
}
}

View File

@@ -1,8 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/settings_provider.dart';
@@ -81,7 +81,7 @@ class GitLab extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
var json = jsonDecode(res.data) as List<dynamic>;
Map<String, List<String>> results = {};
for (var element in json) {
results['https://${hosts[0]}/${element['path_with_namespace']}'] = [
@@ -121,11 +121,9 @@ class GitLab extends AppSource {
String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {});
String optionalAuth = (PAT != null) ? 'private_token=$PAT' : '';
bool trackOnly = additionalSettings['trackOnly'] == true;
// Request data from REST API
Response res = await sourceRequest(
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/${trackOnly ? 'repository/tags' : 'releases'}?$optionalAuth',
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?$optionalAuth',
additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
@@ -133,7 +131,7 @@ class GitLab extends AppSource {
// Extract .apk details from received data
Iterable<APKDetails> apkDetailsList = [];
var json = jsonDecode(res.body) as List<dynamic>;
var json = jsonDecode(res.data) as List<dynamic>;
apkDetailsList = json.map((e) {
var apkUrlsFromAssets = (e['assets']?['links'] as List<dynamic>? ?? [])
.map((e) {
@@ -165,31 +163,20 @@ class GitLab extends AppSource {
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
}
var finalResult = apkDetailsList.first;
// Fallback procedure
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true;
if (finalResult.apkUrls.isEmpty && fallbackToOlderReleases && !trackOnly) {
apkDetailsList =
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
finalResult = apkDetailsList.first;
}
if (finalResult.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError();
}
finalResult.apkUrls = finalResult.apkUrls.map((apkUrl) {
if (RegExp('^$standardUrl/-/jobs/[0-9]+/artifacts/file/[^/]+\$')
.hasMatch(apkUrl.value)) {
return MapEntry(
apkUrl.key, apkUrl.value.replaceFirst('/file/', '/raw/'));
} else {
return apkUrl;
if (fallbackToOlderReleases) {
if (additionalSettings['trackOnly'] != true) {
apkDetailsList =
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
}
}).toList();
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
}
}
return finalResult;
return apkDetailsList.first;
}
}

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/apps_provider.dart';
@@ -213,11 +213,11 @@ class HTML extends AppSource {
// Given an HTTP response, grab some links according to the common additional settings
// (those that apply to intermediate and final steps)
Future<List<MapEntry<String, String>>> grabLinksCommon(
Response res, Map<String, dynamic> additionalSettings) async {
Response res, Uri url, Map<String, dynamic> additionalSettings) async {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
List<MapEntry<String, String>> allLinks = html
.querySelectorAll('a')
.map((element) => MapEntry(
@@ -226,13 +226,12 @@ 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))
.map((e) => MapEntry(ensureAbsoluteUrl(e.key, url), e.value))
.toList();
if (allLinks.isEmpty) {
allLinks = RegExp(
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
.allMatches(res.body)
.allMatches(res.data)
.map((match) =>
MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? ''))
.toList();
@@ -243,28 +242,18 @@ class HTML extends AppSource {
if ((additionalSettings['customLinkFilterRegex'] as String?)?.isNotEmpty ==
true) {
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
links = allLinks.where((element) {
var link = element.key;
try {
link = Uri.decodeFull(element.key);
} catch (e) {
// Some links may not have valid encoding
}
return reg.hasMatch(filterLinkByText ? element.value : link);
}).toList();
links = allLinks
.where((element) =>
reg.hasMatch(filterLinkByText ? element.value : element.key))
.toList();
} else {
links = allLinks.where((element) {
var link = element.key;
try {
link = Uri.decodeFull(element.key);
} catch (e) {
// Some links may not have valid encoding
}
return Uri.parse(filterLinkByText ? element.value : link)
.path
.toLowerCase()
.endsWith('.apk');
}).toList();
links = allLinks
.where((element) =>
Uri.parse(filterLinkByText ? element.value : element.key)
.path
.toLowerCase()
.endsWith('.apk'))
.toList();
}
if (!skipSort) {
links.sort((a, b) => additionalSettings['sortByLastLinkSegment'] == true
@@ -295,6 +284,7 @@ class HTML extends AppSource {
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
var intLinks = await grabLinksCommon(
await sourceRequest(currentUrl, additionalSettings),
Uri.parse(currentUrl),
additionalSettings['intermediateLink'][i]);
if (intLinks.isEmpty) {
throw NoReleasesError();
@@ -308,8 +298,9 @@ class HTML extends AppSource {
if (additionalSettings['directAPKLink'] != true) {
Response res = await sourceRequest(currentUrl, additionalSettings);
versionExtractionWholePageString =
res.body.split('\r\n').join('\n').split('\n').join('\\n');
links = await grabLinksCommon(res, additionalSettings);
res.data.split('\r\n').join('\n').split('\n').join('\\n');
links =
await grabLinksCommon(res, Uri.parse(currentUrl), additionalSettings);
links = filterApks(links, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (links.isEmpty) {
@@ -319,19 +310,13 @@ class HTML extends AppSource {
links = [MapEntry(currentUrl, currentUrl)];
}
var rel = links.last.key;
var relDecoded = rel;
try {
relDecoded = Uri.decodeFull(rel);
} catch (e) {
// Some links may not have valid encoding
}
String? version;
version = extractVersion(
additionalSettings['versionExtractionRegEx'] as String?,
additionalSettings['matchGroupToUse'] as String?,
additionalSettings['versionExtractWholePage'] == true
? versionExtractionWholePageString
: relDecoded);
: rel);
version ??=
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
? rel.hashCode.toString()

View File

@@ -1,12 +1,12 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class HuaweiAppGallery extends AppSource {
HuaweiAppGallery() {
name = 'Huawei AppGallery';
hosts = ['appgallery.huawei.com', 'appgallery.cloud.huawei.com'];
hosts = ['appgallery.huawei.com'];
versionDetectionDisallowed = true;
showReleaseDateAsVersionToggle = true;
}
@@ -14,7 +14,7 @@ class HuaweiAppGallery extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}(/#)?/(app|appdl)/[^/]+',
'^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
@@ -24,7 +24,7 @@ class HuaweiAppGallery extends AppSource {
}
getDlUrl(String standardUrl) =>
'https://${hosts[0].replaceAll('appgallery.huawei', 'appgallery.cloud.huawei')}/appdl/${standardUrl.split('/').last}';
'https://${hosts[0].replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
requestAppdlRedirect(
String dlUrl, Map<String, dynamic> additionalSettings) async {
@@ -57,8 +57,8 @@ class HuaweiAppGallery extends AppSource {
{Map<String, dynamic> additionalSettings = const {}}) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
return res.headers['location'] != null
? appIdFromRedirectDlUrl(res.headers['location']!)
return res.headers.map['location']?.isNotEmpty == true
? appIdFromRedirectDlUrl(res.headers.map['location']![0])
: null;
}
@@ -72,9 +72,12 @@ class HuaweiAppGallery extends AppSource {
if (res.headers['location'] == null) {
throw NoReleasesError();
}
String appId = appIdFromRedirectDlUrl(res.headers['location']!);
var relDateStr =
res.headers['location']?.split('?')[0].split('.').reversed.toList()[1];
String appId = appIdFromRedirectDlUrl(res.headers.map['location']![0]);
var relDateStr = res.headers.map['location']?[0]
.split('?')[0]
.split('.')
.reversed
.toList()[1];
var relDateStrAdj = relDateStr?.split('');
var tempLen = relDateStrAdj?.length ?? 0;
var i = 2;
@@ -84,7 +87,7 @@ class HuaweiAppGallery extends AppSource {
}
var relDate = relDateStrAdj == null
? null
: DateFormat('yy-MM-dd-HH-mm', 'en_US').parse(relDateStrAdj.join(''));
: DateFormat('yy-MM-dd-HH-mm').parse(relDateStrAdj.join(''));
if (relDateStr == null) {
throw NoVersionError();
}

View File

@@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:dio/dio.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -33,7 +33,7 @@ class Jenkins extends AppSource {
Response res = await sourceRequest(
'$standardUrl/lastSuccessfulBuild/api/json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var json = jsonDecode(res.data);
var releaseDate = json['timestamp'] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int);

View File

@@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -33,7 +33,7 @@ class Mullvad extends AppSource {
Response res = await sourceRequest(
'$standardUrl/en/download/android', additionalSettings);
if (res.statusCode == 200) {
var versions = parse(res.body)
var versions = parse(res.data)
.querySelectorAll('p')
.map((e) => e.innerHtml)
.where((p) => p.contains('Latest version: '))

View File

@@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -83,7 +83,7 @@ class NeutronCode extends AppSource {
) async {
Response res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var http = parse(res.data);
var name = http.querySelector('.pd-title')?.innerHtml;
var filename = http.querySelector('.pd-filename .pd-float')?.innerHtml;
if (filename == null) {

View File

@@ -1,5 +1,5 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:dio/dio.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -21,7 +21,7 @@ class Signal extends AppSource {
Response res = await sourceRequest(
'https://updates.${hosts[0]}/android/latest.json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var json = jsonDecode(res.data);
String? apkUrl = json['url'];
List<String> apkUrls = apkUrl == null ? [] : [apkUrl];
String? version = json['versionName'];

View File

@@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -49,7 +49,7 @@ class SourceForge extends AppSource {
'${standardUri.origin}/${standardUri.pathSegments.sublist(0, 2).join('/')}/rss?path=/',
additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var parsedHtml = parse(res.data);
var allDownloadLinks = parsedHtml
.querySelectorAll('guid')
.map((e) => e.innerHtml)

View File

@@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -55,7 +55,7 @@ class SourceHut extends AppSource {
Response res =
await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var parsedHtml = parse(res.data);
List<APKDetails> apkDetailsList = [];
int ind = 0;
@@ -85,7 +85,7 @@ class SourceHut extends AppSource {
var res2 = await sourceRequest(releasePage, additionalSettings);
List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = getApkUrlsFromUrls(parse(res2.body)
apkUrls = getApkUrlsFromUrls(parse(res2.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => e.toLowerCase().endsWith('.apk'))

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -38,7 +38,7 @@ class SteamMobile extends AppSource {
}
String apkInURLRegexPattern =
'/$apkNamePrefix-([0-9]+\\.)*[0-9]+\\.apk\$';
var links = parse(res.body)
var links = parse(res.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => RegExp('https://.*$apkInURLRegexPattern').hasMatch(e))

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -23,7 +23,7 @@ class TelegramApp extends AppSource {
Response res =
await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var http = parse(res.data);
var messages =
http.querySelectorAll('.tgme_widget_message_text.js-message_text');
var version = messages.isNotEmpty

View File

@@ -37,7 +37,7 @@ class Uptodown extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
String? version = html.querySelector('div.version')?.innerHtml;
String? apkUrl =
'${standardUrl.split('/').reversed.toList().sublist(1).reversed.join('/')}/post-download';
@@ -94,7 +94,7 @@ class Uptodown extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
var finalUrlKey =
html.querySelector('.post-download')?.attributes['data-url'];
if (finalUrlKey == null) {

View File

@@ -1,7 +1,8 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/source_provider.dart';
class VLC extends AppSource {
@@ -29,7 +30,7 @@ class VLC extends AppSource {
String standardUrl, Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(dwUrlBase, additionalSettings);
if (res.statusCode == 200) {
var dwLinks = parse(res.body)
var dwLinks = parse(res.data)
.querySelectorAll('a')
.where((element) => element.attributes['href'] != 'last/')
.map((e) => e.attributes['href']?.split('/')[0])
@@ -49,11 +50,11 @@ class VLC extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await get(
Uri.parse('https://www.videolan.org/vlc/download-android.html'));
Response res =
await dio.get('https://www.videolan.org/vlc/download-android.html');
if (res.statusCode == 200) {
var dwUrlBase = 'get.videolan.org/vlc-android';
var dwLinks = parse(res.body)
var dwLinks = parse(res.data)
.querySelectorAll('a')
.where((element) =>
element.attributes['href']?.contains(dwUrlBase) ?? false)
@@ -84,14 +85,14 @@ class VLC extends AppSource {
Response res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode == 200) {
String? apkUrl =
parse(res.body).querySelector('#alt_link')?.attributes['href'];
parse(res.data).querySelector('#alt_link')?.attributes['href'];
if (apkUrl == null) {
throw NoAPKError();
}
return apkUrl;
} else if (res.statusCode == 500 &&
res.body.toLowerCase().indexOf('mirror') > 0) {
var html = parse(res.body);
res.data.toLowerCase().indexOf('mirror') > 0) {
var html = parse(res.data);
var err = '';
html.body?.nodes.forEach((element) {
if (element.text != null) {

View File

@@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -20,7 +20,7 @@ class WhatsApp extends AppSource {
Response res =
await sourceRequest('$standardUrl/android', additionalSettings);
if (res.statusCode == 200) {
var targetLinks = parse(res.body)
var targetLinks = parse(res.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => e.isNotEmpty)

View File

@@ -245,8 +245,8 @@ class _GeneratedFormState extends State<GeneratedForm> {
void someValueChanged({bool isBuilding = false, bool forceInvalid = false}) {
Map<String, dynamic> returnValues = values;
var valid = true;
for (int r = 0; r < formInputs.length; r++) {
for (int i = 0; i < formInputs[r].length; i++) {
for (int r = 0; r < widget.items.length; r++) {
for (int i = 0; i < widget.items[r].length; i++) {
if (formInputs[r][i] is TextFormField) {
valid = valid && validateTextField(formInputs[r][i] as TextFormField);
}

View File

@@ -5,7 +5,6 @@ import 'package:flutter/services.dart';
import 'package:obtainium/pages/home.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/notifications_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart';
@@ -19,6 +18,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
import 'package:dio/dio.dart';
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
@@ -47,6 +47,9 @@ var fdroid = false;
final globalNavigatorKey = GlobalKey<NavigatorState>();
final dio = Dio(BaseOptions(
responseType: ResponseType.plain, receiveDataWhenStatusError: true));
Future<void> loadTranslations() async {
// See easy_localization/issues/210
await EasyLocalizationController.initEasyLocation();
@@ -119,6 +122,8 @@ void main() async {
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
var defaultThemeColour = Colors.deepPurple;
class Obtainium extends StatefulWidget {
const Obtainium({super.key});
@@ -212,13 +217,15 @@ class _ObtainiumState extends State<Obtainium> {
// Decide on a colour/brightness scheme based on OS and user settings
ColorScheme lightColorScheme;
ColorScheme darkColorScheme;
if (lightDynamic != null && darkDynamic != null && settingsProvider.useMaterialYou) {
if (lightDynamic != null &&
darkDynamic != null &&
settingsProvider.colour == ColourSettings.materialYou) {
lightColorScheme = lightDynamic.harmonized();
darkColorScheme = darkDynamic.harmonized();
} else {
lightColorScheme = ColorScheme.fromSeed(seedColor: settingsProvider.themeColor);
lightColorScheme = ColorScheme.fromSeed(seedColor: defaultThemeColour);
darkColorScheme = ColorScheme.fromSeed(
seedColor: settingsProvider.themeColor, brightness: Brightness.dark);
seedColor: defaultThemeColour, brightness: Brightness.dark);
}
// set the background and surface colors to pure black in the amoled theme
@@ -228,8 +235,6 @@ class _ObtainiumState extends State<Obtainium> {
.harmonized();
}
if (settingsProvider.useSystemFont) NativeFeatures.loadSystemFont();
return MaterialApp(
title: 'Obtainium',
localizationsDelegates: context.localizationDelegates,

View File

@@ -1,9 +1,10 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/source_provider.dart';
class GitHubStars implements MassAppUrlSource {
@@ -15,13 +16,12 @@ class GitHubStars implements MassAppUrlSource {
Future<Map<String, List<String>>> getOnePageOfUserStarredUrlsWithDescriptions(
String username, int page) async {
Response res = await get(
Uri.parse(
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
headers: await GitHub().getRequestHeaders({}));
Response res = await dio.get(
'https://api.github.com/users/$username/starred?per_page=100&page=$page',
options: Options(headers: await GitHub().getRequestHeaders({})));
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body) as List<dynamic>)) {
for (var e in (jsonDecode(res.data) as List<dynamic>)) {
urlsWithDescriptions.addAll({
e['html_url'] as String: [
e['full_name'] as String,

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,6 @@ import 'package:obtainium/pages/import_export.dart';
import 'package:obtainium/pages/settings.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart';
import 'package:provider/provider.dart';
class HomePage extends StatefulWidget {
@@ -103,22 +102,13 @@ class _HomePageState extends State<HomePage> {
}) !=
null) {
// ignore: use_build_context_synchronously
var appsProvider = await context.read<AppsProvider>();
var result = await appsProvider.import(action == 'app'
? '{ "apps": [$dataStr] }'
: '{ "apps": $dataStr }');
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.length)]),
context);
await appsProvider
.checkUpdates(specificIds: result.key.map((e) => e.id).toList())
.catchError((e) {
if (e is Map && e['errors'] is MultiAppMultiError) {
showError(e['errors'].toString(), context);
}
return <App>[];
});
tr('importedX', args: [plural('apps', result.key)]), context);
}
} else {
throw ObtainiumError(tr('unknown'));
@@ -129,7 +119,7 @@ class _HomePageState extends State<HomePage> {
}
// Check initial link if app was in cold state (terminated)
final appLink = await _appLinks.getInitialLink();
final appLink = await _appLinks.getInitialAppLink();
if (appLink != null) {
await interpretLink(appLink);
}

View File

@@ -144,7 +144,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
appsProvider.addMissingCategories(settingsProvider);
showMessage(
'${tr('importedX', args: [
plural('apps', value.key.length)
plural('apps', value.key)
])}${value.value ? ' + ${tr('settings')}' : ''}',
context);
});

View File

@@ -1,7 +1,5 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:equations/equations.dart';
import 'package:flex_color_picker/flex_color_picker.dart';
import 'package:flutter/material.dart';
import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.dart';
@@ -14,7 +12,6 @@ import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart';
import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:shizuku_apk_installer/shizuku_apk_installer.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SettingsPage extends StatefulWidget {
@@ -25,184 +22,78 @@ class SettingsPage extends StatefulWidget {
}
class _SettingsPageState extends State<SettingsPage> {
List<int> updateIntervalNodes = [
15,
30,
60,
120,
180,
360,
720,
1440,
4320,
10080,
20160,
43200
];
int updateInterval = 0;
late SplineInterpolation updateIntervalInterpolator; // 🤓
String updateIntervalLabel = tr('neverManualOnly');
bool showIntervalLabel = true;
final Map<ColorSwatch<Object>, String> colorsNameMap =
<ColorSwatch<Object>, String>{
ColorTools.createPrimarySwatch(obtainiumThemeColor): 'Obtainium'
};
void initUpdateIntervalInterpolator() {
List<InterpolationNode> nodes = [];
for (final (index, element) in updateIntervalNodes.indexed) {
nodes.add(
InterpolationNode(x: index.toDouble() + 1, y: element.toDouble()));
}
updateIntervalInterpolator = SplineInterpolation(nodes: nodes);
}
void processIntervalSliderValue(double val) {
if (val < 0.5) {
updateInterval = 0;
updateIntervalLabel = tr('neverManualOnly');
return;
}
int valInterpolated = 0;
if (val < 1) {
valInterpolated = 15;
} else {
valInterpolated = updateIntervalInterpolator.compute(val).round();
}
if (valInterpolated < 60) {
updateInterval = valInterpolated;
updateIntervalLabel = plural('minute', valInterpolated);
} else if (valInterpolated < 8 * 60) {
int valRounded = (valInterpolated / 15).floor() * 15;
updateInterval = valRounded;
updateIntervalLabel = plural('hour', valRounded ~/ 60);
int mins = valRounded % 60;
if (mins != 0) updateIntervalLabel += " ${plural('minute', mins)}";
} else if (valInterpolated < 24 * 60) {
int valRounded = (valInterpolated / 30).floor() * 30;
updateInterval = valRounded;
updateIntervalLabel = plural('hour', valRounded / 60);
} else if (valInterpolated < 7 * 24 * 60) {
int valRounded = (valInterpolated / (12 * 60)).floor() * 12 * 60;
updateInterval = valRounded;
updateIntervalLabel = plural('day', valRounded / (24 * 60));
} else {
int valRounded = (valInterpolated / (24 * 60)).floor() * 24 * 60;
updateInterval = valRounded;
updateIntervalLabel = plural('day', valRounded ~/ (24 * 60));
}
}
@override
Widget build(BuildContext context) {
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
SourceProvider sourceProvider = SourceProvider();
if (settingsProvider.prefs == null) settingsProvider.initializeSettings();
initUpdateIntervalInterpolator();
processIntervalSliderValue(settingsProvider.updateIntervalSliderVal);
var followSystemThemeExplanation = FutureBuilder(
builder: (ctx, val) {
return ((val.data?.version.sdkInt ?? 30) < 29)
? Text(tr('followSystemThemeExplanation'),
style: Theme.of(context).textTheme.labelSmall)
: const SizedBox.shrink();
},
future: DeviceInfoPlugin().androidInfo);
Future<bool> colorPickerDialog() async {
return ColorPicker(
color: settingsProvider.themeColor,
onColorChanged: (Color color) =>
setState(() => settingsProvider.themeColor = color),
actionButtons: const ColorPickerActionButtons(
okButton: true,
closeButton: true,
dialogActionButtons: false,
),
pickersEnabled: const <ColorPickerType, bool>{
ColorPickerType.both: false,
ColorPickerType.primary: false,
ColorPickerType.accent: false,
ColorPickerType.bw: false,
ColorPickerType.custom: true,
ColorPickerType.wheel: true,
},
pickerTypeLabels: <ColorPickerType, String>{
ColorPickerType.custom: tr('standard'),
ColorPickerType.wheel: tr('custom')
},
title: Text(tr('selectX', args: [tr('colour')]),
style: Theme.of(context).textTheme.titleLarge),
wheelDiameter: 192,
wheelSquareBorderRadius: 32,
width: 48,
height: 48,
borderRadius: 24,
spacing: 8,
runSpacing: 8,
enableShadesSelection: false,
customColorSwatchesAndNames: colorsNameMap,
showMaterialName: true,
showColorName: true,
materialNameTextStyle: Theme.of(context).textTheme.bodySmall,
colorNameTextStyle: Theme.of(context).textTheme.bodySmall,
copyPasteBehavior:
const ColorPickerCopyPasteBehavior(longPressMenu: true),
).showPickerDialog(
context,
transitionBuilder: (BuildContext context, Animation<double> a1,
Animation<double> a2, Widget widget) {
final double curvedValue = Curves.easeInCubic.transform(a1.value);
return Transform(
alignment: Alignment.center,
transform: Matrix4.diagonal3Values(curvedValue, curvedValue, 1),
child: Opacity(opacity: curvedValue, child: widget),
);
},
transitionDuration: const Duration(milliseconds: 250),
);
if (settingsProvider.prefs == null) {
settingsProvider.initializeSettings();
}
var colorPicker = ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(tr('selectX', args: [tr('colour')])),
subtitle: Text(
"${ColorTools.nameThatColor(settingsProvider.themeColor)} "
"(${ColorTools.materialNameAndCode(settingsProvider.themeColor, colorSwatchNameMap: colorsNameMap)})"),
trailing: ColorIndicator(
width: 40,
height: 40,
borderRadius: 20,
color: settingsProvider.themeColor,
onSelectFocus: false,
onSelect: () async {
final Color colorBeforeDialog = settingsProvider.themeColor;
if (!(await colorPickerDialog())) {
setState(() {
settingsProvider.themeColor = colorBeforeDialog;
});
}
}));
var installMethodDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('installMethod')),
value: settingsProvider.installMethod,
items: [
DropdownMenuItem(
value: InstallMethodSettings.normal,
child: Text(tr('normal')),
),
const DropdownMenuItem(
value: InstallMethodSettings.shizuku,
child: Text('Shizuku'),
),
DropdownMenuItem(
value: InstallMethodSettings.root,
child: Text(tr('root')),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.installMethod = value;
}
});
var useMaterialThemeSwitch = FutureBuilder(
builder: (ctx, val) {
return ((val.data?.version.sdkInt ?? 0) >= 31)
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('useMaterialYou'))),
Switch(
value: settingsProvider.useMaterialYou,
onChanged: (value) {
settingsProvider.useMaterialYou = value;
})
],
)
: const SizedBox.shrink();
},
future: DeviceInfoPlugin().androidInfo);
var themeDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('theme')),
value: settingsProvider.theme,
items: [
DropdownMenuItem(
value: ThemeSettings.dark,
child: Text(tr('dark')),
),
DropdownMenuItem(
value: ThemeSettings.light,
child: Text(tr('light')),
),
DropdownMenuItem(
value: ThemeSettings.system,
child: Text(tr('followSystem')),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.theme = value;
}
});
var colourDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('colour')),
value: settingsProvider.colour,
items: const [
DropdownMenuItem(
value: ColourSettings.basic,
child: Text('Obtainium'),
),
DropdownMenuItem(
value: ColourSettings.materialYou,
child: Text('Material You'),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.colour = value;
}
});
var sortDropdown = DropdownButtonFormField(
isExpanded: true,
@@ -274,29 +165,30 @@ class _SettingsPageState extends State<SettingsPage> {
}
});
var intervalSlider = Slider(
value: settingsProvider.updateIntervalSliderVal,
max: updateIntervalNodes.length.toDouble(),
divisions: updateIntervalNodes.length * 20,
label: updateIntervalLabel,
onChanged: (double value) {
setState(() {
settingsProvider.updateIntervalSliderVal = value;
processIntervalSliderValue(value);
var intervalDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('bgUpdateCheckInterval')),
value: settingsProvider.updateInterval,
items: updateIntervals.map((e) {
int displayNum = (e < 60
? e
: e < 1440
? e / 60
: e / 1440)
.round();
String display = e == 0
? tr('neverManualOnly')
: (e < 60
? plural('minute', displayNum)
: e < 1440
? plural('hour', displayNum)
: plural('day', displayNum));
return DropdownMenuItem(value: e, child: Text(display));
}).toList(),
onChanged: (value) {
if (value != null) {
settingsProvider.updateInterval = value;
}
});
},
onChangeStart: (double value) {
setState(() {
showIntervalLabel = false;
});
},
onChangeEnd: (double value) {
setState(() {
showIntervalLabel = true;
settingsProvider.updateInterval = updateInterval;
});
},
);
var sourceSpecificFields = sourceProvider.sources.map((e) {
if (e.sourceConfigSettingFormItems.isNotEmpty) {
@@ -347,26 +239,15 @@ class _SettingsPageState extends State<SettingsPage> {
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary),
),
//intervalDropdown,
height16,
if (showIntervalLabel)
SizedBox(
child: Text(
"${tr('bgUpdateCheckInterval')}: $updateIntervalLabel"))
else
const SizedBox(height: 16),
intervalSlider,
intervalDropdown,
FutureBuilder(
builder: (ctx, val) {
return (settingsProvider.updateInterval >
0) &&
(((val.data?.version.sdkInt ?? 0) >=
30) ||
settingsProvider.useShizuku)
return (val.data?.version.sdkInt ?? 0) >= 30
? Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
height16,
Row(
mainAxisAlignment:
MainAxisAlignment
@@ -470,22 +351,6 @@ class _SettingsPageState extends State<SettingsPage> {
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child:
Text(tr('removeOnExternalUninstall'))),
Switch(
value: settingsProvider
.removeOnExternalUninstall,
onChanged: (value) {
settingsProvider
.removeOnExternalUninstall = value;
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -498,85 +363,7 @@ class _SettingsPageState extends State<SettingsPage> {
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(tr(
'beforeNewInstallsShareToAppVerifier')),
GestureDetector(
onTap: () {
launchUrlString(
'https://github.com/soupslurpr/AppVerifier',
mode: LaunchMode
.externalApplication);
},
child: Text(
tr('about'),
style: const TextStyle(
decoration:
TextDecoration.underline,
fontSize: 12),
)),
],
)),
Switch(
value: settingsProvider
.beforeNewInstallsShareToAppVerifier,
onChanged: (value) {
settingsProvider
.beforeNewInstallsShareToAppVerifier =
value;
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('useShizuku'))),
Switch(
value: settingsProvider.useShizuku,
onChanged: (useShizuku) {
if (useShizuku) {
ShizukuApkInstaller.checkPermission()
.then((resCode) {
settingsProvider.useShizuku =
resCode!.startsWith('granted');
switch (resCode) {
case 'binder_not_found':
showError(
ObtainiumError(tr(
'shizukuBinderNotFound')),
context);
case 'old_shizuku':
showError(
ObtainiumError(
tr('shizukuOld')),
context);
case 'old_android_with_adb':
showError(
ObtainiumError(tr(
'shizukuOldAndroidWithADB')),
context);
case 'denied':
showError(
ObtainiumError(
tr('cancelled')),
context);
}
});
} else {
settingsProvider.useShizuku = false;
}
})
],
),
installMethodDropdown,
height32,
Text(
tr('sourceSpecific'),
@@ -592,49 +379,21 @@ class _SettingsPageState extends State<SettingsPage> {
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary),
),
DropdownButtonFormField(
decoration:
InputDecoration(labelText: tr('theme')),
value: settingsProvider.theme,
items: [
DropdownMenuItem(
value: ThemeSettings.system,
child: Text(tr('followSystem')),
),
DropdownMenuItem(
value: ThemeSettings.light,
child: Text(tr('light')),
),
DropdownMenuItem(
value: ThemeSettings.dark,
child: Text(tr('dark')),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.theme = value;
}
}),
height8,
if (settingsProvider.theme == ThemeSettings.system)
followSystemThemeExplanation,
themeDropdown,
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('useBlackTheme'))),
Switch(
value: settingsProvider.useBlackTheme,
onChanged: (value) {
settingsProvider.useBlackTheme = value;
})
],
),
colourDropdown,
height16,
if (settingsProvider.theme != ThemeSettings.light)
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('useBlackTheme'))),
Switch(
value: settingsProvider.useBlackTheme,
onChanged: (value) {
settingsProvider.useBlackTheme =
value;
})
]),
height8,
useMaterialThemeSwitch,
if (!settingsProvider.useMaterialYou) colorPicker,
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -648,46 +407,34 @@ class _SettingsPageState extends State<SettingsPage> {
),
height16,
localeDropdown,
FutureBuilder(
builder: (ctx, val) {
return (val.data?.version.sdkInt ?? 0) >= 34
? Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
height16,
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Flexible(
child: Text(tr(
'useSystemFont'))),
Switch(
value: settingsProvider
.useSystemFont,
onChanged:
(useSystemFont) {
if (useSystemFont) {
NativeFeatures
.loadSystemFont()
.then((val) {
settingsProvider
.useSystemFont =
true;
});
} else {
settingsProvider
.useSystemFont =
false;
}
})
])
])
: const SizedBox.shrink();
},
future: DeviceInfoPlugin().androidInfo),
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,
@@ -727,6 +474,22 @@ class _SettingsPageState extends State<SettingsPage> {
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child:
Text(tr('removeOnExternalUninstall'))),
Switch(
value: settingsProvider
.removeOnExternalUninstall,
onChanged: (value) {
settingsProvider
.removeOnExternalUninstall = value;
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -840,31 +603,17 @@ class _SettingsPageState extends State<SettingsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
TextButton.icon(
onPressed: () {
launchUrlString(settingsProvider.sourceUrl,
mode: LaunchMode.externalApplication);
},
icon: const Icon(Icons.code),
tooltip: tr('appSource'),
label: Text(
tr('appSource'),
),
),
IconButton(
onPressed: () {
launchUrlString('${settingsProvider.sourceUrl}/wiki',
mode: LaunchMode.externalApplication);
},
icon: const Icon(Icons.help_outline_rounded),
tooltip: tr('wiki'),
),
IconButton(
onPressed: () {
launchUrlString('https://apps.obtainium.imranr.dev/',
mode: LaunchMode.externalApplication);
},
icon: const Icon(Icons.apps_rounded),
tooltip: tr('crowdsourcedConfigsLabel'),
),
IconButton(
TextButton.icon(
onPressed: () {
context.read<LogsProvider>().get().then((logs) {
if (logs.isEmpty) {
@@ -880,7 +629,7 @@ class _SettingsPageState extends State<SettingsPage> {
});
},
icon: const Icon(Icons.bug_report_outlined),
tooltip: tr('appLogs'))
label: Text(tr('appLogs'))),
],
),
const SizedBox(

View File

@@ -5,15 +5,15 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:android_intent_plus/flag.dart';
import 'package:android_package_installer/android_package_installer.dart';
import 'package:android_package_manager/android_package_manager.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -29,12 +29,10 @@ import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:obtainium/providers/source_provider.dart';
import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart';
import 'package:flutter_archive/flutter_archive.dart';
import 'package:share_plus/share_plus.dart';
import 'package:shared_storage/shared_storage.dart' as saf;
import 'package:shizuku_apk_installer/shizuku_apk_installer.dart';
import 'native_provider.dart';
final pm = AndroidPackageManager();
@@ -142,20 +140,19 @@ List<MapEntry<String, int>> moveStrToEndMapEntryWithCount(
return arr;
}
Future<File> downloadFileWithRetry(String url, String fileName,
bool fileNameHasExt, Function? onProgress, String destDir,
Future<File> downloadFileWithRetry(
String url, String fileNameNoExt, Function? onProgress, String destDir,
{bool useExisting = true,
Map<String, String>? headers,
int retries = 3}) async {
try {
return await downloadFile(
url, fileName, fileNameHasExt, onProgress, destDir,
return await downloadApk(url, fileNameNoExt, onProgress, destDir,
useExisting: useExisting, headers: headers);
} catch (e) {
if (retries > 0 && e is ClientException) {
if (retries > 0 && e is DioException) {
await Future.delayed(const Duration(seconds: 5));
return await downloadFileWithRetry(
url, fileName, fileNameHasExt, onProgress, destDir,
url, fileNameNoExt, onProgress, destDir,
useExisting: useExisting, headers: headers, retries: (retries - 1));
} else {
rethrow;
@@ -186,36 +183,11 @@ Future<String> checkPartialDownloadHashDynamic(String url,
throw NoVersionError();
}
Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
{Map<String, String>? headers}) async {
var req = Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
req.headers[HttpHeaders.rangeHeader] = 'bytes=0-$bytesToGrab';
var client = http.Client();
var response = await client.send(req);
if (response.statusCode < 200 || response.statusCode > 299) {
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
}
List<List<int>> bytes = await response.stream.take(bytesToGrab).toList();
return hashListOfLists(bytes);
}
Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
Function? onProgress, String destDir,
Future<File> downloadApk(
String url, String fileNameNoExt, Function? onProgress, String destDir,
{bool useExisting = true, Map<String, String>? headers}) async {
// Send the initial request but cancel it as soon as you have the headers
var reqHeaders = headers ?? {};
var req = Request('GET', Uri.parse(url));
req.headers.addAll(reqHeaders);
var client = http.Client();
StreamedResponse response = await client.send(req);
var resHeaders = response.headers;
var resHeaders = await getHeaders(url, headers: headers);
// Use the headers to decide what the file extension is, and
// whether it supports partial downloads (range request), and
// what the total size of the file is (if provided)
String ext = resHeaders['content-disposition']?.split('.').last ?? 'apk';
if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1);
@@ -223,94 +195,129 @@ Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
ext = 'apk';
}
fileName = fileName.split('/').last; // Ensure the fileName is a file name
File downloadedFile = File('$destDir/$fileName.$ext');
if (fileNameHasExt) {
// If the user says the filename already has an ext, ignore whatever you inferred from above
downloadedFile = File('$destDir/$fileName');
}
File file = File('$destDir/$fileNameNoExt.$ext');
bool rangeFeatureEnabled = false;
if (resHeaders['accept-ranges']?.isNotEmpty == true) {
rangeFeatureEnabled =
resHeaders['accept-ranges']?.trim().toLowerCase() == 'bytes';
}
final contentLength = await getContentLengthIfRangeSupported(resHeaders);
// If you have an existing file that is usable,
// decide whether you can use it (either return full or resume partial)
var fullContentLength = response.contentLength;
if (useExisting && downloadedFile.existsSync()) {
var length = downloadedFile.lengthSync();
if (fullContentLength == null || !rangeFeatureEnabled) {
// If there is no content length reported, assume it the existing file is fully downloaded
// Also if the range feature is not supported, don't trust the content length if any (#1542)
client.close();
return downloadedFile;
if (useExisting && file.existsSync()) {
var length = file.lengthSync();
if (contentLength == null) {
return file;
} else {
// Check if resume needed/possible
if (length == fullContentLength) {
client.close();
return downloadedFile;
if (length == contentLength) {
return file;
}
if (length > fullContentLength) {
if (length > contentLength) {
useExisting = false;
}
}
}
// Download to a '.temp' file (to distinguish btn. complete/incomplete files)
File tempDownloadedFile = File('${downloadedFile.path}.part');
double progress = -1;
// If the range feature is not available (or you need to start a ranged req from 0),
// complete the already-started request, else cancel it and start a ranged request,
// and open the file for writing in the appropriate mode
var targetFileLength = useExisting && tempDownloadedFile.existsSync()
? tempDownloadedFile.lengthSync()
: null;
int rangeStart = targetFileLength ?? 0;
IOSink? sink;
if (rangeFeatureEnabled && fullContentLength != null && rangeStart > 0) {
client.close();
client = http.Client();
req = Request('GET', Uri.parse(url));
req.headers.addAll(reqHeaders);
req.headers.addAll({'range': 'bytes=$rangeStart-${fullContentLength - 1}'});
response = await client.send(req);
sink = tempDownloadedFile.openWrite(mode: FileMode.writeOnlyAppend);
} else if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.deleteSync(recursive: true);
}
sink ??= tempDownloadedFile.openWrite(mode: FileMode.writeOnly);
try {
if (contentLength == null) {
Response response = await dio.download(
url,
file.path,
options: Options(headers: headers),
onReceiveProgress: (count, total) {
progress = (total > 0 ? count / total * 100 : 30);
if (onProgress != null) {
onProgress(progress);
}
},
);
if ((response.statusCode ?? 200) < 200 ||
(response.statusCode ?? 200) > 299) {
throw response.statusMessage ?? tr('unexpectedError');
}
} else {
var targetFileLength =
useExisting && file.existsSync() ? file.lengthSync() : null;
int bufferSize = 1024 * 1024; // 1 Megabyte
final sink = file.openWrite(
mode: useExisting ? FileMode.writeOnlyAppend : FileMode.writeOnly,
);
int rangeStart = targetFileLength ?? 0;
int rangeEnd = min(
rangeStart + bufferSize - 1,
contentLength - 1,
);
if (onProgress != null) {
progress = ((rangeStart / contentLength) * 100);
onProgress(progress);
}
while (true) {
var headersCurrent = headers ?? {};
headersCurrent['range'] = 'bytes=$rangeStart-$rangeEnd';
Response response = await dio.get(
url,
onReceiveProgress: (count, total) {
if (onProgress != null) {
final newProgress =
(((rangeStart + count) / contentLength) * 100);
if (newProgress != progress) {
progress = newProgress;
onProgress(progress);
}
}
},
options: Options(
headers: headersCurrent,
responseType: ResponseType.bytes,
),
);
// Perform the download
var received = 0;
double? progress;
if (rangeStart > 0 && fullContentLength != null) {
received = rangeStart;
}
await response.stream.map((s) {
received += s.length;
progress =
(fullContentLength != null ? (received / fullContentLength) * 100 : 30);
if (onProgress != null) {
onProgress(progress);
if ((response.statusCode ?? 200) < 200 ||
(response.statusCode ?? 200) > 299) {
throw response.statusMessage ?? tr('unexpectedError');
}
final Uint8List data = response.data;
sink.add(data);
if (rangeEnd == contentLength - 1) {
break;
}
rangeStart = rangeEnd + 1;
rangeEnd = min(
rangeStart + bufferSize - 1,
contentLength - 1,
);
}
await sink.flush();
await sink.close();
}
} finally {
if (onProgress != null) {
onProgress(null);
}
return s;
}).pipe(sink);
await sink.close();
progress = null;
if (onProgress != null) {
onProgress(progress);
}
if (response.statusCode < 200 || response.statusCode > 299) {
tempDownloadedFile.deleteSync(recursive: true);
throw response.reasonPhrase ?? tr('unexpectedError');
return file;
}
Future<int?> getContentLengthIfRangeSupported(
Map<String, String> headers) async {
try {
int? contentLength;
{
var contentLengthHeaderValue = headers['content-length'];
if (contentLengthHeaderValue?.isNotEmpty == true) {
contentLength = int.tryParse(contentLengthHeaderValue!);
}
}
bool rangeFeatureEnabled = false;
if (headers['accept-ranges']?.isNotEmpty == true) {
rangeFeatureEnabled =
headers['accept-ranges']!.trim().toLowerCase() == 'bytes';
}
if (!rangeFeatureEnabled) {
contentLength = null;
}
return contentLength;
} catch (e) {
return null;
}
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.renameSync(downloadedFile.path);
}
client.close();
return downloadedFile;
}
Future<Map<String, String>> getHeaders(String url,
@@ -329,6 +336,58 @@ Future<Map<String, String>> getHeaders(String url,
return returnHeaders;
}
Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
{Map<String, String>? headers}) async {
var req = http.Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
req.headers[HttpHeaders.rangeHeader] = 'bytes=0-$bytesToGrab';
var client = http.Client();
var response = await client.send(req);
if (response.statusCode < 200 || response.statusCode > 299) {
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
}
List<List<int>> bytes = await response.stream.take(bytesToGrab).toList();
return hashListOfLists(bytes);
}
// Future<File> downloadFile(
// String url, String fileNameNoExt, Function? onProgress, String destDir,
// {bool useExisting = true, Map<String, String>? headers}) async {
// var resHead = await dio.head(url);
// String ext =
// resHead.headers.map['content-disposition']?[0].split('.').last ?? 'apk';
// if (ext.endsWith('"') || ext.endsWith("other")) {
// ext = ext.substring(0, ext.length - 1);
// }
// if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
// ext = 'apk';
// }
// File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
// if (!(downloadedFile.existsSync() && useExisting)) {
// double? progress;
// var response = await dio.download(
// url,
// downloadedFile.path,
// options: Options(headers: headers),
// onReceiveProgress: (count, total) {
// progress = (total > 0 ? count / total * 100 : 30);
// if (onProgress != null) {
// onProgress(progress);
// }
// },
// );
// if (onProgress != null) {
// onProgress(null);
// }
// if (response.statusCode != 200) {
// throw response.statusMessage ?? tr('unexpectedError');
// }
// }
// return downloadedFile;
// }
Future<PackageInfo?> getInstalledInfo(String? packageName,
{bool printErr = true}) async {
if (packageName != null) {
@@ -364,7 +423,7 @@ class AppsProvider with ChangeNotifier {
foregroundStream = FGBGEvents.stream.asBroadcastStream();
foregroundSubscription = foregroundStream?.listen((event) async {
isForeground = event == FGBGType.foreground;
if (isForeground) loadApps();
if (isForeground) await loadApps();
});
() async {
await settingsProvider.initializeSettings();
@@ -396,26 +455,30 @@ class AppsProvider with ChangeNotifier {
}();
}
Future<File> handleAPKIDChange(App app, PackageInfo newInfo,
Future<File> handleAPKIDChange(App app, PackageInfo? newInfo,
File downloadedFile, String downloadUrl) async {
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
// The former case should be handled (give the App its real ID), the latter is a security issue
var isTempIdBool = isTempId(app);
if (app.id != newInfo.packageName) {
if (apps[app.id] != null && !isTempIdBool && !app.allowIdChange) {
throw IDChangedError(newInfo.packageName!);
}
var idChangeWasAllowed = app.allowIdChange;
app.allowIdChange = false;
var originalAppId = app.id;
app.id = newInfo.packageName!;
downloadedFile = downloadedFile.renameSync(
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}');
if (apps[originalAppId] != null) {
await removeApps([originalAppId]);
await saveApps([app],
onlyIfExists: !isTempIdBool && !idChangeWasAllowed);
if (newInfo != null) {
if (app.id != newInfo.packageName) {
if (apps[app.id] != null && !isTempIdBool && !app.allowIdChange) {
throw IDChangedError(newInfo.packageName!);
}
var idChangeWasAllowed = app.allowIdChange;
app.allowIdChange = false;
var originalAppId = app.id;
app.id = newInfo.packageName!;
downloadedFile = downloadedFile.renameSync(
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}');
if (apps[originalAppId] != null) {
await removeApps([originalAppId]);
await saveApps([app],
onlyIfExists: !isTempIdBool && !idChangeWasAllowed);
}
}
} else if (isTempIdBool) {
throw ObtainiumError('Could not get ID from APK');
}
return downloadedFile;
}
@@ -441,8 +504,8 @@ class AppsProvider with ChangeNotifier {
var headers = await source.getRequestHeaders(app.additionalSettings,
forAPKDownload: true);
var downloadedFile = await downloadFileWithRetry(
downloadUrl, fileNameNoExt, false, headers: headers,
(double? progress) {
downloadUrl, fileNameNoExt,
headers: headers, (double? progress) {
int? prog = progress?.ceil();
if (apps[app.id] != null) {
apps[app.id]!.downloadProgress = progress;
@@ -479,10 +542,6 @@ class AppsProvider with ChangeNotifier {
newInfo =
await pm.getPackageArchiveInfo(archiveFilePath: apks.first.path);
}
if (newInfo == null) {
downloadedFile.delete();
throw ObtainiumError('Could not get ID from APK');
}
downloadedFile =
await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
// Delete older versions of the file if any
@@ -513,6 +572,9 @@ class AppsProvider with ChangeNotifier {
.isNotEmpty;
Future<bool> canInstallSilently(App app) async {
if (app.id == obtainiumId) {
return false;
}
if (!settingsProvider.enableBackgroundUpdates) {
return false;
}
@@ -520,7 +582,8 @@ class AppsProvider with ChangeNotifier {
return false;
}
if (app.apkUrls.length > 1) {
return false; // Manual API selection means silent install is not possible
// Manual API selection means silent install is not possible
return false;
}
var osInfo = await DeviceInfoPlugin().androidInfo;
@@ -531,30 +594,20 @@ class AppsProvider with ChangeNotifier {
?.installingPackageName
: (await pm.getInstallerPackageName(packageName: app.id));
} catch (e) {
return false; // App probably not installed
}
int? targetSDK =
(await getInstalledInfo(app.id))?.applicationInfo?.targetSdkVersion;
// The APK should target a new enough API
// https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setRequireUserAction(int)
if (!(targetSDK != null && targetSDK >= (osInfo.version.sdkInt - 3))) {
return false;
}
if (settingsProvider.useShizuku) {
return true;
}
if (app.id == obtainiumId) {
return false;
// Probably not installed - ignore
}
if (installerPackageName != obtainiumId) {
// If we did not install the app, silent install is not possible
// If we did not install the app (or it isn't installed), silent install is not possible
return false;
}
// The OS must also be new enough
return osInfo.version.sdkInt >= 31;
int? targetSDK =
(await getInstalledInfo(app.id))?.applicationInfo?.targetSdkVersion;
// The OS must also be new enough and the APK should target a new enough API
return osInfo.version.sdkInt >= 31 &&
targetSDK != null &&
targetSDK >= // https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setRequireUserAction(int)
(osInfo.version.sdkInt - 3);
}
Future<void> waitForUserToReturnToForeground(BuildContext context) async {
@@ -576,15 +629,13 @@ class AppsProvider with ChangeNotifier {
zipFile: File(filePath), destinationDir: Directory(destinationPath));
}
Future<bool> installXApkDir(
DownloadedXApkDir dir, BuildContext? firstTimeWithContext,
{bool needsBGWorkaround = false,
bool shizukuPretendToBeGooglePlay = false}) async {
Future<void> installXApkDir(DownloadedXApkDir dir,
{bool needsBGWorkaround = false}) async {
// We don't know which APKs in an XAPK are supported by the user's device
// So we try installing all of them and assume success if at least one installed
// If 0 APKs installed, throw the first install error encountered
var somethingInstalled = false;
try {
var somethingInstalled = false;
MultiAppMultiError errors = MultiAppMultiError();
for (var file in dir.extracted
.listSync(recursive: true, followLinks: false)
@@ -592,10 +643,8 @@ class AppsProvider with ChangeNotifier {
if (file.path.toLowerCase().endsWith('.apk')) {
try {
somethingInstalled = somethingInstalled ||
await installApk(
DownloadedApk(dir.appId, file), firstTimeWithContext,
needsBGWorkaround: needsBGWorkaround,
shizukuPretendToBeGooglePlay: shizukuPretendToBeGooglePlay);
await installApk(DownloadedApk(dir.appId, file),
needsBGWorkaround: needsBGWorkaround);
} catch (e) {
logs.add(
'Could not install APK from XAPK \'${file.path}\': ${e.toString()}');
@@ -613,23 +662,10 @@ class AppsProvider with ChangeNotifier {
} finally {
dir.extracted.delete(recursive: true);
}
return somethingInstalled;
}
Future<bool> installApk(
DownloadedApk file, BuildContext? firstTimeWithContext,
{bool needsBGWorkaround = false,
bool shizukuPretendToBeGooglePlay = false}) async {
if (firstTimeWithContext != null &&
settingsProvider.beforeNewInstallsShareToAppVerifier &&
(await getInstalledInfo('dev.soupslurpr.appverifier')) != null) {
XFile f = XFile.fromData(file.file.readAsBytesSync(),
mimeType: 'application/vnd.android.package-archive');
Fluttertoast.showToast(
msg: tr('appVerifierInstructionToast'),
toastLength: Toast.LENGTH_LONG);
await Share.shareXFiles([f]);
}
Future<bool> installApk(DownloadedApk file,
{bool needsBGWorkaround = false}) async {
var newInfo =
await pm.getPackageArchiveInfo(archiveFilePath: file.file.path);
if (newInfo == null) {
@@ -647,7 +683,8 @@ class AppsProvider with ChangeNotifier {
!(await canDowngradeApps())) {
throw DowngradeError();
}
if (needsBGWorkaround) {
if (needsBGWorkaround &&
settingsProvider.installMethod == InstallMethodSettings.normal) {
// The below 'await' will never return if we are in a background process
// To work around this, we should assume the install will be successful
// So we update the app's installed version first as we will never get to the later code
@@ -659,22 +696,24 @@ class AppsProvider with ChangeNotifier {
attemptToCorrectInstallStatus: false);
}
int? code;
if (!settingsProvider.useShizuku) {
code =
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
} else {
code = await ShizukuApkInstaller.installAPK(file.file.uri.toString(),
shizukuPretendToBeGooglePlay ? "com.android.vending" : "");
switch (settingsProvider.installMethod) {
case InstallMethodSettings.normal:
code = await AndroidPackageInstaller.installApk(
apkFilePath: file.file.path);
case InstallMethodSettings.shizuku:
code = (await NativeFeatures.installWithShizuku(
apkFileUri: file.file.uri.toString()))
? 0
: 1;
case InstallMethodSettings.root:
code =
(await NativeFeatures.installWithRoot(apkFilePath: file.file.path))
? 0
: 1;
}
bool installed = false;
if (code != null && code != 0 && code != 3) {
try {
file.file.deleteSync(recursive: true);
} catch (e) {
//
} finally {
throw InstallError(code);
}
throw InstallError(code);
} else if (code == 0) {
installed = true;
apps[file.appId]!.app.installedVersion =
@@ -709,28 +748,23 @@ class AppsProvider with ChangeNotifier {
await intent.launch();
}
Future<MapEntry<String, String>?> confirmAppFileUrl(
App app, BuildContext? context, bool pickAnyAsset) async {
var urlsToSelectFrom = app.apkUrls;
if (pickAnyAsset) {
urlsToSelectFrom = [...urlsToSelectFrom, ...app.otherAssetUrls];
}
Future<MapEntry<String, String>?> confirmApkUrl(
App app, BuildContext? context) async {
// If the App has more than one APK, the user should pick one (if context provided)
MapEntry<String, String>? appFileUrl = urlsToSelectFrom[
app.preferredApkIndex >= 0 ? app.preferredApkIndex : 0];
MapEntry<String, String>? apkUrl =
app.apkUrls[app.preferredApkIndex >= 0 ? app.preferredApkIndex : 0];
// get device supported architecture
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
if (urlsToSelectFrom.length > 1 && context != null) {
appFileUrl = await showDialog(
// ignore: use_build_context_synchronously
if (app.apkUrls.length > 1 && context != null) {
// ignore: use_build_context_synchronously
apkUrl = await showDialog(
context: context,
builder: (BuildContext ctx) {
return AppFilePicker(
return APKPicker(
app: app,
initVal: appFileUrl,
initVal: apkUrl,
archs: archs,
pickAnyAsset: pickAnyAsset,
);
});
}
@@ -740,22 +774,23 @@ class AppsProvider with ChangeNotifier {
}
// If the picked APK comes from an origin different from the source, get user confirmation (if context provided)
if (appFileUrl != null &&
getHost(appFileUrl.value) != getHost(app.url) &&
if (apkUrl != null &&
getHost(apkUrl.value) != getHost(app.url) &&
context != null) {
// ignore: use_build_context_synchronously
if (!(settingsProvider.hideAPKOriginWarning) &&
// ignore: use_build_context_synchronously
await showDialog(
// ignore: use_build_context_synchronously
context: context,
builder: (BuildContext ctx) {
return APKOriginWarningDialog(
sourceUrl: app.url, apkUrl: appFileUrl!.value);
sourceUrl: app.url, apkUrl: apkUrl!.value);
}) !=
true) {
appFileUrl = null;
apkUrl = null;
}
}
return appFileUrl;
return apkUrl;
}
// Given a list of AppIds, uses stored info about the apps to download APKs and install them
@@ -782,7 +817,7 @@ class AppsProvider with ChangeNotifier {
var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true;
if (!trackOnly) {
// ignore: use_build_context_synchronously
apkUrl = await confirmAppFileUrl(apps[id]!.app, context, false);
apkUrl = await confirmApkUrl(apps[id]!.app, context);
}
if (apkUrl != null) {
int urlInd = apps[id]!
@@ -833,23 +868,25 @@ class AppsProvider with ChangeNotifier {
}
id = downloadedFile?.appId ?? downloadedDir!.appId;
bool willBeSilent = await canInstallSilently(apps[id]!.app);
if (!settingsProvider.useShizuku) {
if (!(await settingsProvider.getInstallPermission(enforce: false))) {
throw ObtainiumError(tr('cancelled'));
}
} else {
switch ((await ShizukuApkInstaller.checkPermission())!) {
case 'binder_not_found':
throw ObtainiumError(tr('shizukuBinderNotFound'));
case 'old_shizuku':
throw ObtainiumError(tr('shizukuOld'));
case 'old_android_with_adb':
throw ObtainiumError(tr('shizukuOldAndroidWithADB'));
case 'denied':
switch (settingsProvider.installMethod) {
case InstallMethodSettings.normal:
if (!(await settingsProvider.getInstallPermission(
enforce: false))) {
throw ObtainiumError(tr('cancelled'));
}
}
case InstallMethodSettings.shizuku:
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 NativeFeatures.checkPermissionRoot())) {
throw ObtainiumError(tr('cancelled'));
}
}
if (!willBeSilent && context != null && !settingsProvider.useShizuku) {
if (!willBeSilent && context != null) {
// ignore: use_build_context_synchronously
await waitForUserToReturnToForeground(context);
}
@@ -857,54 +894,25 @@ class AppsProvider with ChangeNotifier {
notifyListeners();
try {
if (!skipInstalls) {
bool sayInstalled = true;
var contextIfNewInstall =
apps[id]?.installedInfo == null ? context : null;
bool needBGWorkaround =
willBeSilent && context == null && !settingsProvider.useShizuku;
if (downloadedFile != null) {
if (needBGWorkaround) {
// ignore: use_build_context_synchronously
installApk(downloadedFile, contextIfNewInstall,
needsBGWorkaround: true);
if (willBeSilent && context == null) {
installApk(downloadedFile, needsBGWorkaround: true);
} else {
// ignore: use_build_context_synchronously
sayInstalled = await installApk(
downloadedFile, contextIfNewInstall,
shizukuPretendToBeGooglePlay:
apps[id]!.app.additionalSettings[
'shizukuPretendToBeGooglePlay'] ==
true);
await installApk(downloadedFile);
}
} else {
if (needBGWorkaround) {
// ignore: use_build_context_synchronously
installXApkDir(downloadedDir!, contextIfNewInstall,
needsBGWorkaround: true);
if (willBeSilent && context == null) {
installXApkDir(downloadedDir!, needsBGWorkaround: true);
} else {
// ignore: use_build_context_synchronously
sayInstalled = await installXApkDir(
downloadedDir!, contextIfNewInstall,
shizukuPretendToBeGooglePlay:
apps[id]!.app.additionalSettings[
'shizukuPretendToBeGooglePlay'] ==
true);
await installXApkDir(downloadedDir!);
}
}
if (willBeSilent && context == null) {
if (!settingsProvider.useShizuku) {
notificationsProvider?.notify(SilentUpdateAttemptNotification(
[apps[id]!.app],
id: id.hashCode));
} else {
notificationsProvider?.notify(SilentUpdateNotification(
[apps[id]!.app], sayInstalled,
id: id.hashCode));
}
}
if (sayInstalled) {
installedIds.add(id);
notificationsProvider?.notify(SilentUpdateAttemptNotification(
[apps[id]!.app],
id: id.hashCode));
}
installedIds.add(id);
}
} finally {
apps[id]?.downloadProgress = null;
@@ -937,78 +945,6 @@ class AppsProvider with ChangeNotifier {
return installedIds;
}
Future<List<String>> downloadAppAssets(
List<String> appIds, BuildContext context,
{bool forceParallelDownloads = false}) async {
NotificationsProvider notificationsProvider =
context.read<NotificationsProvider>();
List<MapEntry<MapEntry<String, String>, App>> filesToDownload = [];
for (var id in appIds) {
if (apps[id] == null) {
throw ObtainiumError(tr('appNotFound'));
}
MapEntry<String, String>? fileUrl;
if (apps[id]!.app.apkUrls.isNotEmpty ||
apps[id]!.app.otherAssetUrls.isNotEmpty) {
// ignore: use_build_context_synchronously
fileUrl = await confirmAppFileUrl(apps[id]!.app, context, true);
}
if (fileUrl != null) {
filesToDownload.add(MapEntry(fileUrl, apps[id]!.app));
}
}
// Prepare to download+install Apps
MultiAppMultiError errors = MultiAppMultiError();
List<String> downloadedIds = [];
Future<void> downloadFn(MapEntry<String, String> fileUrl, App app) async {
try {
var exportDir = await settingsProvider.getExportDir();
String downloadPath = '/storage/emulated/0/Download';
bool downloadsAccessible = false;
try {
downloadsAccessible = Directory(downloadPath).existsSync();
} catch (e) {
//
}
if (!downloadsAccessible && exportDir != null) {
downloadPath = exportDir.path;
}
await downloadFile(fileUrl.value, fileUrl.key, true,
(double? progress) {
notificationsProvider
.notify(DownloadNotification(fileUrl.key, progress?.ceil() ?? 0));
}, downloadPath,
headers: await SourceProvider()
.getSource(app.url, overrideSource: app.overrideSource)
.getRequestHeaders(app.additionalSettings,
forAPKDownload:
fileUrl.key.endsWith('.apk') ? true : false),
useExisting: false);
notificationsProvider
.notify(DownloadedNotification(fileUrl.key, fileUrl.value));
} catch (e) {
errors.add(fileUrl.key, e);
} finally {
notificationsProvider.cancel(DownloadNotification(fileUrl.key, 0).id);
}
}
if (forceParallelDownloads || !settingsProvider.parallelDownloads) {
for (var urlWithApp in filesToDownload) {
await downloadFn(urlWithApp.key, urlWithApp.value);
}
} else {
await Future.wait(filesToDownload
.map((urlWithApp) => downloadFn(urlWithApp.key, urlWithApp.value)));
}
if (errors.idsByErrorString.isNotEmpty) {
throw errors;
}
return downloadedIds;
}
Future<Directory> getAppsDir() async {
Directory appsDir =
Directory('${(await getExternalStorageDirectory())!.path}/app_data');
@@ -1516,7 +1452,7 @@ class AppsProvider with ChangeNotifier {
return returnPath;
}
Future<MapEntry<List<App>, bool>> import(String appsJSON) async {
Future<MapEntry<int, bool>> import(String appsJSON) async {
var decodedJSON = jsonDecode(appsJSON);
var newFormat = decodedJSON is! List;
List<App> importedApps =
@@ -1550,8 +1486,8 @@ class AppsProvider with ChangeNotifier {
}
});
}
return MapEntry<List<App>, bool>(
importedApps, newFormat && decodedJSON['settings'] != null);
return MapEntry<int, bool>(
importedApps.length, newFormat && decodedJSON['settings'] != null);
}
@override
@@ -1580,49 +1516,38 @@ class AppsProvider with ChangeNotifier {
}
}
class AppFilePicker extends StatefulWidget {
const AppFilePicker(
{super.key,
required this.app,
this.initVal,
this.archs,
this.pickAnyAsset = false});
class APKPicker extends StatefulWidget {
const APKPicker({super.key, required this.app, this.initVal, this.archs});
final App app;
final MapEntry<String, String>? initVal;
final List<String>? archs;
final bool pickAnyAsset;
@override
State<AppFilePicker> createState() => _AppFilePickerState();
State<APKPicker> createState() => _APKPickerState();
}
class _AppFilePickerState extends State<AppFilePicker> {
MapEntry<String, String>? fileUrl;
class _APKPickerState extends State<APKPicker> {
MapEntry<String, String>? apkUrl;
@override
Widget build(BuildContext context) {
fileUrl ??= widget.initVal;
var urlsToSelectFrom = widget.app.apkUrls;
if (widget.pickAnyAsset) {
urlsToSelectFrom = [...urlsToSelectFrom, ...widget.app.otherAssetUrls];
}
apkUrl ??= widget.initVal;
return AlertDialog(
scrollable: true,
title: Text(widget.pickAnyAsset
? tr('selectX', args: [tr('releaseAsset').toLowerCase()])
: tr('pickAnAPK')),
title: Text(tr('pickAnAPK')),
content: Column(children: [
Text(tr('appHasMoreThanOnePackage', args: [widget.app.finalName])),
const SizedBox(height: 16),
...urlsToSelectFrom.map(
...widget.app.apkUrls.map(
(u) => RadioListTile<String>(
title: Text(u.key),
value: u.value,
groupValue: fileUrl!.value,
groupValue: apkUrl!.value,
onChanged: (String? val) {
setState(() {
fileUrl = urlsToSelectFrom.where((e) => e.value == val).first;
apkUrl =
widget.app.apkUrls.where((e) => e.value == val).first;
});
}),
),
@@ -1649,7 +1574,7 @@ class _AppFilePickerState extends State<AppFilePicker> {
TextButton(
onPressed: () {
HapticFeedback.selectionClick();
Navigator.of(context).pop(fileUrl);
Navigator.of(context).pop(apkUrl);
},
child: Text(tr('continue')))
],
@@ -1726,7 +1651,7 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
int maxRetryWaitSeconds = 5;
var netResult = await (Connectivity().checkConnectivity());
if (netResult.contains(ConnectivityResult.none)) {
if (netResult == ConnectivityResult.none) {
logs.add('BG update task: No network.');
return;
}
@@ -1763,8 +1688,8 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
var networkRestricted = false;
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
networkRestricted = !netResult.contains(ConnectivityResult.wifi) &&
!netResult.contains(ConnectivityResult.ethernet);
networkRestricted = (netResult != ConnectivityResult.wifi) &&
(netResult != ConnectivityResult.ethernet);
}
if (toCheck.isNotEmpty) {
@@ -1808,8 +1733,8 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
var networkRestricted = false;
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
var netResult = await (Connectivity().checkConnectivity());
networkRestricted = !netResult.contains(ConnectivityResult.wifi) &&
!netResult.contains(ConnectivityResult.ethernet);
networkRestricted = (netResult != ConnectivityResult.wifi) &&
(netResult != ConnectivityResult.ethernet);
}
try {
@@ -1832,7 +1757,7 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
// Next task interval is based on the error with the longest retry time
int minRetryIntervalForThisApp = err is RateLimitError
? (err.remainingMinutes * 60)
: e is ClientException
: e is DioException
? (15 * 60)
: (toCheckApp.value + 1);
if (minRetryIntervalForThisApp > maxRetryWaitSeconds) {

View File

@@ -1,22 +1,75 @@
import 'dart:async';
import 'dart:io';
import 'package:android_system_font/android_system_font.dart';
import 'package:flutter/services.dart';
class NativeFeatures {
static const MethodChannel _channel = MethodChannel('native');
static bool _systemFontLoaded = false;
static bool _callbacksApplied = false;
static int _resPermShizuku = -2; // not set
static Future<ByteData> _readFileBytes(String path) async {
var bytes = await File(path).readAsBytes();
var file = File(path);
var bytes = await file.readAsBytes();
return ByteData.view(bytes.buffer);
}
static Future loadSystemFont() async {
if (_systemFontLoaded) return;
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() {
if (test()) {
Timer(pollInterval, check);
} else {
completer.complete();
}
}
check();
return completer.future;
}
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');
var fontFilePath = await AndroidSystemFont().getFilePath();
fontLoader.addFont(_readFileBytes(fontFilePath!));
fontLoader.load();
fontLoader.addFont(_readFileBytes(getFontRes));
await fontLoader.load();
_systemFontLoaded = true;
return "ok";
}
static Future<int> checkPermissionShizuku() async {
if (!_callbacksApplied) {
_channel.setMethodCallHandler(_handleCalls);
_callbacksApplied = true;
}
int res = await _channel.invokeMethod('checkPermissionShizuku');
if (res == -2) {
await _waitWhile(() => _resPermShizuku == -2);
res = _resPermShizuku;
_resPermShizuku = -2;
}
return res;
}
static Future<bool> checkPermissionRoot() async {
return await _channel.invokeMethod('checkPermissionRoot');
}
static Future<bool> installWithShizuku({required String apkFileUri}) async {
return await _channel.invokeMethod(
'installWithShizuku', {'apkFileUri': apkFileUri});
}
static Future<bool> installWithRoot({required String apkFilePath}) async {
return await _channel.invokeMethod(
'installWithRoot', {'apkFilePath': apkFilePath});
}
}

View File

@@ -41,26 +41,20 @@ class UpdateNotification extends ObtainiumNotification {
}
class SilentUpdateNotification extends ObtainiumNotification {
SilentUpdateNotification(List<App> updates, bool succeeded, {int? id})
SilentUpdateNotification(List<App> updates, {int? id})
: super(
id ?? 3,
succeeded
? tr('appsUpdated')
: tr('appsNotUpdated'),
tr('appsUpdated'),
'',
'APPS_UPDATED',
tr('appsUpdatedNotifChannel'),
tr('appsUpdatedNotifDescription'),
Importance.defaultImportance) {
message = updates.length == 1
? tr(succeeded
? 'xWasUpdatedToY'
: 'xWasNotUpdatedToY',
args: [updates[0].finalName, updates[0].latestVersion])
: plural(succeeded
? 'xAndNMoreUpdatesInstalled'
: "xAndNMoreUpdatesFailed",
updates.length - 1, args: [updates[0].finalName, (updates.length - 1).toString()]);
? tr('xWasUpdatedToY',
args: [updates[0].finalName, updates[0].latestVersion])
: plural('xAndNMoreUpdatesInstalled', updates.length - 1,
args: [updates[0].finalName, (updates.length - 1).toString()]);
}
}
@@ -126,18 +120,6 @@ class DownloadNotification extends ObtainiumNotification {
progPercent: progPercent);
}
class DownloadedNotification extends ObtainiumNotification {
DownloadedNotification(String fileName, String downloadUrl)
: super(
downloadUrl.hashCode,
tr('downloadedX', args: [fileName]),
'',
'FILE_DOWNLOADED',
tr('downloadedXNotifChannel', args: [tr('app')]),
tr('downloadedX', args: [tr('app')]),
Importance.defaultImportance);
}
final completeInstallationNotification = ObtainiumNotification(
1,
tr('completeAppInstallation'),

View File

@@ -17,14 +17,41 @@ import 'package:shared_storage/shared_storage.dart' as saf;
String obtainiumTempId = 'imranr98_obtainium_${GitHub().hosts[0]}';
String obtainiumId = 'dev.imranr.obtainium';
String obtainiumUrl = 'https://github.com/ImranR98/Obtainium';
Color obtainiumThemeColor = const Color(0xFF6438B5);
enum InstallMethodSettings { normal, shizuku, root }
enum ThemeSettings { system, light, dark }
enum ColourSettings { basic, materialYou }
enum SortColumnSettings { added, nameAuthor, authorName, releaseDate }
enum SortOrderSettings { ascending, descending }
const maxAPIRateLimitMinutes = 30;
const minUpdateIntervalMinutes = maxAPIRateLimitMinutes + 30;
const maxUpdateIntervalMinutes = 43200;
List<int> updateIntervals = [
15,
30,
60,
120,
180,
360,
720,
1440,
4320,
10080,
20160,
43200,
0
]
.where((element) =>
(element >= minUpdateIntervalMinutes &&
element <= maxUpdateIntervalMinutes) ||
element == 0)
.toList();
class SettingsProvider with ChangeNotifier {
SharedPreferences? prefs;
String? defaultAppDir;
@@ -48,12 +75,13 @@ class SettingsProvider with ChangeNotifier {
notifyListeners();
}
bool get useShizuku{
return prefs?.getBool('useShizuku') ?? false;
InstallMethodSettings get installMethod {
return InstallMethodSettings.values[
prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
}
set useShizuku(bool useShizuku) {
prefs?.setBool('useShizuku', useShizuku);
set installMethod(InstallMethodSettings t) {
prefs?.setInt('installMethod', t.index);
notifyListeners();
}
@@ -67,23 +95,13 @@ class SettingsProvider with ChangeNotifier {
notifyListeners();
}
Color get themeColor {
int? colorCode = prefs?.getInt('themeColor');
return (colorCode != null) ?
Color(colorCode) : obtainiumThemeColor;
ColourSettings get colour {
return ColourSettings
.values[prefs?.getInt('colour') ?? ColourSettings.basic.index];
}
set themeColor(Color themeColor) {
prefs?.setInt('themeColor', themeColor.value);
notifyListeners();
}
bool get useMaterialYou {
return prefs?.getBool('useMaterialYou') ?? false;
}
set useMaterialYou(bool useMaterialYou) {
prefs?.setBool('useMaterialYou', useMaterialYou);
set colour(ColourSettings t) {
prefs?.setInt('colour', t.index);
notifyListeners();
}
@@ -97,20 +115,21 @@ class SettingsProvider with ChangeNotifier {
}
int get updateInterval {
return prefs?.getInt('updateInterval') ?? 360;
var min = prefs?.getInt('updateInterval') ?? 360;
if (!updateIntervals.contains(min)) {
var temp = updateIntervals[0];
for (var i in updateIntervals) {
if (min > i && i != 0) {
temp = i;
}
}
min = temp;
}
return min;
}
set updateInterval(int min) {
prefs?.setInt('updateInterval', min);
notifyListeners();
}
double get updateIntervalSliderVal {
return prefs?.getDouble('updateIntervalSliderVal') ?? 6.0;
}
set updateIntervalSliderVal(double val) {
prefs?.setDouble('updateIntervalSliderVal', val);
prefs?.setInt('updateInterval', (min < 15 && min != 0) ? 15 : min);
notifyListeners();
}
@@ -460,13 +479,4 @@ class SettingsProvider with ChangeNotifier {
prefs?.setStringList('searchDeselected', list);
notifyListeners();
}
bool get beforeNewInstallsShareToAppVerifier {
return prefs?.getBool('beforeNewInstallsShareToAppVerifier') ?? true;
}
set beforeNewInstallsShareToAppVerifier(bool val) {
prefs?.setBool('beforeNewInstallsShareToAppVerifier', val);
notifyListeners();
}
}

View File

@@ -4,9 +4,9 @@
import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/aptoide.dart';
@@ -31,6 +31,7 @@ import 'package:obtainium/app_sources/vlc.dart';
import 'package:obtainium/app_sources/whatsapp.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart';
import 'package:obtainium/providers/settings_provider.dart';
@@ -47,10 +48,9 @@ class APKDetails {
late AppNames names;
late DateTime? releaseDate;
late String? changeLog;
late List<MapEntry<String, String>> allAssetUrls;
APKDetails(this.version, this.apkUrls, this.names,
{this.releaseDate, this.changeLog, this.allAssetUrls = const []});
{this.releaseDate, this.changeLog});
}
stringMapListTo2DList(List<MapEntry<String, String>> mapList) =>
@@ -224,7 +224,6 @@ class App {
String? installedVersion;
late String latestVersion;
List<MapEntry<String, String>> apkUrls = [];
List<MapEntry<String, String>> otherAssetUrls = [];
late int preferredApkIndex;
late Map<String, dynamic> additionalSettings;
late DateTime? lastUpdateCheck;
@@ -250,8 +249,7 @@ class App {
this.releaseDate,
this.changeLog,
this.overrideSource,
this.allowIdChange = false,
this.otherAssetUrls = const []});
this.allowIdChange = false});
@override
String toString() {
@@ -283,44 +281,41 @@ class App {
changeLog: changeLog,
releaseDate: releaseDate,
overrideSource: overrideSource,
allowIdChange: allowIdChange,
otherAssetUrls: otherAssetUrls);
allowIdChange: allowIdChange);
factory App.fromJson(Map<String, dynamic> json) {
json = appJSONCompatibilityModifiers(json);
return App(
json['id'] as String,
json['url'] as String,
json['author'] as String,
json['name'] as String,
json['installedVersion'] == null
? null
: json['installedVersion'] as String,
(json['latestVersion'] ?? tr('unknown')) as String,
assumed2DlistToStringMapList(
jsonDecode((json['apkUrls'] ?? '[["placeholder", "placeholder"]]'))),
(json['preferredApkIndex'] ?? -1) as int,
jsonDecode(json['additionalSettings']) as Map<String, dynamic>,
json['lastUpdateCheck'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
json['pinned'] ?? false,
categories: json['categories'] != null
? (json['categories'] as List<dynamic>)
.map((e) => e.toString())
.toList()
: json['category'] != null
? [json['category'] as String]
: [],
releaseDate: json['releaseDate'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
changeLog: json['changeLog'] == null ? null : json['changeLog'] as String,
overrideSource: json['overrideSource'],
allowIdChange: json['allowIdChange'] ?? false,
otherAssetUrls: assumed2DlistToStringMapList(
jsonDecode((json['otherAssetUrls'] ?? '[]'))),
);
json['id'] as String,
json['url'] as String,
json['author'] as String,
json['name'] as String,
json['installedVersion'] == null
? null
: json['installedVersion'] as String,
(json['latestVersion'] ?? tr('unknown')) as String,
assumed2DlistToStringMapList(jsonDecode(
(json['apkUrls'] ?? '[["placeholder", "placeholder"]]'))),
(json['preferredApkIndex'] ?? -1) as int,
jsonDecode(json['additionalSettings']) as Map<String, dynamic>,
json['lastUpdateCheck'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
json['pinned'] ?? false,
categories: json['categories'] != null
? (json['categories'] as List<dynamic>)
.map((e) => e.toString())
.toList()
: json['category'] != null
? [json['category'] as String]
: [],
releaseDate: json['releaseDate'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
changeLog:
json['changeLog'] == null ? null : json['changeLog'] as String,
overrideSource: json['overrideSource'],
allowIdChange: json['allowIdChange'] ?? false);
}
Map<String, dynamic> toJson() => {
@@ -331,7 +326,6 @@ class App {
'installedVersion': installedVersion,
'latestVersion': latestVersion,
'apkUrls': jsonEncode(stringMapListTo2DList(apkUrls)),
'otherAssetUrls': jsonEncode(stringMapListTo2DList(otherAssetUrls)),
'preferredApkIndex': preferredApkIndex,
'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
@@ -449,16 +443,9 @@ abstract class AppSource {
String url, Map<String, dynamic> additionalSettings,
{bool followRedirects = true}) async {
var requestHeaders = await getRequestHeaders(additionalSettings);
if (requestHeaders != null || followRedirects == false) {
var req = Request('GET', Uri.parse(url));
req.followRedirects = followRedirects;
if (requestHeaders != null) {
req.headers.addAll(requestHeaders);
}
return Response.fromStream(await Client().send(req));
} else {
return get(Uri.parse(url));
}
return await dio.get(url,
options:
Options(headers: requestHeaders, followRedirects: followRedirects));
}
String sourceSpecificStandardizeURL(String url) {
@@ -521,11 +508,6 @@ abstract class AppSource {
label: tr('autoApkFilterByArch'), defaultValue: true)
],
[GeneratedFormTextField('appName', label: tr('appName'), required: false)],
[
GeneratedFormSwitch('shizukuPretendToBeGooglePlay',
label: tr('shizukuPretendToBeGooglePlay'),
defaultValue: false)
],
[
GeneratedFormSwitch('exemptFromBackgroundUpdates',
label: tr('exemptFromBackgroundUpdates'))
@@ -630,10 +612,10 @@ abstract class AppSource {
}
ObtainiumError getObtainiumHttpError(Response res) {
return ObtainiumError((res.reasonPhrase != null &&
res.reasonPhrase != null &&
res.reasonPhrase!.isNotEmpty)
? res.reasonPhrase!
return ObtainiumError((res.statusMessage != null &&
res.statusMessage != null &&
res.statusMessage!.isNotEmpty)
? res.statusMessage!
: tr('errorWithHttpStatusCode', args: [res.statusCode.toString()]));
}
@@ -748,6 +730,7 @@ class SourceProvider {
FDroid(),
FDroidRepo(),
IzzyOnDroid(),
SourceForge(),
SourceHut(),
APKPure(),
Aptoide(),
@@ -904,10 +887,8 @@ class SourceProvider {
allowIdChange: currentApp?.allowIdChange ??
trackOnly ||
(source.appIdInferIsOptional &&
inferAppIdIfOptional), // Optional ID inferring may be incorrect - allow correction on first install
otherAssetUrls: apk.allAssetUrls
.where((a) => apk.apkUrls.indexWhere((p) => a.key == p.key) < 0)
.toList());
inferAppIdIfOptional) // Optional ID inferring may be incorrect - allow correction on first install
);
return source.endOfGetAppChanges(finalApp);
}

View File

@@ -5,10 +5,10 @@ packages:
dependency: "direct main"
description:
name: android_intent_plus
sha256: "2bfdbee8d65e7c26f88b66f0a91f2863da4d3596d8a658b4162c8de5cf04b074"
sha256: e1c62bb41c90e15083b7fb84dc327fe90396cc9c1445b55ff1082144fabfb4d9
url: "https://pub.dev"
source: hosted
version: "5.0.2"
version: "4.0.3"
android_package_installer:
dependency: "direct main"
description:
@@ -26,15 +26,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.1"
android_system_font:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: "355f897e92a58a803f91d9270d389d9ec40ba550"
url: "https://github.com/re7gog/android_system_font"
source: git
version: "1.0.0"
animations:
dependency: "direct main"
description:
@@ -47,26 +38,26 @@ packages:
dependency: "direct main"
description:
name: app_links
sha256: "1c2b9e9c56d80d17610bcbd111b37187875c5d0ded8654caa1bda14ea753d001"
sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4"
url: "https://pub.dev"
source: hosted
version: "6.0.1"
version: "3.5.1"
archive:
dependency: transitive
description:
name: archive
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev"
source: hosted
version: "3.5.1"
version: "3.4.10"
args:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.4.2"
async:
dependency: transitive
description:
@@ -79,10 +70,10 @@ packages:
dependency: "direct main"
description:
name: background_fetch
sha256: "2fe367c9be0e256dadb75b8b637b0b58a2a2d2317b7c8420bb1ae8b41e23fde3"
sha256: eb3af263d390d7e68ecb90f2ae984d2bfd96dceb4c7b4f72418dd5383b49de0a
url: "https://pub.dev"
source: hosted
version: "1.3.4"
version: "1.2.4"
boolean_selector:
dependency: transitive
description:
@@ -135,18 +126,26 @@ packages:
dependency: "direct main"
description:
name: connectivity_plus
sha256: db7a4e143dc72cc3cb2044ef9b052a7ebfe729513e6a82943bc3526f784365b8
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
version: "5.0.2"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: b6a56efe1e6675be240de39107281d4034b64ac23438026355b4234042a35adb
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
url: "https://pub.dev"
source: hosted
version: "2.0.0"
version: "1.2.4"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
@@ -175,10 +174,10 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.8"
version: "1.0.6"
dbus:
dependency: transitive
description:
@@ -191,10 +190,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110"
url: "https://pub.dev"
source: hosted
version: "10.1.0"
version: "9.1.2"
device_info_plus_platform_interface:
dependency: transitive
description:
@@ -203,6 +202,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
dio:
dependency: "direct main"
description:
name: dio
sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8"
url: "https://pub.dev"
source: hosted
version: "5.4.1"
dynamic_color:
dependency: "direct main"
description:
@@ -215,10 +222,10 @@ packages:
dependency: "direct main"
description:
name: easy_localization
sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201
sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c
url: "https://pub.dev"
source: hosted
version: "3.0.7"
version: "3.0.5"
easy_logger:
dependency: transitive
description:
@@ -227,14 +234,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.0.2"
equations:
dependency: "direct main"
description:
name: equations
sha256: ae30e977d601e19aa1fc3409736c5eac01559d1d653a4c30141fbc4e86aa605c
url: "https://pub.dev"
source: hosted
version: "5.0.2"
fake_async:
dependency: transitive
description:
@@ -263,10 +262,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a"
sha256: caa6bc229eab3e32eb2f37b53a5f9d22a6981474afd210c512a7546c1e1a04f6
url: "https://pub.dev"
source: hosted
version: "8.0.3"
version: "6.2.0"
fixnum:
dependency: transitive
description:
@@ -275,22 +274,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flex_color_picker:
dependency: "direct main"
description:
name: flex_color_picker
sha256: "5c846437069fb7afdd7ade6bf37e628a71d2ab0787095ddcb1253bf9345d5f3a"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
flex_seed_scheme:
dependency: transitive
description:
name: flex_seed_scheme
sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
flutter:
dependency: "direct main"
description: flutter
@@ -300,10 +283,10 @@ packages:
dependency: "direct main"
description:
name: flutter_archive
sha256: "5ca235f304c12bf468979235f400f79846d204169d715939e39197106f5fc970"
sha256: "004132780d382df5171589ab793e2efc9c3eef570fe72d78b4ccfbfbe52762ae"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
version: "6.0.0"
flutter_fgbg:
dependency: "direct main"
description:
@@ -324,18 +307,18 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "3.0.1"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef"
sha256: "55b9b229307a10974b26296ff29f2e132256ba4bd74266939118eaefa941cb00"
url: "https://pub.dev"
source: hosted
version: "17.1.2"
version: "16.3.3"
flutter_local_notifications_linux:
dependency: transitive
description:
@@ -348,10 +331,10 @@ packages:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7"
sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
version: "7.0.0+1"
flutter_localizations:
dependency: transitive
description: flutter
@@ -361,18 +344,18 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: "9921f9deda326f8a885e202b1e35237eadfc1345239a0f6f0f1ff287e047547f"
sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db
url: "https://pub.dev"
source: hosted
version: "0.7.1"
version: "0.6.21"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
url: "https://pub.dev"
source: hosted
version: "2.0.19"
version: "2.0.17"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -387,18 +370,10 @@ packages:
dependency: "direct main"
description:
name: fluttertoast
sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
url: "https://pub.dev"
source: hosted
version: "8.2.5"
fraction:
dependency: transitive
description:
name: fraction
sha256: "09e9504c9177bbd77df56e5d147abfbb3b43360e64bf61510059c14d6a82d524"
url: "https://pub.dev"
source: hosted
version: "5.0.2"
version: "8.2.4"
gtk:
dependency: transitive
description:
@@ -455,14 +430,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.18.1"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.9.0"
version: "4.8.1"
leak_tracker:
dependency: transitive
description:
@@ -491,10 +474,10 @@ packages:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "3.0.0"
markdown:
dependency: transitive
description:
@@ -563,26 +546,26 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
@@ -611,26 +594,26 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
url: "https://pub.dev"
source: hosted
version: "11.3.1"
version: "11.3.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "8bb852cd759488893805c3161d0b2b5db55db52f773dbb014420b304055ba2c5"
sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474"
url: "https://pub.dev"
source: hosted
version: "12.0.6"
version: "12.0.5"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b
url: "https://pub.dev"
source: hosted
version: "9.4.4"
version: "9.4.0"
permission_handler_html:
dependency: transitive
description:
@@ -643,10 +626,10 @@ packages:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
url: "https://pub.dev"
source: hosted
version: "4.2.1"
version: "4.2.0"
permission_handler_windows:
dependency: transitive
description:
@@ -659,10 +642,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "5.4.0"
version: "6.0.2"
platform:
dependency: transitive
description:
@@ -679,6 +662,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted
version: "3.7.4"
provider:
dependency: "direct main"
description:
@@ -691,42 +682,42 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
url: "https://pub.dev"
source: hosted
version: "9.0.0"
version: "7.2.2"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4"
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "3.3.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.2.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
@@ -767,15 +758,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.8.1"
shizuku_apk_installer:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: "25acc02612c2e0fcae40d312e047ac48106f8f6b"
url: "https://github.com/re7gog/shizuku_apk_installer"
source: git
version: "0.0.1"
sky_engine:
dependency: transitive
description: flutter
@@ -801,18 +783,18 @@ packages:
dependency: "direct main"
description:
name: sqflite
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
url: "https://pub.dev"
source: hosted
version: "2.3.3+1"
version: "2.3.2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
version: "2.5.3"
stack_trace:
dependency: transitive
description:
@@ -865,10 +847,10 @@ packages:
dependency: transitive
description:
name: timezone
sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5
sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0"
url: "https://pub.dev"
source: hosted
version: "0.9.3"
version: "0.9.2"
typed_data:
dependency: transitive
description:
@@ -881,26 +863,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
url: "https://pub.dev"
source: hosted
version: "6.2.6"
version: "6.2.5"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
url: "https://pub.dev"
source: hosted
version: "6.3.1"
version: "6.3.0"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.2.5"
url_launcher_linux:
dependency: transitive
description:
@@ -913,10 +895,10 @@ packages:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "3.1.0"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -929,10 +911,10 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.0"
url_launcher_windows:
dependency: transitive
description:
@@ -945,10 +927,10 @@ packages:
dependency: transitive
description:
name: uuid
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev"
source: hosted
version: "4.4.0"
version: "4.3.3"
vector_math:
dependency: transitive
description:
@@ -985,10 +967,10 @@ packages:
dependency: transitive
description:
name: webview_flutter_android
sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91
sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0"
url: "https://pub.dev"
source: hosted
version: "3.16.1"
version: "3.15.0"
webview_flutter_platform_interface:
dependency: transitive
description:
@@ -1001,26 +983,26 @@ packages:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7
sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86"
url: "https://pub.dev"
source: hosted
version: "3.13.0"
version: "3.12.0"
win32:
dependency: transitive
description:
name: win32
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted
version: "5.5.0"
version: "5.3.0"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb"
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
version: "1.1.2"
xdg_directories:
dependency: transitive
description:
@@ -1033,10 +1015,10 @@ packages:
dependency: transitive
description:
name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.5.0"
yaml:
dependency: transitive
description:
@@ -1046,5 +1028,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.3.3 <4.0.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"

View File

@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 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: 1.1.8+2265
version: 1.0.5+2255 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=3.0.0 <4.0.0'
@@ -38,7 +38,7 @@ dependencies:
cupertino_icons: ^1.0.5
path_provider: ^2.0.11
flutter_fgbg: ^0.3.0 # Try removing reliance on this
flutter_local_notifications: ^17.0.0
flutter_local_notifications: ^16.1.0
provider: ^6.0.3
http: ^1.0.0
webview_flutter: ^4.0.0
@@ -48,36 +48,27 @@ dependencies:
url_launcher: ^6.1.5
permission_handler: ^11.0.0
fluttertoast: ^8.0.9
device_info_plus: ^10.0.1
file_picker: ^8.0.0+1
device_info_plus: ^9.0.0
file_picker: ^6.0.0
animations: ^2.0.4
android_package_installer:
git:
url: https://github.com/ImranR98/android_package_installer
ref: main
android_package_manager: ^0.7.0
share_plus: ^9.0.0
share_plus: ^7.0.0
sqflite: ^2.2.0+3
easy_localization: ^3.0.1
android_intent_plus: ^5.0.1
flutter_markdown: ^0.7.1
android_intent_plus: ^4.0.0
flutter_markdown: ^0.6.14
flutter_archive: ^6.0.0
hsluv: ^1.1.3
connectivity_plus: ^6.0.1
connectivity_plus: ^5.0.0
shared_storage: ^0.8.0
crypto: ^3.0.3
app_links: ^6.0.1
app_links: ^3.5.0
background_fetch: ^1.2.1
equations: ^5.0.2
flex_color_picker: ^3.4.1
android_system_font:
git:
url: https://github.com/re7gog/android_system_font
ref: master
shizuku_apk_installer:
git:
url: https://github.com/re7gog/shizuku_apk_installer
ref: master
dio: ^5.4.1
dev_dependencies:
flutter_test:
@@ -89,7 +80,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
flutter_lints: ^3.0.0
flutter_launcher_icons:
android: "ic_launcher"