mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-19 07:49:41 +02:00
Compare commits
152 Commits
v0.14.32-b
...
v0.15.2-be
Author | SHA1 | Date | |
---|---|---|---|
|
1e34048c0c | ||
|
45b1b23262 | ||
|
07842ace4e | ||
|
8e75280093 | ||
|
029b9ef498 | ||
|
51970abce7 | ||
|
f6faa19e5f | ||
|
dd7217ca54 | ||
|
6e13457eb2 | ||
|
3b319ee19b | ||
|
8ea8daa811 | ||
|
eb29b908c2 | ||
|
9935cb482e | ||
|
0d6e7181cf | ||
|
d225650e15 | ||
|
bfe09791d5 | ||
|
85d103f3f6 | ||
|
9e6dbe2465 | ||
|
355e5ccda6 | ||
|
a5f6f05e10 | ||
|
db0d35d80b | ||
|
6fca2a3931 | ||
|
0305a42b02 | ||
|
77d81716ed | ||
|
3e54e80eb6 | ||
|
3c9bb63d32 | ||
|
617ab9efab | ||
|
bc574097e2 | ||
|
4cc64dc233 | ||
|
45fa0a165a | ||
|
0e5c07a078 | ||
|
601a742c71 | ||
|
c972401b6e | ||
|
024e81cf01 | ||
|
975ed402d5 | ||
|
b9e8083744 | ||
|
bb859708bc | ||
|
3cf2c221ac | ||
|
6edd7edcd2 | ||
|
4e26a02d78 | ||
|
bb36a57053 | ||
|
b291c800f1 | ||
|
b63a798d86 | ||
|
eacf3777a4 | ||
|
a5a7436bb1 | ||
|
2a4cc35df7 | ||
|
cdccf58b76 | ||
|
27300383a1 | ||
|
375b9bce30 | ||
|
b6b8db48df | ||
|
36e6c267b9 | ||
|
13066b3b4a | ||
|
ccbe9d00c8 | ||
|
ce291582cb | ||
|
bb37bc3b51 | ||
|
5a7747acd1 | ||
|
1bc2ec9461 | ||
|
2b977fc2b0 | ||
|
de60c4ee9e | ||
|
de67e40c00 | ||
|
cc4b016c64 | ||
|
f64f561d6f | ||
|
80bddf8a6b | ||
|
cbaaec961c | ||
|
5477b3f936 | ||
|
fd59a93ede | ||
|
cd316b7138 | ||
|
d1955192ed | ||
|
9beb839bf4 | ||
|
29ea303093 | ||
|
feff6751ca | ||
|
ca33fdf752 | ||
|
fdcdfe89d6 | ||
|
48ed2115a7 | ||
|
65988f4e08 | ||
|
ede65eda6c | ||
|
5da56acac8 | ||
|
5720c55301 | ||
|
ffefa4b30e | ||
|
80e4986b23 | ||
|
dc92ccda0a | ||
|
f9bab18076 | ||
|
2dec52e221 | ||
|
7413f693d7 | ||
|
415460df75 | ||
|
125a194468 | ||
|
32e9afbf36 | ||
|
3eca704f4a | ||
|
9c95129311 | ||
|
bf34c1bcdb | ||
|
284c687d77 | ||
|
09afb5a3f5 | ||
|
0138721451 | ||
|
2d5f610941 | ||
|
864fa7762b | ||
|
4fde38ee6a | ||
|
6cdf0f10d4 | ||
|
b66592c25f | ||
|
43616c566d | ||
|
62f1dc17a0 | ||
|
0e9a8a937a | ||
|
9a86b245ce | ||
|
64533f7a3f | ||
|
0b7de8d387 | ||
|
8eba4860fe | ||
|
b53e2f57e6 | ||
|
e1e834297b | ||
|
e37dc6e341 | ||
|
c91c896854 | ||
|
7e5dfa03d6 | ||
|
1a4ec3f049 | ||
|
756763fcbe | ||
|
93036c4e67 | ||
|
15bf972ef6 | ||
|
bcb4567382 | ||
|
3890c4ffb9 | ||
|
c8d82f24a8 | ||
|
367e740a9c | ||
|
5b5a51bdad | ||
|
7fc030a53e | ||
|
65087fb5f8 | ||
|
d5e789af3c | ||
|
45212c699f | ||
|
03106f3522 | ||
|
cd2857cb94 | ||
|
b3ddb8658f | ||
|
7ae80b6b49 | ||
|
34f27f9cd4 | ||
|
4c53b58fd8 | ||
|
6102ab4be8 | ||
|
6fcf565974 | ||
|
77bd16c7cf | ||
|
7d593080ec | ||
|
96ea74abeb | ||
|
e50e3957ec | ||
|
8cb9420939 | ||
|
bbb7b8caa2 | ||
|
cc284f3c9e | ||
|
bd8aae5fbf | ||
|
3d162f33df | ||
|
ff3c37f1b4 | ||
|
35160ce385 | ||
|
c8eef91813 | ||
|
6791b20121 | ||
|
3311c49236 | ||
|
5223e58f83 | ||
|
e0f9fc233d | ||
|
509bd21655 | ||
|
5be7229c4b | ||
|
efd3c461e0 | ||
|
e94e195e73 | ||
|
869ebd90df |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -9,6 +9,7 @@ assignees: ''
|
|||||||
|
|
||||||
**Prerequisites**
|
**Prerequisites**
|
||||||
<!-- Please ensure your request is not part of an existing issue. -->
|
<!-- Please ensure your request is not part of an existing issue. -->
|
||||||
|
<!-- Please ensure you have checked the Obtainium Wiki. -->
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -9,6 +9,7 @@ assignees: ''
|
|||||||
|
|
||||||
**Prerequisites**
|
**Prerequisites**
|
||||||
<!-- Please ensure your request is not part of an existing issue. -->
|
<!-- Please ensure your request is not part of an existing issue. -->
|
||||||
|
<!-- Please ensure you have checked the Obtainium Wiki. -->
|
||||||
|
|
||||||
**Describe the feature**
|
**Describe the feature**
|
||||||
<!-- A clear and concise description of what you want to happen.
|
<!-- A clear and concise description of what you want to happen.
|
||||||
|
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -22,7 +22,8 @@ jobs:
|
|||||||
- name: Build APKs
|
- name: Build APKs
|
||||||
run: |
|
run: |
|
||||||
sed -i 's/signingConfig signingConfigs.release//g' android/app/build.gradle
|
sed -i 's/signingConfig signingConfigs.release//g' android/app/build.gradle
|
||||||
flutter build apk && flutter build apk --split-per-abi
|
flutter build apk --flavor normal && flutter build apk --split-per-abi --flavor normal
|
||||||
|
for file in build/app/outputs/flutter-apk/app-*normal*.apk*; do mv "$file" "${file//-normal/}"; done
|
||||||
rm ./build/app/outputs/flutter-apk/*.sha1
|
rm ./build/app/outputs/flutter-apk/*.sha1
|
||||||
ls -l ./build/app/outputs/flutter-apk/
|
ls -l ./build/app/outputs/flutter-apk/
|
||||||
|
|
||||||
|
@@ -6,6 +6,8 @@ Obtainium allows you to install and update Apps directly from their releases pag
|
|||||||
|
|
||||||
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
|
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
|
||||||
|
|
||||||
|
Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
|
||||||
|
|
||||||
Currently supported App sources:
|
Currently supported App sources:
|
||||||
- Open Source - General:
|
- Open Source - General:
|
||||||
- [GitHub](https://github.com/)
|
- [GitHub](https://github.com/)
|
||||||
|
@@ -23,6 +23,7 @@ if (flutterVersionName == null) {
|
|||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'dev.rikka.tools.refine'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
@@ -32,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -52,12 +53,25 @@ android {
|
|||||||
applicationId "dev.imranr.obtainium"
|
applicationId "dev.imranr.obtainium"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 23
|
minSdkVersion 24
|
||||||
targetSdkVersion 33
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions "flavor"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
normal {
|
||||||
|
dimension "flavor"
|
||||||
|
applicationIdSuffix ""
|
||||||
|
}
|
||||||
|
fdroid {
|
||||||
|
dimension "flavor"
|
||||||
|
applicationIdSuffix ".fdroid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
keyAlias keystoreProperties['keyAlias']
|
keyAlias keystoreProperties['keyAlias']
|
||||||
@@ -77,6 +91,24 @@ flutter {
|
|||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
|
def shizuku_version = '13.1.5'
|
||||||
|
implementation "dev.rikka.shizuku:api:$shizuku_version"
|
||||||
|
implementation "dev.rikka.shizuku:provider:$shizuku_version"
|
||||||
|
|
||||||
|
def hidden_api_version = '4.1.0'
|
||||||
|
// DO NOT UPDATE Hidden API without updating the Android tools
|
||||||
|
// and do not update Android tools without updating the whole Flutter
|
||||||
|
// (also in android/build.gradle)
|
||||||
|
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 "com.github.topjohnwu.libsu:core:5.2.2"
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="dev.imranr.obtainium">
|
package="dev.imranr.obtainium">
|
||||||
<application
|
<application
|
||||||
android:label="Obtainium"
|
android:label="Obtainium"
|
||||||
@@ -28,8 +29,15 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action
|
<action
|
||||||
android:name="com.android_package_installer.content.SESSION_API_PACKAGE_INSTALLED"
|
android:name="com.android_package_installer.content.SESSION_API_PACKAGE_INSTALLED"
|
||||||
android:exported="false"/>
|
android:exported="false" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="obtainium" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
@@ -39,10 +47,10 @@
|
|||||||
<service
|
<service
|
||||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
|
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
android:exported="false"/>
|
android:exported="false" />
|
||||||
<receiver
|
<receiver
|
||||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
|
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
|
||||||
android:exported="false"/>
|
android:exported="false" />
|
||||||
<receiver
|
<receiver
|
||||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
|
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
@@ -52,24 +60,31 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="dev.imranr.obtainium"
|
android:authorities="dev.imranr.obtainium"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths"/>
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:name="rikka.shizuku.ShizukuProvider"
|
||||||
|
android:authorities="${applicationId}.shizuku"
|
||||||
|
android:multiprocess="false"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="29"/>
|
android:maxSdkVersion="29" />
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
</manifest>
|
</manifest>
|
@@ -1,6 +0,0 @@
|
|||||||
package dev.imranr.obtainium
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
|
||||||
}
|
|
171
android/app/src/main/kotlin/dev/imranr/obtainium/MainActivity.kt
Normal file
171
android/app/src/main/kotlin/dev/imranr/obtainium/MainActivity.kt
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
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 rikka.shizuku.Shizuku
|
||||||
|
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
||||||
|
import rikka.shizuku.ShizukuBinderWrapper
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
private var installersChannel: 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
|
||||||
|
installersChannel!!.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 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)
|
||||||
|
Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener)
|
||||||
|
installersChannel = MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger, "installers")
|
||||||
|
installersChannel!!.setMethodCallHandler {
|
||||||
|
call, result ->
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}*/
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
android:viewportWidth="142.129"
|
android:viewportWidth="142.129"
|
||||||
android:viewportHeight="142.129"
|
android:viewportHeight="142.129"
|
||||||
android:width="503.6066dp"
|
android:width="108dp"
|
||||||
android:height="503.6066dp">
|
android:height="108dp">
|
||||||
<group
|
<group
|
||||||
android:translateX="-30.39437"
|
android:translateX="-30.39437"
|
||||||
android:translateY="-54.68043">
|
android:translateY="-54.68043">
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
ext.kotlin_version = '1.7.10'
|
||||||
|
ext {
|
||||||
|
compileSdkVersion = 34 // or latest
|
||||||
|
targetSdkVersion = 34 // or latest
|
||||||
|
appCompatVersion = "1.4.2" // or latest
|
||||||
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -8,6 +13,7 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath 'dev.rikka.tools.refine:gradle-plugin:4.1.0' // Do not update!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,6 +21,10 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
// [required] background_fetch
|
||||||
|
url "${project(':background_fetch').projectDir}/libs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Nije instalirano",
|
"notInstalled": "Nije instalirano",
|
||||||
"estimateInBrackets": "(Procjena)",
|
"estimateInBrackets": "(Procjena)",
|
||||||
"selectAll": "Označi sve",
|
"selectAll": "Označi sve",
|
||||||
"deselectN": "Poništi odabir {}",
|
"deselectX": "Poništi odabir {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
|
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
|
||||||
"removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
|
"removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
|
||||||
"removeSelectedApps": "Ukloni odabrane aplikacije",
|
"removeSelectedApps": "Ukloni odabrane aplikacije",
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
"importExport": "Uvoz/izvoz",
|
"importExport": "Uvoz/izvoz",
|
||||||
"settings": "Postavke",
|
"settings": "Postavke",
|
||||||
"exportedTo": "Izvezeno u {}",
|
"exportedTo": "Izvezeno u {}",
|
||||||
"obtainiumExport": "Obtainium Export",
|
"obtainiumExport": "Obtainium izvoz",
|
||||||
"invalidInput": "Neispravan unos.",
|
"invalidInput": "Neispravan unos.",
|
||||||
"importedX": "Uvezeno {}",
|
"importedX": "Uvezeno {}",
|
||||||
"obtainiumImport": "Obtainium uvoz",
|
"obtainiumImport": "Obtainium uvoz",
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"close": "Zatvori",
|
"close": "Zatvori",
|
||||||
"share": "Podijeli",
|
"share": "Podijeli",
|
||||||
"appNotFound": "Aplikacija nije pronađena",
|
"appNotFound": "Aplikacija nije pronađena",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-izvoz",
|
||||||
"pickAnAPK": "Odaberite APK",
|
"pickAnAPK": "Odaberite APK",
|
||||||
"appHasMoreThanOnePackage": "{} ima više od jednog paketa:",
|
"appHasMoreThanOnePackage": "{} ima više od jednog paketa:",
|
||||||
"deviceSupportsXArch": "Vaš uređaj podržava {} arhitekturu procesora.",
|
"deviceSupportsXArch": "Vaš uređaj podržava {} arhitekturu procesora.",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
|
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
|
||||||
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
|
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
|
||||||
"about": "O nama",
|
"about": "O nama",
|
||||||
"requiresCredentialsInSettings": "Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
"requiresCredentialsInSettings": "{}: Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
||||||
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
|
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
|
||||||
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
|
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
|
||||||
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
|
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
|
||||||
@@ -231,50 +231,58 @@
|
|||||||
"checkUpdateOnDetailPage": "Provjerite ima li novosti pri otvaranju stranice s detaljima aplikacije",
|
"checkUpdateOnDetailPage": "Provjerite ima li novosti pri otvaranju stranice s detaljima aplikacije",
|
||||||
"disablePageTransitions": "Ugasite animaciju prijelaza stranice",
|
"disablePageTransitions": "Ugasite animaciju prijelaza stranice",
|
||||||
"reversePageTransitions": "Reverzne animacije prijelaza stranice",
|
"reversePageTransitions": "Reverzne animacije prijelaza stranice",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Najmanji broj zvjezdica",
|
||||||
"addInfoBelow": "Add this info below.",
|
"addInfoBelow": "Dodajte ove informacije ispod.",
|
||||||
"addInfoInSettings": "Add this info in the Settings.",
|
"addInfoInSettings": "Dodajte ove informacije u Postavkama.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "GitHub ograničavanje se može izbjeći korišćenjem tokena za lični pristup.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "GitLab APK preuzimanje možda neće raditi bez tokena za lični pristup.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filtirajte promjene u izdanju po regularnom izrazu",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Prilagođeni APK link filtrira se po regularnom izrazu (Zadano '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "Pokušano ažuriranje aplikacija",
|
||||||
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
|
"appsPossiblyUpdatedNotifDescription": "Obavještava korisnika da je ažuriranje jedne ili više aplikacija potencijalno izvršeno u pozadini",
|
||||||
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
|
"xWasPossiblyUpdatedToY": "{} aplikacija bi trebala biti ažurirana na {}.",
|
||||||
"enableBackgroundUpdates": "Enable background updates",
|
"enableBackgroundUpdates": "Dozvolite ažuriranja u pozadini",
|
||||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
"backgroundUpdateReqsExplanation": "Ažuriranja u pozadini možda neće raditi za sve aplikacije.",
|
||||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
"backgroundUpdateLimitsExplanation": "Uspjeh ažuriranja u pozadini se može provjeriti tek kada otvorite Obtainium.",
|
||||||
"verifyLatestTag": "Verify the 'latest' tag",
|
"verifyLatestTag": "Provjerite 'posljednu' ('latest') oznaku",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"filterByLinkText": "Filter links by link text",
|
||||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
"intermediateLinkNotFound": "Intermediate link nije nađen",
|
||||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
"intermediateLink": "Intermediate link",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"exemptFromBackgroundUpdates": "Izuzmi iz ažuriranja u pozadini (ako su uključeni)",
|
||||||
"versionExtractionRegEx": "Version Extraction RegEx",
|
"bgUpdatesOnWiFiOnly": "Isključite ažuriranje u pozadini kada niste na WiFi-ju",
|
||||||
"matchGroupToUse": "Match Group to Use",
|
"autoSelectHighestVersionCode": "Automatski izaberite najveću (verziju) versionCode APK-a",
|
||||||
"highlightTouchTargets": "Highlight less obvious touch targets",
|
"versionExtractionRegEx": "RegEx ekstrakcija verzije",
|
||||||
"pickExportDir": "Pick Export Directory",
|
"matchGroupToUse": "Podjesite grupu za upotebu",
|
||||||
"autoExportOnChanges": "Auto-export on changes",
|
"highlightTouchTargets": "Istaknite manje vidljive touch mete",
|
||||||
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
"pickExportDir": "Izaberite datoteku za izvoz",
|
||||||
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
"autoExportOnChanges": "Automatski izvezite pri promjenama",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"includeSettings": "Include settings",
|
||||||
"reverseSort": "Reverse sorting",
|
"filterVersionsByRegEx": "Filtrirajte verzije po regulatnom izrazu",
|
||||||
"debugMenu": "Debug Menu",
|
"trySelectingSuggestedVersionCode": "Probajte izabrati preloženu (verziju) versionCode APK-a",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"dontSortReleasesList": "Zadrži redosled izdanja iz API-a",
|
||||||
"runBgCheckNow": "Run Background Update Check Now",
|
"reverseSort": "Obrni redosled",
|
||||||
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
"takeFirstLink": "Take first link",
|
||||||
"installing": "Installing",
|
"skipSort": "Skip sorting",
|
||||||
"skipUpdateNotifications": "Skip update notifications",
|
"debugMenu": "Meni za otkrivanje grešaka",
|
||||||
|
"bgTaskStarted": "Rad u pozadini pokrenut - provjerite log-ove.",
|
||||||
|
"runBgCheckNow": "Pokrenite pozadinsku provjeru ažuriranja sad",
|
||||||
|
"versionExtractWholePage": "Primjenite Regex ekstrakciju verzije na cijelu stranicu",
|
||||||
|
"installing": "Instaliranje",
|
||||||
|
"skipUpdateNotifications": "Ne prikazujte obavještenja ažuriranja",
|
||||||
"updatesAvailableNotifChannel": "Dostupna ažuriranja",
|
"updatesAvailableNotifChannel": "Dostupna ažuriranja",
|
||||||
"appsUpdatedNotifChannel": "Aplikacije su ažurirane",
|
"appsUpdatedNotifChannel": "Aplikacije su ažurirane",
|
||||||
"appsPossiblyUpdatedNotifChannel": "App Updates Attempted",
|
"appsPossiblyUpdatedNotifChannel": "Pokušano ažuriranje aplikacija",
|
||||||
"errorCheckingUpdatesNotifChannel": "Greška pri provjeri ažuriranja",
|
"errorCheckingUpdatesNotifChannel": "Greška pri provjeri ažuriranja",
|
||||||
"appsRemovedNotifChannel": "Aplikacije su uklonjene",
|
"appsRemovedNotifChannel": "Aplikacije su uklonjene",
|
||||||
"downloadingXNotifChannel": "Preuzimanje {}",
|
"downloadingXNotifChannel": "Preuzimanje {}",
|
||||||
"completeAppInstallationNotifChannel": "Dovršite instalaciju aplikacije",
|
"completeAppInstallationNotifChannel": "Dovršite instalaciju aplikacije",
|
||||||
"checkingForUpdatesNotifChannel": "Tražim moguće nadogradnje",
|
"checkingForUpdatesNotifChannel": "Tražim moguće nadogradnje",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Isključivo provjerite ažuriranje za instalirane i aplikacije 'samo za praćenje'",
|
||||||
|
"supportFixedAPKURL": "Podržite fiksne APK URL-ove",
|
||||||
|
"selectX": "Izaberite {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Želite li ukloniti aplikaciju?",
|
"one": "Želite li ukloniti aplikaciju?",
|
||||||
"other": "Želite li ukloniti aplikacije?"
|
"other": "Želite li ukloniti aplikacije?"
|
||||||
@@ -324,7 +332,7 @@
|
|||||||
"other": "{} i još {} aplikacija je ažurirano."
|
"other": "{} i još {} aplikacija je ažurirano."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesPossiblyInstalled": {
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
"one": "{} and 1 more app may have been updated.",
|
"one": "{} i još jedna aplikacija je vjerovatno ažurirana.",
|
||||||
"other": "{} and {} more apps may have been updated."
|
"other": "{} i još {} aplikacija su vjerovatno ažurirane."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,36 +9,36 @@
|
|||||||
"placeholder": "Zástupce",
|
"placeholder": "Zástupce",
|
||||||
"someErrors": "Vyskytly se nějaké chyby",
|
"someErrors": "Vyskytly se nějaké chyby",
|
||||||
"unexpectedError": "Neočekávaná chyba",
|
"unexpectedError": "Neočekávaná chyba",
|
||||||
"ok": "Okay",
|
"ok": "Ok",
|
||||||
"and": "a",
|
"and": "a",
|
||||||
"githubPATLabel": "GitHub Personal Access Token (Raises Rate Limit)",
|
"githubPATLabel": "GitHub Personal Access Token (zvyšuje limit rychlosti)",
|
||||||
"includePrereleases": "includepreleases",
|
"includePrereleases": "Zahrnout předběžné verze",
|
||||||
"fallbackToOlderReleases": "Fallback to older releases",
|
"fallbackToOlderReleases": "Přechod na starší verze",
|
||||||
"filterReleaseTitlesByRegEx": "Názvy vydání podle regulárního výrazu\filtr",
|
"filterReleaseTitlesByRegEx": "Filtrovat názvy verzí podle regulárního výrazu",
|
||||||
"invalidRegEx": "Neplatný regulární výraz",
|
"invalidRegEx": "Neplatný regulární výraz",
|
||||||
"noDescription": "Žádný popis",
|
"noDescription": "Žádný popis",
|
||||||
"cancel": "Zrušit",
|
"cancel": "Zrušit",
|
||||||
"continue": "Pokračovat",
|
"continue": "Pokračovat",
|
||||||
"requiredInBracets": "(Required)",
|
"requiredInBracets": "(Required)",
|
||||||
"dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST",
|
"dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST",
|
||||||
"color": "barva",
|
"colour": "Barva",
|
||||||
"githubStarredRepos": "GitHub Starred Repos",
|
"githubStarredRepos": "GitHub Starred Repos",
|
||||||
"uname": "username",
|
"uname": "Uživatelské jméno",
|
||||||
"wrongArgNum": "Špatný počet předložených argumentů",
|
"wrongArgNum": "Nesprávný počet zadaných argumentů",
|
||||||
"xIsTrackOnly": "{} je určeno pouze pro sledování",
|
"xIsTrackOnly":"{} je určeno pouze pro sledování",
|
||||||
"source": "zdroj",
|
"source": "Zdroj",
|
||||||
"app": "App",
|
"app": "App",
|
||||||
"appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou 'Jen sledovány'.",
|
"appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou Jen sledovány.",
|
||||||
"youPickedTrackOnly": "Vybrali jste možnost 'Jen sledovat'.",
|
"youPickedTrackOnly": "Vybrali jste možnost Jen sledovat.",
|
||||||
"trackOnlyAppDescription": "Aplikace je sledována kvůli aktualizacím, ale Obtainium ji nebude stahovat ani instalovat.",
|
"trackOnlyAppDescription": "Aplikace je sledována kvůli aktualizacím, ale Obtainium ji nebude stahovat ani instalovat.",
|
||||||
"cancelled": "Zrušeno",
|
"cancelled": "Zrušeno",
|
||||||
"appAlreadyAdded": "Aplikace již přidána",
|
"appAlreadyAdded": "Aplikace již přidána",
|
||||||
"alreadyUpToDateQuestion": "App already up to date?",
|
"alreadyUpToDateQuestion": "App already up to date?",
|
||||||
"addApp": "Přidat aplikaci",
|
"addApp": "Přidat aplikaci",
|
||||||
"appSourceURL": "zdrojová adresa URL aplikace",
|
"appSourceURL": "Zdrojová adresa URL aplikace",
|
||||||
"error": "Chyba",
|
"error": "Chyba",
|
||||||
"add": "Přidat",
|
"add": "Přidat",
|
||||||
"searchSomeSourcesLabel": "Vyhledávání (pouze konkrétní zdroje)",
|
"searchSomeSourcesLabel": "Vyhledávání (pouze pro určité zdroje)",
|
||||||
"search": "Hledat",
|
"search": "Hledat",
|
||||||
"additionalOptsFor": "Další možnosti pro {}",
|
"additionalOptsFor": "Další možnosti pro {}",
|
||||||
"supportedSources": "Podporované zdroje",
|
"supportedSources": "Podporované zdroje",
|
||||||
@@ -46,45 +46,45 @@
|
|||||||
"searchableInBrackets": "(s možností vyhledávání)",
|
"searchableInBrackets": "(s možností vyhledávání)",
|
||||||
"appsString": "Apky",
|
"appsString": "Apky",
|
||||||
"noApps": "Žádné aplikace",
|
"noApps": "Žádné aplikace",
|
||||||
"noAppsForFilter": "žádné aplikace pro vybraný filtr",
|
"noAppsForFilter": "Žádné aplikace pro vybraný filtr",
|
||||||
"byX": "By {}",
|
"byX": "Od {}",
|
||||||
"percentProgress": "Pokrok: {}%",
|
"percentProgress": "Pokrok: {}%",
|
||||||
"pleaseWait": "Počkejte prosím",
|
"pleaseWait": "Počkejte prosím",
|
||||||
"updateAvailable": "Aktualizace je k dispozici",
|
"updateAvailable": "Aktualizace je k dispozici",
|
||||||
"estimateInBracketsShort": "(approx.)",
|
"estimateInBracketsShort": "(approx.)",
|
||||||
"notInstalled": "Není nainstalováno",
|
"notInstalled": "Není nainstalováno",
|
||||||
"estimateInBrackets": "(přibližně)",
|
"estimateInBrackets": "(přibližně)",
|
||||||
"selectAll": "Vybrat Vše",
|
"selectAll": "Vybrat vše",
|
||||||
"deselectN": "{} deselected",
|
"deselectX": "{} deselected",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.",
|
"xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.",
|
||||||
"removeSelectedAppsQuestion": "Odebrat vybrané aplikace?",
|
"removeSelectedAppsQuestion": "Odebrat vybrané aplikace?",
|
||||||
"removeSelectedApps": "Odebrat vybrané aplikace",
|
"removeSelectedApps": "Odebrat vybrané aplikace",
|
||||||
"updateX": "Aktualizovat {}",
|
"updateX": "Aktualizovat {}",
|
||||||
"installX": "Instalovat {}",
|
"installX": "Instalovat {}",
|
||||||
"markXTrackOnlyAsUpdated": "Označit {}\n(Track-Only)\njako aktualizované",
|
"markXTrackOnlyAsUpdated": "Označit {}\n(Jen sledované)\njako aktualizované",
|
||||||
"changeX": "Změnit {}",
|
"changeX": "Změnit {}",
|
||||||
"installUpdateApps": "Instalovat/aktualizovat aplikace",
|
"installUpdateApps": "Instalovat/aktualizovat aplikace",
|
||||||
"installUpdateSelectedApps": "Instalovat/aktualizovat vybrané aplikace",
|
"installUpdateSelectedApps": "Instalovat/aktualizovat vybrané aplikace",
|
||||||
"markXSelectedAppsAsUpdated": "označit {} vybrané aplikace jako aktuální?",
|
"markXSelectedAppsAsUpdated": "Označit {} vybrané aplikace jako aktuální?",
|
||||||
"no": "Ne",
|
"no": "Ne",
|
||||||
"yes": "ano",
|
"yes": "Ano",
|
||||||
"markSelectedAppsUpdated": "označit vybrané aplikace jako aktuální",
|
"markSelectedAppsUpdated": "Označit vybrané aplikace jako aktuální",
|
||||||
"pinToTop": "Připnout nahoru",
|
"pinToTop": "Připnout nahoru",
|
||||||
"unpinFromTop": "'Unpin Top'",
|
"unpinFromTop": "Odepnout shora",
|
||||||
"resetInstallStatusForSelectedAppsQuestion": "Obnovit stav instalace vybraných aplikací?",
|
"resetInstallStatusForSelectedAppsQuestion": "Obnovit stav instalace vybraných aplikací?",
|
||||||
"installStatusOfXWillBeResetExplanation": "Stav instalace vybraných aplikací bude resetován. To může být užitečné, pokud je verze aplikace zobrazená v Obtainium nesprávná z důvodu neúspěšných aktualizací nebo jiných problémů.",
|
"installStatusOfXWillBeResetExplanation": "Stav instalace vybraných aplikací bude resetován. To může být užitečné, pokud je verze aplikace zobrazená v Obtainium nesprávná z důvodu neúspěšných aktualizací nebo jiných problémů.",
|
||||||
"shareSelectedAppURLs": "Sdílet adresy URL vybraných aplikací",
|
"shareSelectedAppURLs": "Sdílet adresy URL vybraných aplikací",
|
||||||
"resetInstallStatus": "Obnovení stavu instalace",
|
"resetInstallStatus": "Obnovit stav instalace",
|
||||||
"more": "more",
|
"more": "Více",
|
||||||
"removeOutdatedFilter": "Odstranit filtr aplikace 'Not Current'",
|
"removeOutdatedFilter": "Odstranit filtr Neaktuální",
|
||||||
"showOutdatedOnly": "Zobrazit pouze aplikace, které nejsou aktuální",
|
"showOutdatedOnly": "Zobrazovat pouze zastaralé aplikace",
|
||||||
"filter": "Filtr",
|
"filter": "Filtr",
|
||||||
"filterActive": "Filtr *",
|
"filterActive": "Filtr *",
|
||||||
"filterApps": "Filtrovat aplikace",
|
"filterApps": "Filtrovat aplikace",
|
||||||
"appName": "název aplikace",
|
"appName": "Název aplikace",
|
||||||
"author": "Autor",
|
"author": "Autor",
|
||||||
"upToDateApps": "Apps with current version",
|
"upToDateApps": "Aktuální apky",
|
||||||
"nonInstalledApps": "Apps not installed",
|
"nonInstalledApps": "Neinstalované apky",
|
||||||
"importExport": "Import/Export",
|
"importExport": "Import/Export",
|
||||||
"settings": "Nastavení",
|
"settings": "Nastavení",
|
||||||
"exportedTo": "Exportováno do {}",
|
"exportedTo": "Exportováno do {}",
|
||||||
@@ -93,76 +93,76 @@
|
|||||||
"importedX": "Importováno {}",
|
"importedX": "Importováno {}",
|
||||||
"obtainiumImport": "Obtainium Import",
|
"obtainiumImport": "Obtainium Import",
|
||||||
"importFromURLList": "Import ze seznamu URL",
|
"importFromURLList": "Import ze seznamu URL",
|
||||||
"searchQuery": "Search Query",
|
"searchQuery": "Vyhledávací dotaz",
|
||||||
"appURLList": "App URL List",
|
"appURLList": "Seznam adres aplikací",
|
||||||
"line": "line",
|
"line": "Linka",
|
||||||
"searchX": "Search {}",
|
"searchX": "Search {}",
|
||||||
"noResults": "Nebyly nalezeny žádné výsledky",
|
"noResults": "Nebyly nalezeny žádné výsledky",
|
||||||
"importX": "Import {}",
|
"importX": "Import {}",
|
||||||
"importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalované\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.",
|
"importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalovány\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.",
|
||||||
"importErrors": "Import Errors",
|
"importErrors": "Chyba importu",
|
||||||
"importedXOfYApps": "{}importováno {}aplikací.",
|
"importedXOfYApps": "{}importováno z {} aplikací.",
|
||||||
"followingURLsHadErrors": "U následujících adres URL došlo k chybám:",
|
"followingURLsHadErrors": "U následujících adres došlo k chybám:",
|
||||||
"okay": "Okay",
|
"okay": "Okay",
|
||||||
"selectURL": "Select URL",
|
"selectURL": "Vybrat adresu",
|
||||||
"selectURLs": "Select URLs",
|
"selectURLs": "Select adresy",
|
||||||
"pick": "Vybrat",
|
"pick": "Vybrat",
|
||||||
"theme": "Téma",
|
"theme": "Téma",
|
||||||
"dark": "Tmavé",
|
"dark": "Tmavé",
|
||||||
"light": "Světlé",
|
"light": "Světlé",
|
||||||
"followSystem": "Follow System",
|
"followSystem": "Jako systém",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Použít čistě černé tmavé téma",
|
"useBlackTheme": "Použít čistě černé tmavé téma",
|
||||||
"appSortBy": "Seřadit aplikaci podle",
|
"appSortBy": "Seřadit podle",
|
||||||
"authorName": "autor/jméno",
|
"authorName": "Autor/Jméno",
|
||||||
"nameAuthor": "jméno/autor",
|
"nameAuthor": "Jméno/Autor",
|
||||||
"asAdded": "AsAdded",
|
"asAdded": "Přidáno",
|
||||||
"appSortOrder": "Sort App By",
|
"appSortOrder": "Seřadit",
|
||||||
"ascending": "Vzestupně",
|
"ascending": "Vzestupně",
|
||||||
"descending": "Sestupně",
|
"descending": "Sestupně",
|
||||||
"bgUpdateCheckInterval": "Background Update Check Interval",
|
"bgUpdateCheckInterval": "Interval kontroly aktualizace na pozadí",
|
||||||
"neverManualOnly": "Nikdy - pouze ručně",
|
"neverManualOnly": "Nikdy - pouze ručně",
|
||||||
"appearance": "Vzhled",
|
"appearance": "Vzhled",
|
||||||
"showWebInAppView": "Zobrazit zdrojové webové stránky v zobrazení aplikace",
|
"showWebInAppView": "Zobrazit zdrojové webové stránky v zobrazení aplikace",
|
||||||
"pinUpdates": "Připnout aplikace s aktualizacemi nahoře",
|
"pinUpdates": "Připnout aplikace s aktualizacemi nahoru",
|
||||||
"updates": "Updates",
|
"updates": "Updates",
|
||||||
"sourceSpecific": "source specific",
|
"sourceSpecific": "Specifické pro zdroj",
|
||||||
"appSource": "zdroj aplikace",
|
"appSource": "Zdroj aplikace",
|
||||||
"noLogs": "Žádné protokoly",
|
"noLogs": "Žádné protokoly",
|
||||||
"appLogs": "App Logs",
|
"appLogs": "Záznamy apky",
|
||||||
"close": "Zavřít",
|
"close": "Zavřít",
|
||||||
"share": "Sdílet",
|
"share": "Sdílet",
|
||||||
"appNotFound": "App not found",
|
"appNotFound": "Aplikace nenalezena",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "Vybrat APK",
|
"pickAnAPK": "Vybrat APK",
|
||||||
"appHasMoreThanOnePackage": "{} má více než jeden balíček:",
|
"appHasMoreThanOnePackage": "{} má více než jeden balíček:",
|
||||||
"deviceSupportsXArch": "Vaše zařízení podporuje architekturu CPU {}.",
|
"deviceSupportsXArch": "Vaše zařízení podporuje architekturu CPU {}.",
|
||||||
"deviceSupportsFollowingArchs": "Vaše zařízení podporuje následující architektury CPU:",
|
"deviceSupportsFollowingArchs": "Vaše zařízení podporuje následující architektury CPU:",
|
||||||
"warning": "Varování",
|
"warning": "Varování",
|
||||||
"sourceIsXButPackageFromYPrompt": "The app source is '{}' but the release package is from '{}'. Pokračovat?",
|
"sourceIsXButPackageFromYPrompt": "Zdroj aplikace je '{}', ale balíček pro vydání je z '{}'. Pokračovat?",
|
||||||
"updatesAvailable": "dostupné aktualizace",
|
"updatesAvailable": "Dostupné aktualizace",
|
||||||
"updatesAvailableNotifDescription": "Upozorňuje uživatele, že jsou k dispozici aktualizace pro jednu nebo více aplikací sledovaných Obtainium",
|
"updatesAvailableNotifDescription": "Upozorňuje uživatele, že jsou k dispozici aktualizace pro jednu nebo více aplikací sledovaných Obtainium",
|
||||||
"noNewUpdates": "Žádné nové aktualizace.",
|
"noNewUpdates": "Žádné nové aktualizace.",
|
||||||
"xHasAnUpdate": "{} má aktualizaci.",
|
"xHasAnUpdate": "{} má aktualizaci.",
|
||||||
"appsUpdated": "Aplikace aktualizovány",
|
"appsUpdated": "Aplikace aktualizovány",
|
||||||
"appsUpdatedNotifDescription": "Upozorňuje uživatele, že byly provedeny aktualizace jedné nebo více aplikací na pozadí",
|
"appsUpdatedNotifDescription": "Upozornit, že byly provedeny aktualizace jedné nebo více aplikací na pozadí",
|
||||||
"xWasUpdatedToY": "{} byl aktualizován na {}",
|
"xWasUpdatedToY": "{} byla aktualizována na {}",
|
||||||
"errorCheckingUpdates": "Chybová kontrola aktualizací",
|
"errorCheckingUpdates": "Chyba kontroly aktualizací",
|
||||||
"errorCheckingUpdatesNotifDescription": "Oznámení zobrazené při neúspěšné kontrole aktualizací na pozadí",
|
"errorCheckingUpdatesNotifDescription": "Zobrazit oznámení při neúspěšné kontrole aktualizací na pozadí",
|
||||||
"appsRemoved": "Odstraněné aplikace",
|
"appsRemoved": "Odstraněné aplikace",
|
||||||
"appsRemovedNotifDescription": "Oznámení uživateli, že jedna nebo více aplikací byly odstraněny z důvodu chyb při načítání",
|
"appsRemovedNotifDescription": "Oznámit, že jedna nebo více aplikací bylo odstraněno z důvodu chyb při načítání",
|
||||||
"xWasRemovedDueToErrorY": "{} byla odstraněna z důvodu následující chyby: {}",
|
"xWasRemovedDueToErrorY": "{} byla odstraněna z důvodu následující chyby: {}",
|
||||||
"completeAppInstallation": "Dokončit instalaci aplikace",
|
"completeAppInstallation": "Dokončit instalaci aplikace",
|
||||||
"obtainiumMustBeOpenToInstallApps": "Obtainium musí být otevřeno, aby bylo možné instalovat aplikace",
|
"obtainiumMustBeOpenToInstallApps": "Obtainium musí být otevřeno, aby bylo možné instalovat aplikace",
|
||||||
"completeAppInstallationNotifDescription": "Vyzvat uživatele k návratu do Obtainium pro dokončení instalace aplikací",
|
"completeAppInstallationNotifDescription": "Vyzvat k návratu do Obtainium pro dokončení instalace aplikací",
|
||||||
"checkingForUpdates": "Zkontrolovat aktualizace",
|
"checkingForUpdates": "Zkontrolovat aktualizace",
|
||||||
"checkingForUpdatesNotifDescription": "Dočasné oznámení zobrazené při kontrole aktualizací",
|
"checkingForUpdatesNotifDescription": "Dočasné oznámení zobrazené při kontrole aktualizací",
|
||||||
"pleaseAllowInstallPerm": "Povolte prosím Obtainium instalovat aplikace",
|
"pleaseAllowInstallPerm": "Povolte prosím Obtainium instalovat aplikace",
|
||||||
"trackOnly": "Jen sledovat",
|
"trackOnly": "Jen sledovat",
|
||||||
"errorWithHttpStatusCode": "error {}",
|
"errorWithHttpStatusCode": "Chyba {}",
|
||||||
"versionCorrectionDisabled": "Oprava verze zakázána (zásuvný modul zřejmě nefunguje)",
|
"versionCorrectionDisabled": "Oprava verze zakázána (zásuvný modul zřejmě nefunguje)",
|
||||||
"unknown": "Unknown",
|
"unknown": "Neznám",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"never": "Nikdy",
|
"never": "Nikdy",
|
||||||
"latestVersionX": "Nejnovější verze: {}",
|
"latestVersionX": "Nejnovější verze: {}",
|
||||||
@@ -170,12 +170,12 @@
|
|||||||
"lastUpdateCheckX": "Poslední kontrola aktualizace: {}",
|
"lastUpdateCheckX": "Poslední kontrola aktualizace: {}",
|
||||||
"remove": "Odebrat",
|
"remove": "Odebrat",
|
||||||
"yesMarkUpdated": "Ano, označit jako aktualizované",
|
"yesMarkUpdated": "Ano, označit jako aktualizované",
|
||||||
"fdroid": "F-Droid Official",
|
"fdroid": "Oficiální repozitář F-Droid",
|
||||||
"appIdOrName": "App ID or Name",
|
"appIdOrName": "ID nebo název apky",
|
||||||
"appId": "App ID",
|
"appId": "App ID",
|
||||||
"appWithIdOrNameNotFound": "Žádná aplikace s tímto ID nebo názvem nebyla nalezena",
|
"appWithIdOrNameNotFound": "Žádná aplikace s tímto ID nebo názvem nebyla nalezena",
|
||||||
"reposHaveMultipleApps": "Repozitáře mohou obsahovat více aplikací",
|
"reposHaveMultipleApps": "Repozitáře mohou obsahovat více aplikací",
|
||||||
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
|
"fdroidThirdPartyRepo": "F-Droid repozitář třetí strany",
|
||||||
"steam": "Steam",
|
"steam": "Steam",
|
||||||
"steamMobile": "Steam Mobile",
|
"steamMobile": "Steam Mobile",
|
||||||
"steamChat": "Steam Chat",
|
"steamChat": "Steam Chat",
|
||||||
@@ -183,98 +183,111 @@
|
|||||||
"markInstalled": "Označit jako nainstalovaný",
|
"markInstalled": "Označit jako nainstalovaný",
|
||||||
"update": "Aktualizovat",
|
"update": "Aktualizovat",
|
||||||
"markUpdated": "Označit jako aktuální",
|
"markUpdated": "Označit jako aktuální",
|
||||||
"additionalOptions": "Additional Options",
|
"additionalOptions": "Další možnosti",
|
||||||
"disableVersionDetection": "Zakázat detekci verze",
|
"disableVersionDetection": "Deaktivovat detekci verze",
|
||||||
"noVersionDetectionExplanation": "Tato volba by měla být použita pouze u aplikací, kde detekce verzí nefunguje správně.",
|
"noVersionDetectionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně.",
|
||||||
"downloadingX": "download {}",
|
"downloadingX": "Stáhnout {}",
|
||||||
"downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace",
|
"downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace",
|
||||||
"noAPKFound": "Žádná APK nebyla nalezena",
|
"noAPKFound": "Žádná APK nebyla nalezena",
|
||||||
"noVersionDetection": "Žádná detekce verze",
|
"noVersionDetection": "Žádná detekce verze",
|
||||||
"categorize": "Kategorizovat",
|
"categorize": "Kategorizovat",
|
||||||
"categories": "Kategorie",
|
"categories": "Kategorie",
|
||||||
"category": "kategorie",
|
"category": "Kategorie",
|
||||||
"noCategory": "Žádná kategorie",
|
"noCategory": "Žádná kategorie",
|
||||||
"noCategories": "Žádné kategorie",
|
"noCategories": "Žádné kategorie",
|
||||||
"deleteCategoriesQuestion": "Smazat kategorie?",
|
"deleteCategoriesQuestion": "Smazat kategorie?",
|
||||||
"categoryDeleteWarning": "Všechny aplikace v odstraněných kategoriích budou nastaveny na nekategorizované.",
|
"categoryDeleteWarning": "Všechny aplikace v odstraněných kategoriích budou nastaveny na nekategorizované.",
|
||||||
"addCategory": "přidat kategorii",
|
"addCategory": "Přidat kategorii",
|
||||||
"label": "štítek",
|
"label": "Štítek",
|
||||||
"language": "Jazyk",
|
"language": "Jazyk",
|
||||||
"copiedToClipboard": "zkopírováno do schránky",
|
"copiedToClipboard": "Zkopírováno do schránky",
|
||||||
"storagePermissionDenied": "povolení k ukládání odepřeno",
|
"storagePermissionDenied": "Oprávnění k ukládání odepřeno",
|
||||||
"selectedCategorizeWarning": "Toto nahradí všechna stávající nastavení kategorií pro vybrané aplikace.",
|
"selectedCategorizeWarning": "Toto nahradí všechna stávající nastavení kategorií pro vybrané aplikace.",
|
||||||
"filterAPKsByRegEx": "Filtrovat APK podle regulárního výrazu",
|
"filterAPKsByRegEx": "Filtrovat APK podle regulárního výrazu",
|
||||||
"removeFromObtainium": "Odebrat z Obtainium",
|
"removeFromObtainium": "Odebrat z Obtainium",
|
||||||
"uninstallFromDevice": "Odinstalovat ze zařízení",
|
"uninstallFromDevice": "Odinstalovat ze zařízení",
|
||||||
"onlyWorksWithNonVersionDetectApps": "Funguje pouze pro aplikace s vypnutou detekcí verze.",
|
"onlyWorksWithNonVersionDetectApps": "Funguje pouze pro aplikace s vypnutou detekcí verze.",
|
||||||
"releaseDateAsVersion": "Použít datum vydání jako verzi",
|
"releaseDateAsVersion": "Použít datum vydání jako verzi",
|
||||||
"releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikací, u kterých detekce verze nefunguje správně, ale je k dispozici datum vydání.",
|
"releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně, ale je k dispozici datum vydání.",
|
||||||
"changes": "Změny",
|
"changes": "Změny",
|
||||||
"releaseDate": "datum vydání",
|
"releaseDate": "Datum vydání",
|
||||||
"importFromURLsInFile": "Importovat adresy URL ze souboru (např. OPML)",
|
"importFromURLsInFile": "Importovat adresy URL ze souboru (např. OPML)",
|
||||||
"versionDetection": "detekce verze",
|
"versionDetection": "Detekce verze",
|
||||||
"standardVersionDetection": "standardní detekce verze",
|
"standardVersionDetection": "Standardní detekce verze",
|
||||||
"groupByCategory": "Seskupit podle kategorie",
|
"groupByCategory": "Seskupit podle kategorie",
|
||||||
"autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru",
|
"autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru",
|
||||||
"overrideSource": "Přepsat zdroj",
|
"overrideSource": "Přepsat zdroj",
|
||||||
"dontShowAgain": "Nezobrazovat znovu",
|
"dontShowAgain": "Nezobrazovat znovu",
|
||||||
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Track Only'",
|
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'",
|
||||||
"dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK",
|
"dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK",
|
||||||
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
|
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
|
||||||
"about": "About",
|
"about": "O",
|
||||||
"requiresCredentialsInSettings": "Vyžaduje další pověření (v nastavení)",
|
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
|
||||||
"checkOnStart": "Zkontrolovat jednou při spuštění",
|
"checkOnStart": "Zkontrolovat jednou při spuštění",
|
||||||
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
|
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
|
||||||
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",
|
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",
|
||||||
"pickHighestVersionCode": "Automaticky vybrat APK s kódem nejvyšší verze",
|
"pickHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
|
||||||
"checkUpdateOnDetailPage": "Zkontrolovat aktualizace při otevření stránky s podrobnostmi aplikace",
|
"checkUpdateOnDetailPage": "Zkontrolovat aktualizaci při otevření stránky s podrobnostmi aplikace",
|
||||||
"disablePageTransitions": "Zakázat animace pro přechody stránek",
|
"disablePageTransitions": "Zakázat animace pro přechody stránek",
|
||||||
"reversePageTransitions": "Obrátit animace pro přechody stránek",
|
"reversePageTransitions": "Obrátit animace pro přechody stránek",
|
||||||
"minStarCount": "Minimální počet hvězdiček",
|
"minStarCount": "Minimální počet hvězdiček",
|
||||||
"addInfoBelow": "Přidat tuto informaci na konec stránky",
|
"addInfoBelow": "Přidat tuto informaci na konec stránky.",
|
||||||
"addInfoInSettings": "Přidat tuto informaci do nastavení.",
|
"addInfoInSettings": "Přidat tuto informaci do nastavení.",
|
||||||
"githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.",
|
"githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.",
|
||||||
"gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API",
|
"gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API",
|
||||||
"sortByFileNamesNotLinks": "Řadit podle názvů souborů místo celých odkazů",
|
"sortByLastLinkSegment": "Seřadit pouze podle poslední části odkazu",
|
||||||
"filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu",
|
"filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu",
|
||||||
"customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')",
|
"customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')",
|
||||||
"appsPossiblyUpdated": "Byly provedeny pokusy o aktualizaci aplikací",
|
"appsPossiblyUpdated": "Byly provedeny pokusy o aktualizaci aplikací",
|
||||||
"appsPossiblyUpdatedNotifDescription": "Upozorňuje uživatele, že na pozadí mohly být provedeny aktualizace jedné nebo více aplikací",
|
"appsPossiblyUpdatedNotifDescription": "Upozorňuje uživatele, že na pozadí mohly být provedeny aktualizace jedné nebo více aplikací",
|
||||||
"xWasPossiblyUpdatedToY": "{} mohlo být aktualizováno na {}.",
|
"xWasPossiblyUpdatedToY":"{} mohlo být aktualizováno na {}.",
|
||||||
"enableBackgroundUpdates": "Povolit aktualizace na pozadí",
|
"enableBackgroundUpdates": "Povolit aktualizace na pozadí",
|
||||||
"backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možné pro všechny aplikace.",
|
"backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možná pro všechny aplikace.",
|
||||||
"backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřen Obtainium.",
|
"backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřeno Obtainium.",
|
||||||
"verifyLatestTag": "Ověřit značku 'latest'",
|
"verifyLatestTag": "Zkontrolovat značku latest",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filtrovat mezipropojení, které by mělo být navštíveno jako první",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"filterByLinkText": "Filtrovat odkazy podle textu odkazu",
|
||||||
"exemptFromBackgroundUpdates": "Vyloučit aktualizace na pozadí (pokud jsou povoleny)",
|
"intermediateLinkNotFound": "Připojený odkaz nenalezen",
|
||||||
"bgUpdatesOnWiFiOnly": "Zakázat aktualizace na pozadí, pokud není přítomna Wi-Fi",
|
"intermediateLink": "Připojený odkaz",
|
||||||
"autoSelectHighestVersionCode": "Automatický výběr nejvyššího kódu verze APK",
|
"exemptFromBackgroundUpdates": "Vyloučit z aktualizací na pozadí (je-li povoleno)",
|
||||||
"versionExtractionRegEx": "Version Extraction RegEx",
|
"bgUpdatesOnWiFiOnly": "Deaktivovat aktualizace na pozadí, pokud není k dispozici Wi-Fi",
|
||||||
"matchGroupToUse": "Match Group to Use",
|
"autoSelectHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
|
||||||
|
"versionExtractionRegEx": "Extrakce verze pomocí RegEx",
|
||||||
|
"matchGroupToUse": "Odpovídá použité skupině",
|
||||||
"highlightTouchTargets": "Zvýraznit méně zjevné cíle dotyku",
|
"highlightTouchTargets": "Zvýraznit méně zjevné cíle dotyku",
|
||||||
"pickExportDir": "Vybrat adresář pro export",
|
"pickExportDir": "Vybrat adresář pro export",
|
||||||
"autoExportOnChanges": "Automatický export při změnách",
|
"autoExportOnChanges": "Automatický export při změně",
|
||||||
"filterVersionsByRegEx": "Filtrovat verze podle regulárního výrazu",
|
"includeSettings": "Zahrnout nastavení",
|
||||||
"trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovaný kód verze APK",
|
"filterVersionsByRegEx": "Filtrovat verze podle regulárních výrazů",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovanou verzi APK",
|
||||||
"reverseSort": "Reverse sorting",
|
"dontSortReleasesList": "Seřadit vydání z rozhraní API",
|
||||||
"debugMenu": "Debug Menu",
|
"reverseSort": "Obrácené třídění",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"takeFirstLink": "Použít první odkaz",
|
||||||
"runBgCheckNow": "Run Background Update Check Now",
|
"skipSort": "Přeskočit třídění",
|
||||||
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
"debugMenu": "Nabídka ladění",
|
||||||
"installing": "Installing",
|
"bgTaskStarted": "Spuštěna úloha na pozadí - zkontrolujte protokoly.",
|
||||||
"skipUpdateNotifications": "Skip update notifications",
|
"runBgCheckNow": "Spustit kontrolu aktualizací na pozadí nyní",
|
||||||
"updatesAvailableNotifChannel": "dostupné aktualizace",
|
"versionExtractWholePage": "Použít extrakci verze pomocí RegEx na celou stránku",
|
||||||
"appsUpdatedNotifChannel": "Aplikace aktualizovány",
|
"installing": "Instaluji",
|
||||||
"appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizaci aplikací",
|
"skipUpdateNotifications": "Neposkytovat oznámení o aktualizaci",
|
||||||
"errorCheckingUpdatesNotifChannel": "Chybová kontrola aktualizací",
|
"updatesAvailableNotifChannel": "Dostupné aktualizace",
|
||||||
"appsRemovedNotifChannel": "Odstraněné aplikace",
|
"appsUpdatedNotifChannel": "Apky aktualizovány",
|
||||||
"downloadingXNotifChannel": "download {}",
|
"appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizace aplikací",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Chyba při kontrole aktualizací",
|
||||||
|
"appsRemovedNotifChannel": "Odstraněné apky",
|
||||||
|
"downloadingXNotifChannel": "Stáhnout {}",
|
||||||
"completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
|
"completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
|
||||||
"checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
|
"checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Na aktualizace kontrolovat pouze nainstalované aplikace a aplikace označené Track only",
|
||||||
|
"supportFixedAPKURL": "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í",
|
||||||
|
"installMethod": "Metoda instalace",
|
||||||
|
"normal": "Normální",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Správce",
|
||||||
|
"shizukuBinderNotFound": "Shizuku neběží",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Odstranit Apku?",
|
"one": "Odstranit Apku?",
|
||||||
"other": "Odstranit Apky?"
|
"other": "Odstranit Apky?"
|
||||||
@@ -284,47 +297,47 @@
|
|||||||
"other": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minut"
|
"other": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minut"
|
||||||
},
|
},
|
||||||
"bgUpdateGotErrorRetryInMinutes": {
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
"one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování pokusu bude naplánováno za {} minut",
|
"one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut",
|
||||||
"other": "Během kontroly aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut"
|
"other": "Při kontrole aktualizací na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut"
|
||||||
},
|
},
|
||||||
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
"one": "Při kontrole aktualizací na pozadí nalezena {}aktualizace - v případě potřeby upozorní uživatele",
|
"one": "Při kontrole aktualizací na pozadí nalezena {}aktualizace - v případě potřeby upozorní uživatele",
|
||||||
"other": "Kontrola aktualizací na pozadí našla {} aktualizací - v případě potřeby upozorní uživatele"
|
"other": "Kontrola aktualizací na pozadí nalezla {} aktualizací - v případě potřeby upozorní uživatele"
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"one": "{} App",
|
"one": "{} Apka",
|
||||||
"other": "{} apps"
|
"other": "{} Apky"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"jedna": "{} URL",
|
"one": "{} Adresa",
|
||||||
"other": "{} URLs"
|
"other": "{} Adres"
|
||||||
},
|
},
|
||||||
"minute": {
|
"minute": {
|
||||||
"one": "{} minute",
|
"one": "{} Minuta",
|
||||||
"other": "{} minutes"
|
"other": "{} Minut"
|
||||||
},
|
},
|
||||||
"hour": {
|
"hour": {
|
||||||
"jedna": "{} hodina",
|
"one": "{} Hodina",
|
||||||
"other": "{} hours"
|
"other": "{} Hodin"
|
||||||
},
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"jedna": "{} den",
|
"one": "{} Den",
|
||||||
"other": "{} dny"
|
"other": "{} Dnů"
|
||||||
},
|
},
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"clearedNLogsBeforeXAfterY": {
|
||||||
"one": "{n} log vymazán (před = {před}, po = {po})",
|
"one": "{n} Záznam vymazán (před = {before}, po = {after})",
|
||||||
"other": "{n} logů vymazáno (před = {před}, po = {po})"
|
"other": "{n} Záznamů vymazáno (před = {before}, po = {after})"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "{} a 1 další aplikace mají aktualizace.",
|
"one": "{} a 1 další aplikace mají aktualizace.",
|
||||||
"other": "{} a {} další aplikace mají aktualizace."
|
"other": "{} a {} další aplikace mají aktualizace."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} a {} další aplikace mají aktualizace.",
|
"one": "{} a 1 další aplikace mají aktualizace.",
|
||||||
"další": "{} a {} další aplikace byly aktualizovány."
|
"other": "{} a {} další aplikace byly aktualizovány."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesPossiblyInstalled": {
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
"one": "{} a {} další aplikace byly možná aktualizovány",
|
"one": "{} a 1 další aplikace možno aktualizovat",
|
||||||
"other": "{} a {} další aplikace mohly být aktualizovány."
|
"other": "{} a {} další aplikace mohou být aktualizovány."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
"colour": "Farbe",
|
"colour": "Farbe",
|
||||||
"githubStarredRepos": "GitHub Starred Repos",
|
"githubStarredRepos": "GitHub Starred Repos",
|
||||||
"uname": "Benutzername",
|
"uname": "Benutzername",
|
||||||
"wrongArgNum": "Falsche Anzahl von Argumenten übermittelt",
|
"wrongArgNum": "Falsche Anzahl von Argumenten (Parametern) übermittelt",
|
||||||
"xIsTrackOnly": "{} ist nur zur Nachverfolgung",
|
"xIsTrackOnly": "{} ist nur zur Nachverfolgung",
|
||||||
"source": "Quelle",
|
"source": "Quelle",
|
||||||
"app": "App",
|
"app": "App",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Nicht installiert",
|
"notInstalled": "Nicht installiert",
|
||||||
"estimateInBrackets": "(Ungefähr)",
|
"estimateInBrackets": "(Ungefähr)",
|
||||||
"selectAll": "Alle auswählen",
|
"selectAll": "Alle auswählen",
|
||||||
"deselectN": "{} abgewählt",
|
"deselectX": "{} abgewählt",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} wird aus Obtainium entfernt, bleibt aber auf dem Gerät installiert.",
|
"xWillBeRemovedButRemainInstalled": "{} wird aus Obtainium entfernt, bleibt aber auf dem Gerät installiert.",
|
||||||
"removeSelectedAppsQuestion": "Ausgewählte Apps entfernen?",
|
"removeSelectedAppsQuestion": "Ausgewählte Apps entfernen?",
|
||||||
"removeSelectedApps": "Ausgewählte Apps entfernen",
|
"removeSelectedApps": "Ausgewählte Apps entfernen",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"shareSelectedAppURLs": "Ausgewählte App-URLs teilen",
|
"shareSelectedAppURLs": "Ausgewählte App-URLs teilen",
|
||||||
"resetInstallStatus": "Installationsstatus zurücksetzen",
|
"resetInstallStatus": "Installationsstatus zurücksetzen",
|
||||||
"more": "Mehr",
|
"more": "Mehr",
|
||||||
"removeOutdatedFilter": "App-Filter 'Nicht aktuell' entfernen",
|
"removeOutdatedFilter": "App-Filter ‚Nicht aktuell‘ entfernen",
|
||||||
"showOutdatedOnly": "Nur nicht aktuelle Apps anzeigen",
|
"showOutdatedOnly": "Nur nicht aktuelle Apps anzeigen",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"filterActive": "Filter *",
|
"filterActive": "Filter *",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
|
||||||
"about": "Über",
|
"about": "Über",
|
||||||
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
"requiresCredentialsInSettings": "{}: Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||||
"checkOnStart": "Überprüfe einmalig beim Start",
|
"checkOnStart": "Überprüfe einmalig beim Start",
|
||||||
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
|
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
|
||||||
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
|
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
|
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
|
||||||
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
|
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
|
||||||
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
|
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
|
||||||
"sortByFileNamesNotLinks": "Sortiere nach Dateinamen, anstelle von ganzen Links",
|
"sortByLastLinkSegment": "Sortiere nur nach dem letzten Teil des Links",
|
||||||
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
|
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
|
||||||
"customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')",
|
"customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Aktualisierungen wurden versucht",
|
"appsPossiblyUpdated": "App Aktualisierungen wurden versucht",
|
||||||
@@ -247,7 +247,9 @@
|
|||||||
"backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.",
|
"backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.",
|
||||||
"verifyLatestTag": "Überprüfe das „latest“ Tag",
|
"verifyLatestTag": "Überprüfe das „latest“ Tag",
|
||||||
"intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll",
|
"intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll",
|
||||||
"intermediateLinkNotFound": "„Zwischen“link nicht gefunden",
|
"filterByLinkText": "Filtere Links durch Linktext",
|
||||||
|
"intermediateLinkNotFound": "„Zwischen“-Link nicht gefunden",
|
||||||
|
"intermediateLink": "„Zwischen“-Link",
|
||||||
"exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)",
|
"exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)",
|
||||||
"bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist",
|
"bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist",
|
||||||
"autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen",
|
"autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Weniger offensichtliche Touch-Ziele hervorheben",
|
"highlightTouchTargets": "Weniger offensichtliche Touch-Ziele hervorheben",
|
||||||
"pickExportDir": "Export-Verzeichnis wählen",
|
"pickExportDir": "Export-Verzeichnis wählen",
|
||||||
"autoExportOnChanges": "Automatischer Export bei Änderung(en)",
|
"autoExportOnChanges": "Automatischer Export bei Änderung(en)",
|
||||||
|
"includeSettings": "Einstellungen einbeziehen",
|
||||||
"filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern",
|
"filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern",
|
||||||
"trySelectingSuggestedVersionCode": "Versuchen, den vorgeschlagenen APK-Versionscode auszuwählen",
|
"trySelectingSuggestedVersionCode": "Versuchen, den vorgeschlagenen APK-Versionscode auszuwählen",
|
||||||
"dontSortReleasesList": "Freigaberelease von der API ordern",
|
"dontSortReleasesList": "Freigaberelease von der API ordern",
|
||||||
"reverseSort": "Umgekehrtes Sortieren",
|
"reverseSort": "Umgekehrtes Sortieren",
|
||||||
|
"takeFirstLink": "Verwende den ersten Link",
|
||||||
|
"skipSort": "Überspringe Sortieren",
|
||||||
"debugMenu": "Debug-Menü",
|
"debugMenu": "Debug-Menü",
|
||||||
"bgTaskStarted": "Hintergrundaufgabe gestartet – Logs prüfen.",
|
"bgTaskStarted": "Hintergrundaufgabe gestartet – Logs prüfen.",
|
||||||
"runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen",
|
"runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen",
|
||||||
@@ -274,7 +279,10 @@
|
|||||||
"downloadingXNotifChannel": "Lade {} herunter",
|
"downloadingXNotifChannel": "Lade {} herunter",
|
||||||
"completeAppInstallationNotifChannel": "App Installation abschließen",
|
"completeAppInstallationNotifChannel": "App Installation abschließen",
|
||||||
"checkingForUpdatesNotifChannel": "Nach Aktualisierungen suchen",
|
"checkingForUpdatesNotifChannel": "Nach Aktualisierungen suchen",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps nach Aktualisierungen",
|
"onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps auf Aktualisierungen",
|
||||||
|
"supportFixedAPKURL": "neuere Version anhand der ersten dreißig Zahlen der Checksumme der APK URL erraten, wenn anderweitig nicht unterstützt",
|
||||||
|
"selectX": "Wähle {}",
|
||||||
|
"parallelDownloads": "Erlaube parallele Downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App entfernen?",
|
"one": "App entfernen?",
|
||||||
"other": "Apps entfernen?"
|
"other": "Apps entfernen?"
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Not Installed",
|
"notInstalled": "Not Installed",
|
||||||
"estimateInBrackets": "(Estimate)",
|
"estimateInBrackets": "(Estimate)",
|
||||||
"selectAll": "Select All",
|
"selectAll": "Select All",
|
||||||
"deselectN": "Deselect {}",
|
"deselectX": "Deselect {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} will be removed from Obtainium but remain installed on device.",
|
"xWillBeRemovedButRemainInstalled": "{} will be removed from Obtainium but remain installed on device.",
|
||||||
"removeSelectedAppsQuestion": "Remove Selected Apps?",
|
"removeSelectedAppsQuestion": "Remove Selected Apps?",
|
||||||
"removeSelectedApps": "Remove Selected Apps",
|
"removeSelectedApps": "Remove Selected Apps",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "{} needs additional credentials (in Settings)",
|
||||||
"checkOnStart": "Check for updates on startup",
|
"checkOnStart": "Check for updates on startup",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Add this info in the Settings.",
|
"addInfoInSettings": "Add this info in the Settings.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
||||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
"verifyLatestTag": "Verify the 'latest' tag",
|
"verifyLatestTag": "Verify the 'latest' tag",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
||||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Highlight less obvious touch targets",
|
"highlightTouchTargets": "Highlight less obvious touch targets",
|
||||||
"pickExportDir": "Pick Export Directory",
|
"pickExportDir": "Pick Export Directory",
|
||||||
"autoExportOnChanges": "Auto-export on changes",
|
"autoExportOnChanges": "Auto-export on changes",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
||||||
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"dontSortReleasesList": "Retain release order from API",
|
||||||
"reverseSort": "Reverse sorting",
|
"reverseSort": "Reverse sorting",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Debug Menu",
|
"debugMenu": "Debug Menu",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"bgTaskStarted": "Background task started - check logs.",
|
||||||
"runBgCheckNow": "Run Background Update Check Now",
|
"runBgCheckNow": "Run Background Update Check Now",
|
||||||
@@ -275,6 +280,14 @@
|
|||||||
"completeAppInstallationNotifChannel": "Complete App Installation",
|
"completeAppInstallationNotifChannel": "Complete App Installation",
|
||||||
"checkingForUpdatesNotifChannel": "Checking for Updates",
|
"checkingForUpdatesNotifChannel": "Checking for Updates",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Remove App?",
|
"one": "Remove App?",
|
||||||
"other": "Remove Apps?"
|
"other": "Remove Apps?"
|
||||||
|
@@ -9,28 +9,28 @@
|
|||||||
"placeholder": "Espacio reservado",
|
"placeholder": "Espacio reservado",
|
||||||
"someErrors": "Han ocurrido algunos errores",
|
"someErrors": "Han ocurrido algunos errores",
|
||||||
"unexpectedError": "Error Inesperado",
|
"unexpectedError": "Error Inesperado",
|
||||||
"ok": "Correcto",
|
"ok": "OK",
|
||||||
"and": "y",
|
"and": "y",
|
||||||
"githubPATLabel": "Token de Acceso Personal de GitHub (Reduce tiempos de espera)",
|
"githubPATLabel": "Token Github de Acceso Personal\n(Reduce tiempos de espera)",
|
||||||
"includePrereleases": "Incluir versiones preliminares",
|
"includePrereleases": "Incluir versiones preliminares",
|
||||||
"fallbackToOlderReleases": "Retorceder a versiones previas",
|
"fallbackToOlderReleases": "Retroceder a versiones previas",
|
||||||
"filterReleaseTitlesByRegEx": "Filtra Títulos de Versiones mediantes Expresiones Regulares",
|
"filterReleaseTitlesByRegEx": "Filtrar por título de version",
|
||||||
"invalidRegEx": "Expresión regular inválida",
|
"invalidRegEx": "Expresión inválida",
|
||||||
"noDescription": "Sin descripción",
|
"noDescription": "Sin descripción",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
"requiredInBrackets": "(Requerido)",
|
"requiredInBrackets": "(Requerido)",
|
||||||
"dropdownNoOptsError": "ERROR: EL DESPLEGABLE DEBE TENER AL MENOS UNA OPCIÓN",
|
"dropdownNoOptsError": "ERROR: EL DESPLEGABLE DEBE TENER AL MENOS UNA OPCIÓN",
|
||||||
"colour": "Color",
|
"colour": "Color",
|
||||||
"githubStarredRepos": "Repositorios favoritos de GitHub",
|
"githubStarredRepos": "Repositorios favoritos GitHub",
|
||||||
"uname": "Nombre de usuario",
|
"uname": "Nombre de usuario",
|
||||||
"wrongArgNum": "Número de argumentos provistos inválido",
|
"wrongArgNum": "Número de argumentos provistos inválido",
|
||||||
"xIsTrackOnly": "{} es de 'Solo Seguimiento'",
|
"xIsTrackOnly": "{} es de 'Solo Seguimiento'",
|
||||||
"source": "Origen",
|
"source": "Origen",
|
||||||
"app": "Aplicación",
|
"app": "Aplicación",
|
||||||
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
|
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
|
||||||
"youPickedTrackOnly": "Debes seleccionar la opción de 'Solo Seguimiento'.",
|
"youPickedTrackOnly": "Debe seleccionar la opción de 'Solo Seguimiento'.",
|
||||||
"trackOnlyAppDescription": "Se monitorizará la aplicación en busca de actualizaciones, pero Obtainium no será capaz de descargarla o acutalizarla.",
|
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargarla o actalizarla.",
|
||||||
"cancelled": "Cancelado",
|
"cancelled": "Cancelado",
|
||||||
"appAlreadyAdded": "Aplicación ya añadida",
|
"appAlreadyAdded": "Aplicación ya añadida",
|
||||||
"alreadyUpToDateQuestion": "¿Aplicación ya actualizada?",
|
"alreadyUpToDateQuestion": "¿Aplicación ya actualizada?",
|
||||||
@@ -38,16 +38,16 @@
|
|||||||
"appSourceURL": "URL de Origen de la Aplicación",
|
"appSourceURL": "URL de Origen de la Aplicación",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"add": "Añadir",
|
"add": "Añadir",
|
||||||
"searchSomeSourcesLabel": "Buscar (Solo Algunas Fuentes)",
|
"searchSomeSourcesLabel": "Buscar (solo algunas fuentes)",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
"additionalOptsFor": "Opciones Adicionales para {}",
|
"additionalOptsFor": "Opciones Adicionales para {}",
|
||||||
"supportedSources": "Fuentes Soportadas",
|
"supportedSources": "Fuentes Soportadas",
|
||||||
"trackOnlyInBrackets": "(Solo Seguimiento)",
|
"trackOnlyInBrackets": "(Solo Seguimiento)",
|
||||||
"searchableInBrackets": "(Soporta Búsquedas)",
|
"searchableInBrackets": "(soporta búsqueda)",
|
||||||
"appsString": "Aplicaciones",
|
"appsString": "Aplicaciones",
|
||||||
"noApps": "Sin Aplicaciones",
|
"noApps": "Sin Aplicaciones",
|
||||||
"noAppsForFilter": "Sin Aplicaciones para Filtrar",
|
"noAppsForFilter": "Sin aplicaciones para filtrar",
|
||||||
"byX": "Por {}",
|
"byX": "por: {}",
|
||||||
"percentProgress": "Progreso: {}%",
|
"percentProgress": "Progreso: {}%",
|
||||||
"pleaseWait": "Por favor, espere",
|
"pleaseWait": "Por favor, espere",
|
||||||
"updateAvailable": "Actualización Disponible",
|
"updateAvailable": "Actualización Disponible",
|
||||||
@@ -55,13 +55,13 @@
|
|||||||
"notInstalled": "No Instalado",
|
"notInstalled": "No Instalado",
|
||||||
"estimateInBrackets": "(Aproximado)",
|
"estimateInBrackets": "(Aproximado)",
|
||||||
"selectAll": "Seleccionar Todo",
|
"selectAll": "Seleccionar Todo",
|
||||||
"deselectN": "Deseleccionar {}",
|
"deselectX": "Deseleccionar {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} será borrada de Obtainium pero continuará instalada en el dispositivo.",
|
"xWillBeRemovedButRemainInstalled": "{} será eliminada de Obtainium pero continuará instalada en el dispositivo.",
|
||||||
"removeSelectedAppsQuestion": "¿Borrar aplicaciones seleccionadas?",
|
"removeSelectedAppsQuestion": "¿Eliminar aplicaciones seleccionadas?",
|
||||||
"removeSelectedApps": "Borrar Aplicaciones Seleccionadas",
|
"removeSelectedApps": "Eliminar Aplicaciones Seleccionadas",
|
||||||
"updateX": "Actualizar {}",
|
"updateX": "Actualizar {}",
|
||||||
"installX": "Instalar {}",
|
"installX": "Instalar {}",
|
||||||
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo Seguimient)\ncomo Actualizada",
|
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo Seguimiento)\ncomo Actualizada",
|
||||||
"changeX": "Cambiar {}",
|
"changeX": "Cambiar {}",
|
||||||
"installUpdateApps": "Instalar/Actualizar Aplicaciones",
|
"installUpdateApps": "Instalar/Actualizar Aplicaciones",
|
||||||
"installUpdateSelectedApps": "Instalar/Actualizar Aplicaciones Seleccionadas",
|
"installUpdateSelectedApps": "Instalar/Actualizar Aplicaciones Seleccionadas",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"pinToTop": "Fijar arriba",
|
"pinToTop": "Fijar arriba",
|
||||||
"unpinFromTop": "Desfijar de arriba",
|
"unpinFromTop": "Desfijar de arriba",
|
||||||
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar Estado de Instalación para las Aplicaciones Seleccionadas?",
|
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar Estado de Instalación para las Aplicaciones Seleccionadas?",
|
||||||
"installStatusOfXWillBeResetExplanation": "El estado de instalación de las aplicaciones seleccionadas será restaurado.\n\nEsto puede ser de utilidad cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
|
"installStatusOfXWillBeResetExplanation": "El estado de instalación de las aplicaciones seleccionadas será restaurado.\n\nEsto puede ser de útil cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
|
||||||
"shareSelectedAppURLs": "Compartir URLs de las Aplicaciones Seleccionadas",
|
"shareSelectedAppURLs": "Compartir URLs de las Aplicaciones Seleccionadas",
|
||||||
"resetInstallStatus": "Restaurar Estado de Instalación",
|
"resetInstallStatus": "Restaurar Estado de Instalación",
|
||||||
"more": "Más",
|
"more": "Más",
|
||||||
@@ -98,12 +98,12 @@
|
|||||||
"line": "Línea",
|
"line": "Línea",
|
||||||
"searchX": "Buscar {}",
|
"searchX": "Buscar {}",
|
||||||
"noResults": "Resultados no encontrados",
|
"noResults": "Resultados no encontrados",
|
||||||
"importX": "Importar {}",
|
"importX": "Importar desde {}",
|
||||||
"importedAppsIdDisclaimer": "Las Aplicaciones Importadas pueden mostrarse incorrectamente como \"No Instalada\".\nPara arreglar esto, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
|
"importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como \"No Instalada\".\nPara solucionarlo, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
|
||||||
"importErrors": "Import Errors",
|
"importErrors": "Errores de Importación",
|
||||||
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
|
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
|
||||||
"followingURLsHadErrors": "Las siguientes URLs tuvieron problemas:",
|
"followingURLsHadErrors": "Las siguientes URLs han tenido problemas:",
|
||||||
"okay": "Correcto",
|
"okay": "Aceptar",
|
||||||
"selectURL": "Seleccionar URL",
|
"selectURL": "Seleccionar URL",
|
||||||
"selectURLs": "Seleccionar URLs",
|
"selectURLs": "Seleccionar URLs",
|
||||||
"pick": "Escoger",
|
"pick": "Escoger",
|
||||||
@@ -113,12 +113,12 @@
|
|||||||
"followSystem": "Seguir al Sistema",
|
"followSystem": "Seguir al Sistema",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Usar tema oscuro con negros puros",
|
"useBlackTheme": "Negro puro en tema Oscuro",
|
||||||
"appSortBy": "Ordenar Aplicaciones Por",
|
"appSortBy": "Ordenar Apps Por",
|
||||||
"authorName": "Autor/Nombre",
|
"authorName": "Autor/Nombre",
|
||||||
"nameAuthor": "Nombre/Autor",
|
"nameAuthor": "Nombre/Autor",
|
||||||
"asAdded": "Según se Añadieron",
|
"asAdded": "Según se Añadieron",
|
||||||
"appSortOrder": "Orden de Clasificación de Aplicaciones",
|
"appSortOrder": "Orden de Clasificación",
|
||||||
"ascending": "Ascendente",
|
"ascending": "Ascendente",
|
||||||
"descending": "Descendente",
|
"descending": "Descendente",
|
||||||
"bgUpdateCheckInterval": "Intervalo de Comprobación de Actualizaciones en Segundo Plano",
|
"bgUpdateCheckInterval": "Intervalo de Comprobación de Actualizaciones en Segundo Plano",
|
||||||
@@ -135,10 +135,10 @@
|
|||||||
"share": "Compartir",
|
"share": "Compartir",
|
||||||
"appNotFound": "Aplicación no encontrada",
|
"appNotFound": "Aplicación no encontrada",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "Elige una APK",
|
"pickAnAPK": "Seleccione una APK",
|
||||||
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
|
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
|
||||||
"deviceSupportsXArch": "Tu dispositivo soporta las siguientes arquitecturas de procesador: {}.",
|
"deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.",
|
||||||
"deviceSupportsFollowingArchs": "Tu dispositivo soporta las siguientes arquitecturas de procesador:",
|
"deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:",
|
||||||
"warning": "Aviso",
|
"warning": "Aviso",
|
||||||
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
|
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
|
||||||
"updatesAvailable": "Actualizaciones Disponibles",
|
"updatesAvailable": "Actualizaciones Disponibles",
|
||||||
@@ -154,11 +154,11 @@
|
|||||||
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
|
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
|
||||||
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
|
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
|
||||||
"completeAppInstallation": "Instalación Completa de la Aplicación",
|
"completeAppInstallation": "Instalación Completa de la Aplicación",
|
||||||
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierta para instalar aplicaciones",
|
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierto para instalar aplicaciones",
|
||||||
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para teminar de instalar una aplicación",
|
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación",
|
||||||
"checkingForUpdates": "Buscando Actualizaciones",
|
"checkingForUpdates": "Buscando Actualizaciones",
|
||||||
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
|
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
|
||||||
"pleaseAllowInstallPerm": "Por favor, permite a Obtainium instalar aplicaciones",
|
"pleaseAllowInstallPerm": "Por favor, permita que Obtainium instale aplicaciones",
|
||||||
"trackOnly": "Solo Seguimiento",
|
"trackOnly": "Solo Seguimiento",
|
||||||
"errorWithHttpStatusCode": "Error {}",
|
"errorWithHttpStatusCode": "Error {}",
|
||||||
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
|
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
|
||||||
@@ -170,24 +170,24 @@
|
|||||||
"lastUpdateCheckX": "Última Comprobación: {}",
|
"lastUpdateCheckX": "Última Comprobación: {}",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"yesMarkUpdated": "Sí, Marcar como Actualizada",
|
"yesMarkUpdated": "Sí, Marcar como Actualizada",
|
||||||
"fdroid": "Repositorio oficial de F-Droid",
|
"fdroid": "Repositorio oficial F-Droid",
|
||||||
"appIdOrName": "ID o Nombre de la Aplicación",
|
"appIdOrName": "ID o Nombre de la Aplicación",
|
||||||
"appId": "ID de la Aplicación",
|
"appId": "ID de la Aplicación",
|
||||||
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
|
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
|
||||||
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
|
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
|
||||||
"fdroidThirdPartyRepo": "Rpositorios de terceros de F-Droid",
|
"fdroidThirdPartyRepo": "Rpositorios de terceros F-Droid",
|
||||||
"steam": "Steam",
|
"steam": "Steam",
|
||||||
"steamMobile": "Steam Mobile",
|
"steamMobile": "Steam Mobile",
|
||||||
"steamChat": "Steam Chat",
|
"steamChat": "Steam Chat",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
"markInstalled": "Marcar como Instalda",
|
"markInstalled": "Marcar como Instalada",
|
||||||
"update": "Actualizar",
|
"update": "Actualizar",
|
||||||
"markUpdated": "Marcar como Actualizada",
|
"markUpdated": "Marcar como Actualizada",
|
||||||
"additionalOptions": "Opciones Adicionales",
|
"additionalOptions": "Opciones Adicionales",
|
||||||
"disableVersionDetection": "Descativar Detección de Versiones",
|
"disableVersionDetection": "Descativar Detección de Versiones",
|
||||||
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda no funcionar correctamente.",
|
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda no funcionar correctamente.",
|
||||||
"downloadingX": "Descargando {}",
|
"downloadingX": "Descargando {}",
|
||||||
"downloadNotifDescription": "Notifica al usuario de progreso de descarga de una aplicación",
|
"downloadNotifDescription": "Notifica al usuario del progreso de descarga de una aplicación",
|
||||||
"noAPKFound": "APK no encontrada",
|
"noAPKFound": "APK no encontrada",
|
||||||
"noVersionDetection": "Sin detección de versiones",
|
"noVersionDetection": "Sin detección de versiones",
|
||||||
"categorize": "Catogorizar",
|
"categorize": "Catogorizar",
|
||||||
@@ -195,86 +195,94 @@
|
|||||||
"category": "Categoría",
|
"category": "Categoría",
|
||||||
"noCategory": "Sin Categoría",
|
"noCategory": "Sin Categoría",
|
||||||
"noCategories": "Sin Categorías",
|
"noCategories": "Sin Categorías",
|
||||||
"deleteCategoriesQuestion": "¿Borrar Categorías?",
|
"deleteCategoriesQuestion": "¿Eliminar Categorías?",
|
||||||
"categoryDeleteWarning": "Todas las aplicaciones en las categorías borradas serán margadas 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",
|
"addCategory": "Añadir Categoría",
|
||||||
"label": "Nombre",
|
"label": "Nombre",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"copiedToClipboard": "Copiado al Portapapeles",
|
"copiedToClipboard": "Copiado al Portapapeles",
|
||||||
"storagePermissionDenied": "Permiso de Almacenamiento rechazado",
|
"storagePermissionDenied": "Permiso de Almacenamiento rechazado",
|
||||||
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaicones seleccionadas.",
|
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaciones seleccionadas.",
|
||||||
"filterAPKsByRegEx": "Filtrar APKs mediante Expresiones Regulares",
|
"filterAPKsByRegEx": "Filtrar por APKs",
|
||||||
"removeFromObtainium": "Eliminar de Obtainium",
|
"removeFromObtainium": "Eliminar de Obtainium",
|
||||||
"uninstallFromDevice": "Desinstalar del Dispositivo",
|
"uninstallFromDevice": "Desinstalar del Dispositivo",
|
||||||
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
|
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
|
||||||
"releaseDateAsVersion": "Usar Fecha de Publicación como Versión",
|
"releaseDateAsVersion": "Por fecha de publicación",
|
||||||
"releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.",
|
"releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.",
|
||||||
"changes": "Cambios",
|
"changes": "Cambios",
|
||||||
"releaseDate": "Fecha de Publicación",
|
"releaseDate": "Fecha de Publicación",
|
||||||
"importFromURLsInFile": "Importar de URls en un Archivo (como OPML)",
|
"importFromURLsInFile": "Importar URLs desde archivo (como OPML)",
|
||||||
"versionDetection": "Detección de Versiones",
|
"versionDetection": "Detección de Versiones",
|
||||||
"standardVersionDetection": "Detección de versiones estándar",
|
"standardVersionDetection": "Por versión",
|
||||||
"groupByCategory": "Agrupar por Categoría",
|
"groupByCategory": "Agrupar por Categoría",
|
||||||
"autoApkFilterByArch": "Tratar de filtrar las APKs mediante arquitecturas de procesador si es posible",
|
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador, si es posible",
|
||||||
"overrideSource": "Sobrescribir Fuente",
|
"overrideSource": "Sobrescribir Fuente",
|
||||||
"dontShowAgain": "No mostrar de nuevo",
|
"dontShowAgain": "No mostrar de nuevo",
|
||||||
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
|
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
|
||||||
"dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APks",
|
"dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APKs",
|
||||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas en la Parte Inferior de la Vista de Aplicaciones",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
"gitlabPATLabel": "Token GitLab de Acceso Personal\n(Habilita la Búsqueda y Mejor Detección de APKs)",
|
||||||
"about": "About",
|
"about": "Acerca",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en Ajustes)",
|
||||||
"checkOnStart": "Check for updates on startup",
|
"checkOnStart": "Comprobar actualizaciones durante el inicio",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Intentar deducir la ID de la APP por el código fuente",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Auto eliminar Apps desinstaladas externamente",
|
||||||
"pickHighestVersionCode": "Auto-select highest version code APK",
|
"pickHighestVersionCode": "Auto selección versión superior del código APK",
|
||||||
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
|
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la App",
|
||||||
"disablePageTransitions": "Disable page transition animations",
|
"disablePageTransitions": "Deshabilitar animaciones de transición de la página",
|
||||||
"reversePageTransitions": "Reverse page transition animations",
|
"reversePageTransitions": "Invertir animaciones de transición de la página",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Número Mínimo de Estrellas",
|
||||||
"addInfoBelow": "Add this info below.",
|
"addInfoBelow": "Añadir esta información debajo.",
|
||||||
"addInfoInSettings": "Add this info in the Settings.",
|
"addInfoInSettings": "Puede añadir esta información en Ajustes.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con una clave API.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin una clave API.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filtrar por notas de nersión (release notes)",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Filtro personalizado de Enlace APK (por defecto '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "Actualización de Apps intentada",
|
||||||
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
|
"appsPossiblyUpdatedNotifDescription": "Notifica al usuario que las actualizaciones en segundo plano podrían haberse realizado para una o más aplicaciones",
|
||||||
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
|
"xWasPossiblyUpdatedToY": "{} podría estar actualizada a {}.",
|
||||||
"enableBackgroundUpdates": "Enable background updates",
|
"enableBackgroundUpdates": "Habilitar actualizaciones en segundo plano",
|
||||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
"backgroundUpdateReqsExplanation": "Las actualizaciones en segundo plano pueden no estar disponibles para todas las aplicaciones.",
|
||||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
"backgroundUpdateLimitsExplanation": "El éxito de las instalaciones en segundo plano solo se puede comprobar con Obtainium abierto.",
|
||||||
"verifyLatestTag": "Verify the 'latest' tag",
|
"verifyLatestTag": "Comprueba la etiqueta 'Latest'",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filtrar por enlace 'intermedio' para visitar primero",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"filterByLinkText": "Filter links by link text",
|
||||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
"intermediateLinkNotFound": "Enlace intermedio no encontrado",
|
||||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
"intermediateLink": "Intermediate link",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"exemptFromBackgroundUpdates": "Exenta de actualizciones en segundo plano (si están habilitadas)",
|
||||||
"versionExtractionRegEx": "Version Extraction RegEx",
|
"bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi",
|
||||||
"matchGroupToUse": "Match Group to Use",
|
"autoSelectHighestVersionCode": "Auto Selección de la versionCode APK superior",
|
||||||
"highlightTouchTargets": "Highlight less obvious touch targets",
|
"versionExtractionRegEx": "Versión de Extracción de RegEx",
|
||||||
"pickExportDir": "Pick Export Directory",
|
"matchGroupToUse": "Coincidir en Grupo a Usar",
|
||||||
"autoExportOnChanges": "Auto-export on changes",
|
"highlightTouchTargets": "Resaltar objetivos menos obvios",
|
||||||
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
"pickExportDir": "Selecciona el Directorio para Exportar",
|
||||||
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
"autoExportOnChanges": "Auto Exportar cuando haya cambios",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"includeSettings": "Incluir ajustes",
|
||||||
"reverseSort": "Reverse sorting",
|
"filterVersionsByRegEx": "Filtrar por Versiones",
|
||||||
"debugMenu": "Debug Menu",
|
"trySelectingSuggestedVersionCode": "Pruebe seleccionando la versionCode APK sugerida",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"dontSortReleasesList": "Mantener el order de publicación desde API",
|
||||||
"runBgCheckNow": "Run Background Update Check Now",
|
"reverseSort": "Orden inverso",
|
||||||
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
"takeFirstLink": "Usar primer enlace",
|
||||||
"installing": "Installing",
|
"skipSort": "Omitir orden",
|
||||||
"skipUpdateNotifications": "Skip update notifications",
|
"debugMenu": "Menu Depurar",
|
||||||
|
"bgTaskStarted": "Iniciada tarea en segundo plano - revisa los logs.",
|
||||||
|
"runBgCheckNow": "Ejecutar verficiación de actualizaciones en segundo plano",
|
||||||
|
"versionExtractWholePage": "Aplicar la Versión de Extracción Regex a la Página Entera",
|
||||||
|
"installing": "Instalando",
|
||||||
|
"skipUpdateNotifications": "Omitir de notificaciones sobre actualizaciones",
|
||||||
"updatesAvailableNotifChannel": "Actualizaciones Disponibles",
|
"updatesAvailableNotifChannel": "Actualizaciones Disponibles",
|
||||||
"appsUpdatedNotifChannel": "Aplicaciones Actualizadas",
|
"appsUpdatedNotifChannel": "Aplicaciones Actualizadas",
|
||||||
"appsPossiblyUpdatedNotifChannel": "App Updates Attempted",
|
"appsPossiblyUpdatedNotifChannel": "Se ha Intentado Actualizar la Aplicación",
|
||||||
"errorCheckingUpdatesNotifChannel": "Error Buscando Actualizaciones",
|
"errorCheckingUpdatesNotifChannel": "Error Buscando Actualizaciones",
|
||||||
"appsRemovedNotifChannel": "Aplicaciones Eliminadas",
|
"appsRemovedNotifChannel": "Aplicaciones Eliminadas",
|
||||||
"downloadingXNotifChannel": "Descargando {}",
|
"downloadingXNotifChannel": "Descargando {}",
|
||||||
"completeAppInstallationNotifChannel": "Instalación Completa de la Aplicación",
|
"completeAppInstallationNotifChannel": "Instalación Completa de la Aplicación",
|
||||||
"checkingForUpdatesNotifChannel": "Buscando Actualizaciones",
|
"checkingForUpdatesNotifChannel": "Buscando Actualizaciones",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Comprobar actualizaciones solo para apps instaladas y en seguimiento",
|
||||||
|
"supportFixedAPKURL": "Soporte para URLs fijas de APK",
|
||||||
|
"selectX": "Selecciona {}",
|
||||||
|
"parallelDownloads": "Permitir descargas paralelas",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "¿Eliminar Aplicación?",
|
"one": "¿Eliminar Aplicación?",
|
||||||
"other": "¿Eliminar Aplicaciones?"
|
"other": "¿Eliminar Aplicaciones?"
|
||||||
@@ -312,19 +320,19 @@
|
|||||||
"other": "{} Días"
|
"other": "{} Días"
|
||||||
},
|
},
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"clearedNLogsBeforeXAfterY": {
|
||||||
"one": "Borrado {n} log (previo a = {before}, posterior a = {after})",
|
"one": "Eliminado {n} log (previo a = {before}, posterior a = {after})",
|
||||||
"other": "Borrados {n} logs (previos a = {before}, posteriores a = {after})"
|
"other": "Eliminados {n} logs (previos a = {before}, posteriores a = {after})"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "{} y 1 aplicación más tiene actualizaciones.",
|
"one": "{} y 1 aplicación más tiene actualizaciones.",
|
||||||
"other": "{} y {} aplicaciones más tiene actualizaciones."
|
"other": "{} y {} aplicaciones más tienen actualizaciones."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} y 1 aplicación más han sido actualizadas.",
|
"one": "{} y 1 aplicación más han sido actualizadas.",
|
||||||
"other": "{} y {} aplicaciones más han sido actualizadas."
|
"other": "{} y {} aplicaciones más han sido actualizadas."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesPossiblyInstalled": {
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
"one": "{} and 1 more app may have been updated.",
|
"one": "{} y 1 aplicación más podría haber sido actualizada.",
|
||||||
"other": "{} and {} more apps may have been updated."
|
"other": "{} y {} aplicaciones más podrían haber sido actualizadas."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "نصب نشده",
|
"notInstalled": "نصب نشده",
|
||||||
"estimateInBrackets": "(تخمین زدن)",
|
"estimateInBrackets": "(تخمین زدن)",
|
||||||
"selectAll": "انتخاب همه",
|
"selectAll": "انتخاب همه",
|
||||||
"deselectN": "لغو انتخاب {}",
|
"deselectX": "لغو انتخاب {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف میشود اما روی دستگاه نصب میماند.",
|
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف میشود اما روی دستگاه نصب میماند.",
|
||||||
"removeSelectedAppsQuestion": "برنامه های انتخابی حذف شود؟",
|
"removeSelectedAppsQuestion": "برنامه های انتخابی حذف شود؟",
|
||||||
"removeSelectedApps": "حذف برنامه های انتخاب شده",
|
"removeSelectedApps": "حذف برنامه های انتخاب شده",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
|
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
|
||||||
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
|
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
|
||||||
"about": "درباره",
|
"about": "درباره",
|
||||||
"requiresCredentialsInSettings": "این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
"requiresCredentialsInSettings": "{}: این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
||||||
"checkOnStart": "بررسی در شروع",
|
"checkOnStart": "بررسی در شروع",
|
||||||
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
|
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
|
||||||
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",
|
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",
|
||||||
@@ -236,45 +236,53 @@
|
|||||||
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
|
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
|
||||||
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
|
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
|
||||||
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
|
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
|
||||||
"sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
|
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
|
||||||
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیشفرض '.apk$')",
|
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیشفرض '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "بهروزرسانی برنامه انجام شد",
|
||||||
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
|
"appsPossiblyUpdatedNotifDescription": "به کاربر اطلاع میدهد که بهروزرسانیهای یک یا چند برنامه به طور بالقوه در پسزمینه اعمال شده است",
|
||||||
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
|
"xWasPossiblyUpdatedToY": "ممکن است {} به {} به روز شده باشد.",
|
||||||
"enableBackgroundUpdates": "Enable background updates",
|
"enableBackgroundUpdates": "به روز رسانی پس زمینه را فعال کنید",
|
||||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
"backgroundUpdateReqsExplanation": "به روز رسانی پس زمینه ممکن است برای همه برنامه ها امکان پذیر نباشد.",
|
||||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
"backgroundUpdateLimitsExplanation": "موفقیت نصب پسزمینه تنها زمانی مشخص میشود که Obtainium باز شود.",
|
||||||
"verifyLatestTag": "Verify the 'latest' tag",
|
"verifyLatestTag": "برچسب \"آخرین\" را تأیید کنید",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"filterByLinkText": "Filter links by link text",
|
||||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
"intermediateLinkNotFound": "لینک میانی پیدا نشد",
|
||||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
"intermediateLink": "Intermediate link",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"exemptFromBackgroundUpdates": "معاف از بهروزرسانیهای پسزمینه (در صورت فعال بودن)",
|
||||||
"versionExtractionRegEx": "Version Extraction RegEx",
|
"bgUpdatesOnWiFiOnly": "بهروزرسانیهای پسزمینه را در صورت عدم اتصال به WiFi غیرفعال کنید",
|
||||||
"matchGroupToUse": "Match Group to Use",
|
"autoSelectHighestVersionCode": "انتخاب خودکار بالاترین نسخه کد APK",
|
||||||
"highlightTouchTargets": "Highlight less obvious touch targets",
|
"versionExtractionRegEx": "نسخه استخراج RegEx",
|
||||||
"pickExportDir": "Pick Export Directory",
|
"matchGroupToUse": "گروه مورد استفاده را مطابقت دهید",
|
||||||
"autoExportOnChanges": "Auto-export on changes",
|
"highlightTouchTargets": "اهداف لمسی کمتر واضح را برجسته کنید",
|
||||||
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
"pickExportDir": "فهرست صادرات را انتخاب کنید",
|
||||||
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
"autoExportOnChanges": "صادرات خودکار تغییرات",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"includeSettings": "Include settings",
|
||||||
"reverseSort": "Reverse sorting",
|
"filterVersionsByRegEx": "فیلتر کردن نسخه ها با RegEx",
|
||||||
"debugMenu": "Debug Menu",
|
"trySelectingSuggestedVersionCode": "نسخه پیشنهادی APK نسخه کد را انتخاب کنید",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"dontSortReleasesList": "حفظ سفارش انتشار از API",
|
||||||
"runBgCheckNow": "Run Background Update Check Now",
|
"reverseSort": "مرتب سازی معکوس",
|
||||||
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
"takeFirstLink": "Take first link",
|
||||||
"installing": "Installing",
|
"skipSort": "Skip sorting",
|
||||||
"skipUpdateNotifications": "Skip update notifications",
|
"debugMenu": "منوی اشکال زدایی",
|
||||||
|
"bgTaskStarted": "کار پس زمینه شروع شد - لاگ های مربوط را بررسی کنید.",
|
||||||
|
"runBgCheckNow": "اکنون بهروزرسانی پسزمینه را بررسی کنید",
|
||||||
|
"versionExtractWholePage": "نسخه Extraction Regex را در کل صفحه اعمال کنید",
|
||||||
|
"installing": "در حال نصب",
|
||||||
|
"skipUpdateNotifications": "رد شدن از اعلان های به روز رسانی",
|
||||||
"updatesAvailableNotifChannel": "بروزرسانی در دسترس ",
|
"updatesAvailableNotifChannel": "بروزرسانی در دسترس ",
|
||||||
"appsUpdatedNotifChannel": "برنامه ها به روز شدند",
|
"appsUpdatedNotifChannel": "برنامه ها به روز شدند",
|
||||||
"appsPossiblyUpdatedNotifChannel": "App Updates Attempted",
|
"appsPossiblyUpdatedNotifChannel": "بهروزرسانی برنامه انجام شد",
|
||||||
"errorCheckingUpdatesNotifChannel": "خطا در بررسی بهروزرسانیها",
|
"errorCheckingUpdatesNotifChannel": "خطا در بررسی بهروزرسانیها",
|
||||||
"appsRemovedNotifChannel": "برنامه ها حذف شدند",
|
"appsRemovedNotifChannel": "برنامه ها حذف شدند",
|
||||||
"downloadingXNotifChannel": "در حال دانلود {}",
|
"downloadingXNotifChannel": "در حال دانلود {}",
|
||||||
"completeAppInstallationNotifChannel": "نصب کامل برنامه",
|
"completeAppInstallationNotifChannel": "نصب کامل برنامه",
|
||||||
"checkingForUpdatesNotifChannel": "بررسی بهروزرسانیها",
|
"checkingForUpdatesNotifChannel": "بررسی بهروزرسانیها",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "فقط برنامه های نصب شده و فقط ردیابی را برای به روز رسانی بررسی کنید",
|
||||||
|
"supportFixedAPKURL": "پشتیبانی از URL های APK ثابت",
|
||||||
|
"selectX": "انتخاب کنید {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "برنامه حذف شود؟",
|
"one": "برنامه حذف شود؟",
|
||||||
"other": "برنامه ها حذف شوند؟"
|
"other": "برنامه ها حذف شوند؟"
|
||||||
@@ -324,7 +332,7 @@
|
|||||||
"other": "{} و {} برنامه دیگر به روز شدند."
|
"other": "{} و {} برنامه دیگر به روز شدند."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesPossiblyInstalled": {
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
"one": "{} and 1 more app may have been updated.",
|
"one": "{} و 1 برنامه دیگر ممکن است به روز شده باشند.",
|
||||||
"other": "{} and {} more apps may have been updated."
|
"other": "ممکن است {} و {} برنامه های دیگر به روز شده باشند."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Pas installé",
|
"notInstalled": "Pas installé",
|
||||||
"estimateInBrackets": "(Estimation)",
|
"estimateInBrackets": "(Estimation)",
|
||||||
"selectAll": "Tout sélectionner",
|
"selectAll": "Tout sélectionner",
|
||||||
"deselectN": "Déselectionner {}",
|
"deselectX": "Déselectionner {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} sera supprimé d'Obtainium mais restera installé sur l'appareil.",
|
"xWillBeRemovedButRemainInstalled": "{} sera supprimé d'Obtainium mais restera installé sur l'appareil.",
|
||||||
"removeSelectedAppsQuestion": "Supprimer les applications sélectionnées ?",
|
"removeSelectedAppsQuestion": "Supprimer les applications sélectionnées ?",
|
||||||
"removeSelectedApps": "Supprimer les applications sélectionnées",
|
"removeSelectedApps": "Supprimer les applications sélectionnées",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||||
"checkOnStart": "Check for updates on startup",
|
"checkOnStart": "Check for updates on startup",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Add this info in the Settings.",
|
"addInfoInSettings": "Add this info in the Settings.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
||||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
"verifyLatestTag": "Verify the 'latest' tag",
|
"verifyLatestTag": "Verify the 'latest' tag",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
||||||
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Highlight less obvious touch targets",
|
"highlightTouchTargets": "Highlight less obvious touch targets",
|
||||||
"pickExportDir": "Pick Export Directory",
|
"pickExportDir": "Pick Export Directory",
|
||||||
"autoExportOnChanges": "Auto-export on changes",
|
"autoExportOnChanges": "Auto-export on changes",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
||||||
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"dontSortReleasesList": "Retain release order from API",
|
||||||
"reverseSort": "Reverse sorting",
|
"reverseSort": "Reverse sorting",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Debug Menu",
|
"debugMenu": "Debug Menu",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"bgTaskStarted": "Background task started - check logs.",
|
||||||
"runBgCheckNow": "Run Background Update Check Now",
|
"runBgCheckNow": "Run Background Update Check Now",
|
||||||
@@ -275,6 +280,9 @@
|
|||||||
"completeAppInstallationNotifChannel": "Installation complète de l'application",
|
"completeAppInstallationNotifChannel": "Installation complète de l'application",
|
||||||
"checkingForUpdatesNotifChannel": "Vérification des mises à jour",
|
"checkingForUpdatesNotifChannel": "Vérification des mises à jour",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Supprimer l'application ?",
|
"one": "Supprimer l'application ?",
|
||||||
"other": "Supprimer les applications ?"
|
"other": "Supprimer les applications ?"
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Nem telepített",
|
"notInstalled": "Nem telepített",
|
||||||
"estimateInBrackets": "(Becslés)",
|
"estimateInBrackets": "(Becslés)",
|
||||||
"selectAll": "Mindet kiválaszt",
|
"selectAll": "Mindet kiválaszt",
|
||||||
"deselectN": "Törölje {} kijelölését",
|
"deselectX": "Törölje {} kijelölését",
|
||||||
"xWillBeRemovedButRemainInstalled": "A(z) {} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.",
|
"xWillBeRemovedButRemainInstalled": "A(z) {} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.",
|
||||||
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
|
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
|
||||||
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
|
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
"followSystem": "Rendszer szerint",
|
"followSystem": "Rendszer szerint",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Használjon tiszta fekete sötét témát",
|
"useBlackTheme": "Használjon teljesen fekete sötét témát",
|
||||||
"appSortBy": "App rendezés...",
|
"appSortBy": "App rendezés...",
|
||||||
"authorName": "Szerző/Név",
|
"authorName": "Szerző/Név",
|
||||||
"nameAuthor": "Név/Szerző",
|
"nameAuthor": "Név/Szerző",
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
"categories": "Kategóriák",
|
"categories": "Kategóriák",
|
||||||
"category": "Kategória",
|
"category": "Kategória",
|
||||||
"noCategory": "Nincs kategória",
|
"noCategory": "Nincs kategória",
|
||||||
"noCategories": "No Categories",
|
"noCategories": "Nincsenek kategóriák",
|
||||||
"deleteCategoryQuestion": "Törli a kategóriát?",
|
"deleteCategoryQuestion": "Törli a kategóriát?",
|
||||||
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
|
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
|
||||||
"addCategory": "Új kategória",
|
"addCategory": "Új kategória",
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
"versionDetection": "Verzió érzékelés",
|
"versionDetection": "Verzió érzékelés",
|
||||||
"standardVersionDetection": "Alapért. verzió érzékelés",
|
"standardVersionDetection": "Alapért. verzió érzékelés",
|
||||||
"groupByCategory": "Csoportosítás Kategória alapján",
|
"groupByCategory": "Csoportosítás Kategória alapján",
|
||||||
"autoApkFilterByArch": "Ha lehetséges, próbálja CPU architektúra szerint szűrni az APK-okat",
|
"autoApkFilterByArch": "Ha lehetséges, próbálja CPU architektúra szerint szűrni az APK-kat",
|
||||||
"overrideSource": "Forrás felülbírálása",
|
"overrideSource": "Forrás felülbírálása",
|
||||||
"dontShowAgain": "Ne mutassa ezt újra",
|
"dontShowAgain": "Ne mutassa ezt újra",
|
||||||
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
|
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
|
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
|
||||||
"about": "Rólunk",
|
"about": "Rólunk",
|
||||||
"requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
||||||
"checkOnStart": "Egyszer az alkalmazás indításakor is",
|
"checkOnStart": "Egyszer az alkalmazás indításakor is",
|
||||||
"tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból",
|
"tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból",
|
||||||
"removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
|
"removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.",
|
"addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.",
|
||||||
"githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.",
|
"githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.",
|
||||||
"gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.",
|
"gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.",
|
||||||
"sortByFileNamesNotLinks": "Fájlnevek szerinti elrendezés teljes linkek helyett",
|
"sortByLastLinkSegment": "Rendezés csak a link utolsó szegmense szerint",
|
||||||
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
|
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
|
||||||
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
|
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
|
||||||
"appsPossiblyUpdated": "App frissítési kísérlet",
|
"appsPossiblyUpdated": "App frissítési kísérlet",
|
||||||
@@ -245,8 +245,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Előfordulhat, hogy nem minden appnál lehetséges a háttérbeli frissítés.",
|
"backgroundUpdateReqsExplanation": "Előfordulhat, hogy nem minden appnál lehetséges a háttérbeli frissítés.",
|
||||||
"backgroundUpdateLimitsExplanation": "A háttérben történő telepítés sikeressége csak az Obtainium megnyitásakor állapítható meg.",
|
"backgroundUpdateLimitsExplanation": "A háttérben történő telepítés sikeressége csak az Obtainium megnyitásakor állapítható meg.",
|
||||||
"verifyLatestTag": "Ellenőrizze a „legújabb” címkét",
|
"verifyLatestTag": "Ellenőrizze a „legújabb” címkét",
|
||||||
"intermediateLinkRegex": "Szűrés egy 'közvetítő' linkre, amelyet először meg kell látogatni",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Közvetítő link nem található",
|
"intermediateLinkNotFound": "Közvetítő link nem található",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)",
|
"exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)",
|
||||||
"bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n",
|
"bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n",
|
||||||
"autoSelectHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
|
"autoSelectHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
|
||||||
@@ -255,10 +257,13 @@
|
|||||||
"highlightTouchTargets": "Emelje ki a kevésbé nyilvánvaló érintési célokat",
|
"highlightTouchTargets": "Emelje ki a kevésbé nyilvánvaló érintési célokat",
|
||||||
"pickExportDir": "Válassza az Exportálási könyvtárat",
|
"pickExportDir": "Válassza az Exportálási könyvtárat",
|
||||||
"autoExportOnChanges": "Auto-exportálás a változások után",
|
"autoExportOnChanges": "Auto-exportálás a változások után",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Verziók szűrése reguláris kifejezéssel",
|
"filterVersionsByRegEx": "Verziók szűrése reguláris kifejezéssel",
|
||||||
"trySelectingSuggestedVersionCode": "Próbálja ki a javasolt verziókódú APK-t",
|
"trySelectingSuggestedVersionCode": "Próbálja ki a javasolt verziókódú APK-t",
|
||||||
"dontSortReleasesList": "Az API-ból származó kiadási sorrend megőrzése",
|
"dontSortReleasesList": "Az API-ból származó kiadási sorrend megőrzése",
|
||||||
"reverseSort": "Fordított rendezés",
|
"reverseSort": "Fordított rendezés",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Hibakereső menü",
|
"debugMenu": "Hibakereső menü",
|
||||||
"bgTaskStarted": "A háttérfeladat elindult – ellenőrizze a naplókat.",
|
"bgTaskStarted": "A háttérfeladat elindult – ellenőrizze a naplókat.",
|
||||||
"enableBackgroundUpdates": "Frissítések a háttérben",
|
"enableBackgroundUpdates": "Frissítések a háttérben",
|
||||||
@@ -275,6 +280,9 @@
|
|||||||
"completeAppInstallationNotifChannel": "Teljes app telepítés",
|
"completeAppInstallationNotifChannel": "Teljes app telepítés",
|
||||||
"checkingForUpdatesNotifChannel": "Frissítések keresése",
|
"checkingForUpdatesNotifChannel": "Frissítések keresése",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
|
"onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
|
||||||
|
"supportFixedAPKURL": "Támogatja a rögzített APK URL-eket",
|
||||||
|
"selectX": "Kiválaszt {}",
|
||||||
|
"parallelDownloads": "Párhuzamos letöltéseket enged",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Eltávolítja az alkalmazást?",
|
"one": "Eltávolítja az alkalmazást?",
|
||||||
"other": "Eltávolítja az alkalmazást?"
|
"other": "Eltávolítja az alkalmazást?"
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Non installato",
|
"notInstalled": "Non installato",
|
||||||
"estimateInBrackets": "(stimato)",
|
"estimateInBrackets": "(stimato)",
|
||||||
"selectAll": "Seleziona tutto",
|
"selectAll": "Seleziona tutto",
|
||||||
"deselectN": "Deseleziona {}",
|
"deselectX": "Deseleziona {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.",
|
"xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.",
|
||||||
"removeSelectedAppsQuestion": "Rimuovere le app selezionate?",
|
"removeSelectedAppsQuestion": "Rimuovere le app selezionate?",
|
||||||
"removeSelectedApps": "Rimuovi le app selezionate",
|
"removeSelectedApps": "Rimuovi le app selezionate",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
|
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca e migliora la rilevazione di apk)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca e migliora la rilevazione di apk)",
|
||||||
"about": "Informazioni",
|
"about": "Informazioni",
|
||||||
"requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)",
|
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
|
||||||
"checkOnStart": "Controlla una volta all'avvio",
|
"checkOnStart": "Controlla una volta all'avvio",
|
||||||
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
|
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
|
||||||
"removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente",
|
"removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Aggiungi questa info nelle impostazioni.",
|
"addInfoInSettings": "Aggiungi questa info nelle impostazioni.",
|
||||||
"githubSourceNote": "Il limite di ricerca GitHub può essere evitato usando una chiave API.",
|
"githubSourceNote": "Il limite di ricerca GitHub può essere evitato usando una chiave API.",
|
||||||
"gitlabSourceNote": "L'estrazione di APK da GitLab potrebbe non funzionare senza chiave API.",
|
"gitlabSourceNote": "L'estrazione di APK da GitLab potrebbe non funzionare senza chiave API.",
|
||||||
"sortByFileNamesNotLinks": "Ordina per nome del file invece dei link completi",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtra le note di rilascio con espressione regolare",
|
"filterReleaseNotesByRegEx": "Filtra le note di rilascio con espressione regolare",
|
||||||
"customLinkFilterRegex": "Filtra link APK personalizzato con espressione regolare (predefinito '.apk$')",
|
"customLinkFilterRegex": "Filtra link APK personalizzato con espressione regolare (predefinito '.apk$')",
|
||||||
"appsPossiblyUpdated": "Aggiornamenti app tentati",
|
"appsPossiblyUpdated": "Aggiornamenti app tentati",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Gli aggiornamenti in secondo piano potrebbero non essere possibili per tutte le app.",
|
"backgroundUpdateReqsExplanation": "Gli aggiornamenti in secondo piano potrebbero non essere possibili per tutte le app.",
|
||||||
"backgroundUpdateLimitsExplanation": "La riuscita di un'installazione in secondo piano può essere determinata solo quando viene aperto Obtainium.",
|
"backgroundUpdateLimitsExplanation": "La riuscita di un'installazione in secondo piano può essere determinata solo quando viene aperto Obtainium.",
|
||||||
"verifyLatestTag": "Verifica l'etichetta 'Latest'",
|
"verifyLatestTag": "Verifica l'etichetta 'Latest'",
|
||||||
"intermediateLinkRegex": "Filtra un link 'Intermedio' da visitare prima",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Link intermedio non trovato",
|
"intermediateLinkNotFound": "Link intermedio non trovato",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Esente da aggiornamenti in secondo piano (se attivo)",
|
"exemptFromBackgroundUpdates": "Esente da aggiornamenti in secondo piano (se attivo)",
|
||||||
"bgUpdatesOnWiFiOnly": "Disattiva aggiornamenti in secondo piano quando non si usa il WiFi",
|
"bgUpdatesOnWiFiOnly": "Disattiva aggiornamenti in secondo piano quando non si usa il WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-seleziona APK con versionCode più alto",
|
"autoSelectHighestVersionCode": "Auto-seleziona APK con versionCode più alto",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Evidenzia elementi toccabili meno ovvi",
|
"highlightTouchTargets": "Evidenzia elementi toccabili meno ovvi",
|
||||||
"pickExportDir": "Scegli cartella esp.",
|
"pickExportDir": "Scegli cartella esp.",
|
||||||
"autoExportOnChanges": "Auto-esporta dopo modifiche",
|
"autoExportOnChanges": "Auto-esporta dopo modifiche",
|
||||||
|
"includeSettings": "Includi impostazioni",
|
||||||
"filterVersionsByRegEx": "Filtra versioni con espressione regolare",
|
"filterVersionsByRegEx": "Filtra versioni con espressione regolare",
|
||||||
"trySelectingSuggestedVersionCode": "Prova a selezionare APK con versionCode suggerito",
|
"trySelectingSuggestedVersionCode": "Prova a selezionare APK con versionCode suggerito",
|
||||||
"dontSortReleasesList": "Conserva l'ordine di release da API",
|
"dontSortReleasesList": "Conserva l'ordine di release da API",
|
||||||
"reverseSort": "Ordine inverso",
|
"reverseSort": "Ordine inverso",
|
||||||
|
"takeFirstLink": "Prendi il primo link",
|
||||||
|
"skipSort": "Salta ordinamento",
|
||||||
"debugMenu": "Menu di debug",
|
"debugMenu": "Menu di debug",
|
||||||
"bgTaskStarted": "Attività in secondo piano iniziata - controllo log.",
|
"bgTaskStarted": "Attività in secondo piano iniziata - controllo log.",
|
||||||
"runBgCheckNow": "Inizia aggiornamento in secondo piano ora",
|
"runBgCheckNow": "Inizia aggiornamento in secondo piano ora",
|
||||||
@@ -275,6 +280,14 @@
|
|||||||
"completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
|
"completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
|
||||||
"checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
|
"checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
|
"onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
|
||||||
|
"supportFixedAPKURL": "Supporta URL fissi di APK",
|
||||||
|
"selectX": "Seleziona {}",
|
||||||
|
"parallelDownloads": "Permetti download paralleli",
|
||||||
|
"installMethod": "Metodo d'installazione",
|
||||||
|
"normal": "Normale",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Rimuovere l'app?",
|
"one": "Rimuovere l'app?",
|
||||||
"other": "Rimuovere le app?"
|
"other": "Rimuovere le app?"
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "未インストール",
|
"notInstalled": "未インストール",
|
||||||
"estimateInBrackets": "(推定)",
|
"estimateInBrackets": "(推定)",
|
||||||
"selectAll": "すべて選択",
|
"selectAll": "すべて選択",
|
||||||
"deselectN": "{}件の選択を解除",
|
"deselectX": "{}件の選択を解除",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
|
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
|
||||||
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
|
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
|
||||||
"removeSelectedApps": "選択したアプリを削除する",
|
"removeSelectedApps": "選択したアプリを削除する",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
|
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
|
||||||
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
|
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
|
||||||
"about": "概要",
|
"about": "概要",
|
||||||
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
|
"requiresCredentialsInSettings": "{}: これには追加の認証が必要です (設定にて)",
|
||||||
"checkOnStart": "起動時にアップデートを確認する",
|
"checkOnStart": "起動時にアップデートを確認する",
|
||||||
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
|
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
|
||||||
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
|
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "設定でこの情報を追加してください。",
|
"addInfoInSettings": "設定でこの情報を追加してください。",
|
||||||
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
|
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
|
||||||
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
|
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
|
||||||
"sortByFileNamesNotLinks": "フルのリンクではなくファイル名でソートする",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
|
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
|
||||||
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
|
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
|
||||||
"appsPossiblyUpdated": "アプリのアップデートを試行",
|
"appsPossiblyUpdated": "アプリのアップデートを試行",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
|
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
|
||||||
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
|
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
|
||||||
"verifyLatestTag": "'latest'タグを確認する",
|
"verifyLatestTag": "'latest'タグを確認する",
|
||||||
"intermediateLinkRegex": "最初にアクセスする「中間」リンクをフィルタリングする",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "中間リンクが見つかりませんでした",
|
"intermediateLinkNotFound": "中間リンクが見つかりませんでした",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
|
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
|
||||||
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする",
|
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする",
|
||||||
"autoSelectHighestVersionCode": "最も高いバージョンコードのAPKを自動で選択する",
|
"autoSelectHighestVersionCode": "最も高いバージョンコードのAPKを自動で選択する",
|
||||||
@@ -256,16 +258,19 @@
|
|||||||
"highlightTouchTargets": "目立たないタップ可能な対象をハイライトする",
|
"highlightTouchTargets": "目立たないタップ可能な対象をハイライトする",
|
||||||
"pickExportDir": "エクスポートディレクトリを選択",
|
"pickExportDir": "エクスポートディレクトリを選択",
|
||||||
"autoExportOnChanges": "変更があった際に自動でエクスポートする",
|
"autoExportOnChanges": "変更があった際に自動でエクスポートする",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "正規表現でバージョンをフィルタリングする",
|
"filterVersionsByRegEx": "正規表現でバージョンをフィルタリングする",
|
||||||
"trySelectingSuggestedVersionCode": "提案されたバージョンコードのAPKを選択する",
|
"trySelectingSuggestedVersionCode": "提案されたバージョンコードのAPKを選択する",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"dontSortReleasesList": "APIからのリリース順を保持する",
|
||||||
"reverseSort": "Reverse sorting",
|
"reverseSort": "逆順ソート",
|
||||||
"debugMenu": "Debug Menu",
|
"takeFirstLink": "Take first link",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"skipSort": "Skip sorting",
|
||||||
"runBgCheckNow": "Run Background Update Check Now",
|
"debugMenu": "デバッグメニュー",
|
||||||
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
"bgTaskStarted": "バックグラウンドタスクが開始されました - ログを確認してください。",
|
||||||
"installing": "Installing",
|
"runBgCheckNow": "今すぐバックグラウンドでのアップデート確認を開始する",
|
||||||
"skipUpdateNotifications": "Skip update notifications",
|
"versionExtractWholePage": "バージョン抽出の正規表現をページ全体に適用する",
|
||||||
|
"installing": "インストール中",
|
||||||
|
"skipUpdateNotifications": "アップデート通知を行わない",
|
||||||
"updatesAvailableNotifChannel": "アップデートが利用可能",
|
"updatesAvailableNotifChannel": "アップデートが利用可能",
|
||||||
"appsUpdatedNotifChannel": "アプリをアップデートしました",
|
"appsUpdatedNotifChannel": "アプリをアップデートしました",
|
||||||
"appsPossiblyUpdatedNotifChannel": "アプリのアップデートを試行",
|
"appsPossiblyUpdatedNotifChannel": "アプリのアップデートを試行",
|
||||||
@@ -274,7 +279,10 @@
|
|||||||
"downloadingXNotifChannel": "{} をダウンロード中",
|
"downloadingXNotifChannel": "{} をダウンロード中",
|
||||||
"completeAppInstallationNotifChannel": "アプリのインストールを完了する",
|
"completeAppInstallationNotifChannel": "アプリのインストールを完了する",
|
||||||
"checkingForUpdatesNotifChannel": "アップデートを確認中",
|
"checkingForUpdatesNotifChannel": "アップデートを確認中",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "インストール済みのアプリと「追跡のみ」のアプリのアップデートのみを確認する",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "アプリを削除しますか?",
|
"one": "アプリを削除しますか?",
|
||||||
"other": "アプリを削除しますか?"
|
"other": "アプリを削除しますか?"
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
"unexpectedError": "Onverwachte fout",
|
"unexpectedError": "Onverwachte fout",
|
||||||
"ok": "Ok",
|
"ok": "Ok",
|
||||||
"and": "en",
|
"and": "en",
|
||||||
"githubPATLabel": "GitHub Personal Access Token (Verhoogt limiet aantal verzoeken)",
|
"githubPATLabel": "GitHub Personal Access Token\n(Verhoogt limiet aantal verzoeken)",
|
||||||
"includePrereleases": "Bevat prereleases",
|
"includePrereleases": "Bevat prereleases",
|
||||||
"fallbackToOlderReleases": "Terugvallen op oudere releases",
|
"fallbackToOlderReleases": "Terugvallen op oudere releases",
|
||||||
"filterReleaseTitlesByRegEx": "Filter release-titels met reguliere expressies.",
|
"filterReleaseTitlesByRegEx": "Filter release-titels met reguliere expressies.",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Niet geinstalleerd",
|
"notInstalled": "Niet geinstalleerd",
|
||||||
"estimateInBrackets": "(Ongeveer)",
|
"estimateInBrackets": "(Ongeveer)",
|
||||||
"selectAll": "Selecteer alles",
|
"selectAll": "Selecteer alles",
|
||||||
"deselectN": "Deselecteer {}",
|
"deselectX": "Deselecteer {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} zal worden verwijderd uit Obtainium, maar blijft geïnstalleerd op het apparaat.",
|
"xWillBeRemovedButRemainInstalled": "{} zal worden verwijderd uit Obtainium, maar blijft geïnstalleerd op het apparaat.",
|
||||||
"removeSelectedAppsQuestion": "Geselecteerde apps verwijderen??",
|
"removeSelectedAppsQuestion": "Geselecteerde apps verwijderen??",
|
||||||
"removeSelectedApps": "Geselecteerde apps verwijderen",
|
"removeSelectedApps": "Geselecteerde apps verwijderen",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
|
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Maakt het mogelijk beter te zoeken naar APK's)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Maakt het mogelijk beter te zoeken naar APK's)",
|
||||||
"about": "Over",
|
"about": "Over",
|
||||||
"requiresCredentialsInSettings": "Dit vereist aanvullende referenties (in Instellingen)",
|
"requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
|
||||||
"checkOnStart": "Controleren op updates bij opstarten",
|
"checkOnStart": "Controleren op updates bij opstarten",
|
||||||
"tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode",
|
"tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode",
|
||||||
"removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen",
|
"removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
|
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
|
||||||
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
|
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
|
||||||
"gitlabSourceNote": "GitLab APK-extractie werkt mogelijk niet zonder een API-sleutel.",
|
"gitlabSourceNote": "GitLab APK-extractie werkt mogelijk niet zonder een API-sleutel.",
|
||||||
"sortByFileNamesNotLinks": "Sorteren op bestandsnamen in plaats van volledige links.",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
|
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
|
||||||
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",
|
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",
|
||||||
"appsPossiblyUpdated": "Poging tot app-updates",
|
"appsPossiblyUpdated": "Poging tot app-updates",
|
||||||
@@ -244,10 +244,12 @@
|
|||||||
"xWasPossiblyUpdatedToY": "{} mogelijk bijgewerkt naar {}.",
|
"xWasPossiblyUpdatedToY": "{} mogelijk bijgewerkt naar {}.",
|
||||||
"enableBackgroundUpdates": "Achtergrondupdates inschakelen",
|
"enableBackgroundUpdates": "Achtergrondupdates inschakelen",
|
||||||
"backgroundUpdateReqsExplanation": "Achtergrondupdates zijn mogelijk niet voor alle apps mogelijk.",
|
"backgroundUpdateReqsExplanation": "Achtergrondupdates zijn mogelijk niet voor alle apps mogelijk.",
|
||||||
"backgroundUpdateLimitsExplanation": "THet succes van een installatie in de achtergrond kan alleen worden bepaald wanneer Obtainium is geopend.",
|
"backgroundUpdateLimitsExplanation": "Het succes van een installatie in de achtergrond kan alleen worden bepaald wanneer Obtainium is geopend.",
|
||||||
"verifyLatestTag": "Verifieer de 'Laatste'-tag",
|
"verifyLatestTag": "Verifieer de 'Laatste'-tag",
|
||||||
"intermediateLinkRegex": "Filter voor een 'tussenliggende' link om eerst te bezoeken",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
|
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrondupdates (indien ingeschakeld)",
|
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrondupdates (indien ingeschakeld)",
|
||||||
"bgUpdatesOnWiFiOnly": "Achtergrondupdates uitschakelen wanneer niet verbonden met WiFi",
|
"bgUpdatesOnWiFiOnly": "Achtergrondupdates uitschakelen wanneer niet verbonden met WiFi",
|
||||||
"autoSelectHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
|
"autoSelectHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Markeer minder voor de hand liggende aanraakdoelen.",
|
"highlightTouchTargets": "Markeer minder voor de hand liggende aanraakdoelen.",
|
||||||
"pickExportDir": "Kies de exportmap",
|
"pickExportDir": "Kies de exportmap",
|
||||||
"autoExportOnChanges": "Automatisch exporteren bij wijzigingen",
|
"autoExportOnChanges": "Automatisch exporteren bij wijzigingen",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Filter versies met een reguliere expressie",
|
"filterVersionsByRegEx": "Filter versies met een reguliere expressie",
|
||||||
"trySelectingSuggestedVersionCode": "Probeer de voorgestelde versiecode APK te selecteren",
|
"trySelectingSuggestedVersionCode": "Probeer de voorgestelde versiecode APK te selecteren",
|
||||||
"dontSortReleasesList": "Volgorde van releases behouden vanuit de API",
|
"dontSortReleasesList": "Volgorde van releases behouden vanuit de API",
|
||||||
"reverseSort": "Sortering omkeren",
|
"reverseSort": "Sortering omkeren",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Debug menu",
|
"debugMenu": "Debug menu",
|
||||||
"bgTaskStarted": "Achtergrondtaak gestart - controleer de logs.",
|
"bgTaskStarted": "Achtergrondtaak gestart - controleer de logs.",
|
||||||
"runBgCheckNow": "Voer nu een achtergrondupdatecontrole uit",
|
"runBgCheckNow": "Voer nu een achtergrondupdatecontrole uit",
|
||||||
@@ -275,6 +280,9 @@
|
|||||||
"completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
|
"completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
|
||||||
"checkingForUpdatesNotifChannel": "Controleren op updates",
|
"checkingForUpdatesNotifChannel": "Controleren op updates",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App verwijderen?",
|
"one": "App verwijderen?",
|
||||||
"other": "Apps verwijderen?"
|
"other": "Apps verwijderen?"
|
||||||
@@ -305,7 +313,7 @@
|
|||||||
},
|
},
|
||||||
"hour": {
|
"hour": {
|
||||||
"one": "{} uur",
|
"one": "{} uur",
|
||||||
"other": "{} uren"
|
"other": "{} uur"
|
||||||
},
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"one": "{} dag",
|
"one": "{} dag",
|
||||||
@@ -327,4 +335,4 @@
|
|||||||
"one": "{} en nog 1 app zijn mogelijk bijgewerkt.",
|
"one": "{} en nog 1 app zijn mogelijk bijgewerkt.",
|
||||||
"other": "{} en {} meer apps zijn mogelijk bijgwerkt."
|
"other": "{} en {} meer apps zijn mogelijk bijgwerkt."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,8 +28,8 @@
|
|||||||
"xIsTrackOnly": "{} jest tylko obserwowane",
|
"xIsTrackOnly": "{} jest tylko obserwowane",
|
||||||
"source": "Źródło",
|
"source": "Źródło",
|
||||||
"app": "Aplikacja",
|
"app": "Aplikacja",
|
||||||
"appsFromSourceAreTrackOnly": "Aplikacje z tego źródła są „tylko obserwowane”.",
|
"appsFromSourceAreTrackOnly": "Aplikacje z tego źródła są tylko obserwowane.",
|
||||||
"youPickedTrackOnly": "Wybrano opcję „Tylko obserwuj”.",
|
"youPickedTrackOnly": "Wybrano opcję \"Tylko obserwuj\".",
|
||||||
"trackOnlyAppDescription": "Aplikacja będzie obserwowana pod kątem aktualizacji, ale Obtainium nie będzie w stanie jej pobrać ani zainstalować.",
|
"trackOnlyAppDescription": "Aplikacja będzie obserwowana pod kątem aktualizacji, ale Obtainium nie będzie w stanie jej pobrać ani zainstalować.",
|
||||||
"cancelled": "Anulowano",
|
"cancelled": "Anulowano",
|
||||||
"appAlreadyAdded": "Aplikacja już została dodana",
|
"appAlreadyAdded": "Aplikacja już została dodana",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Nie zainstalowano",
|
"notInstalled": "Nie zainstalowano",
|
||||||
"estimateInBrackets": "(Szacunkowo)",
|
"estimateInBrackets": "(Szacunkowo)",
|
||||||
"selectAll": "Zaznacz wszystkie",
|
"selectAll": "Zaznacz wszystkie",
|
||||||
"deselectN": "Odznacz {}",
|
"deselectX": "Odznacz {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
|
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
|
||||||
"removeSelectedAppsQuestion": "Usunąć wybrane aplikacje?",
|
"removeSelectedAppsQuestion": "Usunąć wybrane aplikacje?",
|
||||||
"removeSelectedApps": "Usuń wybrane aplikacje",
|
"removeSelectedApps": "Usuń wybrane aplikacje",
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
"searchX": "Przeszukaj {}",
|
"searchX": "Przeszukaj {}",
|
||||||
"noResults": "Nie znaleziono wyników",
|
"noResults": "Nie znaleziono wyników",
|
||||||
"importX": "Importuj {}",
|
"importX": "Importuj {}",
|
||||||
"importedAppsIdDisclaimer": "Zaimportowane aplikacje mogą być wyświetlane jako „Niezainstalowane”.\nAby to naprawić, zainstaluj je ponownie za pomocą Obtainium.\nNie powinno to mieć wpływu na dane aplikacji.\n\nDotyczy tylko adresów URL i metod importu innych aplikacji.",
|
"importedAppsIdDisclaimer": "Zaimportowane aplikacje mogą być wyświetlane jako niezainstalowane.\nAby to naprawić, przeinstaluj je za pomocą Obtainium.\nNie powinno to mieć wpływu na dane aplikacji.\n\nDotyczy tylko adresu URL i innych metod importu.",
|
||||||
"importErrors": "Błędy importowania",
|
"importErrors": "Błędy importowania",
|
||||||
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
|
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
|
||||||
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
|
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
|
||||||
@@ -218,12 +218,12 @@
|
|||||||
"autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
|
"autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
|
||||||
"overrideSource": "Nadpisz źródło",
|
"overrideSource": "Nadpisz źródło",
|
||||||
"dontShowAgain": "Nie pokazuj tego ponownie",
|
"dontShowAgain": "Nie pokazuj tego ponownie",
|
||||||
"dontShowTrackOnlyWarnings": "Nie pokazuj ostrzeżeń „Tylko obserwowana”",
|
"dontShowTrackOnlyWarnings": "Nie pokazuj ostrzeżeń \"Tylko obserwowana\"",
|
||||||
"dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK",
|
"dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK",
|
||||||
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
|
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
|
||||||
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
|
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
|
||||||
"about": "Więcej informacji",
|
"about": "Więcej informacji",
|
||||||
"requiresCredentialsInSettings": "Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
"requiresCredentialsInSettings": "{}: Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
||||||
"checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
|
"checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
|
||||||
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
|
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
|
||||||
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
|
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
|
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
|
||||||
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
|
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
|
||||||
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
|
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
|
||||||
"sortByFileNamesNotLinks": "Sortuj wg nazw plików zamiast pełnych linków",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
|
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
|
||||||
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",
|
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",
|
||||||
"appsPossiblyUpdated": "Aplikacje mogły zostać zaktualizowane",
|
"appsPossiblyUpdated": "Aplikacje mogły zostać zaktualizowane",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Aktualizacje w tle mogą nie być możliwe dla wszystkich aplikacji.",
|
"backgroundUpdateReqsExplanation": "Aktualizacje w tle mogą nie być możliwe dla wszystkich aplikacji.",
|
||||||
"backgroundUpdateLimitsExplanation": "Powodzenie instalacji w tle można określić dopiero po otwarciu Obtainium.",
|
"backgroundUpdateLimitsExplanation": "Powodzenie instalacji w tle można określić dopiero po otwarciu Obtainium.",
|
||||||
"verifyLatestTag": "Zweryfikuj najnowszy tag",
|
"verifyLatestTag": "Zweryfikuj najnowszy tag",
|
||||||
"intermediateLinkRegex": "Filtr linków \"pośrednich\" do odwiedzenia w pierwszej kolejności",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego",
|
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)",
|
"exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)",
|
||||||
"bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi",
|
"bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi",
|
||||||
"autoSelectHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
|
"autoSelectHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Wyróżnij mniej oczywiste elementy dotykowe",
|
"highlightTouchTargets": "Wyróżnij mniej oczywiste elementy dotykowe",
|
||||||
"pickExportDir": "Wybierz katalog eksportu",
|
"pickExportDir": "Wybierz katalog eksportu",
|
||||||
"autoExportOnChanges": "Automatyczny eksport po wprowadzeniu zmian",
|
"autoExportOnChanges": "Automatyczny eksport po wprowadzeniu zmian",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Filtruj wersje według wyrażenia regularnego",
|
"filterVersionsByRegEx": "Filtruj wersje według wyrażenia regularnego",
|
||||||
"trySelectingSuggestedVersionCode": "Spróbuj wybierać sugerowany kod wersji APK",
|
"trySelectingSuggestedVersionCode": "Spróbuj wybierać sugerowany kod wersji APK",
|
||||||
"dontSortReleasesList": "Utrzymaj kolejność wydań z interfejsu API",
|
"dontSortReleasesList": "Utrzymaj kolejność wydań z interfejsu API",
|
||||||
"reverseSort": "Odwrotne sortowanie",
|
"reverseSort": "Odwrotne sortowanie",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Menu debugowania",
|
"debugMenu": "Menu debugowania",
|
||||||
"bgTaskStarted": "Uruchomiono zadanie w tle - sprawdź logi.",
|
"bgTaskStarted": "Uruchomiono zadanie w tle - sprawdź logi.",
|
||||||
"runBgCheckNow": "Wymuś sprawdzenie aktualizacji w tle",
|
"runBgCheckNow": "Wymuś sprawdzenie aktualizacji w tle",
|
||||||
@@ -275,6 +280,9 @@
|
|||||||
"completeAppInstallationNotifChannel": "Ukończenie instalacji aplikacji",
|
"completeAppInstallationNotifChannel": "Ukończenie instalacji aplikacji",
|
||||||
"checkingForUpdatesNotifChannel": "Sprawdzanie dostępności aktualizacji",
|
"checkingForUpdatesNotifChannel": "Sprawdzanie dostępności aktualizacji",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Sprawdzaj tylko zainstalowane i obserwowane aplikacje pod kątem aktualizacji",
|
"onlyCheckInstalledOrTrackOnlyApps": "Sprawdzaj tylko zainstalowane i obserwowane aplikacje pod kątem aktualizacji",
|
||||||
|
"supportFixedAPKURL": "Obsługuj stałe adresy URL APK",
|
||||||
|
"selectX": "Wybierz {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Usunąć aplikację?",
|
"one": "Usunąć aplikację?",
|
||||||
"few": "Usunąć aplikacje?",
|
"few": "Usunąć aplikacje?",
|
||||||
@@ -353,4 +361,4 @@
|
|||||||
"many": "{} i {} innych apek mogło zostać zaktualizowanych.",
|
"many": "{} i {} innych apek mogło zostać zaktualizowanych.",
|
||||||
"other": "{} i {} inne apki mogły zostać zaktualizowane."
|
"other": "{} i {} inne apki mogły zostać zaktualizowane."
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Não Instalado",
|
"notInstalled": "Não Instalado",
|
||||||
"estimateInBrackets": "(Aproximado)",
|
"estimateInBrackets": "(Aproximado)",
|
||||||
"selectAll": "Selecionar All",
|
"selectAll": "Selecionar All",
|
||||||
"deselectN": "Deselecionar {}",
|
"deselectX": "Deselecionar {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} sera removido do Obtainium mais permanecerá instalado no dispositivo.",
|
"xWillBeRemovedButRemainInstalled": "{} sera removido do Obtainium mais permanecerá instalado no dispositivo.",
|
||||||
"removeSelectedAppsQuestion": "Remover Apps Selecionados?",
|
"removeSelectedAppsQuestion": "Remover Apps Selecionados?",
|
||||||
"removeSelectedApps": "Remover Apps Selecionados",
|
"removeSelectedApps": "Remover Apps Selecionados",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
|
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
|
||||||
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
|
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
|
||||||
"about": "Sobre",
|
"about": "Sobre",
|
||||||
"requiresCredentialsInSettings": "Isso requer credenciais adicionais (em Configurações)",
|
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
|
||||||
"checkOnStart": "Checar por atualizações ao iniciar ",
|
"checkOnStart": "Checar por atualizações ao iniciar ",
|
||||||
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
|
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
|
||||||
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
|
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Adicionar essa informação nas configurações.",
|
"addInfoInSettings": "Adicionar essa informação nas configurações.",
|
||||||
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
|
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
|
||||||
"gitlabSourceNote": "A extração de APK do GitLab pode não funcionar sem uma chave de API.",
|
"gitlabSourceNote": "A extração de APK do GitLab pode não funcionar sem uma chave de API.",
|
||||||
"sortByFileNamesNotLinks": "Classifique por nomes de arquivos em vez de links completos",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
|
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
|
||||||
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
|
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
|
||||||
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
|
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Atualizações em segundo plano podem não ser possíveis para todos os Apps.",
|
"backgroundUpdateReqsExplanation": "Atualizações em segundo plano podem não ser possíveis para todos os Apps.",
|
||||||
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
|
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
|
||||||
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
|
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
|
||||||
"intermediateLinkRegex": "Filtre por um Link 'Intermediário' para Visitar Primeiro",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Link intermediário não encontrado",
|
"intermediateLinkNotFound": "Link intermediário não encontrado",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
|
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
|
||||||
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
|
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
|
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Destaque areas de toque menos óbvias",
|
"highlightTouchTargets": "Destaque areas de toque menos óbvias",
|
||||||
"pickExportDir": "Escolher Diretorio de Exportação",
|
"pickExportDir": "Escolher Diretorio de Exportação",
|
||||||
"autoExportOnChanges": "Auto-exportar em mudanças",
|
"autoExportOnChanges": "Auto-exportar em mudanças",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Filtrar Versões por Expressão Regular",
|
"filterVersionsByRegEx": "Filtrar Versões por Expressão Regular",
|
||||||
"trySelectingSuggestedVersionCode": "Tente selecionar a versão sugerida",
|
"trySelectingSuggestedVersionCode": "Tente selecionar a versão sugerida",
|
||||||
"dontSortReleasesList": "Reter a ordem de lançamento da API",
|
"dontSortReleasesList": "Reter a ordem de lançamento da API",
|
||||||
"reverseSort": "Ordenação reversa",
|
"reverseSort": "Ordenação reversa",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Menu Debug",
|
"debugMenu": "Menu Debug",
|
||||||
"bgTaskStarted": "Tarefa em segundo plano iniciada - verifique os logs.",
|
"bgTaskStarted": "Tarefa em segundo plano iniciada - verifique os logs.",
|
||||||
"runBgCheckNow": "Execute a verificação de atualização em segundo plano agora",
|
"runBgCheckNow": "Execute a verificação de atualização em segundo plano agora",
|
||||||
@@ -274,7 +279,15 @@
|
|||||||
"downloadingXNotifChannel": "Baixando {}",
|
"downloadingXNotifChannel": "Baixando {}",
|
||||||
"completeAppInstallationNotifChannel": "Instalação completa do App",
|
"completeAppInstallationNotifChannel": "Instalação completa do App",
|
||||||
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
|
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Apenas checar apps instalados e 'Apenas Seguir' por updates",
|
||||||
|
"supportFixedAPKURL": "Suporte APK com URLs fixas",
|
||||||
|
"selectX": "Selecionar {}",
|
||||||
|
"parallelDownloads": "Permitir downloads paralelos",
|
||||||
|
"installMethod": "Método de instalação",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku não esta rodando",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Remover App?",
|
"one": "Remover App?",
|
||||||
"other": "Remover Apps?"
|
"other": "Remover Apps?"
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Не установлено",
|
"notInstalled": "Не установлено",
|
||||||
"estimateInBrackets": "(Оценка)",
|
"estimateInBrackets": "(Оценка)",
|
||||||
"selectAll": "Выбрать всё",
|
"selectAll": "Выбрать всё",
|
||||||
"deselectN": "Отменить выбор {}",
|
"deselectX": "Отменить выбор {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется на устройстве",
|
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется на устройстве",
|
||||||
"removeSelectedAppsQuestion": "Удалить выбранные приложения?",
|
"removeSelectedAppsQuestion": "Удалить выбранные приложения?",
|
||||||
"removeSelectedApps": "Удалить выбранные приложения",
|
"removeSelectedApps": "Удалить выбранные приложения",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
|
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
|
||||||
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
|
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
|
||||||
"about": "Описание",
|
"about": "Описание",
|
||||||
"requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)",
|
"requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
|
||||||
"checkOnStart": "Проверять наличие обновлений при запуске",
|
"checkOnStart": "Проверять наличие обновлений при запуске",
|
||||||
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
|
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
|
||||||
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
|
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Добавьте эту информацию в Настройки",
|
"addInfoInSettings": "Добавьте эту информацию в Настройки",
|
||||||
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
|
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
|
||||||
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
|
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
|
||||||
"sortByFileNamesNotLinks": "Сортировать по именам файлов, а не ссылкам целиком",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
|
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
|
||||||
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",
|
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",
|
||||||
"appsPossiblyUpdated": "Попытки обновления приложений",
|
"appsPossiblyUpdated": "Попытки обновления приложений",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Фоновые обновления могут быть возможны не для всех приложений",
|
"backgroundUpdateReqsExplanation": "Фоновые обновления могут быть возможны не для всех приложений",
|
||||||
"backgroundUpdateLimitsExplanation": "Успешность фоновой установки можно определить только после открытия Obtainium",
|
"backgroundUpdateLimitsExplanation": "Успешность фоновой установки можно определить только после открытия Obtainium",
|
||||||
"verifyLatestTag": "Проверять тег 'latest'",
|
"verifyLatestTag": "Проверять тег 'latest'",
|
||||||
"intermediateLinkRegex": "Фильтр промежуточных ссылок для первоочередного посещения\n(регулярное выражение)",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
|
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
|
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
|
||||||
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
|
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
|
||||||
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
|
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
|
||||||
@@ -256,10 +258,13 @@
|
|||||||
"highlightTouchTargets": "Выделить менее очевидные элементы управления касанием",
|
"highlightTouchTargets": "Выделить менее очевидные элементы управления касанием",
|
||||||
"pickExportDir": "Выбрать каталог для экспорта",
|
"pickExportDir": "Выбрать каталог для экспорта",
|
||||||
"autoExportOnChanges": "Автоэкспорт при изменениях",
|
"autoExportOnChanges": "Автоэкспорт при изменениях",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Фильтровать версии по регулярному выражению",
|
"filterVersionsByRegEx": "Фильтровать версии по регулярному выражению",
|
||||||
"trySelectingSuggestedVersionCode": "Попробуйте выбрать предложенный код версии APK",
|
"trySelectingSuggestedVersionCode": "Попробуйте выбрать предложенный код версии APK",
|
||||||
"dontSortReleasesList": "Сохранить порядок релизов от API",
|
"dontSortReleasesList": "Сохранить порядок релизов от API",
|
||||||
"reverseSort": "Обратная сортировка",
|
"reverseSort": "Обратная сортировка",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Меню отладки",
|
"debugMenu": "Меню отладки",
|
||||||
"bgTaskStarted": "Фоновая задача начата — проверьте журналы",
|
"bgTaskStarted": "Фоновая задача начата — проверьте журналы",
|
||||||
"runBgCheckNow": "Запустить проверку фонового обновления сейчас",
|
"runBgCheckNow": "Запустить проверку фонового обновления сейчас",
|
||||||
@@ -274,7 +279,15 @@
|
|||||||
"downloadingXNotifChannel": "Загрузка {}",
|
"downloadingXNotifChannel": "Загрузка {}",
|
||||||
"completeAppInstallationNotifChannel": "Завершение установки приложения",
|
"completeAppInstallationNotifChannel": "Завершение установки приложения",
|
||||||
"checkingForUpdatesNotifChannel": "Проверка обновлений",
|
"checkingForUpdatesNotifChannel": "Проверка обновлений",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "Проверять обновления только у установленных или отслеживаемых приложений",
|
||||||
|
"supportFixedAPKURL": "Поддержка фиксированных URL-адресов APK",
|
||||||
|
"selectX": "Выбрать {}",
|
||||||
|
"parallelDownloads": "Разрешить параллельные загрузки",
|
||||||
|
"installMethod": "Метод установки",
|
||||||
|
"normal": "Нормальный",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Суперпользователь",
|
||||||
|
"shizukuBinderNotFound": "Shizuku не запущен",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Удалить приложение?",
|
"one": "Удалить приложение?",
|
||||||
"other": "Удалить приложения?"
|
"other": "Удалить приложения?"
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "Inte Installerad",
|
"notInstalled": "Inte Installerad",
|
||||||
"estimateInBrackets": "(Uppskattning)",
|
"estimateInBrackets": "(Uppskattning)",
|
||||||
"selectAll": "Välj Alla",
|
"selectAll": "Välj Alla",
|
||||||
"deselectN": "Avmarkera {}",
|
"deselectX": "Avmarkera {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} kommer tas bort från Obtainium men kommer vara fortsatt installerad på enheten.",
|
"xWillBeRemovedButRemainInstalled": "{} kommer tas bort från Obtainium men kommer vara fortsatt installerad på enheten.",
|
||||||
"removeSelectedAppsQuestion": "Ta bort markerade Appar?",
|
"removeSelectedAppsQuestion": "Ta bort markerade Appar?",
|
||||||
"removeSelectedApps": "Ta bort markerade Appar",
|
"removeSelectedApps": "Ta bort markerade Appar",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||||
"about": "Om",
|
"about": "Om",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||||
"checkOnStart": "Kolla efter uppdateringar vid start",
|
"checkOnStart": "Kolla efter uppdateringar vid start",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"addInfoInSettings": "Lägg till denna information i Inställningar.",
|
"addInfoInSettings": "Lägg till denna information i Inställningar.",
|
||||||
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
"appsPossiblyUpdated": "App Updates Attempted",
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
@@ -246,8 +246,10 @@
|
|||||||
"backgroundUpdateReqsExplanation": "Bakgrundsuppdateringar är inte möjligt för alla appar.",
|
"backgroundUpdateReqsExplanation": "Bakgrundsuppdateringar är inte möjligt för alla appar.",
|
||||||
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
"verifyLatestTag": "Verifiera 'senaste'-taggen",
|
"verifyLatestTag": "Verifiera 'senaste'-taggen",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
"intermediateLinkNotFound": "Intermediate link not found",
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
"exemptFromBackgroundUpdates": "Undta från bakgrundsuppdateringar (om aktiverad)",
|
"exemptFromBackgroundUpdates": "Undta från bakgrundsuppdateringar (om aktiverad)",
|
||||||
"bgUpdatesOnWiFiOnly": "Inaktivera Bakgrundsuppdateringar utan WiFi",
|
"bgUpdatesOnWiFiOnly": "Inaktivera Bakgrundsuppdateringar utan WiFi",
|
||||||
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
@@ -256,13 +258,17 @@
|
|||||||
"highlightTouchTargets": "Highlight less obvious touch targets",
|
"highlightTouchTargets": "Highlight less obvious touch targets",
|
||||||
"pickExportDir": "Välj Exportsökväg",
|
"pickExportDir": "Välj Exportsökväg",
|
||||||
"autoExportOnChanges": "Automatisk export vid ändringar",
|
"autoExportOnChanges": "Automatisk export vid ändringar",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
||||||
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
||||||
"dontSortReleasesList": "Retain release order from API",
|
"dontSortReleasesList": "Retain release order from API",
|
||||||
"reverseSort": "Omvänd sortering",
|
"reverseSort": "Omvänd sortering",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
"debugMenu": "Felsökningsmeny",
|
"debugMenu": "Felsökningsmeny",
|
||||||
"bgTaskStarted": "Background task started - check logs.",
|
"bgTaskStarted": "Background task started - check logs.",
|
||||||
"runBgCheckNow": "Kör Bakgrundsuppdateringskoll Nu",
|
"runBgCheckNow": "Kör Bakgrundsuppdateringskoll Nu",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Ta Bort App?",
|
"one": "Ta Bort App?",
|
||||||
"other": "Ta Bort Appar?"
|
"other": "Ta Bort Appar?"
|
||||||
|
338
assets/translations/tr.json
Normal file
338
assets/translations/tr.json
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "Geçerli bir {} Uygulama URL'si değil",
|
||||||
|
"noReleaseFound": "Uygun bir sürüm bulunamadı",
|
||||||
|
"noVersionFound": "Sürüm bulunamadı",
|
||||||
|
"urlMatchesNoSource": "URL, bilinen bir kaynağa uymuyor",
|
||||||
|
"cantInstallOlderVersion": "Eski bir sürümü yükleyemem",
|
||||||
|
"appIdMismatch": "İndirilen paket kimliği mevcut Uygulama kimliği ile eşleşmiyor",
|
||||||
|
"functionNotImplemented": "Bu sınıf bu işlevi uygulamamıştır",
|
||||||
|
"placeholder": "Yer Tutucu",
|
||||||
|
"someErrors": "Bazı Hatalar Oluştu",
|
||||||
|
"unexpectedError": "Beklenmeyen Hata",
|
||||||
|
"ok": "Tamam",
|
||||||
|
"and": "ve",
|
||||||
|
"githubPATLabel": "GitHub Kişisel Erişim Anahtarı (Sınırlamayı Artırır)",
|
||||||
|
"includePrereleases": "Ön sürümleri dahil et",
|
||||||
|
"fallbackToOlderReleases": "Daha eski sürümlere geri dön",
|
||||||
|
"filterReleaseTitlesByRegEx": "Düzenli İfadelerle Sürüm Başlıklarını Filtrele",
|
||||||
|
"invalidRegEx": "Geçersiz düzenli ifade",
|
||||||
|
"noDescription": "Açıklama yok",
|
||||||
|
"cancel": "İptal",
|
||||||
|
"continue": "Devam Et",
|
||||||
|
"requiredInBrackets": "(Gerekli)",
|
||||||
|
"dropdownNoOptsError": "HATA: DİPLOMADA EN AZ BİR SEÇENEK OLMALI",
|
||||||
|
"colour": "Renk",
|
||||||
|
"githubStarredRepos": "GitHub'a Yıldızlı Depolar",
|
||||||
|
"uname": "Kullanıcı Adı",
|
||||||
|
"wrongArgNum": "Hatalı argüman sayısı sağlandı",
|
||||||
|
"xIsTrackOnly": "{} yalnızca Takip Edilen",
|
||||||
|
"source": "Kaynak",
|
||||||
|
"app": "Uygulama",
|
||||||
|
"appsFromSourceAreTrackOnly": "Bu kaynaktan gelen uygulamalar 'Yalnızca Takip Edilen'dir.",
|
||||||
|
"youPickedTrackOnly": "'Yalnızca Takip Edilen' seçeneğini seçtiniz.",
|
||||||
|
"trackOnlyAppDescription": "Uygulama güncellemeleri için takip edilecek, ancak Obtainium onu indiremeyecek veya kuramayacaktır.",
|
||||||
|
"cancelled": "İptal Edildi",
|
||||||
|
"appAlreadyAdded": "Uygulama zaten eklenmiş",
|
||||||
|
"alreadyUpToDateQuestion": "Uygulama Zaten Güncel mi?",
|
||||||
|
"addApp": "Uygulama Ekle",
|
||||||
|
"appSourceURL": "Uygulama Kaynak URL'si",
|
||||||
|
"error": "Hata",
|
||||||
|
"add": "Ekle",
|
||||||
|
"searchSomeSourcesLabel": "Ara (Bazı Kaynaklar Yalnızca)",
|
||||||
|
"search": "Ara",
|
||||||
|
"additionalOptsFor": "{} İçin Ek Seçenekler",
|
||||||
|
"supportedSources": "Desteklenen Kaynaklar",
|
||||||
|
"trackOnlyInBrackets": "(Yalnızca Takip)",
|
||||||
|
"searchableInBrackets": "(Aranabilir)",
|
||||||
|
"appsString": "Uygulamalar",
|
||||||
|
"noApps": "Uygulama Yok",
|
||||||
|
"noAppsForFilter": "Filtre İçin Uygulama Yok",
|
||||||
|
"byX": "{} Tarafından",
|
||||||
|
"percentProgress": "İlerleme: {}%",
|
||||||
|
"pleaseWait": "Lütfen Bekleyin",
|
||||||
|
"updateAvailable": "Güncelleme Var",
|
||||||
|
"estimateInBracketsShort": "(Est.)",
|
||||||
|
"notInstalled": "Yüklenmedi",
|
||||||
|
"estimateInBrackets": "(Tahmini)",
|
||||||
|
"selectAll": "Hepsini Seç",
|
||||||
|
"deselectX": "{}'yi Seçimden Kaldır",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} Obtainium'dan kaldırılacak ancak cihazınızda yüklü kalacaktır.",
|
||||||
|
"removeSelectedAppsQuestion": "Seçilen Uygulamaları Kaldırmak İstiyor musunuz?",
|
||||||
|
"removeSelectedApps": "Seçilen Uygulamaları Kaldır",
|
||||||
|
"updateX": "{}'yi Güncelle",
|
||||||
|
"installX": "{}'yi Yükle",
|
||||||
|
"markXTrackOnlyAsUpdated": "{}(Takip Edilen) olarak Güncellendi olarak İşaretle",
|
||||||
|
"changeX": "{}'yi Değiştir",
|
||||||
|
"installUpdateApps": "Uygulamaları Yükle/Güncelle",
|
||||||
|
"installUpdateSelectedApps": "Seçilen Uygulamaları Yükle/Güncelle",
|
||||||
|
"markXSelectedAppsAsUpdated": "Seçilen Uygulamaları {} olarak Güncellendi olarak İşaretle?",
|
||||||
|
"no": "Hayır",
|
||||||
|
"yes": "Evet",
|
||||||
|
"markSelectedAppsUpdated": "Seçilen Uygulamaları Güncellendi olarak İşaretle",
|
||||||
|
"pinToTop": "Üstte Tut",
|
||||||
|
"unpinFromTop": "Üstten Kaldır",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Seçilen Uygulamaların Yükleme Durumunu Sıfırlamak İstiyor musunuz?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "Seçilen Uygulamaların yükleme durumu sıfırlanacak.\n\nBu, Obtainium'da gösterilen uygulama sürümünün başarısız güncellemeler veya diğer sorunlar nedeniyle yanlış olması durumunda yardımcı olabilir.",
|
||||||
|
"shareSelectedAppURLs": "Seçilen Uygulama URL'larını Paylaş",
|
||||||
|
"resetInstallStatus": "Yükleme Durumunu Sıfırla",
|
||||||
|
"more": "Daha Fazla",
|
||||||
|
"removeOutdatedFilter": "Güncel Olmayan Uygulama Filtresini Kaldır",
|
||||||
|
"showOutdatedOnly": "Yalnızca Güncel Olmayan Uygulamaları Göster",
|
||||||
|
"filter": "Filtre",
|
||||||
|
"filterActive": "Filtre *",
|
||||||
|
"filterApps": "Uygulamaları Filtrele",
|
||||||
|
"appName": "Uygulama Adı",
|
||||||
|
"author": "Yazar",
|
||||||
|
"upToDateApps": "Güncel Uygulamalar",
|
||||||
|
"nonInstalledApps": "Yüklenmemiş Uygulamalar",
|
||||||
|
"importExport": "İçe/Dışa Aktar",
|
||||||
|
"settings": "Ayarlar",
|
||||||
|
"exportedTo": "{}'e Dışa Aktarıldı",
|
||||||
|
"obtainiumExport": "Obtainium Dışa Aktar",
|
||||||
|
"invalidInput": "Geçersiz Giriş",
|
||||||
|
"importedX": "{} İçe Aktarıldı",
|
||||||
|
"obtainiumImport": "Obtainium İçe Aktar",
|
||||||
|
"importFromURLList": "URL Listesinden İçe Aktar (Örneğin OPML)",
|
||||||
|
"searchQuery": "Arama Sorgusu",
|
||||||
|
"appURLList": "Uygulama URL Listesi",
|
||||||
|
"line": "Satır",
|
||||||
|
"searchX": "{}'yi Ara",
|
||||||
|
"noResults": "Sonuç Bulunamadı",
|
||||||
|
"importX": "{} İçe Aktar",
|
||||||
|
"importedAppsIdDisclaimer": "İçe Aktarılan Uygulamalar yanlışlıkla \"Yüklenmedi\" olarak gösterilebilir.\nBunu düzeltmek için bunları Obtainium üzerinden yeniden yükleyin.\nBu, yalnızca URL ve üçüncü taraf içe aktarma yöntemlerini etkiler.\n\nYalnızca URL ve üçüncü taraf içe aktarma yöntemlerini etkiler.",
|
||||||
|
"importErrors": "İçe Aktarma Hataları",
|
||||||
|
"importedXOfYApps": "{}'den {} Uygulama İçe Aktarıldı.",
|
||||||
|
"followingURLsHadErrors": "Aşağıdaki URL'lerde hatalar oluştu:",
|
||||||
|
"okay": "Tamam",
|
||||||
|
"selectURL": "URL Seç",
|
||||||
|
"selectURLs": "URL'leri Seç",
|
||||||
|
"pick": "Seç",
|
||||||
|
"theme": "Tema",
|
||||||
|
"dark": "Koyu",
|
||||||
|
"light": "Aydınlık",
|
||||||
|
"followSystem": "Sistemi Takip Et",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Saf siyah koyu temasını kullan",
|
||||||
|
"appSortBy": "Uygulama Sıralama Ölçütü",
|
||||||
|
"authorName": "Yazar/Ad",
|
||||||
|
"nameAuthor": "Ad/Yazar",
|
||||||
|
"asAdded": "Eklendiği Gibi",
|
||||||
|
"appSortOrder": "Uygulama Sıralama Sırası",
|
||||||
|
"ascending": "Artan",
|
||||||
|
"descending": "Azalan",
|
||||||
|
"bgUpdateCheckInterval": "Arka Planda Güncelleme Kontrol Aralığı",
|
||||||
|
"neverManualOnly": "Asla - Yalnızca El ile",
|
||||||
|
"appearance": "Görünüm",
|
||||||
|
"showWebInAppView": "Kaynağı Uygulama Görünümünde Göster",
|
||||||
|
"pinUpdates": "Güncellemeleri Uygulamalar Görünümünün Üstüne Sabitle",
|
||||||
|
"updates": "Güncellemeler",
|
||||||
|
"sourceSpecific": "Kaynak Özel",
|
||||||
|
"appSource": "Uygulama Kaynağı",
|
||||||
|
"noLogs": "Günlük Yok",
|
||||||
|
"appLogs": "Uygulama Günlükleri",
|
||||||
|
"close": "Kapat",
|
||||||
|
"share": "Paylaş",
|
||||||
|
"appNotFound": "Uygulama Bulunamadı",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "obtainium-ihracat",
|
||||||
|
"pickAnAPK": "APK Seç",
|
||||||
|
"appHasMoreThanOnePackage": "{}'nin birden fazla paketi var:",
|
||||||
|
"deviceSupportsXArch": "Cihazınız {} CPU mimarisini destekliyor.",
|
||||||
|
"deviceSupportsFollowingArchs": "Cihazınız aşağıdaki CPU mimarilerini destekliyor:",
|
||||||
|
"warning": "Uyarı",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "Uygulama kaynağı '{}', ancak dağıtım paketi '{}'. Devam etmek istiyor musunuz?",
|
||||||
|
"updatesAvailable": "Güncellemeler Var",
|
||||||
|
"updatesAvailableNotifDescription": "Kullanıcıya Obtainium tarafından takip edilen bir veya daha fazla uygulama için güncelleme bulunduğuna dair bilgi verir",
|
||||||
|
"noNewUpdates": "Yeni güncelleme yok.",
|
||||||
|
"xHasAnUpdate": "{} güncelleme alıyor.",
|
||||||
|
"appsUpdated": "Uygulamalar Güncellendi",
|
||||||
|
"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: {}.",
|
||||||
|
"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ı",
|
||||||
|
"appsRemovedNotifDescription": "Bir veya daha fazla uygulamanın yüklenirken hata nedeniyle kaldırıldığını bildiren bir bildirim",
|
||||||
|
"xWasRemovedDueToErrorY": "{} şu hatadan dolayı kaldırıldı: {}",
|
||||||
|
"completeAppInstallation": "Uygulama Yüklemeyi Tamamla",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Uygulamaları yüklemek için Obtainium'un açık olması gerekiyor",
|
||||||
|
"completeAppInstallationNotifDescription": "Kullanıcıdan Obtainium'a geri dönüp bir uygulama yüklemeyi tamamlamasını isteyen bir bildirim",
|
||||||
|
"checkingForUpdates": "Güncellemeler Kontrol Ediliyor",
|
||||||
|
"checkingForUpdatesNotifDescription": "Güncellemeler kontrol edildiğinde görünen geçici bir bildirim",
|
||||||
|
"pleaseAllowInstallPerm": "Lütfen Obtainium'un uygulama yüklemesine izin verin",
|
||||||
|
"trackOnly": "Sadece Takip Et",
|
||||||
|
"errorWithHttpStatusCode": "Hata {}",
|
||||||
|
"versionCorrectionDisabled": "Sürüm düzeltme devre dışı bırakıldı (eklenti çalışmıyor gibi görünüyor)",
|
||||||
|
"unknown": "Bilinmiyor",
|
||||||
|
"none": "Hiçbiri",
|
||||||
|
"never": "Asla",
|
||||||
|
"latestVersionX": "En Son Sürüm: {}",
|
||||||
|
"installedVersionX": "Yüklenen Sürüm: {}",
|
||||||
|
"lastUpdateCheckX": "Son Güncelleme Kontrolü: {}",
|
||||||
|
"remove": "Kaldır",
|
||||||
|
"yesMarkUpdated": "Evet, Güncellendi olarak İşaretle",
|
||||||
|
"fdroid": "F-Droid Resmi",
|
||||||
|
"appIdOrName": "Uygulama Kimliği veya Adı",
|
||||||
|
"appId": "Uygulama Kimliği",
|
||||||
|
"appWithIdOrNameNotFound": "Bu kimlik veya ada sahip bir uygulama bulunamadı",
|
||||||
|
"reposHaveMultipleApps": "Depolar birden fazla uygulama içerebilir",
|
||||||
|
"fdroidThirdPartyRepo": "F-Droid Üçüncü Taraf Depo",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Sohbet",
|
||||||
|
"install": "Yükle",
|
||||||
|
"markInstalled": "Yüklendi olarak İşaretle",
|
||||||
|
"update": "Güncelle",
|
||||||
|
"markUpdated": "Güncellendi olarak İşaretle",
|
||||||
|
"additionalOptions": "Ek Seçenekler",
|
||||||
|
"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",
|
||||||
|
"downloadNotifDescription": "Bir uygulamanın indirme sürecinde ilerlemeyi bildiren bir bildirim",
|
||||||
|
"noAPKFound": "APK bulunamadı",
|
||||||
|
"noVersionDetection": "Sürüm Algılanamıyor",
|
||||||
|
"categorize": "Kategorize Et",
|
||||||
|
"categories": "Kategoriler",
|
||||||
|
"category": "Kategori",
|
||||||
|
"noCategory": "Kategori Yok",
|
||||||
|
"noCategories": "Kategoriler Yok",
|
||||||
|
"deleteCategoriesQuestion": "Kategorileri Silmek İstiyor musunuz?",
|
||||||
|
"categoryDeleteWarning": "Silinen kategorilerdeki tüm uygulamalar kategorisiz olarak ayarlanacaktır.",
|
||||||
|
"addCategory": "Kategori Ekle",
|
||||||
|
"label": "Etiket",
|
||||||
|
"language": "Dil",
|
||||||
|
"copiedToClipboard": "Panoya Kopyalandı",
|
||||||
|
"storagePermissionDenied": "Depolama izni reddedildi",
|
||||||
|
"selectedCategorizeWarning": "Bu, seçilen uygulamalar için mevcut kategori ayarlarını değiştirecektir.",
|
||||||
|
"filterAPKsByRegEx": "APK'leri Düzenli İfade ile Filtrele",
|
||||||
|
"removeFromObtainium": "Obtainium'dan Kaldır",
|
||||||
|
"uninstallFromDevice": "Cihazdan Kaldır",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Yalnızca Sürüm Algılaması Devre Dışı Uygulamalar İçin Çalışır.",
|
||||||
|
"releaseDateAsVersion": "Sürüm Olarak Yayın Tarihi Kullan",
|
||||||
|
"releaseDateAsVersionExplanation": "Bu seçenek, sürüm algılamanın doğru çalışmadığı ancak bir sürüm tarihinin mevcut olduğu uygulamalar için kullanılmalıdır.",
|
||||||
|
"changes": "Değişiklikler",
|
||||||
|
"releaseDate": "Yayın Tarihi",
|
||||||
|
"importFromURLsInFile": "Dosyadaki URL'lerden İçe Aktar",
|
||||||
|
"versionDetection": "Sürüm Tespiti",
|
||||||
|
"standardVersionDetection": "Standart sürüm tespiti",
|
||||||
|
"groupByCategory": "Kategoriye Göre Grupla",
|
||||||
|
"autoApkFilterByArch": "Mümkünse APK'leri CPU mimarisi ile filtreleme girişimi",
|
||||||
|
"overrideSource": "Kaynağı Geçersiz Kıl",
|
||||||
|
"dontShowAgain": "Bunu tekrar gösterme",
|
||||||
|
"dontShowTrackOnlyWarnings": "'Yalnızca Takip Edilen' uyarılarını gösterme",
|
||||||
|
"dontShowAPKOriginWarnings": "APK kaynağı uyarılarını gösterme",
|
||||||
|
"moveNonInstalledAppsToBottom": "Yüklenmemiş Uygulamaları Uygulamalar Görünümünün Altına Taşı",
|
||||||
|
"gitlabPATLabel": "GitLab Kişisel Erişim Belirteci\n(Arama ve Daha İyi APK Keşfi İçin)",
|
||||||
|
"about": "Hakkında",
|
||||||
|
"requiresCredentialsInSettings": "{}: Bu, ek kimlik bilgilerine ihtiyaç duyar (Ayarlar'da)",
|
||||||
|
"checkOnStart": "Başlangıçta güncellemeleri kontrol et",
|
||||||
|
"tryInferAppIdFromCode": "Uygulama kimliğini kaynak kodundan çıkarma girişimi",
|
||||||
|
"removeOnExternalUninstall": "Harici kaldırmada otomatik olarak kaldırılan uygulamalar",
|
||||||
|
"pickHighestVersionCode": "Otomatik olarak en yüksek sürüm kodlu APK'yi seç",
|
||||||
|
"checkUpdateOnDetailPage": "Uygulama detay sayfasını açarken güncellemeleri kontrol et",
|
||||||
|
"disablePageTransitions": "Sayfa geçiş animasyonlarını devre dışı bırak",
|
||||||
|
"reversePageTransitions": "Sayfa geçiş animasyonlarını tersine çevir",
|
||||||
|
"minStarCount": "Minimum Yıldız Sayısı",
|
||||||
|
"addInfoBelow": "Bu bilgiyi aşağıya ekle.",
|
||||||
|
"addInfoInSettings": "Bu bilgiyi Ayarlar'da ekleyin.",
|
||||||
|
"githubSourceNote": "GitHub hız sınırlaması bir API anahtarı kullanılarak atlanabilir.",
|
||||||
|
"gitlabSourceNote": "GitLab APK çıkarma işlemi bir API anahtarı olmadan çalışmayabilir.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Sürüm Notlarını Düzenli İfade ile Filtrele",
|
||||||
|
"customLinkFilterRegex": "Özel APK Bağlantı Filtresi Düzenli İfade ile (Varsayılan '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Uygulama Güncellemeleri Denendi",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Kullanıcıya bir veya daha fazla uygulamanın arka planda potansiyel olarak güncellendiğini bildiren bildirim",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} muhtemelen {} sürümüne güncellendi.",
|
||||||
|
"enableBackgroundUpdates": "Arka plan güncellemelerini etkinleştir",
|
||||||
|
"backgroundUpdateReqsExplanation": "Arka plan güncellemeleri tüm uygulamalar için mümkün olmayabilir.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Arka plan kurulumunun başarısı, Obtainium'un açıldığında ancak belirlenebilir.",
|
||||||
|
"verifyLatestTag": "'latest' etiketini doğrula",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Ara bağlantı bulunamadı",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Arka plan güncellemelerinden muaf tut (etkinse)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "WiFi olmadığında arka plan güncellemelerini devre dışı bırak",
|
||||||
|
"autoSelectHighestVersionCode": "Otomatik olarak en yüksek sürüm kodunu seç",
|
||||||
|
"versionExtractionRegEx": "Sürüm Çıkarma Düzenli İfade",
|
||||||
|
"matchGroupToUse": "Sürüm Çıkarma Regex için Kullanılacak Eşleşme Grubu",
|
||||||
|
"highlightTouchTargets": "Daha az belirgin dokunma hedeflerini vurgula",
|
||||||
|
"pickExportDir": "Dışa Aktarılacak Klasörü Seç",
|
||||||
|
"autoExportOnChanges": "Değişikliklerde otomatik olarak dışa aktar",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Sürümleri Düzenli İfade ile Filtrele",
|
||||||
|
"trySelectingSuggestedVersionCode": "Önerilen sürüm kodunu seçmeyi dene",
|
||||||
|
"dontSortReleasesList": "API'den sıralama düzenini koru",
|
||||||
|
"reverseSort": "Ters sıralama",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Hata Ayıklama Menüsü",
|
||||||
|
"bgTaskStarted": "Arka plan görevi başladı - günlükleri kontrol et.",
|
||||||
|
"runBgCheckNow": "Arka Plan Güncelleme Kontrolünü Şimdi Çalıştır",
|
||||||
|
"versionExtractWholePage": "Tüm Sayfaya Sürüm Çıkarma Regex'i Uygula",
|
||||||
|
"installing": "Yükleniyor",
|
||||||
|
"skipUpdateNotifications": "Güncelleme bildirimlerini atla",
|
||||||
|
"updatesAvailableNotifChannel": "Güncellemeler Mevcut",
|
||||||
|
"appsUpdatedNotifChannel": "Güncellenen Uygulamalar",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Uygulama Güncellemeleri Denendi",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Güncellemeler Kontrol Edilirken Hata",
|
||||||
|
"appsRemovedNotifChannel": "Kaldırılan Uygulamalar",
|
||||||
|
"downloadingXNotifChannel": "{} İndiriliyor",
|
||||||
|
"completeAppInstallationNotifChannel": "Uygulama Kurulumu Tamamlandı",
|
||||||
|
"checkingForUpdatesNotifChannel": "Güncellemeler Kontrol Ediliyor",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Yalnızca yüklü ve Yalnızca İzleme Uygulamalarını güncelleme",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"removeAppQuestion": {
|
||||||
|
"one": "Uygulamayı Kaldır?",
|
||||||
|
"other": "Uygulamaları Kaldır?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
|
"one": "Çok fazla istek (hız sınırlı) - {} dakika sonra tekrar deneyin",
|
||||||
|
"other": "Çok fazla istek (hız sınırlı) - {} dakika sonra tekrar deneyin"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "Arka plan güncelleme kontrolü bir hatayla karşılaştı, {} dakika sonra tekrar kontrol edilecek",
|
||||||
|
"other": "Arka plan güncelleme kontrolü bir hatayla karşılaştı, {} dakika sonra tekrar kontrol edilecek"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
|
"one": "Arka plan güncelleme kontrolü {} güncelleme buldu - gerektiğinde kullanıcıyı bilgilendirecek",
|
||||||
|
"other": "Arka plan güncelleme kontrolü {} güncelleme buldu - gerektiğinde kullanıcıyı bilgilendirecek"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"one": "{} Uygulama",
|
||||||
|
"other": "{} Uygulamalar"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URL'ler"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "{} Dakika",
|
||||||
|
"other": "{} Dakika"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "{} Saat",
|
||||||
|
"other": "{} Saat"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "{} Gün",
|
||||||
|
"other": "{} Gün"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY": {
|
||||||
|
"one": "{n} log temizlendi (önce = {before}, sonra = {after})",
|
||||||
|
"other": "{n} log temizlendi (önce = {before}, sonra = {after})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable": {
|
||||||
|
"one": "{} ve 1 diğer uygulama güncellemeye sahip.",
|
||||||
|
"other": "{} ve {} daha fazla uygulama güncellemeye sahip."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled": {
|
||||||
|
"one": "{} ve 1 diğer uygulama güncellendi.",
|
||||||
|
"other": "{} ve {} daha fazla uygulama güncellendi."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} ve 1 diğer uygulama muhtemelen güncellendi.",
|
||||||
|
"other": "{} ve {} daha fazla uygulama muhtemelen güncellendi."
|
||||||
|
}
|
||||||
|
}
|
338
assets/translations/vi.json
Normal file
338
assets/translations/vi.json
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "URL ứng dụng {} không hợp lệ",
|
||||||
|
"noReleaseFound": "Không thể tìm thấy bản phát hành phù hợp",
|
||||||
|
"noVersionFound": "Không thể xác định phiên bản phát hành",
|
||||||
|
"urlMatchesNoSource": "URL không khớp với nguồn đã biết",
|
||||||
|
"cantInstallOlderVersion": "Không thể cài đặt phiên bản cũ hơn của Ứng dụng",
|
||||||
|
"appIdMismatch": "ID gói đã tải xuống không khớp với ID ứng dụng hiện tại",
|
||||||
|
"functionNotImplemented": "Lớp này chưa triển khai chức năng này",
|
||||||
|
"placeholder": "Giữ chỗ",
|
||||||
|
"someErrors": "Đã xảy ra một số lỗi",
|
||||||
|
"unexpectedError": "Lỗi không mong đợi",
|
||||||
|
"ok": "Ôkê",
|
||||||
|
"and": "và",
|
||||||
|
"githubPATLabel": "Mã thông báo truy cập cá nhân GitHub (Tăng tốc độ giới hạn)",
|
||||||
|
"includePrereleases": "Bao gồm các bản phát hành trước",
|
||||||
|
"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",
|
||||||
|
"invalidRegEx": "Biểu thức chính quy không hợp lệ",
|
||||||
|
"noDescription": "Không có mô tả",
|
||||||
|
"cancel": "Hủy bỏ",
|
||||||
|
"continue": "Tiếp tục",
|
||||||
|
"requiredInBrackets": "(Yêu cầu)",
|
||||||
|
"dropdownNoOptsError": "LỖI: TẢI XUỐNG PHẢI CÓ ÍT NHẤT MỘT LỰA CHỌN",
|
||||||
|
"colour": "Màu sắc",
|
||||||
|
"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",
|
||||||
|
"xIsTrackOnly": "{}là Chỉ-Theo dõi",
|
||||||
|
"source": "Nguồn",
|
||||||
|
"app": "Ứng dụng",
|
||||||
|
"appsFromSourceAreTrackOnly": "Các ứng dụng từ nguồn này là 'Chỉ-Theo dõi'.",
|
||||||
|
"youPickedTrackOnly": "Bạn đã chọn tùy chọn 'Chỉ-Theo dõi'.",
|
||||||
|
"trackOnlyAppDescription": "Ứng dụng sẽ được theo dõi để cập nhật, nhưng Obtainium sẽ không thể tải xuống hoặc cài đặt nó.",
|
||||||
|
"cancelled": "Đã hủy",
|
||||||
|
"appAlreadyAdded": "Ứng dụng được thêm rồi",
|
||||||
|
"alreadyUpToDateQuestion": "Ứng dụng đã được cập nhật?",
|
||||||
|
"addApp": "Thêm ứng dụng",
|
||||||
|
"appSourceURL": "URL nguồn ứng dụng",
|
||||||
|
"error": "Lỗi",
|
||||||
|
"add": "Thêm",
|
||||||
|
"searchSomeSourcesLabel": "Tìm kiếm (Chỉ một số nguồn)",
|
||||||
|
"search": "Tìm kiếm",
|
||||||
|
"additionalOptsFor": "Tùy chọn bổ sung cho {}",
|
||||||
|
"supportedSources": "Nguồn được hỗ trợ",
|
||||||
|
"trackOnlyInBrackets": "(Chỉ-Theo dõi)",
|
||||||
|
"searchableInBrackets": "(Có thể tìm kiếm)",
|
||||||
|
"appsString": "Ứng dụng",
|
||||||
|
"noApps": "Không có ứng dụng",
|
||||||
|
"noAppsForFilter": "Không có ứng dụng cho bộ lọc",
|
||||||
|
"byX": "Bởi {}",
|
||||||
|
"percentProgress": "Tiến triển: {}%",
|
||||||
|
"pleaseWait": "Vui lòng chờ",
|
||||||
|
"updateAvailable": "Có sẵn bản cập nhật",
|
||||||
|
"estimateInBracketsShort": "(Ước lượng.)",
|
||||||
|
"notInstalled": "Chưa cài đặt",
|
||||||
|
"estimateInBrackets": "(Ước lượng)",
|
||||||
|
"selectAll": "Chọn tất cả",
|
||||||
|
"deselectX": "Bỏ chọn {}",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} sẽ bị xóa khỏi Obtainium nhưng vẫn còn cài đặt trên thiết bị.",
|
||||||
|
"removeSelectedAppsQuestion": "Xóa ứng dụng đã chọn?",
|
||||||
|
"removeSelectedApps": "Xóa ứng dụng đã chọn",
|
||||||
|
"updateX": "Cập nhật {}",
|
||||||
|
"installX": "Cài đặt {}",
|
||||||
|
"markXTrackOnlyAsUpdated": "Đánh dấu {}\n(Chỉ-Theo dõi)\nnhư là đã cập nhật",
|
||||||
|
"changeX": "Thay đổi {}",
|
||||||
|
"installUpdateApps": "Cài đặt/Cập nhật ứng dụng",
|
||||||
|
"installUpdateSelectedApps": "Cài đặt/Cập nhật ứng dụng đã chọn",
|
||||||
|
"markXSelectedAppsAsUpdated": "Đánh dấu {} ứng dụng đã chọn là đã cập nhật?",
|
||||||
|
"no": "Không",
|
||||||
|
"yes": "Đúng",
|
||||||
|
"markSelectedAppsUpdated": "Đánh dấu các ứng dụng đã chọn là đã cập nhật",
|
||||||
|
"pinToTop": "Ghim đầu trang",
|
||||||
|
"unpinFromTop": "Bỏ ghim khỏi đầu trang",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Đặt lại trạng thái cài đặt cho ứng dụng đã chọn?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "Trạng thái cài đặt của mọi Ứng dụng đã chọn sẽ được đặt lại.\n\nĐiều này có thể hữu ích khi phiên bản Ứng dụng hiển thị trong Obtainium không chính xác do cập nhật không thành công hoặc các sự cố khác.",
|
||||||
|
"shareSelectedAppURLs": "Chia sẻ URL ứng dụng đã chọn",
|
||||||
|
"resetInstallStatus": "Đặt lại trạng thái cài đặt",
|
||||||
|
"more": "Nhiều hơn",
|
||||||
|
"removeOutdatedFilter": "Xóa bộ lọc ứng dụng lỗi thời",
|
||||||
|
"showOutdatedOnly": "Chỉ hiển thị các ứng dụng lỗi thời",
|
||||||
|
"filter": "Lọc",
|
||||||
|
"filterActive": "Lọc *",
|
||||||
|
"filterApps": "Lọc ứng dụng",
|
||||||
|
"appName": "Tên ứng dụng",
|
||||||
|
"author": "Tác giả",
|
||||||
|
"upToDateApps": "Ứng dụng cập nhật",
|
||||||
|
"nonInstalledApps": "Ứng dụng chưa được cài đặt",
|
||||||
|
"importExport": "Nhập/Xuất",
|
||||||
|
"settings": "Cài đặt",
|
||||||
|
"exportedTo": "Đã xuất sang {}",
|
||||||
|
"obtainiumExport": "Xuất Obtainium",
|
||||||
|
"invalidInput": "Đầu vào không hợp lệ",
|
||||||
|
"importedX": "Đã nhập {}",
|
||||||
|
"obtainiumImport": "Nhập Obtainium",
|
||||||
|
"importFromURLList": "Nhập từ danh sách URL",
|
||||||
|
"searchQuery": "Truy vấn tìm kiếm",
|
||||||
|
"appURLList": "Danh sách URL ứng dụng",
|
||||||
|
"line": "Hàng",
|
||||||
|
"searchX": "Tìm kiếm {}",
|
||||||
|
"noResults": "Không có kết quả nào được tìm thấy",
|
||||||
|
"importX": "Nhập {}",
|
||||||
|
"importedAppsIdDisclaimer": "Ứng dụng đã nhập có thể hiển thị không chính xác là \"Chưa được cài đặt\".\nĐể khắc phục sự cố này, hãy cài đặt lại chúng thông qua Obtainium.\nĐiều này sẽ không ảnh hưởng đến dữ liệu Ứng dụng.\n\nChỉ ảnh hưởng đến URL và phương thức nhập của bên thứ ba.",
|
||||||
|
"importErrors": "Lỗi nhập",
|
||||||
|
"importedXOfYApps": "{} trong số {} Ứng dụng đã được nhập.",
|
||||||
|
"followingURLsHadErrors": "Các URL sau có lỗi:",
|
||||||
|
"okay": "Ôkê",
|
||||||
|
"selectURL": "Chọn URL",
|
||||||
|
"selectURLs": "Chọn URL",
|
||||||
|
"pick": "Chọn",
|
||||||
|
"theme": "Chủ đề",
|
||||||
|
"dark": "Tối",
|
||||||
|
"light": "Sáng",
|
||||||
|
"followSystem": "Theo hệ thống",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Sử dụng chủ đề tối màu đen thuần túy",
|
||||||
|
"appSortBy": "Sắp xếp ứng dụng theo",
|
||||||
|
"authorName": "Tác giả/Tên",
|
||||||
|
"nameAuthor": "Tên/Tác giả",
|
||||||
|
"asAdded": "Như đã thêm",
|
||||||
|
"appSortOrder": "Thứ tự sắp xếp ứng dụng",
|
||||||
|
"ascending": "Tăng dần",
|
||||||
|
"descending": "Giảm dần",
|
||||||
|
"bgUpdateCheckInterval": "Khoảng thời gian kiểm tra cập nhật nền",
|
||||||
|
"neverManualOnly": "Không bao giờ - Chỉ thủ công",
|
||||||
|
"appearance": "Vẻ ngoài",
|
||||||
|
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem Ứng dụng",
|
||||||
|
"pinUpdates": "Ghim nội dung cập nhật lên đầu chế độ xem Ứng dụng",
|
||||||
|
"updates": "Cập nhật",
|
||||||
|
"sourceSpecific": "Nguồn cụ thể",
|
||||||
|
"appSource": "Nguồn ứng dụng",
|
||||||
|
"noLogs": "Không có nhật ký",
|
||||||
|
"appLogs": "Nhật ký ứng dụng",
|
||||||
|
"close": "Đóng",
|
||||||
|
"share": "Chia sẻ",
|
||||||
|
"appNotFound": "Không tìm thấy ứng dụng",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "xuất khẩu-obtainium",
|
||||||
|
"pickAnAPK": "Chọn một APK",
|
||||||
|
"appHasMoreThanOnePackage": "{} có nhiều gói:",
|
||||||
|
"deviceSupportsXArch": "Thiết bị của bạn hỗ trợ kiến trúc CPU {}.",
|
||||||
|
"deviceSupportsFollowingArchs": "Thiết bị của bạn hỗ trợ các kiến trúc CPU sau:",
|
||||||
|
"warning": "Cảnh báo",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "Nguồn ứng dụng là '{}' nhưng gói phát hành đến từ '{}'. Tiếp tục?",
|
||||||
|
"updatesAvailable": "Cập nhật có sẵn",
|
||||||
|
"updatesAvailableNotifDescription": "Thông báo cho người dùng rằng có bản cập nhật cho một hoặc nhiều Ứng dụng được theo dõi bởi Obtainium",
|
||||||
|
"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 ",
|
||||||
|
"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 {}.",
|
||||||
|
"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ỏ",
|
||||||
|
"appsRemovedNotifDescription": "Thông báo cho người dùng rằng một hoặc nhiều Ứng dụng đã bị loại bỏ do lỗi khi tải chúng",
|
||||||
|
"xWasRemovedDueToErrorY": "{} đã bị loại bỏ do lỗi này: {}",
|
||||||
|
"completeAppInstallation": "Hoàn tất cài đặt ứng dụng",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Obtainium phải được mở để cài đặt Ứng dụng",
|
||||||
|
"completeAppInstallationNotifDescription": "Yêu cầu người dùng quay lại Obtainium để hoàn tất cài đặt Ứng dụng",
|
||||||
|
"checkingForUpdates": "Đang kiểm tra cập nhật",
|
||||||
|
"checkingForUpdatesNotifDescription": "Thông báo tạm thời xuất hiện khi kiểm tra bản cập nhật",
|
||||||
|
"pleaseAllowInstallPerm": "Vui lòng cho phép Obtainium cài đặt Ứng dụng",
|
||||||
|
"trackOnly": "Chỉ-Theo dõi",
|
||||||
|
"errorWithHttpStatusCode": "Lỗi {}",
|
||||||
|
"versionCorrectionDisabled": "Tính năng sửa phiên bản bị vô hiệu hóa (plugin dường như không hoạt động)",
|
||||||
|
"unknown": "Không xác định",
|
||||||
|
"none": "Không",
|
||||||
|
"never": "Không bao giờ",
|
||||||
|
"latestVersionX": "Phiên bản mới nhất: {}",
|
||||||
|
"installedVersionX": "Phiên bản đã cài đặt: {}",
|
||||||
|
"lastUpdateCheckX": "Kiểm tra cập nhật lần cuối: {}",
|
||||||
|
"remove": "Loại bỏ",
|
||||||
|
"yesMarkUpdated": "Có, Đánh dấu là đã cập nhật",
|
||||||
|
"fdroid": "Chính thức của F-Droid",
|
||||||
|
"appIdOrName": "ID hoặc tên ứng dụng",
|
||||||
|
"appId": "ID ứng dụng",
|
||||||
|
"appWithIdOrNameNotFound": "Không tìm thấy ứng dụng nào có ID hoặc tên đó",
|
||||||
|
"reposHaveMultipleApps": "Kho có thể chứa nhiều Ứng dụng",
|
||||||
|
"fdroidThirdPartyRepo": "Kho lưu trữ bên thứ ba F-Droid",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Chat",
|
||||||
|
"install": "Cài đặt",
|
||||||
|
"markInstalled": "Đánh dấu là đã cài đặt",
|
||||||
|
"update": "Cập nhật",
|
||||||
|
"markUpdated": "Đánh dấu đã cập nhật",
|
||||||
|
"additionalOptions": "Tùy chọn bổ sung",
|
||||||
|
"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 {}",
|
||||||
|
"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",
|
||||||
|
"categorize": "Phân loại",
|
||||||
|
"categories": "Thể loại",
|
||||||
|
"category": "Thể loại",
|
||||||
|
"noCategory": "Không thể loại",
|
||||||
|
"noCategories": "Không thể loại",
|
||||||
|
"deleteCategoriesQuestion": "Xóa thể loại?",
|
||||||
|
"categoryDeleteWarning": "Tất cả ứng dụng trong thể loại đã xóa sẽ được đặt thành chưa được phân loại.",
|
||||||
|
"addCategory": "Thêm thể loại",
|
||||||
|
"label": "Nhãn",
|
||||||
|
"language": "Ngôn ngữ",
|
||||||
|
"copiedToClipboard": "Sao chép vào clipboard",
|
||||||
|
"storagePermissionDenied": "Quyền lưu trữ bị từ chối",
|
||||||
|
"selectedCategorizeWarning": "Điều này sẽ thay thế mọi cài đặt thể loại hiện có cho Ứng dụng đã chọn.",
|
||||||
|
"filterAPKsByRegEx": "Lọc APK theo biểu thức chính quy",
|
||||||
|
"removeFromObtainium": "Loại khỏi Obtainium",
|
||||||
|
"uninstallFromDevice": "Gỡ cài đặt khỏi thiết bị",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Chỉ hoạt động với Ứng dụng đã tắt tính năng phát hiện phiên bản.",
|
||||||
|
"releaseDateAsVersion": "Sử dụng ngày phát hành làm phiên bản",
|
||||||
|
"releaseDateAsVersionExplanation": "Chỉ nên sử dụng tùy chọn này cho Ứng dụng trong đó tính năng phát hiện phiên bản không hoạt động chính xác nhưng đã có ngày phát hành.",
|
||||||
|
"changes": "Thay đổi",
|
||||||
|
"releaseDate": "Ngày phát hành",
|
||||||
|
"importFromURLsInFile": "Nhập từ URL trong Tệp (như OPML)",
|
||||||
|
"versionDetection": "Phát hiện phiên bản",
|
||||||
|
"standardVersionDetection": "Phát hiện phiên bản tiêu chuẩn",
|
||||||
|
"groupByCategory": "Nhóm theo thể loại",
|
||||||
|
"autoApkFilterByArch": "Cố gắng lọc APK theo kiến trúc CPU nếu có thể",
|
||||||
|
"overrideSource": "Ghi đè nguồn",
|
||||||
|
"dontShowAgain": "Đừng hiển thị thông tin này nữa",
|
||||||
|
"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": "Di chuyển Ứng dụng chưa được cài đặt xuống cuối chế độ xem Ứng dụng",
|
||||||
|
"gitlabPATLabel": "Mã thông báo truy cập cá nhân GitLab\n(Cho phép tìm kiếm và khám phá APK tốt hơn)",
|
||||||
|
"about": "Giới thiệu",
|
||||||
|
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
||||||
|
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
|
||||||
|
"tryInferAppIdFromCode": "Thử suy ra ID ứng dụng từ mã nguồn",
|
||||||
|
"removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",
|
||||||
|
"pickHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
|
||||||
|
"checkUpdateOnDetailPage": "Kiểm tra các bản cập nhật khi mở trang chi tiết Ứng dụng",
|
||||||
|
"disablePageTransitions": "Tắt hoạt ảnh chuyển trang",
|
||||||
|
"reversePageTransitions": "Hoạt ảnh chuyển đổi trang đảo ngược",
|
||||||
|
"minStarCount": "Số lượng sao tối thiểu",
|
||||||
|
"addInfoBelow": "Thêm thông tin này vào bên dưới.",
|
||||||
|
"addInfoInSettings": "Thêm thông tin này vào Cài đặt.",
|
||||||
|
"githubSourceNote": "Có thể tránh được việc giới hạn tốc độ GitHub bằng cách sử dụng khóa API.",
|
||||||
|
"gitlabSourceNote": "Trích xuất APK GitLab có thể không hoạt động nếu không có khóa API.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Lọc ghi chú phát hành theo biểu thức chính quy",
|
||||||
|
"customLinkFilterRegex": "Bộ lọc liên kết APK tùy chỉnh theo biểu thức chính quy (Mặc định '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "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ó khả năng được áp dụng trong nền",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} có thể đã được cập nhật thành {}.",
|
||||||
|
"enableBackgroundUpdates": "Bật cập nhật nền",
|
||||||
|
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật nền cho tất cả ứng dụng.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt nền chỉ có thể được xác định khi mở Obtainium.",
|
||||||
|
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Không tìm thấy liên kết trung gian",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Miễn cập nhật nền (nếu được bật)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Tắt cập nhật nền khi không có WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
|
||||||
|
"versionExtractionRegEx": "Trích xuất phiên bản RegEx",
|
||||||
|
"matchGroupToUse": "Nhóm đối sánh để sử dụng cho Regex trích xuất phiên bản",
|
||||||
|
"highlightTouchTargets": "Đánh dấu các mục tiêu cảm ứng ít rõ ràng hơn",
|
||||||
|
"pickExportDir": "Chọn thư mục xuất",
|
||||||
|
"autoExportOnChanges": "Tự động xuất khi thay đổi",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Lọc phiên bản theo biểu thức chính quy",
|
||||||
|
"trySelectingSuggestedVersionCode": "Thử chọn APK Mã phiên bản được đề xuất",
|
||||||
|
"dontSortReleasesList": "Giữ lại thứ tự phát hành từ API",
|
||||||
|
"reverseSort": "Sắp xếp ngược",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Danh sách gỡ lỗi",
|
||||||
|
"bgTaskStarted": "Tác vụ nền đã bắt đầu - kiểm tra nhật ký.",
|
||||||
|
"runBgCheckNow": "Chạy kiểm tra cập nhật nền ngay bây giờ",
|
||||||
|
"versionExtractWholePage": "Áp dụng Regex trích xuất phiên bản cho toàn bộ trang",
|
||||||
|
"installing": "Đang cài đặt",
|
||||||
|
"skipUpdateNotifications": "Bỏ qua thông báo cập nhật",
|
||||||
|
"updatesAvailableNotifChannel": "Cập nhật có sẵn",
|
||||||
|
"appsUpdatedNotifChannel": "Đã cập nhật ứng dụng",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Đã cố gắng cập nhật ứng dụng",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Lỗi kiểm tra bản cập nhật",
|
||||||
|
"appsRemovedNotifChannel": "Ứng dụng đã bị loại bỏ",
|
||||||
|
"downloadingXNotifChannel": "Đang tải xuống {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
|
||||||
|
"checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"removeAppQuestion":{
|
||||||
|
"one": "Gỡ ứng dụng?",
|
||||||
|
"other": "Gỡ ứng dụng?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes":{
|
||||||
|
"one": "Quá nhiều yêu cầu (tốc độ giới hạn) - hãy thử lại sau {} phút",
|
||||||
|
"other": "Quá nhiều yêu cầu (tốc độ giới hạn) - hãy thử lại sau {} phút"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes":{
|
||||||
|
"one": "Việc kiểm tra bản cập nhật BG gặp phải {}, sẽ lên lịch kiểm tra lại sau {} phút",
|
||||||
|
"other": "Việc kiểm tra bản cập nhật BG gặp phải {}, sẽ lên lịch kiểm tra lại sau {} phút"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded":{
|
||||||
|
"one": "Đang kiểm tra bản cập nhật BG tìm thấy {} bản cập nhật - sẽ thông báo cho người dùng nếu cần",
|
||||||
|
"other": "Đang kiểm tra bản cập nhật BG tìm thấy {} bản cập nhật - sẽ thông báo cho người dùng nếu cần"
|
||||||
|
},
|
||||||
|
"apps":{
|
||||||
|
"one": "{} Ứng dụng",
|
||||||
|
"other": "{} Ứng dụng"
|
||||||
|
},
|
||||||
|
"url":{
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URL"
|
||||||
|
},
|
||||||
|
"minute":{
|
||||||
|
"one": "{} Phút",
|
||||||
|
"other": "{} Phút"
|
||||||
|
},
|
||||||
|
"hour":{
|
||||||
|
"one": "{} Giờ",
|
||||||
|
"other": "{} Giờ"
|
||||||
|
},
|
||||||
|
"day":{
|
||||||
|
"one": "{} Ngày",
|
||||||
|
"other": "{} ngày"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY":{
|
||||||
|
"one": "Đã xóa {n} nhật ký (trước = {trước}, sau = {sau})",
|
||||||
|
"other": "Đã xóa {n} nhật ký (trước = {trước}, sau = {sau})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable":{
|
||||||
|
"one": "{} và 1 ứng dụng khác có bản cập nhật.",
|
||||||
|
"other": "{} và {} ứng dụng khác có bản cập nhật."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled":{
|
||||||
|
"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."
|
||||||
|
},
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
}
|
@@ -14,7 +14,7 @@
|
|||||||
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
|
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
|
||||||
"includePrereleases": "包含预发行版",
|
"includePrereleases": "包含预发行版",
|
||||||
"fallbackToOlderReleases": "将旧发行版作为备选",
|
"fallbackToOlderReleases": "将旧发行版作为备选",
|
||||||
"filterReleaseTitlesByRegEx": "使用正则表达式筛选发行标题",
|
"filterReleaseTitlesByRegEx": "筛选发行标题(正则表达式)",
|
||||||
"invalidRegEx": "无效的正则表达式",
|
"invalidRegEx": "无效的正则表达式",
|
||||||
"noDescription": "无描述",
|
"noDescription": "无描述",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"notInstalled": "未安装",
|
"notInstalled": "未安装",
|
||||||
"estimateInBrackets": "(推测)",
|
"estimateInBrackets": "(推测)",
|
||||||
"selectAll": "全选",
|
"selectAll": "全选",
|
||||||
"deselectN": "取消选择 {}",
|
"deselectX": "取消选择 {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
||||||
"removeSelectedAppsQuestion": "是否删除选中的应用?",
|
"removeSelectedAppsQuestion": "是否删除选中的应用?",
|
||||||
"removeSelectedApps": "删除选中的应用",
|
"removeSelectedApps": "删除选中的应用",
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
"copiedToClipboard": "已复制至剪贴板",
|
"copiedToClipboard": "已复制至剪贴板",
|
||||||
"storagePermissionDenied": "已拒绝授予存储权限",
|
"storagePermissionDenied": "已拒绝授予存储权限",
|
||||||
"selectedCategorizeWarning": "这将覆盖选中应用当前的类别设置。",
|
"selectedCategorizeWarning": "这将覆盖选中应用当前的类别设置。",
|
||||||
"filterAPKsByRegEx": "使用正则表达式筛选 APK 文件",
|
"filterAPKsByRegEx": "筛选 APK 文件(正则表达式)",
|
||||||
"removeFromObtainium": "从 Obtainium 中删除",
|
"removeFromObtainium": "从 Obtainium 中删除",
|
||||||
"uninstallFromDevice": "从设备中卸载",
|
"uninstallFromDevice": "从设备中卸载",
|
||||||
"onlyWorksWithNonVersionDetectApps": "仅适用于禁用版本检测的应用。",
|
"onlyWorksWithNonVersionDetectApps": "仅适用于禁用版本检测的应用。",
|
||||||
@@ -221,9 +221,9 @@
|
|||||||
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
|
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
|
||||||
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
|
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
|
||||||
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
||||||
"gitlabPATLabel": "GitLab 个人访问令牌\n(启用搜索功能并增强 APK 发现)",
|
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
|
||||||
"about": "相关文档",
|
"about": "相关文档",
|
||||||
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
|
"requiresCredentialsInSettings": "{}:此功能需要额外的凭据(在“设置”中添加)",
|
||||||
"checkOnStart": "启动时进行一次检查",
|
"checkOnStart": "启动时进行一次检查",
|
||||||
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
|
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
|
||||||
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
|
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
|
||||||
@@ -236,9 +236,9 @@
|
|||||||
"addInfoInSettings": "在“设置”中添加此凭据。",
|
"addInfoInSettings": "在“设置”中添加此凭据。",
|
||||||
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
|
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
|
||||||
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
|
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
|
||||||
"sortByFileNamesNotLinks": "使用文件名代替链接进行排序",
|
"sortByLastLinkSegment": "仅根据链接的末尾部分进行筛选",
|
||||||
"filterReleaseNotesByRegEx": "使用正则表达式筛选发行说明",
|
"filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)",
|
||||||
"customLinkFilterRegex": "使用正则表达式筛选自定义来源 APK 文件链接\n(未填写时,默认匹配模式为“.apk$”)",
|
"customLinkFilterRegex": "筛选自定义来源的 APK 文件链接\n(正则表达式,默认匹配模式为“.apk$”)",
|
||||||
"appsPossiblyUpdated": "已尝试更新应用",
|
"appsPossiblyUpdated": "已尝试更新应用",
|
||||||
"appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知",
|
"appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知",
|
||||||
"xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。",
|
"xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。",
|
||||||
@@ -246,26 +246,31 @@
|
|||||||
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
|
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
|
||||||
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
|
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
|
||||||
"verifyLatestTag": "验证“Latest”标签",
|
"verifyLatestTag": "验证“Latest”标签",
|
||||||
"intermediateLinkRegex": "筛选一个首先访问的“中转”链接(正则表达式)",
|
"intermediateLinkRegex": "筛选中转链接(正则表达式)",
|
||||||
"intermediateLinkNotFound": "未找到“中转”链接",
|
"filterByLinkText": "根据链接文本进行筛选",
|
||||||
"exemptFromBackgroundUpdates": "单独禁用后台更新(若已经全局启用)",
|
"intermediateLinkNotFound": "未找到中转链接",
|
||||||
|
"intermediateLink": "中转链接",
|
||||||
|
"exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用)",
|
||||||
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
|
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
|
||||||
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
||||||
"versionExtractionRegEx": "获取版本号的正则表达式",
|
"versionExtractionRegEx": "版本号提取规则(正则表达式)",
|
||||||
"matchGroupToUse": "引用的捕获组",
|
"matchGroupToUse": "引用的捕获组",
|
||||||
"highlightTouchTargets": "突出展示不明显的触摸区域",
|
"highlightTouchTargets": "突出展示不明显的触摸区域",
|
||||||
"pickExportDir": "选择导出文件夹",
|
"pickExportDir": "选择导出文件夹",
|
||||||
"autoExportOnChanges": "数据变更时自动导出",
|
"autoExportOnChanges": "数据变更时自动导出",
|
||||||
"filterVersionsByRegEx": "使用正则表达式筛选版本号",
|
"includeSettings": "同时导出应用设置",
|
||||||
|
"filterVersionsByRegEx": "筛选版本号(正则表达式)",
|
||||||
"trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件",
|
"trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件",
|
||||||
"dontSortReleasesList": "保持来自 API 的发行顺序",
|
"dontSortReleasesList": "保持来自 API 的发行顺序",
|
||||||
"reverseSort": "反转排序",
|
"reverseSort": "反转排序",
|
||||||
|
"takeFirstLink": "选取第一个链接",
|
||||||
|
"skipSort": "不进行排序",
|
||||||
"debugMenu": "调试选项",
|
"debugMenu": "调试选项",
|
||||||
"bgTaskStarted": "后台任务已启动 - 详见日志",
|
"bgTaskStarted": "后台任务已启动 - 详见日志",
|
||||||
"runBgCheckNow": "立即进行后台更新检查",
|
"runBgCheckNow": "立即进行后台更新检查",
|
||||||
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
"versionExtractWholePage": "将版本号提取规则应用于完整页面",
|
||||||
"installing": "Installing",
|
"installing": "正在安装",
|
||||||
"skipUpdateNotifications": "Skip update notifications",
|
"skipUpdateNotifications": "忽略更新通知",
|
||||||
"updatesAvailableNotifChannel": "更新可用",
|
"updatesAvailableNotifChannel": "更新可用",
|
||||||
"appsUpdatedNotifChannel": "应用已更新",
|
"appsUpdatedNotifChannel": "应用已更新",
|
||||||
"appsPossiblyUpdatedNotifChannel": "已尝试更新应用",
|
"appsPossiblyUpdatedNotifChannel": "已尝试更新应用",
|
||||||
@@ -274,7 +279,15 @@
|
|||||||
"downloadingXNotifChannel": "正在下载{}",
|
"downloadingXNotifChannel": "正在下载{}",
|
||||||
"completeAppInstallationNotifChannel": "完成应用安装",
|
"completeAppInstallationNotifChannel": "完成应用安装",
|
||||||
"checkingForUpdatesNotifChannel": "正在检查更新",
|
"checkingForUpdatesNotifChannel": "正在检查更新",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
"onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查",
|
||||||
|
"supportFixedAPKURL": "支持固定的 APK 文件链接",
|
||||||
|
"selectX": "选择 {}",
|
||||||
|
"parallelDownloads": "启用并行下载",
|
||||||
|
"installMethod": "安装方式",
|
||||||
|
"normal": "常规",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku 服务未运行",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "是否删除应用?",
|
"one": "是否删除应用?",
|
||||||
"other": "是否删除应用?"
|
"other": "是否删除应用?"
|
||||||
|
7
build.sh
7
build.sh
@@ -7,8 +7,11 @@ trap "cd "$CURR_DIR"" EXIT
|
|||||||
if [ -z "$1" ]; then
|
if [ -z "$1" ]; then
|
||||||
git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date
|
git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date
|
||||||
fi
|
fi
|
||||||
rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any
|
rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any
|
||||||
flutter build apk && flutter build apk --split-per-abi # Build (both split and combined APKs)
|
flutter build apk --flavor normal && flutter build apk --split-per-abi --flavor normal # Build (both split and combined APKs)
|
||||||
|
for file in ./build/app/outputs/flutter-apk/app-*normal*.apk*; do mv "$file" "${file//-normal/}"; done
|
||||||
|
flutter build apk --flavor fdroid -t lib/main_fdroid.dart && # Do the same for the F-Droid flavour
|
||||||
|
flutter build apk --split-per-abi --flavor fdroid -t lib/main_fdroid.dart
|
||||||
for file in ./build/app/outputs/flutter-apk/*.sha1; do gpg --sign --detach-sig "$file"; done # Generate PGP signatures
|
for file in ./build/app/outputs/flutter-apk/*.sha1; do gpg --sign --detach-sig "$file"; done # Generate PGP signatures
|
||||||
rsync -r ./build/app/outputs/flutter-apk/ ~/Downloads/Obtainium-build/ # Dropoff in Downloads to allow for drag-drop into Flatpak Firefox
|
rsync -r ./build/app/outputs/flutter-apk/ ~/Downloads/Obtainium-build/ # Dropoff in Downloads to allow for drag-drop into Flatpak Firefox
|
||||||
cd ~/Downloads/Obtainium-build/ # Make zips just in case (for in-comment uploads)
|
cd ~/Downloads/Obtainium-build/ # Make zips just in case (for in-comment uploads)
|
||||||
|
@@ -32,7 +32,8 @@ class APKMirror extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
|
RegExp standardUrlRegEx =
|
||||||
|
RegExp('^https?://(www\\.)?$host/apk/[^/]+/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
|
@@ -7,7 +7,7 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
class Aptoide extends AppSource {
|
class Aptoide extends AppSource {
|
||||||
Aptoide() {
|
Aptoide() {
|
||||||
host = 'aptoide.com';
|
host = 'aptoide.com';
|
||||||
name = tr('Aptoide');
|
name = 'Aptoide';
|
||||||
allowSubDomains = true;
|
allowSubDomains = true;
|
||||||
naiveStandardVersionDetection = true;
|
naiveStandardVersionDetection = true;
|
||||||
}
|
}
|
||||||
|
@@ -65,7 +65,7 @@ class FDroid extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
String? appId = await tryInferringAppId(standardUrl);
|
String? appId = await tryInferringAppId(standardUrl);
|
||||||
String host = Uri.parse(standardUrl).host;
|
String host = Uri.parse(standardUrl).host;
|
||||||
return getAPKUrlsFromFDroidPackagesAPIResponse(
|
var details = getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await sourceRequest('https://$host/api/v1/packages/$appId'),
|
await sourceRequest('https://$host/api/v1/packages/$appId'),
|
||||||
'https://$host/repo/$appId',
|
'https://$host/repo/$appId',
|
||||||
standardUrl,
|
standardUrl,
|
||||||
@@ -80,6 +80,23 @@ class FDroid extends AppSource {
|
|||||||
true
|
true
|
||||||
? additionalSettings['filterVersionsByRegEx']
|
? additionalSettings['filterVersionsByRegEx']
|
||||||
: null);
|
: null);
|
||||||
|
if (!hostChanged) {
|
||||||
|
try {
|
||||||
|
var res = await sourceRequest(
|
||||||
|
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml');
|
||||||
|
String author = res.body
|
||||||
|
.split('\n')
|
||||||
|
.where((l) => l.startsWith('AuthorName: '))
|
||||||
|
.first
|
||||||
|
.split(': ')
|
||||||
|
.sublist(1)
|
||||||
|
.join(': ');
|
||||||
|
details.names.author = author;
|
||||||
|
} catch (e) {
|
||||||
|
// Fail silently
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -111,79 +128,79 @@ class FDroid extends AppSource {
|
|||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
|
APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
Response res, String apkUrlPrefix, String standardUrl, String sourceName,
|
Response res, String apkUrlPrefix, String standardUrl, String sourceName,
|
||||||
{bool autoSelectHighestVersionCode = false,
|
{bool autoSelectHighestVersionCode = false,
|
||||||
bool trySelectingSuggestedVersionCode = false,
|
bool trySelectingSuggestedVersionCode = false,
|
||||||
String? filterVersionsByRegEx}) {
|
String? filterVersionsByRegEx}) {
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var response = jsonDecode(res.body);
|
var response = jsonDecode(res.body);
|
||||||
List<dynamic> releases = response['packages'] ?? [];
|
List<dynamic> releases = response['packages'] ?? [];
|
||||||
if (releases.isEmpty) {
|
if (releases.isEmpty) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
|
||||||
String? version;
|
|
||||||
Iterable<dynamic> releaseChoices = [];
|
|
||||||
// Grab the versionCode suggested if the user chose to do that
|
|
||||||
// Only do so at this stage if the user has no release filter
|
|
||||||
if (trySelectingSuggestedVersionCode &&
|
|
||||||
response['suggestedVersionCode'] != null &&
|
|
||||||
filterVersionsByRegEx == null) {
|
|
||||||
var suggestedReleases = releases.where((element) =>
|
|
||||||
element['versionCode'] == response['suggestedVersionCode']);
|
|
||||||
if (suggestedReleases.isNotEmpty) {
|
|
||||||
releaseChoices = suggestedReleases;
|
|
||||||
version = suggestedReleases.first['versionName'];
|
|
||||||
}
|
}
|
||||||
}
|
String? version;
|
||||||
// Apply the release filter if any
|
Iterable<dynamic> releaseChoices = [];
|
||||||
if (filterVersionsByRegEx != null) {
|
// Grab the versionCode suggested if the user chose to do that
|
||||||
version = null;
|
// Only do so at this stage if the user has no release filter
|
||||||
releaseChoices = [];
|
if (trySelectingSuggestedVersionCode &&
|
||||||
for (var i = 0; i < releases.length; i++) {
|
response['suggestedVersionCode'] != null &&
|
||||||
if (RegExp(filterVersionsByRegEx)
|
filterVersionsByRegEx == null) {
|
||||||
.hasMatch(releases[i]['versionName'])) {
|
var suggestedReleases = releases.where((element) =>
|
||||||
version = releases[i]['versionName'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (version == null) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Default to the highest version
|
|
||||||
version ??= releases[0]['versionName'];
|
|
||||||
if (version == null) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
// If a suggested release was not already picked, pick all those with the selected version
|
|
||||||
if (releaseChoices.isEmpty) {
|
|
||||||
releaseChoices =
|
|
||||||
releases.where((element) => element['versionName'] == version);
|
|
||||||
}
|
|
||||||
// For the remaining releases, use the toggles to auto-select one if possible
|
|
||||||
if (releaseChoices.length > 1) {
|
|
||||||
if (autoSelectHighestVersionCode) {
|
|
||||||
releaseChoices = [releaseChoices.first];
|
|
||||||
} else if (trySelectingSuggestedVersionCode &&
|
|
||||||
response['suggestedVersionCode'] != null) {
|
|
||||||
var suggestedReleases = releaseChoices.where((element) =>
|
|
||||||
element['versionCode'] == response['suggestedVersionCode']);
|
element['versionCode'] == response['suggestedVersionCode']);
|
||||||
if (suggestedReleases.isNotEmpty) {
|
if (suggestedReleases.isNotEmpty) {
|
||||||
releaseChoices = suggestedReleases;
|
releaseChoices = suggestedReleases;
|
||||||
|
version = suggestedReleases.first['versionName'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Apply the release filter if any
|
||||||
|
if (filterVersionsByRegEx?.isNotEmpty == true) {
|
||||||
|
version = null;
|
||||||
|
releaseChoices = [];
|
||||||
|
for (var i = 0; i < releases.length; i++) {
|
||||||
|
if (RegExp(filterVersionsByRegEx!)
|
||||||
|
.hasMatch(releases[i]['versionName'])) {
|
||||||
|
version = releases[i]['versionName'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default to the highest version
|
||||||
|
version ??= releases[0]['versionName'];
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
// If a suggested release was not already picked, pick all those with the selected version
|
||||||
|
if (releaseChoices.isEmpty) {
|
||||||
|
releaseChoices =
|
||||||
|
releases.where((element) => element['versionName'] == version);
|
||||||
|
}
|
||||||
|
// For the remaining releases, use the toggles to auto-select one if possible
|
||||||
|
if (releaseChoices.length > 1) {
|
||||||
|
if (autoSelectHighestVersionCode) {
|
||||||
|
releaseChoices = [releaseChoices.first];
|
||||||
|
} else if (trySelectingSuggestedVersionCode &&
|
||||||
|
response['suggestedVersionCode'] != null) {
|
||||||
|
var suggestedReleases = releaseChoices.where((element) =>
|
||||||
|
element['versionCode'] == response['suggestedVersionCode']);
|
||||||
|
if (suggestedReleases.isNotEmpty) {
|
||||||
|
releaseChoices = suggestedReleases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (releaseChoices.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
List<String> apkUrls = releaseChoices
|
||||||
|
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
|
||||||
|
.toList();
|
||||||
|
return APKDetails(version, getApkUrlsFromUrls(apkUrls.toSet().toList()),
|
||||||
|
AppNames(sourceName, Uri.parse(standardUrl).pathSegments.last));
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
if (releaseChoices.isEmpty) {
|
|
||||||
throw NoReleasesError();
|
|
||||||
}
|
|
||||||
List<String> apkUrls = releaseChoices
|
|
||||||
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
|
|
||||||
.toList();
|
|
||||||
return APKDetails(version, getApkUrlsFromUrls(apkUrls.toSet().toList()),
|
|
||||||
AppNames(sourceName, Uri.parse(standardUrl).pathSegments.last));
|
|
||||||
} else {
|
|
||||||
throw getObtainiumHttpError(res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -54,17 +54,25 @@ class FDroidRepo extends AppSource {
|
|||||||
@override
|
@override
|
||||||
Future<Map<String, List<String>>> search(String query,
|
Future<Map<String, List<String>>> search(String query,
|
||||||
{Map<String, dynamic> querySettings = const {}}) async {
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
query = removeQueryParamsFromUrl(standardizeUrl(query));
|
String? url = querySettings['url'];
|
||||||
var res = await sourceRequest('$query/index.xml');
|
if (url == null) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
url = removeQueryParamsFromUrl(standardizeUrl(url));
|
||||||
|
var res = await sourceRequest('$url/index.xml');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var body = parse(res.body);
|
var body = parse(res.body);
|
||||||
Map<String, List<String>> results = {};
|
Map<String, List<String>> results = {};
|
||||||
body.querySelectorAll('application').toList().forEach((app) {
|
body.querySelectorAll('application').toList().forEach((app) {
|
||||||
String appId = app.attributes['id']!;
|
String appId = app.attributes['id']!;
|
||||||
results['$query?appId=$appId'] = [
|
String appName = app.querySelector('name')?.innerHtml ?? appId;
|
||||||
app.querySelector('name')?.innerHtml ?? appId,
|
String appDesc = app.querySelector('desc')?.innerHtml ?? '';
|
||||||
app.querySelector('desc')?.innerHtml ?? ''
|
if (query.isEmpty ||
|
||||||
];
|
appId.contains(query) ||
|
||||||
|
appName.contains(query) ||
|
||||||
|
appDesc.contains(query)) {
|
||||||
|
results['$url?appId=$appId'] = [appName, appDesc];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -117,22 +117,23 @@ class GitHub extends AppSource {
|
|||||||
.decode(body['content'].toString().split('\n').join('')))
|
.decode(body['content'].toString().split('\n').join('')))
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((e) => e.trim());
|
.map((e) => e.trim());
|
||||||
var appId = trimmedLines
|
var appIds = trimmedLines.where((l) =>
|
||||||
.where((l) =>
|
l.startsWith('applicationId "') ||
|
||||||
l.startsWith('applicationId "') ||
|
l.startsWith('applicationId \''));
|
||||||
l.startsWith('applicationId \''))
|
appIds = appIds.map((appId) => appId
|
||||||
.first;
|
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1]);
|
||||||
appId = appId
|
appIds = appIds.map((appId) {
|
||||||
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1];
|
if (appId.startsWith('\${') && appId.endsWith('}')) {
|
||||||
if (appId.startsWith('\${') && appId.endsWith('}')) {
|
appId = trimmedLines
|
||||||
appId = trimmedLines
|
.where((l) => l.startsWith(
|
||||||
.where((l) => l.startsWith(
|
'def ${appId.substring(2, appId.length - 1)}'))
|
||||||
'def ${appId.substring(2, appId.length - 1)}'))
|
.first;
|
||||||
.first;
|
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
|
||||||
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
|
}
|
||||||
}
|
|
||||||
if (appId.isNotEmpty) {
|
|
||||||
return appId;
|
return appId;
|
||||||
|
}).where((appId) => appId.isNotEmpty);
|
||||||
|
if (appIds.length == 1) {
|
||||||
|
return appIds.first;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
LogsProvider().add(
|
LogsProvider().add(
|
||||||
@@ -233,7 +234,7 @@ class GitHub extends AppSource {
|
|||||||
bool verifyLatestTag = additionalSettings['verifyLatestTag'] == true;
|
bool verifyLatestTag = additionalSettings['verifyLatestTag'] == true;
|
||||||
bool dontSortReleasesList =
|
bool dontSortReleasesList =
|
||||||
additionalSettings['dontSortReleasesList'] == true;
|
additionalSettings['dontSortReleasesList'] == true;
|
||||||
String? latestTag;
|
dynamic latestRelease;
|
||||||
if (verifyLatestTag) {
|
if (verifyLatestTag) {
|
||||||
var temp = requestUrl.split('?');
|
var temp = requestUrl.split('?');
|
||||||
Response res = await sourceRequest(
|
Response res = await sourceRequest(
|
||||||
@@ -244,12 +245,20 @@ class GitHub extends AppSource {
|
|||||||
}
|
}
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
var jsres = jsonDecode(res.body);
|
latestRelease = jsonDecode(res.body);
|
||||||
latestTag = jsres['tag_name'] ?? jsres['name'];
|
|
||||||
}
|
}
|
||||||
Response res = await sourceRequest(requestUrl);
|
Response res = await sourceRequest(requestUrl);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||||
|
if (latestRelease != null) {
|
||||||
|
var latestTag = latestRelease['tag_name'] ?? latestRelease['name'];
|
||||||
|
if (releases
|
||||||
|
.where((element) =>
|
||||||
|
(element['tag_name'] ?? element['name']) == latestTag)
|
||||||
|
.isEmpty) {
|
||||||
|
releases = [latestRelease, ...releases];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
|
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
|
||||||
(release['assets'] as List<dynamic>?)
|
(release['assets'] as List<dynamic>?)
|
||||||
@@ -298,13 +307,13 @@ class GitHub extends AppSource {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (latestTag != null &&
|
if (latestRelease != null &&
|
||||||
releases.isNotEmpty &&
|
releases.isNotEmpty &&
|
||||||
latestTag !=
|
latestRelease !=
|
||||||
(releases[releases.length - 1]['tag_name'] ??
|
(releases[releases.length - 1]['tag_name'] ??
|
||||||
releases[0]['name'])) {
|
releases[0]['name'])) {
|
||||||
var ind = releases.indexWhere(
|
var ind = releases.indexWhere((element) =>
|
||||||
(element) => latestTag == (element['tag_name'] ?? element['name']));
|
latestRelease == (element['tag_name'] ?? element['name']));
|
||||||
if (ind >= 0) {
|
if (ind >= 0) {
|
||||||
releases.add(releases.removeAt(ind));
|
releases.add(releases.removeAt(ind));
|
||||||
}
|
}
|
||||||
|
@@ -80,12 +80,8 @@ class GitLab extends AppSource {
|
|||||||
@override
|
@override
|
||||||
Future<Map<String, List<String>>> search(String query,
|
Future<Map<String, List<String>>> search(String query,
|
||||||
{Map<String, dynamic> querySettings = const {}}) async {
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
String? PAT = await getPATIfAny({});
|
|
||||||
if (PAT == null) {
|
|
||||||
throw CredsNeededError(name);
|
|
||||||
}
|
|
||||||
var url =
|
var url =
|
||||||
'https://$host/api/v4/search?private_token=$PAT&scope=projects&search=${Uri.encodeQueryComponent(query)}';
|
'https://$host/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
|
||||||
var res = await sourceRequest(url);
|
var res = await sourceRequest(url);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
@@ -174,7 +170,6 @@ class GitLab extends AppSource {
|
|||||||
...getLinksFromParsedHTML(entryContent,
|
...getLinksFromParsedHTML(entryContent,
|
||||||
RegExp('/[^/]+\\.apk\$', caseSensitive: false), '')
|
RegExp('/[^/]+\\.apk\$', caseSensitive: false), '')
|
||||||
.where((element) => Uri.parse(element).host != '')
|
.where((element) => Uri.parse(element).host != '')
|
||||||
.toList()
|
|
||||||
];
|
];
|
||||||
var entryId = entry.querySelector('id')?.innerHtml;
|
var entryId = entry.querySelector('id')?.innerHtml;
|
||||||
var version =
|
var version =
|
||||||
@@ -192,7 +187,7 @@ class GitLab extends AppSource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (apkDetailsList.isEmpty) {
|
if (apkDetailsList.isEmpty) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError(note: tr('gitlabSourceNote'));
|
||||||
}
|
}
|
||||||
if (fallbackToOlderReleases) {
|
if (fallbackToOlderReleases) {
|
||||||
if (additionalSettings['trackOnly'] != true) {
|
if (additionalSettings['trackOnly'] != true) {
|
||||||
@@ -200,7 +195,7 @@ class GitLab extends AppSource {
|
|||||||
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
|
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
|
||||||
}
|
}
|
||||||
if (apkDetailsList.isEmpty) {
|
if (apkDetailsList.isEmpty) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError(note: tr('gitlabSourceNote'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return apkDetailsList.first;
|
return apkDetailsList.first;
|
||||||
|
@@ -4,6 +4,7 @@ import 'package:html/parser.dart';
|
|||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:obtainium/components/generated_form.dart';
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
||||||
@@ -87,60 +88,80 @@ bool _isNumeric(String s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HTML extends AppSource {
|
class HTML extends AppSource {
|
||||||
|
var finalStepFormitems = [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('customLinkFilterRegex',
|
||||||
|
label: tr('customLinkFilterRegex'),
|
||||||
|
hint: 'download/(.*/)?(android|apk|mobile)',
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
return regExValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('versionExtractionRegEx',
|
||||||
|
label: tr('versionExtractionRegEx'),
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [(value) => regExValidator(value)]),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('matchGroupToUse',
|
||||||
|
label: tr('matchGroupToUse'),
|
||||||
|
required: false,
|
||||||
|
hint: '0',
|
||||||
|
textInputType: const TextInputType.numberWithOptions(),
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
if (value?.isEmpty == true) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
value ??= '0';
|
||||||
|
return intValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('versionExtractWholePage',
|
||||||
|
label: tr('versionExtractWholePage'))
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('supportFixedAPKURL',
|
||||||
|
defaultValue: true, label: tr('supportFixedAPKURL')),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
var commonFormItems = [
|
||||||
|
[GeneratedFormSwitch('filterByLinkText', label: tr('filterByLinkText'))],
|
||||||
|
[GeneratedFormSwitch('skipSort', label: tr('skipSort'))],
|
||||||
|
[GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('sortByLastLinkSegment',
|
||||||
|
label: tr('sortByLastLinkSegment'))
|
||||||
|
],
|
||||||
|
];
|
||||||
|
var intermediateFormItems = [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('customLinkFilterRegex',
|
||||||
|
label: tr('intermediateLinkRegex'),
|
||||||
|
hint: '([0-9]+.)*[0-9]+/\$',
|
||||||
|
required: true,
|
||||||
|
additionalValidators: [(value) => regExValidator(value)])
|
||||||
|
],
|
||||||
|
];
|
||||||
HTML() {
|
HTML() {
|
||||||
additionalSourceAppSpecificSettingFormItems = [
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
[
|
[
|
||||||
GeneratedFormSwitch('sortByFileNamesNotLinks',
|
GeneratedFormSubForm(
|
||||||
label: tr('sortByFileNamesNotLinks'))
|
'intermediateLink', [...intermediateFormItems, ...commonFormItems],
|
||||||
|
label: tr('intermediateLink'))
|
||||||
],
|
],
|
||||||
[GeneratedFormSwitch('reverseSort', label: tr('reverseSort'))],
|
finalStepFormitems[0],
|
||||||
[
|
...commonFormItems,
|
||||||
GeneratedFormTextField('customLinkFilterRegex',
|
...finalStepFormitems.sublist(1)
|
||||||
label: tr('customLinkFilterRegex'),
|
|
||||||
hint: 'download/(.*/)?(android|apk|mobile)',
|
|
||||||
required: false,
|
|
||||||
additionalValidators: [
|
|
||||||
(value) {
|
|
||||||
return regExValidator(value);
|
|
||||||
}
|
|
||||||
])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('intermediateLinkRegex',
|
|
||||||
label: tr('intermediateLinkRegex'),
|
|
||||||
hint: '([0-9]+.)*[0-9]+/\$',
|
|
||||||
required: false,
|
|
||||||
additionalValidators: [(value) => regExValidator(value)])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('versionExtractionRegEx',
|
|
||||||
label: tr('versionExtractionRegEx'),
|
|
||||||
required: false,
|
|
||||||
additionalValidators: [(value) => regExValidator(value)]),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('matchGroupToUse',
|
|
||||||
label: tr('matchGroupToUse'),
|
|
||||||
required: false,
|
|
||||||
hint: '0',
|
|
||||||
textInputType: const TextInputType.numberWithOptions(),
|
|
||||||
additionalValidators: [
|
|
||||||
(value) {
|
|
||||||
if (value?.isEmpty == true) {
|
|
||||||
value = null;
|
|
||||||
}
|
|
||||||
value ??= '0';
|
|
||||||
return intValidator(value);
|
|
||||||
}
|
|
||||||
])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormSwitch('versionExtractWholePage',
|
|
||||||
label: tr('versionExtractWholePage'))
|
|
||||||
]
|
|
||||||
];
|
];
|
||||||
overrideVersionDetectionFormDefault('noVersionDetection',
|
overrideVersionDetectionFormDefault('noVersionDetection',
|
||||||
disableStandard: true, disableRelDate: true);
|
disableStandard: false, disableRelDate: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -158,97 +179,120 @@ class HTML extends AppSource {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given an HTTP response, grab some links according to the common additional settings
|
||||||
|
// (those that apply to intermediate and final steps)
|
||||||
|
Future<List<MapEntry<String, String>>> grabLinksCommon(
|
||||||
|
Response res, Map<String, dynamic> additionalSettings) async {
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var html = parse(res.body);
|
||||||
|
List<MapEntry<String, String>> allLinks = html
|
||||||
|
.querySelectorAll('a')
|
||||||
|
.map((element) => MapEntry(
|
||||||
|
element.attributes['href'] ?? '',
|
||||||
|
element.text.isNotEmpty
|
||||||
|
? element.text
|
||||||
|
: (element.attributes['href'] ?? '').split('/').last))
|
||||||
|
.where((element) => element.key.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
if (allLinks.isEmpty) {
|
||||||
|
allLinks = RegExp(
|
||||||
|
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
|
||||||
|
.allMatches(res.body)
|
||||||
|
.map((match) =>
|
||||||
|
MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? ''))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
List<MapEntry<String, String>> links = [];
|
||||||
|
bool skipSort = additionalSettings['skipSort'] == true;
|
||||||
|
bool filterLinkByText = additionalSettings['filterByLinkText'] == true;
|
||||||
|
if ((additionalSettings['customLinkFilterRegex'] as String?)?.isNotEmpty ==
|
||||||
|
true) {
|
||||||
|
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
|
||||||
|
links = allLinks
|
||||||
|
.where((element) =>
|
||||||
|
reg.hasMatch(filterLinkByText ? element.value : element.key))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
links = allLinks
|
||||||
|
.where((element) =>
|
||||||
|
Uri.parse(filterLinkByText ? element.value : element.key)
|
||||||
|
.path
|
||||||
|
.toLowerCase()
|
||||||
|
.endsWith('.apk'))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
if (!skipSort) {
|
||||||
|
links.sort((a, b) => additionalSettings['sortByLastLinkSegment'] == true
|
||||||
|
? compareAlphaNumeric(
|
||||||
|
a.key.split('/').where((e) => e.isNotEmpty).last,
|
||||||
|
b.key.split('/').where((e) => e.isNotEmpty).last)
|
||||||
|
: compareAlphaNumeric(a.key, b.key));
|
||||||
|
}
|
||||||
|
if (additionalSettings['reverseSort'] == true) {
|
||||||
|
links = links.reversed.toList();
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
var uri = Uri.parse(standardUrl);
|
var currentUrl = standardUrl;
|
||||||
Response res = await sourceRequest(standardUrl);
|
for (int i = 0;
|
||||||
if (res.statusCode == 200) {
|
i < (additionalSettings['intermediateLink']?.length ?? 0);
|
||||||
var html = parse(res.body);
|
i++) {
|
||||||
List<String> allLinks = html
|
var intLinks = await grabLinksCommon(await sourceRequest(currentUrl),
|
||||||
.querySelectorAll('a')
|
additionalSettings['intermediateLink'][i]);
|
||||||
.map((element) => element.attributes['href'] ?? '')
|
if (intLinks.isEmpty) {
|
||||||
.where((element) => element.isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
if (allLinks.isEmpty) {
|
|
||||||
allLinks = RegExp(
|
|
||||||
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
|
|
||||||
.allMatches(res.body)
|
|
||||||
.map((match) => match.group(0)!)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
List<String> links = [];
|
|
||||||
if ((additionalSettings['intermediateLinkRegex'] as String?)
|
|
||||||
?.isNotEmpty ==
|
|
||||||
true) {
|
|
||||||
var reg = RegExp(additionalSettings['intermediateLinkRegex']);
|
|
||||||
links = allLinks.where((element) => reg.hasMatch(element)).toList();
|
|
||||||
links.sort((a, b) => compareAlphaNumeric(a, b));
|
|
||||||
if (links.isEmpty) {
|
|
||||||
throw ObtainiumError(tr('intermediateLinkNotFound'));
|
|
||||||
}
|
|
||||||
Map<String, dynamic> additionalSettingsTemp =
|
|
||||||
Map.from(additionalSettings);
|
|
||||||
additionalSettingsTemp['intermediateLinkRegex'] = null;
|
|
||||||
return getLatestAPKDetails(
|
|
||||||
ensureAbsoluteUrl(links.last, uri), additionalSettingsTemp);
|
|
||||||
}
|
|
||||||
if ((additionalSettings['customLinkFilterRegex'] as String?)
|
|
||||||
?.isNotEmpty ==
|
|
||||||
true) {
|
|
||||||
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
|
|
||||||
links = allLinks.where((element) => reg.hasMatch(element)).toList();
|
|
||||||
} else {
|
|
||||||
links = allLinks
|
|
||||||
.where((element) =>
|
|
||||||
Uri.parse(element).path.toLowerCase().endsWith('.apk'))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
links.sort((a, b) => additionalSettings['sortByFileNamesNotLinks'] == true
|
|
||||||
? compareAlphaNumeric(a.split('/').where((e) => e.isNotEmpty).last,
|
|
||||||
b.split('/').where((e) => e.isNotEmpty).last)
|
|
||||||
: compareAlphaNumeric(a, b));
|
|
||||||
if (additionalSettings['reverseSort'] == true) {
|
|
||||||
links = links.reversed.toList();
|
|
||||||
}
|
|
||||||
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty ==
|
|
||||||
true) {
|
|
||||||
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
|
||||||
links = links.where((element) => reg.hasMatch(element)).toList();
|
|
||||||
}
|
|
||||||
if (links.isEmpty) {
|
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
|
} else {
|
||||||
|
currentUrl = intLinks.last.key;
|
||||||
}
|
}
|
||||||
var rel = links.last;
|
|
||||||
String? version = rel.hashCode.toString();
|
|
||||||
var versionExtractionRegEx =
|
|
||||||
additionalSettings['versionExtractionRegEx'] as String?;
|
|
||||||
if (versionExtractionRegEx?.isNotEmpty == true) {
|
|
||||||
var match = RegExp(versionExtractionRegEx!).allMatches(
|
|
||||||
additionalSettings['versionExtractWholePage'] == true
|
|
||||||
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
|
|
||||||
: rel);
|
|
||||||
if (match.isEmpty) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
String matchGroupString =
|
|
||||||
(additionalSettings['matchGroupToUse'] as String).trim();
|
|
||||||
if (matchGroupString.isEmpty) {
|
|
||||||
matchGroupString = "0";
|
|
||||||
}
|
|
||||||
version = match.last.group(int.parse(matchGroupString));
|
|
||||||
if (version?.isEmpty == true) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<String> apkUrls =
|
|
||||||
[rel].map((e) => ensureAbsoluteUrl(e, uri)).toList();
|
|
||||||
return APKDetails(version!, apkUrls.map((e) => MapEntry(e, e)).toList(),
|
|
||||||
AppNames(uri.host, tr('app')));
|
|
||||||
} else {
|
|
||||||
throw getObtainiumHttpError(res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uri = Uri.parse(currentUrl);
|
||||||
|
Response res = await sourceRequest(currentUrl);
|
||||||
|
var links = await grabLinksCommon(res, additionalSettings);
|
||||||
|
|
||||||
|
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) {
|
||||||
|
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
||||||
|
links = links.where((element) => reg.hasMatch(element.key)).toList();
|
||||||
|
}
|
||||||
|
if (links.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
var rel = links.last.key;
|
||||||
|
String? version;
|
||||||
|
if (additionalSettings['supportFixedAPKURL'] != true) {
|
||||||
|
version = rel.hashCode.toString();
|
||||||
|
}
|
||||||
|
var versionExtractionRegEx =
|
||||||
|
additionalSettings['versionExtractionRegEx'] as String?;
|
||||||
|
if (versionExtractionRegEx?.isNotEmpty == true) {
|
||||||
|
var match = RegExp(versionExtractionRegEx!).allMatches(
|
||||||
|
additionalSettings['versionExtractWholePage'] == true
|
||||||
|
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
|
||||||
|
: rel);
|
||||||
|
if (match.isEmpty) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
String matchGroupString =
|
||||||
|
(additionalSettings['matchGroupToUse'] as String).trim();
|
||||||
|
if (matchGroupString.isEmpty) {
|
||||||
|
matchGroupString = "0";
|
||||||
|
}
|
||||||
|
version = match.last.group(int.parse(matchGroupString));
|
||||||
|
if (version?.isEmpty == true) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rel = ensureAbsoluteUrl(rel, uri);
|
||||||
|
version ??= (await checkDownloadHash(rel)).toString();
|
||||||
|
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||||
|
AppNames(uri.host, tr('app')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,7 @@ class IzzyOnDroid extends AppSource {
|
|||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
String? appId = await tryInferringAppId(standardUrl);
|
String? appId = await tryInferringAppId(standardUrl);
|
||||||
return getAPKUrlsFromFDroidPackagesAPIResponse(
|
return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await sourceRequest(
|
await sourceRequest(
|
||||||
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
|
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
|
||||||
'https://android.izzysoft.de/frepo/$appId',
|
'https://android.izzysoft.de/frepo/$appId',
|
||||||
|
@@ -4,6 +4,7 @@ import 'package:hsluv/hsluv.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:obtainium/components/generated_form_modal.dart';
|
import 'package:obtainium/components/generated_form_modal.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
abstract class GeneratedFormItem {
|
abstract class GeneratedFormItem {
|
||||||
late String key;
|
late String key;
|
||||||
@@ -27,21 +28,17 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
|||||||
late bool password;
|
late bool password;
|
||||||
late TextInputType? textInputType;
|
late TextInputType? textInputType;
|
||||||
|
|
||||||
GeneratedFormTextField(String key,
|
GeneratedFormTextField(super.key,
|
||||||
{String label = 'Input',
|
{super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
String defaultValue = '',
|
String super.defaultValue = '',
|
||||||
List<String? Function(String? value)> additionalValidators = const [],
|
List<String? Function(String? value)> super.additionalValidators =
|
||||||
|
const [],
|
||||||
this.required = true,
|
this.required = true,
|
||||||
this.max = 1,
|
this.max = 1,
|
||||||
this.hint,
|
this.hint,
|
||||||
this.password = false,
|
this.password = false,
|
||||||
this.textInputType})
|
this.textInputType});
|
||||||
: super(key,
|
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String ensureType(val) {
|
String ensureType(val) {
|
||||||
@@ -54,18 +51,14 @@ class GeneratedFormDropdown extends GeneratedFormItem {
|
|||||||
List<String>? disabledOptKeys;
|
List<String>? disabledOptKeys;
|
||||||
|
|
||||||
GeneratedFormDropdown(
|
GeneratedFormDropdown(
|
||||||
String key,
|
super.key,
|
||||||
this.opts, {
|
this.opts, {
|
||||||
String label = 'Input',
|
super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
String defaultValue = '',
|
String super.defaultValue = '',
|
||||||
this.disabledOptKeys,
|
this.disabledOptKeys,
|
||||||
List<String? Function(String? value)> additionalValidators = const [],
|
List<String? Function(String? value)> super.additionalValidators = const [],
|
||||||
}) : super(key,
|
});
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String ensureType(val) {
|
String ensureType(val) {
|
||||||
@@ -75,16 +68,12 @@ class GeneratedFormDropdown extends GeneratedFormItem {
|
|||||||
|
|
||||||
class GeneratedFormSwitch extends GeneratedFormItem {
|
class GeneratedFormSwitch extends GeneratedFormItem {
|
||||||
GeneratedFormSwitch(
|
GeneratedFormSwitch(
|
||||||
String key, {
|
super.key, {
|
||||||
String label = 'Input',
|
super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
bool defaultValue = false,
|
bool super.defaultValue = false,
|
||||||
List<String? Function(bool value)> additionalValidators = const [],
|
List<String? Function(bool value)> super.additionalValidators = const [],
|
||||||
}) : super(key,
|
});
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool ensureType(val) {
|
bool ensureType(val) {
|
||||||
@@ -98,22 +87,17 @@ class GeneratedFormTagInput extends GeneratedFormItem {
|
|||||||
late WrapAlignment alignment;
|
late WrapAlignment alignment;
|
||||||
late String emptyMessage;
|
late String emptyMessage;
|
||||||
late bool showLabelWhenNotEmpty;
|
late bool showLabelWhenNotEmpty;
|
||||||
GeneratedFormTagInput(String key,
|
GeneratedFormTagInput(super.key,
|
||||||
{String label = 'Input',
|
{super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
Map<String, MapEntry<int, bool>> defaultValue = const {},
|
Map<String, MapEntry<int, bool>> super.defaultValue = const {},
|
||||||
List<String? Function(Map<String, MapEntry<int, bool>> value)>
|
List<String? Function(Map<String, MapEntry<int, bool>> value)>
|
||||||
additionalValidators = const [],
|
super.additionalValidators = const [],
|
||||||
this.deleteConfirmationMessage,
|
this.deleteConfirmationMessage,
|
||||||
this.singleSelect = false,
|
this.singleSelect = false,
|
||||||
this.alignment = WrapAlignment.start,
|
this.alignment = WrapAlignment.start,
|
||||||
this.emptyMessage = 'Input',
|
this.emptyMessage = 'Input',
|
||||||
this.showLabelWhenNotEmpty = true})
|
this.showLabelWhenNotEmpty = true});
|
||||||
: super(key,
|
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, MapEntry<int, bool>> ensureType(val) {
|
Map<String, MapEntry<int, bool>> ensureType(val) {
|
||||||
@@ -135,6 +119,18 @@ class GeneratedForm extends StatefulWidget {
|
|||||||
State<GeneratedForm> createState() => _GeneratedFormState();
|
State<GeneratedForm> createState() => _GeneratedFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GeneratedFormSubForm extends GeneratedFormItem {
|
||||||
|
final List<List<GeneratedFormItem>> items;
|
||||||
|
|
||||||
|
GeneratedFormSubForm(super.key, this.items,
|
||||||
|
{super.label, super.belowWidgets, super.defaultValue});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ensureType(val) {
|
||||||
|
return val; // Not easy to validate List<Map<String, dynamic>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generates a color in the HSLuv (Pastel) color space
|
// Generates a color in the HSLuv (Pastel) color space
|
||||||
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
|
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
|
||||||
Color generateRandomLightColor() {
|
Color generateRandomLightColor() {
|
||||||
@@ -151,28 +147,39 @@ Color generateRandomLightColor() {
|
|||||||
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
|
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int generateRandomNumber(int seed1,
|
||||||
|
{int seed2 = 0, int seed3 = 0, max = 10000}) {
|
||||||
|
int combinedSeed = seed1.hashCode ^ seed2.hashCode ^ seed3.hashCode;
|
||||||
|
Random random = Random(combinedSeed);
|
||||||
|
int randomNumber = random.nextInt(max);
|
||||||
|
return randomNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateTextField(TextFormField tf) =>
|
||||||
|
(tf.key as GlobalKey<FormFieldState>).currentState?.isValid == true;
|
||||||
|
|
||||||
class _GeneratedFormState extends State<GeneratedForm> {
|
class _GeneratedFormState extends State<GeneratedForm> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
Map<String, dynamic> values = {};
|
Map<String, dynamic> values = {};
|
||||||
late List<List<Widget>> formInputs;
|
late List<List<Widget>> formInputs;
|
||||||
List<List<Widget>> rows = [];
|
List<List<Widget>> rows = [];
|
||||||
String? initKey;
|
String? initKey;
|
||||||
|
int forceUpdateKeyCount = 0;
|
||||||
|
|
||||||
// If any value changes, call this to update the parent with value and validity
|
// If any value changes, call this to update the parent with value and validity
|
||||||
void someValueChanged({bool isBuilding = false}) {
|
void someValueChanged({bool isBuilding = false, bool forceInvalid = false}) {
|
||||||
Map<String, dynamic> returnValues = values;
|
Map<String, dynamic> returnValues = values;
|
||||||
var valid = true;
|
var valid = true;
|
||||||
for (int r = 0; r < widget.items.length; r++) {
|
for (int r = 0; r < widget.items.length; r++) {
|
||||||
for (int i = 0; i < widget.items[r].length; i++) {
|
for (int i = 0; i < widget.items[r].length; i++) {
|
||||||
if (formInputs[r][i] is TextFormField) {
|
if (formInputs[r][i] is TextFormField) {
|
||||||
var fieldState =
|
valid = valid && validateTextField(formInputs[r][i] as TextFormField);
|
||||||
(formInputs[r][i].key as GlobalKey<FormFieldState>).currentState;
|
|
||||||
if (fieldState != null) {
|
|
||||||
valid = valid && fieldState.isValid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (forceInvalid) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
widget.onValueChanges(returnValues, valid, isBuilding);
|
widget.onValueChanges(returnValues, valid, isBuilding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +254,17 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else if (formItem is GeneratedFormSubForm) {
|
||||||
|
values[formItem.key] = [];
|
||||||
|
for (Map<String, dynamic> v
|
||||||
|
in ((formItem.defaultValue ?? []) as List<dynamic>)) {
|
||||||
|
var fullDefaults = getDefaultValuesFromFormItems(formItem.items);
|
||||||
|
for (var element in v.entries) {
|
||||||
|
fullDefaults[element.key] = element.value;
|
||||||
|
}
|
||||||
|
values[formItem.key].add(fullDefaults);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
} else {
|
} else {
|
||||||
return Container(); // Some input types added in build
|
return Container(); // Some input types added in build
|
||||||
}
|
}
|
||||||
@@ -268,6 +286,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
}
|
}
|
||||||
for (var r = 0; r < formInputs.length; r++) {
|
for (var r = 0; r < formInputs.length; r++) {
|
||||||
for (var e = 0; e < formInputs[r].length; e++) {
|
for (var e = 0; e < formInputs[r].length; e++) {
|
||||||
|
String fieldKey = widget.items[r][e].key;
|
||||||
if (widget.items[r][e] is GeneratedFormSwitch) {
|
if (widget.items[r][e] is GeneratedFormSwitch) {
|
||||||
formInputs[r][e] = Row(
|
formInputs[r][e] = Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -277,10 +296,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
Switch(
|
Switch(
|
||||||
value: values[widget.items[r][e].key],
|
value: values[fieldKey],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
values[widget.items[r][e].key] = value;
|
values[fieldKey] = value;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -289,8 +308,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
} else if (widget.items[r][e] is GeneratedFormTagInput) {
|
} else if (widget.items[r][e] is GeneratedFormTagInput) {
|
||||||
formInputs[r][e] =
|
formInputs[r][e] =
|
||||||
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
if ((values[widget.items[r][e].key]
|
if ((values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.isNotEmpty ==
|
?.isNotEmpty ==
|
||||||
true &&
|
true &&
|
||||||
(widget.items[r][e] as GeneratedFormTagInput)
|
(widget.items[r][e] as GeneratedFormTagInput)
|
||||||
@@ -313,8 +331,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
(widget.items[r][e] as GeneratedFormTagInput).alignment,
|
(widget.items[r][e] as GeneratedFormTagInput).alignment,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.isEmpty ==
|
?.isEmpty ==
|
||||||
true
|
true
|
||||||
? Text(
|
? Text(
|
||||||
@@ -322,8 +339,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
.emptyMessage,
|
.emptyMessage,
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
...(values[widget.items[r][e].key]
|
...(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.entries
|
?.entries
|
||||||
.map((e2) {
|
.map((e2) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -336,11 +352,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
selected: e2.value.value,
|
selected: e2.value.value,
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
(values[widget.items[r][e].key] as Map<String,
|
(values[fieldKey] as Map<String,
|
||||||
MapEntry<int, bool>>)[e2.key] =
|
MapEntry<int, bool>>)[e2.key] =
|
||||||
MapEntry(
|
MapEntry(
|
||||||
(values[widget.items[r][e].key] as Map<
|
(values[fieldKey] as Map<String,
|
||||||
String,
|
|
||||||
MapEntry<int, bool>>)[e2.key]!
|
MapEntry<int, bool>>)[e2.key]!
|
||||||
.key,
|
.key,
|
||||||
value);
|
value);
|
||||||
@@ -348,22 +363,18 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
as GeneratedFormTagInput)
|
as GeneratedFormTagInput)
|
||||||
.singleSelect &&
|
.singleSelect &&
|
||||||
value == true) {
|
value == true) {
|
||||||
for (var key in (values[
|
for (var key in (values[fieldKey]
|
||||||
widget.items[r][e].key]
|
|
||||||
as Map<String, MapEntry<int, bool>>)
|
as Map<String, MapEntry<int, bool>>)
|
||||||
.keys) {
|
.keys) {
|
||||||
if (key != e2.key) {
|
if (key != e2.key) {
|
||||||
(values[widget.items[r][e].key] as Map<
|
(values[fieldKey] as Map<
|
||||||
String,
|
String,
|
||||||
MapEntry<int, bool>>)[key] =
|
MapEntry<int,
|
||||||
MapEntry(
|
bool>>)[key] = MapEntry(
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String,
|
||||||
as Map<
|
MapEntry<int, bool>>)[key]!
|
||||||
String,
|
.key,
|
||||||
MapEntry<int,
|
false);
|
||||||
bool>>)[key]!
|
|
||||||
.key,
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,8 +384,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
));
|
));
|
||||||
}) ??
|
}) ??
|
||||||
[const SizedBox.shrink()],
|
[const SizedBox.shrink()],
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.values
|
?.values
|
||||||
.where((e) => e.value)
|
.where((e) => e.value)
|
||||||
.length ==
|
.length ==
|
||||||
@@ -384,7 +394,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>;
|
as Map<String, MapEntry<int, bool>>;
|
||||||
// get selected category str where bool is true
|
// get selected category str where bool is true
|
||||||
final oldEntry = temp.entries
|
final oldEntry = temp.entries
|
||||||
@@ -397,7 +407,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
// Update entry with new color, remain selected
|
// Update entry with new color, remain selected
|
||||||
temp.update(oldEntry.key,
|
temp.update(oldEntry.key,
|
||||||
(old) => MapEntry(newColor, old.value));
|
(old) => MapEntry(newColor, old.value));
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -406,8 +416,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
tooltip: tr('colour'),
|
tooltip: tr('colour'),
|
||||||
))
|
))
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.values
|
?.values
|
||||||
.where((e) => e.value)
|
.where((e) => e.value)
|
||||||
.isNotEmpty ==
|
.isNotEmpty ==
|
||||||
@@ -418,10 +427,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
fn() {
|
fn() {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>;
|
as Map<String, MapEntry<int, bool>>;
|
||||||
temp.removeWhere((key, value) => value.value);
|
temp.removeWhere((key, value) => value.value);
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -472,7 +481,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
String? label = value?['label'];
|
String? label = value?['label'];
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>?;
|
as Map<String, MapEntry<int, bool>>?;
|
||||||
temp ??= {};
|
temp ??= {};
|
||||||
if (temp[label] == null) {
|
if (temp[label] == null) {
|
||||||
@@ -485,7 +494,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
temp[label] = MapEntry(
|
temp[label] = MapEntry(
|
||||||
generateRandomLightColor().value,
|
generateRandomLightColor().value,
|
||||||
!(someSelected && singleSelect));
|
!(someSelected && singleSelect));
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -499,6 +508,93 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
} else if (widget.items[r][e] is GeneratedFormSubForm) {
|
||||||
|
List<Widget> subformColumn = [];
|
||||||
|
for (int i = 0; i < values[fieldKey].length; i++) {
|
||||||
|
var items = (widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.items
|
||||||
|
.map((x) => x.map((y) {
|
||||||
|
y.defaultValue = values[fieldKey]?[i]?[y.key];
|
||||||
|
return y;
|
||||||
|
}).toList())
|
||||||
|
.toList();
|
||||||
|
var internalFormKey = ValueKey(generateRandomNumber(
|
||||||
|
values[fieldKey].length,
|
||||||
|
seed2: i,
|
||||||
|
seed3: forceUpdateKeyCount));
|
||||||
|
subformColumn.add(Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
GeneratedForm(
|
||||||
|
key: internalFormKey,
|
||||||
|
items: items,
|
||||||
|
onValueChanges: (values, valid, isBuilding) {
|
||||||
|
if (valid) {
|
||||||
|
this.values[fieldKey]?[i] = values;
|
||||||
|
}
|
||||||
|
someValueChanged(
|
||||||
|
isBuilding: isBuilding, forceInvalid: !valid);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).colorScheme.error),
|
||||||
|
onPressed: (values[fieldKey].length > 0)
|
||||||
|
? () {
|
||||||
|
var temp = List.from(values[fieldKey]);
|
||||||
|
temp.removeAt(i);
|
||||||
|
values[fieldKey] = List.from(temp);
|
||||||
|
forceUpdateKeyCount++;
|
||||||
|
someValueChanged();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
label: Text(
|
||||||
|
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
||||||
|
),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete_outline_rounded,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
subformColumn.add(Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: values[fieldKey].length > 0 ? 24 : 0, top: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
values[fieldKey].add(getDefaultValuesFromFormItems(
|
||||||
|
(widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.items));
|
||||||
|
forceUpdateKeyCount++;
|
||||||
|
someValueChanged();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text((widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.label))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
if (values[fieldKey].length > 0) {
|
||||||
|
subformColumn.add(const Divider());
|
||||||
|
}
|
||||||
|
formInputs[r][e] = Column(children: subformColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,9 @@ class CredsNeededError extends ObtainiumError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NoReleasesError extends ObtainiumError {
|
class NoReleasesError extends ObtainiumError {
|
||||||
NoReleasesError() : super(tr('noReleaseFound'));
|
NoReleasesError({String? note})
|
||||||
|
: super(
|
||||||
|
'${tr('noReleaseFound')}${note?.isNotEmpty == true ? '\n\n$note' : ''}');
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoAPKError extends ObtainiumError {
|
class NoAPKError extends ObtainiumError {
|
||||||
|
103
lib/main.dart
103
lib/main.dart
@@ -12,14 +12,14 @@ import 'package:permission_handler/permission_handler.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
import 'package:background_fetch/background_fetch.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/easy_localization_controller.dart';
|
import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/localization.dart';
|
import 'package:easy_localization/src/localization.dart';
|
||||||
|
|
||||||
const String currentVersion = '0.14.32';
|
const String currentVersion = '0.15.2';
|
||||||
const String currentReleaseTag =
|
const String currentReleaseTag =
|
||||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||||
|
|
||||||
@@ -42,9 +42,12 @@ List<MapEntry<Locale, String>> supportedLocales = const [
|
|||||||
MapEntry(Locale('cs'), 'Česky'),
|
MapEntry(Locale('cs'), 'Česky'),
|
||||||
MapEntry(Locale('sv'), 'Svenska'),
|
MapEntry(Locale('sv'), 'Svenska'),
|
||||||
MapEntry(Locale('nl'), 'Nederlands'),
|
MapEntry(Locale('nl'), 'Nederlands'),
|
||||||
|
MapEntry(Locale('vi'), 'Tiếng Việt'),
|
||||||
|
MapEntry(Locale('tr'), 'Türkçe'),
|
||||||
];
|
];
|
||||||
const fallbackLocale = Locale('en');
|
const fallbackLocale = Locale('en');
|
||||||
const localeDir = 'assets/translations';
|
const localeDir = 'assets/translations';
|
||||||
|
var fdroid = false;
|
||||||
|
|
||||||
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@@ -73,6 +76,19 @@ Future<void> loadTranslations() async {
|
|||||||
fallbackTranslations: controller.fallbackTranslations);
|
fallbackTranslations: controller.fallbackTranslations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void backgroundFetchHeadlessTask(HeadlessTask task) async {
|
||||||
|
String taskId = task.taskId;
|
||||||
|
bool isTimeout = task.timeout;
|
||||||
|
if (isTimeout) {
|
||||||
|
print('BG update task timed out.');
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await bgUpdateCheck(taskId, null);
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
try {
|
try {
|
||||||
@@ -90,7 +106,6 @@ void main() async {
|
|||||||
);
|
);
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
}
|
}
|
||||||
await AndroidAlarmManager.initialize();
|
|
||||||
runApp(MultiProvider(
|
runApp(MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
||||||
@@ -105,6 +120,7 @@ void main() async {
|
|||||||
useOnlyLangCode: true,
|
useOnlyLangCode: true,
|
||||||
child: const Obtainium()),
|
child: const Obtainium()),
|
||||||
));
|
));
|
||||||
|
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultThemeColour = Colors.deepPurple;
|
var defaultThemeColour = Colors.deepPurple;
|
||||||
@@ -119,6 +135,33 @@ class Obtainium extends StatefulWidget {
|
|||||||
class _ObtainiumState extends State<Obtainium> {
|
class _ObtainiumState extends State<Obtainium> {
|
||||||
var existingUpdateInterval = -1;
|
var existingUpdateInterval = -1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initPlatformState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initPlatformState() async {
|
||||||
|
await BackgroundFetch.configure(
|
||||||
|
BackgroundFetchConfig(
|
||||||
|
minimumFetchInterval: 15,
|
||||||
|
stopOnTerminate: false,
|
||||||
|
enableHeadless: true,
|
||||||
|
requiresBatteryNotLow: false,
|
||||||
|
requiresCharging: false,
|
||||||
|
requiresStorageNotLow: false,
|
||||||
|
requiresDeviceIdle: false,
|
||||||
|
requiredNetworkType: NetworkType.ANY), (String taskId) async {
|
||||||
|
// We don't want periodic tasks in the foreground - ignore
|
||||||
|
await bgUpdateCheck(taskId, null);
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
}, (String taskId) async {
|
||||||
|
context.read<LogsProvider>().add('BG update task timed out.');
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
||||||
@@ -133,20 +176,22 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
logs.add('This is the first ever run of Obtainium.');
|
logs.add('This is the first ever run of Obtainium.');
|
||||||
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list
|
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list
|
||||||
Permission.notification.request();
|
Permission.notification.request();
|
||||||
appsProvider.saveApps([
|
if (!fdroid) {
|
||||||
App(
|
appsProvider.saveApps([
|
||||||
obtainiumId,
|
App(
|
||||||
'https://github.com/ImranR98/Obtainium',
|
obtainiumId,
|
||||||
'ImranR98',
|
'https://github.com/ImranR98/Obtainium',
|
||||||
'Obtainium',
|
'ImranR98',
|
||||||
currentReleaseTag,
|
'Obtainium',
|
||||||
currentReleaseTag,
|
currentReleaseTag,
|
||||||
[],
|
currentReleaseTag,
|
||||||
0,
|
[],
|
||||||
{'includePrereleases': true},
|
0,
|
||||||
null,
|
{'includePrereleases': true},
|
||||||
false)
|
null,
|
||||||
], onlyIfExists: false);
|
false)
|
||||||
|
], onlyIfExists: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!supportedLocales
|
if (!supportedLocales
|
||||||
.map((e) => e.key.languageCode)
|
.map((e) => e.key.languageCode)
|
||||||
@@ -156,30 +201,6 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
context.locale.languageCode)) {
|
context.locale.languageCode)) {
|
||||||
settingsProvider.resetLocaleSafe(context);
|
settingsProvider.resetLocaleSafe(context);
|
||||||
}
|
}
|
||||||
// Register the background update task according to the user's setting
|
|
||||||
var actualUpdateInterval = settingsProvider.updateInterval;
|
|
||||||
if (existingUpdateInterval != actualUpdateInterval) {
|
|
||||||
if (actualUpdateInterval == 0) {
|
|
||||||
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
|
|
||||||
} else {
|
|
||||||
var settingChanged = existingUpdateInterval != -1;
|
|
||||||
var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
|
|
||||||
settingsProvider.lastBGCheckTime
|
|
||||||
.add(Duration(minutes: actualUpdateInterval + 60))
|
|
||||||
.isBefore(DateTime.now());
|
|
||||||
if (settingChanged || lastCheckWasTooLongAgo) {
|
|
||||||
logs.add(
|
|
||||||
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
|
|
||||||
AndroidAlarmManager.periodic(
|
|
||||||
Duration(minutes: actualUpdateInterval),
|
|
||||||
bgUpdateCheckAlarmId,
|
|
||||||
bgUpdateCheck,
|
|
||||||
rescheduleOnReboot: true,
|
|
||||||
wakeup: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existingUpdateInterval = actualUpdateInterval;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
|
6
lib/main_fdroid.dart
Normal file
6
lib/main_fdroid.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import 'main.dart' as m;
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
m.fdroid = true;
|
||||||
|
m.main();
|
||||||
|
}
|
@@ -21,10 +21,10 @@ class AddAppPage extends StatefulWidget {
|
|||||||
const AddAppPage({super.key});
|
const AddAppPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AddAppPage> createState() => _AddAppPageState();
|
State<AddAppPage> createState() => AddAppPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddAppPageState extends State<AddAppPage> {
|
class AddAppPageState extends State<AddAppPage> {
|
||||||
bool gettingAppInfo = false;
|
bool gettingAppInfo = false;
|
||||||
bool searching = false;
|
bool searching = false;
|
||||||
|
|
||||||
@@ -36,9 +36,62 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
bool additionalSettingsValid = true;
|
bool additionalSettingsValid = true;
|
||||||
bool inferAppIdIfOptional = true;
|
bool inferAppIdIfOptional = true;
|
||||||
List<String> pickedCategories = [];
|
List<String> pickedCategories = [];
|
||||||
int searchnum = 0;
|
int urlInputKey = 0;
|
||||||
SourceProvider sourceProvider = SourceProvider();
|
SourceProvider sourceProvider = SourceProvider();
|
||||||
|
|
||||||
|
linkFn(String input) {
|
||||||
|
try {
|
||||||
|
if (input.isEmpty) {
|
||||||
|
throw UnsupportedURLError();
|
||||||
|
}
|
||||||
|
sourceProvider.getSource(input);
|
||||||
|
changeUserInput(input, true, false, updateUrlInput: true);
|
||||||
|
} catch (e) {
|
||||||
|
showError(e, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeUserInput(String input, bool valid, bool isBuilding,
|
||||||
|
{bool updateUrlInput = false}) {
|
||||||
|
userInput = input;
|
||||||
|
if (!isBuilding) {
|
||||||
|
setState(() {
|
||||||
|
if (updateUrlInput) {
|
||||||
|
urlInputKey++;
|
||||||
|
}
|
||||||
|
var prevHost = pickedSource?.host;
|
||||||
|
try {
|
||||||
|
var naturalSource =
|
||||||
|
valid ? sourceProvider.getSource(userInput) : null;
|
||||||
|
if (naturalSource != null &&
|
||||||
|
naturalSource.runtimeType.toString() !=
|
||||||
|
HTML().runtimeType.toString()) {
|
||||||
|
// If input has changed to match a regular source, reset the override
|
||||||
|
pickedSourceOverride = null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
var source = valid
|
||||||
|
? sourceProvider.getSource(userInput,
|
||||||
|
overrideSource: pickedSourceOverride)
|
||||||
|
: null;
|
||||||
|
if (pickedSource.runtimeType != source.runtimeType ||
|
||||||
|
(prevHost != null && prevHost != source?.host)) {
|
||||||
|
pickedSource = source;
|
||||||
|
additionalSettings = source != null
|
||||||
|
? getDefaultValuesFromFormItems(
|
||||||
|
source.combinedAppSpecificSettingFormItems)
|
||||||
|
: {};
|
||||||
|
additionalSettingsValid = source != null
|
||||||
|
? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
|
||||||
|
: true;
|
||||||
|
inferAppIdIfOptional = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
AppsProvider appsProvider = context.read<AppsProvider>();
|
AppsProvider appsProvider = context.read<AppsProvider>();
|
||||||
@@ -48,47 +101,6 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
|
|
||||||
bool doingSomething = gettingAppInfo || searching;
|
bool doingSomething = gettingAppInfo || searching;
|
||||||
|
|
||||||
changeUserInput(String input, bool valid, bool isBuilding,
|
|
||||||
{bool isSearch = false}) {
|
|
||||||
userInput = input;
|
|
||||||
if (!isBuilding) {
|
|
||||||
setState(() {
|
|
||||||
if (isSearch) {
|
|
||||||
searchnum++;
|
|
||||||
}
|
|
||||||
var prevHost = pickedSource?.host;
|
|
||||||
try {
|
|
||||||
var naturalSource =
|
|
||||||
valid ? sourceProvider.getSource(userInput) : null;
|
|
||||||
if (naturalSource != null &&
|
|
||||||
naturalSource.runtimeType.toString() !=
|
|
||||||
HTML().runtimeType.toString()) {
|
|
||||||
// If input has changed to match a regular source, reset the override
|
|
||||||
pickedSourceOverride = null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
var source = valid
|
|
||||||
? sourceProvider.getSource(userInput,
|
|
||||||
overrideSource: pickedSourceOverride)
|
|
||||||
: null;
|
|
||||||
if (pickedSource.runtimeType != source.runtimeType ||
|
|
||||||
(prevHost != null && prevHost != source?.host)) {
|
|
||||||
pickedSource = source;
|
|
||||||
additionalSettings = source != null
|
|
||||||
? getDefaultValuesFromFormItems(
|
|
||||||
source.combinedAppSpecificSettingFormItems)
|
|
||||||
: {};
|
|
||||||
additionalSettingsValid = source != null
|
|
||||||
? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
|
|
||||||
: true;
|
|
||||||
inferAppIdIfOptional = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> getTrackOnlyConfirmationIfNeeded(bool userPickedTrackOnly,
|
Future<bool> getTrackOnlyConfirmationIfNeeded(bool userPickedTrackOnly,
|
||||||
{bool ignoreHideSetting = false}) async {
|
{bool ignoreHideSetting = false}) async {
|
||||||
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
|
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
|
||||||
@@ -205,7 +217,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GeneratedForm(
|
child: GeneratedForm(
|
||||||
key: Key(searchnum.toString()),
|
key: Key(urlInputKey.toString()),
|
||||||
items: [
|
items: [
|
||||||
[
|
[
|
||||||
GeneratedFormTextField('appSourceURL',
|
GeneratedFormTextField('appSourceURL',
|
||||||
@@ -254,57 +266,79 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
runSearch() async {
|
runSearch({bool filtered = true}) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
searching = true;
|
searching = true;
|
||||||
});
|
});
|
||||||
|
var sourceStrings = <String, List<String>>{};
|
||||||
|
sourceProvider.sources
|
||||||
|
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
||||||
|
.forEach((s) {
|
||||||
|
sourceStrings[s.name] = [s.name];
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
var results = await Future.wait(sourceProvider.sources
|
var searchSources = await showDialog<List<String>?>(
|
||||||
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
|
||||||
.map((e) async {
|
|
||||||
try {
|
|
||||||
return await e.search(searchQuery);
|
|
||||||
} catch (err) {
|
|
||||||
if (err is! CredsNeededError) {
|
|
||||||
rethrow;
|
|
||||||
} else {
|
|
||||||
return <String, List<String>>{};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// .then((results) async {
|
|
||||||
// Interleave results instead of simple reduce
|
|
||||||
Map<String, List<String>> res = {};
|
|
||||||
var si = 0;
|
|
||||||
var done = false;
|
|
||||||
while (!done) {
|
|
||||||
done = true;
|
|
||||||
for (var r in results) {
|
|
||||||
if (r.length > si) {
|
|
||||||
done = false;
|
|
||||||
res.addEntries([r.entries.elementAt(si)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
si++;
|
|
||||||
}
|
|
||||||
if (res.isEmpty) {
|
|
||||||
throw ObtainiumError(tr('noResults'));
|
|
||||||
}
|
|
||||||
List<String>? selectedUrls = res.isEmpty
|
|
||||||
? []
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
: await showDialog<List<String>?>(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return UrlSelectionModal(
|
return SelectionModal(
|
||||||
urlsWithDescriptions: res,
|
title: tr('selectX', args: [plural('source', 2)]),
|
||||||
selectedByDefault: false,
|
entries: sourceStrings,
|
||||||
onlyOneSelectionAllowed: true,
|
selectedByDefault: true,
|
||||||
|
onlyOneSelectionAllowed: false,
|
||||||
|
titlesAreLinks: false,
|
||||||
);
|
);
|
||||||
});
|
}) ??
|
||||||
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
[];
|
||||||
changeUserInput(selectedUrls[0], true, false, isSearch: true);
|
if (searchSources.isNotEmpty) {
|
||||||
|
var results = await Future.wait(sourceProvider.sources
|
||||||
|
.where((e) => searchSources.contains(e.name))
|
||||||
|
.map((e) async {
|
||||||
|
try {
|
||||||
|
return await e.search(searchQuery);
|
||||||
|
} catch (err) {
|
||||||
|
if (err is! CredsNeededError) {
|
||||||
|
rethrow;
|
||||||
|
} else {
|
||||||
|
err.unexpected = true;
|
||||||
|
showError(err, context);
|
||||||
|
return <String, List<String>>{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// .then((results) async {
|
||||||
|
// Interleave results instead of simple reduce
|
||||||
|
Map<String, List<String>> res = {};
|
||||||
|
var si = 0;
|
||||||
|
var done = false;
|
||||||
|
while (!done) {
|
||||||
|
done = true;
|
||||||
|
for (var r in results) {
|
||||||
|
if (r.length > si) {
|
||||||
|
done = false;
|
||||||
|
res.addEntries([r.entries.elementAt(si)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
si++;
|
||||||
|
}
|
||||||
|
if (res.isEmpty) {
|
||||||
|
throw ObtainiumError(tr('noResults'));
|
||||||
|
}
|
||||||
|
List<String>? selectedUrls = res.isEmpty
|
||||||
|
? []
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
: await showDialog<List<String>?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext ctx) {
|
||||||
|
return SelectionModal(
|
||||||
|
entries: res,
|
||||||
|
selectedByDefault: false,
|
||||||
|
onlyOneSelectionAllowed: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
||||||
|
changeUserInput(selectedUrls[0], true, false, updateUrlInput: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
@@ -470,23 +504,21 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
...sourceProvider.sources
|
...sourceProvider.sources.map((e) => GestureDetector(
|
||||||
.map((e) => GestureDetector(
|
onTap: e.host != null
|
||||||
onTap: e.host != null
|
? () {
|
||||||
? () {
|
launchUrlString('https://${e.host}',
|
||||||
launchUrlString('https://${e.host}',
|
mode: LaunchMode.externalApplication);
|
||||||
mode: LaunchMode.externalApplication);
|
}
|
||||||
}
|
: null,
|
||||||
: null,
|
child: Text(
|
||||||
child: Text(
|
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
||||||
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
decoration: e.host != null
|
||||||
decoration: e.host != null
|
? TextDecoration.underline
|
||||||
? TextDecoration.underline
|
: TextDecoration.none,
|
||||||
: TextDecoration.none,
|
fontStyle: FontStyle.italic),
|
||||||
fontStyle: FontStyle.italic),
|
)))
|
||||||
)))
|
|
||||||
.toList()
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@@ -145,6 +145,29 @@ class _AppPageState extends State<AppPage> {
|
|||||||
appsProvider.saveApps([app.app]);
|
appsProvider.saveApps([app.app]);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
if (app?.app.additionalSettings['about'] is String &&
|
||||||
|
app?.app.additionalSettings['about'].isNotEmpty)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
Clipboard.setData(ClipboardData(
|
||||||
|
text: app?.app.additionalSettings['about'] ?? ''));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text(tr('copiedToClipboard')),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
app?.app.additionalSettings['about'],
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -496,14 +496,8 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
var transparent =
|
var transparent =
|
||||||
Theme.of(context).colorScheme.background.withAlpha(0).value;
|
Theme.of(context).colorScheme.background.withAlpha(0).value;
|
||||||
List<double> stops = [
|
List<double> stops = [
|
||||||
...listedApps[index]
|
...listedApps[index].app.categories.asMap().entries.map(
|
||||||
.app
|
(e) => ((e.key / (listedApps[index].app.categories.length - 1)))),
|
||||||
.categories
|
|
||||||
.asMap()
|
|
||||||
.entries
|
|
||||||
.map((e) =>
|
|
||||||
((e.key / (listedApps[index].app.categories.length - 1))))
|
|
||||||
.toList(),
|
|
||||||
1
|
1
|
||||||
];
|
];
|
||||||
if (stops.length == 2) {
|
if (stops.length == 2) {
|
||||||
@@ -516,13 +510,9 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
begin: const Alignment(-1, 0),
|
begin: const Alignment(-1, 0),
|
||||||
end: const Alignment(-0.97, 0),
|
end: const Alignment(-0.97, 0),
|
||||||
colors: [
|
colors: [
|
||||||
...listedApps[index]
|
...listedApps[index].app.categories.map((e) =>
|
||||||
.app
|
Color(settingsProvider.categories[e] ?? transparent)
|
||||||
.categories
|
.withAlpha(255)),
|
||||||
.map((e) =>
|
|
||||||
Color(settingsProvider.categories[e] ?? transparent)
|
|
||||||
.withAlpha(255))
|
|
||||||
.toList(),
|
|
||||||
Color(transparent)
|
Color(transparent)
|
||||||
])),
|
])),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
@@ -881,7 +871,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
String urls = '';
|
String urls = '';
|
||||||
for (var a in selectedApps) {
|
for (var a in selectedApps) {
|
||||||
urls += '${a.url}\n';
|
urls += 'obtainium://add/${a.url}\n';
|
||||||
}
|
}
|
||||||
urls = urls.substring(0, urls.length - 1);
|
urls = urls.substring(0, urls.length - 1);
|
||||||
Share.share(urls,
|
Share.share(urls,
|
||||||
@@ -981,10 +971,8 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
defaultValue: filter.sourceFilter,
|
defaultValue: filter.sourceFilter,
|
||||||
[
|
[
|
||||||
MapEntry('', tr('none')),
|
MapEntry('', tr('none')),
|
||||||
...sourceProvider.sources
|
...sourceProvider.sources.map(
|
||||||
.map((e) =>
|
(e) => MapEntry(e.runtimeType.toString(), e.name))
|
||||||
MapEntry(e.runtimeType.toString(), e.name))
|
|
||||||
.toList()
|
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/pages/add_app.dart';
|
import 'package:obtainium/pages/add_app.dart';
|
||||||
import 'package:obtainium/pages/apps.dart';
|
import 'package:obtainium/pages/apps.dart';
|
||||||
import 'package:obtainium/pages/import_export.dart';
|
import 'package:obtainium/pages/import_export.dart';
|
||||||
@@ -30,58 +34,119 @@ class _HomePageState extends State<HomePage> {
|
|||||||
bool isReversing = false;
|
bool isReversing = false;
|
||||||
int prevAppCount = -1;
|
int prevAppCount = -1;
|
||||||
bool prevIsLoading = true;
|
bool prevIsLoading = true;
|
||||||
|
late AppLinks _appLinks;
|
||||||
|
StreamSubscription<Uri>? _linkSubscription;
|
||||||
|
bool isLinkActivity = false;
|
||||||
|
|
||||||
List<NavigationPageItem> pages = [
|
List<NavigationPageItem> pages = [
|
||||||
NavigationPageItem(tr('appsString'), Icons.apps,
|
NavigationPageItem(tr('appsString'), Icons.apps,
|
||||||
AppsPage(key: GlobalKey<AppsPageState>())),
|
AppsPage(key: GlobalKey<AppsPageState>())),
|
||||||
NavigationPageItem(tr('addApp'), Icons.add, const AddAppPage()),
|
NavigationPageItem(
|
||||||
|
tr('addApp'), Icons.add, AddAppPage(key: GlobalKey<AddAppPageState>())),
|
||||||
NavigationPageItem(
|
NavigationPageItem(
|
||||||
tr('importExport'), Icons.import_export, const ImportExportPage()),
|
tr('importExport'), Icons.import_export, const ImportExportPage()),
|
||||||
NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage())
|
NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage())
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initDeepLinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initDeepLinks() async {
|
||||||
|
_appLinks = AppLinks();
|
||||||
|
|
||||||
|
goToAddApp(String data) async {
|
||||||
|
switchToPage(1);
|
||||||
|
while (
|
||||||
|
(pages[1].widget.key as GlobalKey<AddAppPageState>?)?.currentState ==
|
||||||
|
null) {
|
||||||
|
await Future.delayed(const Duration(microseconds: 1));
|
||||||
|
}
|
||||||
|
(pages[1].widget.key as GlobalKey<AddAppPageState>?)
|
||||||
|
?.currentState
|
||||||
|
?.linkFn(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
interpretLink(Uri uri) async {
|
||||||
|
isLinkActivity = true;
|
||||||
|
var action = uri.host;
|
||||||
|
var data = uri.path.length > 1 ? uri.path.substring(1) : "";
|
||||||
|
try {
|
||||||
|
if (action == 'add') {
|
||||||
|
await goToAddApp(data);
|
||||||
|
} else if (action == 'app') {
|
||||||
|
await context
|
||||||
|
.read<AppsProvider>()
|
||||||
|
.import('{ "apps": [${Uri.decodeComponent(data)}] }');
|
||||||
|
} else if (action == 'apps') {
|
||||||
|
await context
|
||||||
|
.read<AppsProvider>()
|
||||||
|
.import('{ "apps": ${Uri.decodeComponent(data)} }');
|
||||||
|
} else {
|
||||||
|
throw ObtainiumError(tr('unknown'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showError(e, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check initial link if app was in cold state (terminated)
|
||||||
|
final appLink = await _appLinks.getInitialAppLink();
|
||||||
|
if (appLink != null) {
|
||||||
|
await interpretLink(appLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle link when app is in warm state (front or background)
|
||||||
|
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
||||||
|
await interpretLink(uri);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsReversing(int targetIndex) {
|
||||||
|
bool reversing = selectedIndexHistory.isNotEmpty &&
|
||||||
|
selectedIndexHistory.last > targetIndex;
|
||||||
|
setState(() {
|
||||||
|
isReversing = reversing;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToPage(int index) async {
|
||||||
|
setIsReversing(index);
|
||||||
|
if (index == 0) {
|
||||||
|
while ((pages[0].widget.key as GlobalKey<AppsPageState>).currentState !=
|
||||||
|
null) {
|
||||||
|
// Avoid duplicate GlobalKey error
|
||||||
|
await Future.delayed(const Duration(microseconds: 1));
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
selectedIndexHistory.clear();
|
||||||
|
});
|
||||||
|
} else if (selectedIndexHistory.isEmpty ||
|
||||||
|
(selectedIndexHistory.isNotEmpty &&
|
||||||
|
selectedIndexHistory.last != index)) {
|
||||||
|
setState(() {
|
||||||
|
int existingInd = selectedIndexHistory.indexOf(index);
|
||||||
|
if (existingInd >= 0) {
|
||||||
|
selectedIndexHistory.removeAt(existingInd);
|
||||||
|
}
|
||||||
|
selectedIndexHistory.add(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
AppsProvider appsProvider = context.watch<AppsProvider>();
|
AppsProvider appsProvider = context.watch<AppsProvider>();
|
||||||
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
||||||
|
|
||||||
setIsReversing(int targetIndex) {
|
|
||||||
bool reversing = selectedIndexHistory.isNotEmpty &&
|
|
||||||
selectedIndexHistory.last > targetIndex;
|
|
||||||
setState(() {
|
|
||||||
isReversing = reversing;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToPage(int index) async {
|
|
||||||
setIsReversing(index);
|
|
||||||
if (index == 0) {
|
|
||||||
while ((pages[0].widget.key as GlobalKey<AppsPageState>).currentState !=
|
|
||||||
null) {
|
|
||||||
// Avoid duplicate GlobalKey error
|
|
||||||
await Future.delayed(const Duration(microseconds: 1));
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
selectedIndexHistory.clear();
|
|
||||||
});
|
|
||||||
} else if (selectedIndexHistory.isEmpty ||
|
|
||||||
(selectedIndexHistory.isNotEmpty &&
|
|
||||||
selectedIndexHistory.last != index)) {
|
|
||||||
setState(() {
|
|
||||||
int existingInd = selectedIndexHistory.indexOf(index);
|
|
||||||
if (existingInd >= 0) {
|
|
||||||
selectedIndexHistory.removeAt(existingInd);
|
|
||||||
}
|
|
||||||
selectedIndexHistory.add(index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prevIsLoading &&
|
if (!prevIsLoading &&
|
||||||
prevAppCount >= 0 &&
|
prevAppCount >= 0 &&
|
||||||
appsProvider.apps.length > prevAppCount &&
|
appsProvider.apps.length > prevAppCount &&
|
||||||
selectedIndexHistory.isNotEmpty &&
|
selectedIndexHistory.isNotEmpty &&
|
||||||
selectedIndexHistory.last == 1) {
|
selectedIndexHistory.last == 1 &&
|
||||||
|
!isLinkActivity) {
|
||||||
switchToPage(0);
|
switchToPage(0);
|
||||||
}
|
}
|
||||||
prevAppCount = appsProvider.apps.length;
|
prevAppCount = appsProvider.apps.length;
|
||||||
@@ -129,6 +194,11 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
|
if (isLinkActivity &&
|
||||||
|
selectedIndexHistory.length == 1 &&
|
||||||
|
selectedIndexHistory.last == 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
setIsReversing(selectedIndexHistory.length >= 2
|
setIsReversing(selectedIndexHistory.length >= 2
|
||||||
? selectedIndexHistory.reversed.toList()[1]
|
? selectedIndexHistory.reversed.toList()[1]
|
||||||
: 0);
|
: 0);
|
||||||
@@ -143,4 +213,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
?.clearSelected();
|
?.clearSelected();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_linkSubscription?.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:obtainium/app_sources/fdroidrepo.dart';
|
||||||
import 'package:obtainium/components/custom_app_bar.dart';
|
import 'package:obtainium/components/custom_app_bar.dart';
|
||||||
import 'package:obtainium/components/generated_form.dart';
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/components/generated_form_modal.dart';
|
import 'package:obtainium/components/generated_form_modal.dart';
|
||||||
@@ -105,7 +106,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
runObtainiumExport({bool pickOnly = false}) async {
|
runObtainiumExport({bool pickOnly = false}) async {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
appsProvider
|
appsProvider
|
||||||
.exportApps(
|
.export(
|
||||||
pickOnly:
|
pickOnly:
|
||||||
pickOnly || (await settingsProvider.getExportDir()) == null,
|
pickOnly || (await settingsProvider.getExportDir()) == null,
|
||||||
sp: settingsProvider)
|
sp: settingsProvider)
|
||||||
@@ -131,7 +132,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ObtainiumError(tr('invalidInput'));
|
throw ObtainiumError(tr('invalidInput'));
|
||||||
}
|
}
|
||||||
appsProvider.importApps(data).then((value) {
|
appsProvider.import(data).then((value) {
|
||||||
var cats = settingsProvider.categories;
|
var cats = settingsProvider.categories;
|
||||||
appsProvider.apps.forEach((key, value) {
|
appsProvider.apps.forEach((key, value) {
|
||||||
for (var c in value.app.categories) {
|
for (var c in value.app.categories) {
|
||||||
@@ -142,7 +143,10 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
});
|
});
|
||||||
appsProvider.addMissingCategories(settingsProvider);
|
appsProvider.addMissingCategories(settingsProvider);
|
||||||
showMessage(
|
showMessage(
|
||||||
tr('importedX', args: [plural('apps', value)]), context);
|
'${tr('importedX', args: [
|
||||||
|
plural('apps', value.key)
|
||||||
|
])}${value.value ? ' + ${tr('settings')}' : ''}',
|
||||||
|
context);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// User canceled the picker
|
// User canceled the picker
|
||||||
@@ -189,17 +193,29 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
items: [
|
items: [
|
||||||
[
|
[
|
||||||
GeneratedFormTextField('searchQuery',
|
GeneratedFormTextField('searchQuery',
|
||||||
label: tr('searchQuery'))
|
label: tr('searchQuery'),
|
||||||
|
required: source.name != FDroidRepo().name)
|
||||||
|
],
|
||||||
|
...source.searchQuerySettingFormItems.map((e) => [e]),
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('url',
|
||||||
|
label: source.host != null
|
||||||
|
? tr('overrideSource')
|
||||||
|
: plural('url', 1).substring(2),
|
||||||
|
defaultValue: source.host ?? '',
|
||||||
|
required: true)
|
||||||
],
|
],
|
||||||
...source.searchQuerySettingFormItems.map((e) => [e])
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (values != null &&
|
if (values != null) {
|
||||||
(values['searchQuery'] as String?)?.isNotEmpty == true) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
importInProgress = true;
|
importInProgress = true;
|
||||||
});
|
});
|
||||||
|
if (values['url'] != source.host) {
|
||||||
|
source = sourceProvider.getSource(values['url'],
|
||||||
|
overrideSource: source.runtimeType.toString());
|
||||||
|
}
|
||||||
var urlsWithDescriptions = await source
|
var urlsWithDescriptions = await source
|
||||||
.search(values['searchQuery'] as String, querySettings: values);
|
.search(values['searchQuery'] as String, querySettings: values);
|
||||||
if (urlsWithDescriptions.isNotEmpty) {
|
if (urlsWithDescriptions.isNotEmpty) {
|
||||||
@@ -208,13 +224,14 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
await showDialog<List<String>?>(
|
await showDialog<List<String>?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return UrlSelectionModal(
|
return SelectionModal(
|
||||||
urlsWithDescriptions: urlsWithDescriptions,
|
entries: urlsWithDescriptions,
|
||||||
selectedByDefault: false,
|
selectedByDefault: false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
||||||
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
var errors = await appsProvider.addAppsByURL(selectedUrls,
|
||||||
|
sourceOverride: source);
|
||||||
if (errors.isEmpty) {
|
if (errors.isEmpty) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
showMessage(
|
showMessage(
|
||||||
@@ -268,8 +285,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
await showDialog<List<String>?>(
|
await showDialog<List<String>?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return UrlSelectionModal(
|
return SelectionModal(entries: urlsWithDescriptions);
|
||||||
urlsWithDescriptions: urlsWithDescriptions);
|
|
||||||
});
|
});
|
||||||
if (selectedUrls != null) {
|
if (selectedUrls != null) {
|
||||||
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
||||||
@@ -299,6 +315,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sourceStrings = <String, List<String>>{};
|
||||||
|
sourceProvider.sources.where((e) => e.canSearch).forEach((s) {
|
||||||
|
sourceStrings[s.name] = [s.name];
|
||||||
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
body: CustomScrollView(slivers: <Widget>[
|
body: CustomScrollView(slivers: <Widget>[
|
||||||
@@ -326,7 +347,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
: () {
|
: () {
|
||||||
runObtainiumExport(pickOnly: true);
|
runObtainiumExport(pickOnly: true);
|
||||||
},
|
},
|
||||||
child: Text(tr('pickExportDir')),
|
child: Text(tr('pickExportDir'),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
)),
|
)),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
@@ -339,7 +361,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
snapshot.data == null
|
snapshot.data == null
|
||||||
? null
|
? null
|
||||||
: runObtainiumExport,
|
: runObtainiumExport,
|
||||||
child: Text(tr('obtainiumExport')),
|
child: Text(tr('obtainiumExport'),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -354,7 +377,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
onPressed: importInProgress
|
onPressed: importInProgress
|
||||||
? null
|
? null
|
||||||
: runObtainiumImport,
|
: runObtainiumImport,
|
||||||
child: Text(tr('obtainiumImport')))),
|
child: Text(tr('obtainiumImport'),
|
||||||
|
textAlign: TextAlign.center))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (snapshot.data != null)
|
if (snapshot.data != null)
|
||||||
@@ -370,6 +394,14 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
defaultValue: settingsProvider
|
defaultValue: settingsProvider
|
||||||
.autoExportOnChanges,
|
.autoExportOnChanges,
|
||||||
)
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch(
|
||||||
|
'exportSettings',
|
||||||
|
label: tr('includeSettings'),
|
||||||
|
defaultValue: settingsProvider
|
||||||
|
.exportSettings,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
onValueChanges:
|
onValueChanges:
|
||||||
@@ -382,6 +414,12 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
'autoExportOnChanges'] ==
|
'autoExportOnChanges'] ==
|
||||||
true;
|
true;
|
||||||
}
|
}
|
||||||
|
if (value['exportSettings'] !=
|
||||||
|
null) {
|
||||||
|
settingsProvider.exportSettings =
|
||||||
|
value['exportSettings'] ==
|
||||||
|
true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@@ -408,6 +446,54 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
const Divider(
|
const Divider(
|
||||||
height: 32,
|
height: 32,
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: importInProgress
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
var searchSourceName =
|
||||||
|
await showDialog<
|
||||||
|
List<String>?>(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(BuildContext
|
||||||
|
ctx) {
|
||||||
|
return SelectionModal(
|
||||||
|
title: tr(
|
||||||
|
'selectX',
|
||||||
|
args: [
|
||||||
|
tr('source')
|
||||||
|
]),
|
||||||
|
entries:
|
||||||
|
sourceStrings,
|
||||||
|
selectedByDefault:
|
||||||
|
false,
|
||||||
|
onlyOneSelectionAllowed:
|
||||||
|
true,
|
||||||
|
titlesAreLinks:
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}) ??
|
||||||
|
[];
|
||||||
|
var searchSource =
|
||||||
|
sourceProvider.sources
|
||||||
|
.where((e) =>
|
||||||
|
searchSourceName
|
||||||
|
.contains(
|
||||||
|
e.name))
|
||||||
|
.toList();
|
||||||
|
if (searchSource.isNotEmpty) {
|
||||||
|
runSourceSearch(
|
||||||
|
searchSource[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(tr('searchX',
|
||||||
|
args: [tr('source')])))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
importInProgress ? null : urlListImport,
|
importInProgress ? null : urlListImport,
|
||||||
@@ -423,39 +509,19 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...sourceProvider.sources
|
...sourceProvider.massUrlSources.map((source) => Column(
|
||||||
.where((element) => element.canSearch)
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
.map((source) => Column(
|
children: [
|
||||||
crossAxisAlignment:
|
const SizedBox(height: 8),
|
||||||
CrossAxisAlignment.stretch,
|
TextButton(
|
||||||
children: [
|
onPressed: importInProgress
|
||||||
const SizedBox(height: 8),
|
? null
|
||||||
TextButton(
|
: () {
|
||||||
onPressed: importInProgress
|
runMassSourceImport(source);
|
||||||
? null
|
},
|
||||||
: () {
|
child: Text(
|
||||||
runSourceSearch(source);
|
tr('importX', args: [source.name])))
|
||||||
},
|
])),
|
||||||
child: Text(
|
|
||||||
tr('searchX', args: [source.name])))
|
|
||||||
]))
|
|
||||||
.toList(),
|
|
||||||
...sourceProvider.massUrlSources
|
|
||||||
.map((source) => Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
TextButton(
|
|
||||||
onPressed: importInProgress
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
runMassSourceImport(source);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
tr('importX', args: [source.name])))
|
|
||||||
]))
|
|
||||||
.toList(),
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Divider(
|
const Divider(
|
||||||
height: 32,
|
height: 32,
|
||||||
@@ -517,7 +583,7 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
|
|||||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
}).toList()
|
})
|
||||||
]),
|
]),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -531,112 +597,171 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class UrlSelectionModal extends StatefulWidget {
|
class SelectionModal extends StatefulWidget {
|
||||||
UrlSelectionModal(
|
SelectionModal(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.urlsWithDescriptions,
|
required this.entries,
|
||||||
this.selectedByDefault = true,
|
this.selectedByDefault = true,
|
||||||
this.onlyOneSelectionAllowed = false});
|
this.onlyOneSelectionAllowed = false,
|
||||||
|
this.titlesAreLinks = true,
|
||||||
|
this.title});
|
||||||
|
|
||||||
Map<String, List<String>> urlsWithDescriptions;
|
String? title;
|
||||||
|
Map<String, List<String>> entries;
|
||||||
bool selectedByDefault;
|
bool selectedByDefault;
|
||||||
bool onlyOneSelectionAllowed;
|
bool onlyOneSelectionAllowed;
|
||||||
|
bool titlesAreLinks;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UrlSelectionModal> createState() => _UrlSelectionModalState();
|
State<SelectionModal> createState() => _SelectionModalState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
class _SelectionModalState extends State<SelectionModal> {
|
||||||
Map<MapEntry<String, List<String>>, bool> urlWithDescriptionSelections = {};
|
Map<MapEntry<String, List<String>>, bool> entrySelections = {};
|
||||||
|
String filterRegex = '';
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
for (var url in widget.urlsWithDescriptions.entries) {
|
for (var url in widget.entries.entries) {
|
||||||
urlWithDescriptionSelections.putIfAbsent(url,
|
entrySelections.putIfAbsent(url,
|
||||||
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
|
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
|
||||||
}
|
}
|
||||||
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
|
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
|
||||||
selectOnlyOne(widget.urlsWithDescriptions.entries.first.key);
|
selectOnlyOne(widget.entries.entries.first.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOnlyOne(String url) {
|
selectOnlyOne(String url) {
|
||||||
for (var uwd in urlWithDescriptionSelections.keys) {
|
for (var e in entrySelections.keys) {
|
||||||
urlWithDescriptionSelections[uwd] = uwd.key == url;
|
entrySelections[e] = e.key == url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Map<MapEntry<String, List<String>>, bool> filteredEntrySelections = {};
|
||||||
|
entrySelections.forEach((key, value) {
|
||||||
|
var searchableText = key.value.isEmpty ? key.key : key.value[0];
|
||||||
|
if (filterRegex.isEmpty || RegExp(filterRegex).hasMatch(searchableText)) {
|
||||||
|
filteredEntrySelections.putIfAbsent(key, () => value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (filterRegex.isNotEmpty && filteredEntrySelections.isEmpty) {
|
||||||
|
entrySelections.forEach((key, value) {
|
||||||
|
var searchableText = key.value.isEmpty ? key.key : key.value[0];
|
||||||
|
if (filterRegex.isEmpty ||
|
||||||
|
RegExp(filterRegex, caseSensitive: false)
|
||||||
|
.hasMatch(searchableText)) {
|
||||||
|
filteredEntrySelections.putIfAbsent(key, () => value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
title: Text(
|
title: Text(widget.title ?? tr('pick')),
|
||||||
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
|
|
||||||
content: Column(children: [
|
content: Column(children: [
|
||||||
...urlWithDescriptionSelections.keys.map((urlWithD) {
|
GeneratedForm(
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('filter',
|
||||||
|
label: tr('filter'),
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
return regExValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
]
|
||||||
|
],
|
||||||
|
onValueChanges: (value, valid, isBuilding) {
|
||||||
|
if (valid && !isBuilding) {
|
||||||
|
if (value['filter'] != null) {
|
||||||
|
setState(() {
|
||||||
|
filterRegex = value['filter'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...filteredEntrySelections.keys.map((entry) {
|
||||||
selectThis(bool? value) {
|
selectThis(bool? value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
value ??= false;
|
value ??= false;
|
||||||
if (value! && widget.onlyOneSelectionAllowed) {
|
if (value! && widget.onlyOneSelectionAllowed) {
|
||||||
selectOnlyOne(urlWithD.key);
|
selectOnlyOne(entry.key);
|
||||||
} else {
|
} else {
|
||||||
urlWithDescriptionSelections[urlWithD] = value!;
|
entrySelections[entry] = value!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var urlLink = GestureDetector(
|
var urlLink = GestureDetector(
|
||||||
onTap: () {
|
onTap: !widget.titlesAreLinks
|
||||||
launchUrlString(urlWithD.key,
|
? null
|
||||||
mode: LaunchMode.externalApplication);
|
: () {
|
||||||
},
|
launchUrlString(entry.key,
|
||||||
|
mode: LaunchMode.externalApplication);
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
urlWithD.value[0],
|
entry.value.isEmpty ? entry.key : entry.value[0],
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: widget.titlesAreLinks
|
||||||
|
? TextDecoration.underline
|
||||||
|
: null,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
),
|
),
|
||||||
Text(
|
if (widget.titlesAreLinks)
|
||||||
Uri.parse(urlWithD.key).host,
|
Text(
|
||||||
style: const TextStyle(
|
Uri.parse(entry.key).host,
|
||||||
decoration: TextDecoration.underline, fontSize: 12),
|
style: const TextStyle(
|
||||||
)
|
decoration: TextDecoration.underline, fontSize: 12),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|
||||||
var descriptionText = Text(
|
var descriptionText = entry.value.length <= 1
|
||||||
urlWithD.value[1].length > 128
|
? const SizedBox.shrink()
|
||||||
? '${urlWithD.value[1].substring(0, 128)}...'
|
: Text(
|
||||||
: urlWithD.value[1],
|
entry.value[1].length > 128
|
||||||
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
|
? '${entry.value[1].substring(0, 128)}...'
|
||||||
);
|
: entry.value[1],
|
||||||
|
style: const TextStyle(
|
||||||
|
fontStyle: FontStyle.italic, fontSize: 12),
|
||||||
|
);
|
||||||
|
|
||||||
var selectedUrlsWithDs = urlWithDescriptionSelections.entries
|
var selectedEntries =
|
||||||
.where((e) => e.value)
|
entrySelections.entries.where((e) => e.value).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
var singleSelectTile = ListTile(
|
var singleSelectTile = ListTile(
|
||||||
title: urlLink,
|
title: GestureDetector(
|
||||||
subtitle: GestureDetector(
|
onTap: widget.titlesAreLinks
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
selectOnlyOne(urlWithD.key);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: descriptionText,
|
|
||||||
),
|
|
||||||
leading: Radio<String>(
|
|
||||||
value: urlWithD.key,
|
|
||||||
groupValue: selectedUrlsWithDs.isEmpty
|
|
||||||
? null
|
? null
|
||||||
: selectedUrlsWithDs.first.key.key,
|
: () {
|
||||||
|
selectThis(!(entrySelections[entry] ?? false));
|
||||||
|
},
|
||||||
|
child: urlLink,
|
||||||
|
),
|
||||||
|
subtitle: entry.value.length <= 1
|
||||||
|
? null
|
||||||
|
: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectOnlyOne(entry.key);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: descriptionText,
|
||||||
|
),
|
||||||
|
leading: Radio<String>(
|
||||||
|
value: entry.key,
|
||||||
|
groupValue: selectedEntries.isEmpty
|
||||||
|
? null
|
||||||
|
: selectedEntries.first.key.key,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectOnlyOne(urlWithD.key);
|
selectOnlyOne(entry.key);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -644,7 +769,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
|||||||
|
|
||||||
var multiSelectTile = Row(children: [
|
var multiSelectTile = Row(children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: urlWithDescriptionSelections[urlWithD],
|
value: entrySelections[entry],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
selectThis(value);
|
selectThis(value);
|
||||||
}),
|
}),
|
||||||
@@ -659,14 +784,22 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
urlLink,
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: widget.titlesAreLinks
|
||||||
selectThis(
|
? null
|
||||||
!(urlWithDescriptionSelections[urlWithD] ?? false));
|
: () {
|
||||||
},
|
selectThis(!(entrySelections[entry] ?? false));
|
||||||
child: descriptionText,
|
},
|
||||||
|
child: urlLink,
|
||||||
),
|
),
|
||||||
|
entry.value.length <= 1
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
selectThis(!(entrySelections[entry] ?? false));
|
||||||
|
},
|
||||||
|
child: descriptionText,
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
)
|
)
|
||||||
@@ -686,24 +819,18 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
|||||||
},
|
},
|
||||||
child: Text(tr('cancel'))),
|
child: Text(tr('cancel'))),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed: entrySelections.values.where((b) => b).isEmpty
|
||||||
urlWithDescriptionSelections.values.where((b) => b).isEmpty
|
? null
|
||||||
? null
|
: () {
|
||||||
: () {
|
Navigator.of(context).pop(entrySelections.entries
|
||||||
Navigator.of(context).pop(urlWithDescriptionSelections
|
.where((entry) => entry.value)
|
||||||
.entries
|
.map((e) => e.key.key)
|
||||||
.where((entry) => entry.value)
|
.toList());
|
||||||
.map((e) => e.key.key)
|
},
|
||||||
.toList());
|
|
||||||
},
|
|
||||||
child: Text(widget.onlyOneSelectionAllowed
|
child: Text(widget.onlyOneSelectionAllowed
|
||||||
? tr('pick')
|
? tr('pick')
|
||||||
: tr('importX', args: [
|
: tr('selectX', args: [
|
||||||
plural(
|
entrySelections.values.where((b) => b).length.toString()
|
||||||
'url',
|
|
||||||
urlWithDescriptionSelections.values
|
|
||||||
.where((b) => b)
|
|
||||||
.length)
|
|
||||||
])))
|
])))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -30,6 +29,29 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
settingsProvider.initializeSettings();
|
settingsProvider.initializeSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var installMethodDropdown = DropdownButtonFormField(
|
||||||
|
decoration: InputDecoration(labelText: tr('installMethod')),
|
||||||
|
value: settingsProvider.installMethod,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: InstallMethodSettings.normal,
|
||||||
|
child: Text(tr('normal')),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: InstallMethodSettings.shizuku,
|
||||||
|
child: Text(tr('shizuku')),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: InstallMethodSettings.root,
|
||||||
|
child: Text(tr('root')),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
settingsProvider.installMethod = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var themeDropdown = DropdownButtonFormField(
|
var themeDropdown = DropdownButtonFormField(
|
||||||
decoration: InputDecoration(labelText: tr('theme')),
|
decoration: InputDecoration(labelText: tr('theme')),
|
||||||
value: settingsProvider.theme,
|
value: settingsProvider.theme,
|
||||||
@@ -327,6 +349,21 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
})
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
height16,
|
||||||
|
installMethodDropdown,
|
||||||
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(child: Text(tr('parallelDownloads'))),
|
||||||
|
Switch(
|
||||||
|
value: settingsProvider.parallelDownloads,
|
||||||
|
onChanged: (value) {
|
||||||
|
settingsProvider.parallelDownloads =
|
||||||
|
value;
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
height32,
|
height32,
|
||||||
Text(
|
Text(
|
||||||
tr('sourceSpecific'),
|
tr('sourceSpecific'),
|
||||||
@@ -570,38 +607,35 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
const Divider(
|
const Divider(
|
||||||
height: 32,
|
height: 32,
|
||||||
),
|
),
|
||||||
Padding(
|
// Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
// padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
child: Column(children: [
|
// child: Column(children: [
|
||||||
Row(
|
// Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
// children: [
|
||||||
Flexible(child: Text(tr('debugMenu'))),
|
// Flexible(child: Text(tr('debugMenu'))),
|
||||||
Switch(
|
// Switch(
|
||||||
value: settingsProvider.showDebugOpts,
|
// value: settingsProvider.showDebugOpts,
|
||||||
onChanged: (value) {
|
// onChanged: (value) {
|
||||||
settingsProvider.showDebugOpts = value;
|
// settingsProvider.showDebugOpts = value;
|
||||||
})
|
// })
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
if (settingsProvider.showDebugOpts)
|
// if (settingsProvider.showDebugOpts)
|
||||||
Column(
|
// Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
// children: [
|
||||||
height16,
|
// height16,
|
||||||
TextButton(
|
// TextButton(
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
AndroidAlarmManager.oneShot(
|
// bgUpdateCheck('taskId', null);
|
||||||
const Duration(seconds: 0),
|
// showMessage(tr('bgTaskStarted'), context);
|
||||||
bgUpdateCheckAlarmId + 200,
|
// },
|
||||||
bgUpdateCheck);
|
// child: Text(tr('runBgCheckNow')))
|
||||||
showMessage(tr('bgTaskStarted'), context);
|
// ],
|
||||||
},
|
// ),
|
||||||
child: Text(tr('runBgCheckNow')))
|
// ]),
|
||||||
],
|
// ),
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@@ -6,8 +6,8 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
|
||||||
import 'package:android_intent_plus/flag.dart';
|
import 'package:android_intent_plus/flag.dart';
|
||||||
import 'package:android_package_installer/android_package_installer.dart';
|
import 'package:android_package_installer/android_package_installer.dart';
|
||||||
import 'package:android_package_manager/android_package_manager.dart';
|
import 'package:android_package_manager/android_package_manager.dart';
|
||||||
@@ -32,6 +32,7 @@ import 'package:http/http.dart';
|
|||||||
import 'package:android_intent_plus/android_intent.dart';
|
import 'package:android_intent_plus/android_intent.dart';
|
||||||
import 'package:flutter_archive/flutter_archive.dart';
|
import 'package:flutter_archive/flutter_archive.dart';
|
||||||
import 'package:shared_storage/shared_storage.dart' as saf;
|
import 'package:shared_storage/shared_storage.dart' as saf;
|
||||||
|
import 'installers_provider.dart';
|
||||||
|
|
||||||
final pm = AndroidPackageManager();
|
final pm = AndroidPackageManager();
|
||||||
|
|
||||||
@@ -139,6 +140,100 @@ List<MapEntry<String, int>> moveStrToEndMapEntryWithCount(
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, fileNameNoExt, onProgress, destDir,
|
||||||
|
useExisting: useExisting, headers: headers);
|
||||||
|
} catch (e) {
|
||||||
|
if (retries > 0 && e is ClientException) {
|
||||||
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
|
return await downloadFileWithRetry(
|
||||||
|
url, fileNameNoExt, onProgress, destDir,
|
||||||
|
useExisting: useExisting, headers: headers, retries: (retries - 1));
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String hashListOfLists(List<List<int>> data) {
|
||||||
|
var bytes = utf8.encode(jsonEncode(data));
|
||||||
|
var digest = sha256.convert(bytes);
|
||||||
|
var hash = digest.toString();
|
||||||
|
return hash.hashCode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> checkDownloadHash(String url,
|
||||||
|
{int bytesToGrab = 1024, 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 fileNameNoExt, Function? onProgress, String destDir,
|
||||||
|
{bool useExisting = true, Map<String, String>? headers}) async {
|
||||||
|
var req = Request('GET', Uri.parse(url));
|
||||||
|
if (headers != null) {
|
||||||
|
req.headers.addAll(headers);
|
||||||
|
}
|
||||||
|
var client = http.Client();
|
||||||
|
StreamedResponse response = await client.send(req);
|
||||||
|
String ext =
|
||||||
|
response.headers['content-disposition']?.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)) {
|
||||||
|
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
||||||
|
if (tempDownloadedFile.existsSync()) {
|
||||||
|
tempDownloadedFile.deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
var length = response.contentLength;
|
||||||
|
var received = 0;
|
||||||
|
double? progress;
|
||||||
|
var sink = tempDownloadedFile.openWrite();
|
||||||
|
await response.stream.map((s) {
|
||||||
|
received += s.length;
|
||||||
|
progress = (length != null ? received / length * 100 : 30);
|
||||||
|
if (onProgress != null) {
|
||||||
|
onProgress(progress);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}).pipe(sink);
|
||||||
|
await sink.close();
|
||||||
|
progress = null;
|
||||||
|
if (onProgress != null) {
|
||||||
|
onProgress(progress);
|
||||||
|
}
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
tempDownloadedFile.deleteSync(recursive: true);
|
||||||
|
throw response.reasonPhrase ?? tr('unexpectedError');
|
||||||
|
}
|
||||||
|
tempDownloadedFile.renameSync(downloadedFile.path);
|
||||||
|
} else {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
return downloadedFile;
|
||||||
|
}
|
||||||
|
|
||||||
class AppsProvider with ChangeNotifier {
|
class AppsProvider with ChangeNotifier {
|
||||||
// In memory App state (should always be kept in sync with local storage versions)
|
// In memory App state (should always be kept in sync with local storage versions)
|
||||||
Map<String, AppInMemory> apps = {};
|
Map<String, AppInMemory> apps = {};
|
||||||
@@ -192,77 +287,6 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File> downloadFileWithRetry(
|
|
||||||
String url, String fileNameNoExt, Function? onProgress,
|
|
||||||
{bool useExisting = true,
|
|
||||||
Map<String, String>? headers,
|
|
||||||
int retries = 3}) async {
|
|
||||||
try {
|
|
||||||
return await downloadFile(url, fileNameNoExt, onProgress,
|
|
||||||
useExisting: useExisting, headers: headers);
|
|
||||||
} catch (e) {
|
|
||||||
if (retries > 0 && e is ClientException) {
|
|
||||||
await Future.delayed(const Duration(seconds: 5));
|
|
||||||
return await downloadFileWithRetry(url, fileNameNoExt, onProgress,
|
|
||||||
useExisting: useExisting, headers: headers, retries: (retries - 1));
|
|
||||||
} else {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> downloadFile(
|
|
||||||
String url, String fileNameNoExt, Function? onProgress,
|
|
||||||
{bool useExisting = true, Map<String, String>? headers}) async {
|
|
||||||
var destDir = APKDir.path;
|
|
||||||
var req = Request('GET', Uri.parse(url));
|
|
||||||
if (headers != null) {
|
|
||||||
req.headers.addAll(headers);
|
|
||||||
}
|
|
||||||
var client = http.Client();
|
|
||||||
StreamedResponse response = await client.send(req);
|
|
||||||
String ext =
|
|
||||||
response.headers['content-disposition']?.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)) {
|
|
||||||
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
|
||||||
if (tempDownloadedFile.existsSync()) {
|
|
||||||
tempDownloadedFile.deleteSync(recursive: true);
|
|
||||||
}
|
|
||||||
var length = response.contentLength;
|
|
||||||
var received = 0;
|
|
||||||
double? progress;
|
|
||||||
var sink = tempDownloadedFile.openWrite();
|
|
||||||
await response.stream.map((s) {
|
|
||||||
received += s.length;
|
|
||||||
progress = (length != null ? received / length * 100 : 30);
|
|
||||||
if (onProgress != null) {
|
|
||||||
onProgress(progress);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}).pipe(sink);
|
|
||||||
await sink.close();
|
|
||||||
progress = null;
|
|
||||||
if (onProgress != null) {
|
|
||||||
onProgress(progress);
|
|
||||||
}
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
tempDownloadedFile.deleteSync(recursive: true);
|
|
||||||
throw response.reasonPhrase ?? tr('unexpectedError');
|
|
||||||
}
|
|
||||||
tempDownloadedFile.renameSync(downloadedFile.path);
|
|
||||||
} else {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
return downloadedFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> handleAPKIDChange(App app, PackageInfo? newInfo,
|
Future<File> handleAPKIDChange(App app, PackageInfo? newInfo,
|
||||||
File downloadedFile, String downloadUrl) async {
|
File downloadedFile, String downloadUrl) async {
|
||||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
||||||
@@ -322,7 +346,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
notificationsProvider?.notify(notif);
|
notificationsProvider?.notify(notif);
|
||||||
}
|
}
|
||||||
prevProg = prog;
|
prevProg = prog;
|
||||||
});
|
}, APKDir.path);
|
||||||
// Set to 90 for remaining steps, will make null in 'finally'
|
// Set to 90 for remaining steps, will make null in 'finally'
|
||||||
if (apps[app.id] != null) {
|
if (apps[app.id] != null) {
|
||||||
apps[app.id]!.downloadProgress = -1;
|
apps[app.id]!.downloadProgress = -1;
|
||||||
@@ -480,7 +504,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
!(await canDowngradeApps())) {
|
!(await canDowngradeApps())) {
|
||||||
throw DowngradeError();
|
throw DowngradeError();
|
||||||
}
|
}
|
||||||
if (needsBGWorkaround) {
|
if (needsBGWorkaround &&
|
||||||
|
settingsProvider.installMethod == InstallMethodSettings.normal) {
|
||||||
// The below 'await' will never return if we are in a background process
|
// 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
|
// 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
|
// So we update the app's installed version first as we will never get to the later code
|
||||||
@@ -491,8 +516,21 @@ class AppsProvider with ChangeNotifier {
|
|||||||
await saveApps([apps[file.appId]!.app],
|
await saveApps([apps[file.appId]!.app],
|
||||||
attemptToCorrectInstallStatus: false);
|
attemptToCorrectInstallStatus: false);
|
||||||
}
|
}
|
||||||
int? code =
|
int? code;
|
||||||
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
|
switch (settingsProvider.installMethod) {
|
||||||
|
case InstallMethodSettings.normal:
|
||||||
|
code = await AndroidPackageInstaller.installApk(
|
||||||
|
apkFilePath: file.file.path);
|
||||||
|
case InstallMethodSettings.shizuku:
|
||||||
|
code = (await Installers.installWithShizuku(
|
||||||
|
apkFileUri: file.file.uri.toString()))
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
|
case InstallMethodSettings.root:
|
||||||
|
code = (await Installers.installWithRoot(apkFilePath: file.file.path))
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
bool installed = false;
|
bool installed = false;
|
||||||
if (code != null && code != 0 && code != 3) {
|
if (code != null && code != 0 && code != 3) {
|
||||||
throw InstallError(code);
|
throw InstallError(code);
|
||||||
@@ -582,7 +620,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
|
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
|
||||||
Future<List<String>> downloadAndInstallLatestApps(
|
Future<List<String>> downloadAndInstallLatestApps(
|
||||||
List<String> appIds, BuildContext? context,
|
List<String> appIds, BuildContext? context,
|
||||||
{NotificationsProvider? notificationsProvider}) async {
|
{NotificationsProvider? notificationsProvider,
|
||||||
|
bool forceParallelDownloads = false}) async {
|
||||||
notificationsProvider =
|
notificationsProvider =
|
||||||
notificationsProvider ?? context?.read<NotificationsProvider>();
|
notificationsProvider ?? context?.read<NotificationsProvider>();
|
||||||
List<String> appsToInstall = [];
|
List<String> appsToInstall = [];
|
||||||
@@ -633,7 +672,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
appsToInstall =
|
appsToInstall =
|
||||||
moveStrToEnd(appsToInstall, obtainiumId, strB: obtainiumTempId);
|
moveStrToEnd(appsToInstall, obtainiumId, strB: obtainiumTempId);
|
||||||
|
|
||||||
for (var id in appsToInstall) {
|
Future<void> updateFn(String id, {bool skipInstalls = false}) async {
|
||||||
try {
|
try {
|
||||||
var downloadedArtifact =
|
var downloadedArtifact =
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
@@ -648,8 +687,23 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
var appId = downloadedFile?.appId ?? downloadedDir!.appId;
|
var appId = downloadedFile?.appId ?? downloadedDir!.appId;
|
||||||
bool willBeSilent = await canInstallSilently(apps[appId]!.app);
|
bool willBeSilent = await canInstallSilently(apps[appId]!.app);
|
||||||
if (!(await settingsProvider.getInstallPermission(enforce: false))) {
|
switch (settingsProvider.installMethod) {
|
||||||
throw ObtainiumError(tr('cancelled'));
|
case InstallMethodSettings.normal:
|
||||||
|
if (!(await settingsProvider.getInstallPermission(
|
||||||
|
enforce: false))) {
|
||||||
|
throw ObtainiumError(tr('cancelled'));
|
||||||
|
}
|
||||||
|
case InstallMethodSettings.shizuku:
|
||||||
|
int code = await Installers.checkPermissionShizuku();
|
||||||
|
if (code == -1) {
|
||||||
|
throw ObtainiumError(tr('shizukuBinderNotFound'));
|
||||||
|
} else if (code == 0) {
|
||||||
|
throw ObtainiumError(tr('cancelled'));
|
||||||
|
}
|
||||||
|
case InstallMethodSettings.root:
|
||||||
|
if (!(await Installers.checkPermissionRoot())) {
|
||||||
|
throw ObtainiumError(tr('cancelled'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!willBeSilent && context != null) {
|
if (!willBeSilent && context != null) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
@@ -658,24 +712,26 @@ class AppsProvider with ChangeNotifier {
|
|||||||
apps[id]?.downloadProgress = -1;
|
apps[id]?.downloadProgress = -1;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
if (downloadedFile != null) {
|
if (!skipInstalls) {
|
||||||
if (willBeSilent && context == null) {
|
if (downloadedFile != null) {
|
||||||
installApk(downloadedFile, needsBGWorkaround: true);
|
if (willBeSilent && context == null) {
|
||||||
|
installApk(downloadedFile, needsBGWorkaround: true);
|
||||||
|
} else {
|
||||||
|
await installApk(downloadedFile);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await installApk(downloadedFile);
|
if (willBeSilent && context == null) {
|
||||||
|
installXApkDir(downloadedDir!, needsBGWorkaround: true);
|
||||||
|
} else {
|
||||||
|
await installXApkDir(downloadedDir!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (willBeSilent && context == null) {
|
if (willBeSilent && context == null) {
|
||||||
installXApkDir(downloadedDir!, needsBGWorkaround: true);
|
notificationsProvider?.notify(SilentUpdateAttemptNotification(
|
||||||
} else {
|
[apps[appId]!.app],
|
||||||
await installXApkDir(downloadedDir!);
|
id: appId.hashCode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (willBeSilent && context == null) {
|
|
||||||
notificationsProvider?.notify(SilentUpdateAttemptNotification(
|
|
||||||
[apps[appId]!.app],
|
|
||||||
id: appId.hashCode));
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
apps[id]?.downloadProgress = null;
|
apps[id]?.downloadProgress = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -686,6 +742,20 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (forceParallelDownloads || !settingsProvider.parallelDownloads) {
|
||||||
|
for (var id in appsToInstall) {
|
||||||
|
await updateFn(id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await Future.wait(
|
||||||
|
appsToInstall.map((id) => updateFn(id, skipInstalls: true)));
|
||||||
|
for (var id in appsToInstall) {
|
||||||
|
if (!errors.appIdNames.containsKey(id)) {
|
||||||
|
await updateFn(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.idsByErrorString.isNotEmpty) {
|
if (errors.idsByErrorString.isNotEmpty) {
|
||||||
throw errors;
|
throw errors;
|
||||||
}
|
}
|
||||||
@@ -702,12 +772,15 @@ class AppsProvider with ChangeNotifier {
|
|||||||
return appsDir;
|
return appsDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PackageInfo?> getInstalledInfo(String? packageName) async {
|
Future<PackageInfo?> getInstalledInfo(String? packageName,
|
||||||
|
{bool printErr = true}) async {
|
||||||
if (packageName != null) {
|
if (packageName != null) {
|
||||||
try {
|
try {
|
||||||
return await pm.getPackageInfo(packageName: packageName);
|
return await pm.getPackageInfo(packageName: packageName);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e); // OK
|
if (printErr) {
|
||||||
|
print(e); // OK
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -950,7 +1023,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
exportApps(isAuto: true);
|
export(isAuto: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeApps(List<String> appIds) async {
|
Future<void> removeApps(List<String> appIds) async {
|
||||||
@@ -972,7 +1045,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
if (appIds.isNotEmpty) {
|
if (appIds.isNotEmpty) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
exportApps(isAuto: true);
|
export(isAuto: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1149,7 +1222,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
return updateAppIds;
|
return updateAppIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> exportApps(
|
Future<String?> export(
|
||||||
{bool pickOnly = false, isAuto = false, SettingsProvider? sp}) async {
|
{bool pickOnly = false, isAuto = false, SettingsProvider? sp}) async {
|
||||||
SettingsProvider settingsProvider = sp ?? this.settingsProvider;
|
SettingsProvider settingsProvider = sp ?? this.settingsProvider;
|
||||||
var exportDir = await settingsProvider.getExportDir();
|
var exportDir = await settingsProvider.getExportDir();
|
||||||
@@ -1179,12 +1252,22 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
String? returnPath;
|
String? returnPath;
|
||||||
if (!pickOnly) {
|
if (!pickOnly) {
|
||||||
|
Map<String, dynamic> finalExport = {};
|
||||||
|
finalExport['apps'] = apps.values.map((e) => e.app.toJson()).toList();
|
||||||
|
if (settingsProvider.exportSettings) {
|
||||||
|
finalExport['settings'] = Map<String, Object?>.fromEntries(
|
||||||
|
(settingsProvider.prefs
|
||||||
|
?.getKeys()
|
||||||
|
.map((key) =>
|
||||||
|
MapEntry(key, settingsProvider.prefs?.get(key)))
|
||||||
|
.toList()) ??
|
||||||
|
[]);
|
||||||
|
}
|
||||||
var result = await saf.createFile(exportDir,
|
var result = await saf.createFile(exportDir,
|
||||||
displayName:
|
displayName:
|
||||||
'${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().toIso8601String().replaceAll(':', '-')}${isAuto ? '-auto' : ''}.json',
|
'${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().toIso8601String().replaceAll(':', '-')}${isAuto ? '-auto' : ''}.json',
|
||||||
mimeType: 'application/json',
|
mimeType: 'application/json',
|
||||||
bytes: Uint8List.fromList(utf8.encode(
|
bytes: Uint8List.fromList(utf8.encode(jsonEncode(finalExport))));
|
||||||
jsonEncode(apps.values.map((e) => e.app.toJson()).toList()))));
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
throw ObtainiumError(tr('unexpectedError'));
|
throw ObtainiumError(tr('unexpectedError'));
|
||||||
}
|
}
|
||||||
@@ -1194,21 +1277,36 @@ class AppsProvider with ChangeNotifier {
|
|||||||
return returnPath;
|
return returnPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> importApps(String appsJSON) async {
|
Future<MapEntry<int, bool>> import(String appsJSON) async {
|
||||||
List<App> importedApps = (jsonDecode(appsJSON) as List<dynamic>)
|
var decodedJSON = jsonDecode(appsJSON);
|
||||||
.map((e) => App.fromJson(e))
|
var newFormat = decodedJSON is! List;
|
||||||
.toList();
|
List<App> importedApps =
|
||||||
|
((newFormat ? decodedJSON['apps'] : decodedJSON) as List<dynamic>)
|
||||||
|
.map((e) => App.fromJson(e))
|
||||||
|
.toList();
|
||||||
while (loadingApps) {
|
while (loadingApps) {
|
||||||
await Future.delayed(const Duration(microseconds: 1));
|
await Future.delayed(const Duration(microseconds: 1));
|
||||||
}
|
}
|
||||||
for (App a in importedApps) {
|
for (App a in importedApps) {
|
||||||
if (apps[a.id]?.app.installedVersion != null) {
|
a.installedVersion =
|
||||||
a.installedVersion = apps[a.id]?.app.installedVersion;
|
(await getInstalledInfo(a.id, printErr: false))?.versionName;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await saveApps(importedApps, onlyIfExists: false);
|
await saveApps(importedApps, onlyIfExists: false);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return importedApps.length;
|
if (newFormat && decodedJSON['settings'] != null) {
|
||||||
|
var settingsMap = decodedJSON['settings'] as Map<String, Object?>;
|
||||||
|
settingsMap.forEach((key, value) {
|
||||||
|
if (value is int) {
|
||||||
|
settingsProvider.prefs?.setInt(key, value);
|
||||||
|
} else if (value is bool) {
|
||||||
|
settingsProvider.prefs?.setBool(key, value);
|
||||||
|
} else {
|
||||||
|
settingsProvider.prefs?.setString(key, value as String);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return MapEntry<int, bool>(
|
||||||
|
importedApps.length, newFormat && decodedJSON['settings'] != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1217,9 +1315,11 @@ class AppsProvider with ChangeNotifier {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<List<String>>> addAppsByURL(List<String> urls) async {
|
Future<List<List<String>>> addAppsByURL(List<String> urls,
|
||||||
|
{AppSource? sourceOverride}) async {
|
||||||
List<dynamic> results = await SourceProvider().getAppsByURLNaive(urls,
|
List<dynamic> results = await SourceProvider().getAppsByURLNaive(urls,
|
||||||
alreadyAddedUrls: apps.values.map((e) => e.app.url).toList());
|
alreadyAddedUrls: apps.values.map((e) => e.app.url).toList(),
|
||||||
|
sourceOverride: sourceOverride);
|
||||||
List<App> pps = results[0];
|
List<App> pps = results[0];
|
||||||
Map<String, dynamic> errorsMap = results[1];
|
Map<String, dynamic> errorsMap = results[1];
|
||||||
for (var app in pps) {
|
for (var app in pps) {
|
||||||
@@ -1348,19 +1448,17 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
|
|||||||
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
|
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
|
||||||
/// In update mode, all apps in toCheck are checked for updates (in parallel).
|
/// In update mode, all apps in toCheck are checked for updates (in parallel).
|
||||||
/// If an update is available and it cannot be installed silently, the user is notified of the available update.
|
/// If an update is available and it cannot be installed silently, the user is notified of the available update.
|
||||||
/// If there are any errors, the task is run again for the remaining apps after a few minutes (based on the error with the longest retry interval).
|
/// If there are any errors, we recursively call the same function with retry count for the relevant apps decremented (if zero, the user is notified).
|
||||||
/// Any app that has reached it's retry limit, the user is notified that it could not be checked.
|
|
||||||
///
|
///
|
||||||
/// Once all update checks are complete, the task is run again in install mode.
|
/// Once all update checks are complete, the task is run again in install mode.
|
||||||
/// In this mode, all pending silent updates are downloaded and installed in the background (serially - one at a time).
|
/// In this mode, all pending silent updates are downloaded (in parallel) and installed in the background.
|
||||||
/// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried.
|
/// If there is an error, the user is notified.
|
||||||
/// If an app repeatedly fails to install up to its retry limit, the user is notified.
|
|
||||||
///
|
///
|
||||||
@pragma('vm:entry-point')
|
Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
|
||||||
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
// ignore: avoid_print
|
||||||
|
print('Started $taskId: ${params.toString()}');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
await AndroidAlarmManager.initialize();
|
|
||||||
await loadTranslations();
|
await loadTranslations();
|
||||||
|
|
||||||
LogsProvider logs = LogsProvider();
|
LogsProvider logs = LogsProvider();
|
||||||
@@ -1369,11 +1467,20 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
await appsProvider.loadApps();
|
await appsProvider.loadApps();
|
||||||
|
|
||||||
int maxAttempts = 4;
|
int maxAttempts = 4;
|
||||||
|
int maxRetryWaitSeconds = 5;
|
||||||
|
|
||||||
|
var netResult = await (Connectivity().checkConnectivity());
|
||||||
|
if (netResult == ConnectivityResult.none) {
|
||||||
|
logs.add('BG update task: No network.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
params ??= {};
|
params ??= {};
|
||||||
if (params['toCheck'] == null) {
|
|
||||||
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
|
bool firstEverUpdateTask = DateTime.fromMillisecondsSinceEpoch(0)
|
||||||
}
|
.compareTo(appsProvider.settingsProvider.lastCompletedBGCheckTime) ==
|
||||||
|
0;
|
||||||
|
|
||||||
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
||||||
...(params['toCheck']
|
...(params['toCheck']
|
||||||
?.map((entry) => MapEntry<String, int>(
|
?.map((entry) => MapEntry<String, int>(
|
||||||
@@ -1381,6 +1488,11 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
.toList() ??
|
.toList() ??
|
||||||
appsProvider
|
appsProvider
|
||||||
.getAppsSortedByUpdateCheckTime(
|
.getAppsSortedByUpdateCheckTime(
|
||||||
|
ignoreAppsCheckedAfter: params['toCheck'] == null
|
||||||
|
? firstEverUpdateTask
|
||||||
|
? null
|
||||||
|
: appsProvider.settingsProvider.lastCompletedBGCheckTime
|
||||||
|
: null,
|
||||||
onlyCheckInstalledOrTrackOnlyApps: appsProvider
|
onlyCheckInstalledOrTrackOnlyApps: appsProvider
|
||||||
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
|
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
|
||||||
.map((e) => MapEntry(e, 0)))
|
.map((e) => MapEntry(e, 0)))
|
||||||
@@ -1393,51 +1505,34 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
(<List<MapEntry<String, int>>>[]))
|
(<List<MapEntry<String, int>>>[]))
|
||||||
];
|
];
|
||||||
|
|
||||||
var netResult = await (Connectivity().checkConnectivity());
|
|
||||||
|
|
||||||
if (netResult == ConnectivityResult.none) {
|
|
||||||
var networkBasedRetryInterval = 15;
|
|
||||||
var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
|
|
||||||
.add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
|
|
||||||
var potentialNetworkRetryCheck =
|
|
||||||
DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
|
|
||||||
var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
|
|
||||||
logs.add(
|
|
||||||
'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
|
|
||||||
AndroidAlarmManager.oneShot(
|
|
||||||
const Duration(minutes: 15), taskId + 1, bgUpdateCheck,
|
|
||||||
params: {
|
|
||||||
'toCheck': toCheck
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
'toInstall': toInstall
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var networkRestricted = false;
|
var networkRestricted = false;
|
||||||
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
||||||
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
||||||
(netResult != ConnectivityResult.ethernet);
|
(netResult != ConnectivityResult.ethernet);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool installMode =
|
if (toCheck.isNotEmpty) {
|
||||||
toCheck.isEmpty; // Task is either in update mode or install mode
|
// Task is either in update mode or install mode
|
||||||
|
|
||||||
logs.add(
|
|
||||||
'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
|
|
||||||
|
|
||||||
if (!installMode) {
|
|
||||||
// If in update mode, we check for updates.
|
// If in update mode, we check for updates.
|
||||||
// We divide the results into 4 groups:
|
// We divide the results into 4 groups:
|
||||||
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
|
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
|
||||||
// - toRetry - Apps with update check errors that will be retried in a while
|
|
||||||
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
|
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
|
||||||
// After grouping the updates, we take care of toNotify and toThrow first
|
// After grouping the updates, we take care of toNotify and toThrow first
|
||||||
// Then if toRetry is not empty, we schedule another update task to run in a while
|
// Then we run the function again in install mode (toCheck is empty)
|
||||||
// If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty)
|
|
||||||
|
var enoughTimePassed = appsProvider.settingsProvider.updateInterval != 0 &&
|
||||||
|
appsProvider.settingsProvider.lastCompletedBGCheckTime
|
||||||
|
.add(
|
||||||
|
Duration(minutes: appsProvider.settingsProvider.updateInterval))
|
||||||
|
.isBefore(DateTime.now());
|
||||||
|
if (!enoughTimePassed) {
|
||||||
|
// ignore: avoid_print
|
||||||
|
print(
|
||||||
|
'BG update task: Too early for another check (last check was ${appsProvider.settingsProvider.lastCompletedBGCheckTime.toIso8601String()}, interval is ${appsProvider.settingsProvider.updateInterval}).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logs.add('BG update task: Started (${toCheck.length}).');
|
||||||
|
|
||||||
// Init. vars.
|
// Init. vars.
|
||||||
List<App> updates = []; // All updates found (silent and non-silent)
|
List<App> updates = []; // All updates found (silent and non-silent)
|
||||||
@@ -1445,8 +1540,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
[]; // All non-silent updates that the user will be notified about
|
[]; // All non-silent updates that the user will be notified about
|
||||||
List<MapEntry<String, int>> toRetry =
|
List<MapEntry<String, int>> toRetry =
|
||||||
[]; // All apps that got errors while checking
|
[]; // All apps that got errors while checking
|
||||||
var retryAfterXSeconds =
|
var retryAfterXSeconds = 0;
|
||||||
0; // How long to wait until the next attempt (if there are errors)
|
|
||||||
MultiAppMultiError?
|
MultiAppMultiError?
|
||||||
errors; // All errors including those that will lead to a retry
|
errors; // All errors including those that will lead to a retry
|
||||||
MultiAppMultiError toThrow =
|
MultiAppMultiError toThrow =
|
||||||
@@ -1469,27 +1563,32 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
specificIds: toCheck.map((e) => e.key).toList(),
|
specificIds: toCheck.map((e) => e.key).toList(),
|
||||||
sp: appsProvider.settingsProvider);
|
sp: appsProvider.settingsProvider);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If there were errors, group them into toRetry and toThrow based on max retry count per app
|
|
||||||
if (e is Map) {
|
if (e is Map) {
|
||||||
updates = e['updates'];
|
updates = e['updates'];
|
||||||
errors = e['errors'];
|
errors = e['errors'];
|
||||||
errors!.rawErrors.forEach((key, err) {
|
errors!.rawErrors.forEach((key, err) {
|
||||||
logs.add(
|
logs.add(
|
||||||
'BG update task $taskId: Got error on checking for $key \'${err.toString()}\'.');
|
'BG update task: Got error on checking for $key \'${err.toString()}\'.');
|
||||||
|
|
||||||
var toCheckApp = toCheck.where((element) => element.key == key).first;
|
var toCheckApp = toCheck.where((element) => element.key == key).first;
|
||||||
if (toCheckApp.value < maxAttempts) {
|
if (toCheckApp.value < maxAttempts) {
|
||||||
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
|
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
|
||||||
// Next task interval is based on the error with the longest retry time
|
// Next task interval is based on the error with the longest retry time
|
||||||
var minRetryIntervalForThisApp = err is RateLimitError
|
int minRetryIntervalForThisApp = err is RateLimitError
|
||||||
? (err.remainingMinutes * 60)
|
? (err.remainingMinutes * 60)
|
||||||
: e is ClientException
|
: e is ClientException
|
||||||
? (15 * 60)
|
? (15 * 60)
|
||||||
: pow(toCheckApp.value + 1, 2).toInt();
|
: (toCheckApp.value + 1);
|
||||||
|
if (minRetryIntervalForThisApp > maxRetryWaitSeconds) {
|
||||||
|
minRetryIntervalForThisApp = maxRetryWaitSeconds;
|
||||||
|
}
|
||||||
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
|
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
|
||||||
retryAfterXSeconds = minRetryIntervalForThisApp;
|
retryAfterXSeconds = minRetryIntervalForThisApp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
if (err is! RateLimitError) {
|
||||||
|
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -1524,37 +1623,32 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
id: Random().nextInt(10000)));
|
id: Random().nextInt(10000)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there are update checks to retry, schedule a retry task
|
// if there are update checks to retry, schedule a retry task
|
||||||
|
logs.add('BG update task: Done checking for updates.');
|
||||||
if (toRetry.isNotEmpty) {
|
if (toRetry.isNotEmpty) {
|
||||||
logs.add(
|
logs.add(
|
||||||
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
|
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
|
||||||
AndroidAlarmManager.oneShot(
|
return await bgUpdateCheck(taskId, {
|
||||||
Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck,
|
'toCheck': toRetry
|
||||||
params: {
|
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||||
'toCheck': toRetry
|
.toList(),
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
'toInstall': toInstall
|
||||||
.toList(),
|
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||||
'toInstall': toInstall
|
.toList(),
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
});
|
||||||
.toList(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// If there are no more update checks, schedule an install task
|
// If there are no more update checks, call the function in install mode
|
||||||
logs.add(
|
logs.add('BG update task: Done checking for updates.');
|
||||||
'BG update task $taskId: Done. Scheduling install task to run immediately.');
|
return await bgUpdateCheck(taskId, {
|
||||||
AndroidAlarmManager.oneShot(
|
'toCheck': [],
|
||||||
const Duration(minutes: 0), taskId + 1, bgUpdateCheck,
|
'toInstall': toInstall
|
||||||
params: {
|
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||||
'toCheck': [],
|
.toList()
|
||||||
'toInstall': toInstall
|
});
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In install mode...
|
// In install mode...
|
||||||
// If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates
|
// If you haven't explicitly been given updates to install, grab all available silent updates
|
||||||
if (toInstall.isEmpty && !networkRestricted) {
|
if (toInstall.isEmpty && !networkRestricted) {
|
||||||
var temp = appsProvider.findExistingUpdates(installedOnly: true);
|
var temp = appsProvider.findExistingUpdates(installedOnly: true);
|
||||||
for (var i = 0; i < temp.length; i++) {
|
for (var i = 0; i < temp.length; i++) {
|
||||||
@@ -1564,60 +1658,34 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var didCompleteInstalling = false;
|
if (toInstall.isNotEmpty) {
|
||||||
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
|
logs.add('BG install task: Started (${toInstall.length}).');
|
||||||
if (tempObtArr.isNotEmpty) {
|
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
|
||||||
// Move obtainium to the end of the list as it must always install last
|
if (tempObtArr.isNotEmpty) {
|
||||||
var obt = tempObtArr.first;
|
// Move obtainium to the end of the list as it must always install last
|
||||||
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
|
var obt = tempObtArr.first;
|
||||||
}
|
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
|
||||||
// Loop through all updates and install each
|
}
|
||||||
for (var i = 0; i < toInstall.length; i++) {
|
// Loop through all updates and install each
|
||||||
var appId = toInstall[i].key;
|
|
||||||
var retryCount = toInstall[i].value;
|
|
||||||
try {
|
try {
|
||||||
logs.add(
|
await appsProvider.downloadAndInstallLatestApps(
|
||||||
'BG install task $taskId: Attempting to update $appId in the background.');
|
toInstall.map((e) => e.key).toList(), null,
|
||||||
await appsProvider.downloadAndInstallLatestApps([appId], null,
|
notificationsProvider: notificationsProvider,
|
||||||
notificationsProvider: notificationsProvider);
|
forceParallelDownloads: true);
|
||||||
await Future.delayed(const Duration(
|
|
||||||
seconds:
|
|
||||||
5)); // Just in case task ending causes install fail (not clear)
|
|
||||||
if (i == (toCheck.length - 1)) {
|
|
||||||
didCompleteInstalling = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly
|
if (e is MultiAppMultiError) {
|
||||||
logs.add(
|
e.idsByErrorString.forEach((key, value) {
|
||||||
'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.');
|
notificationsProvider.notify(ErrorCheckingUpdatesNotification(
|
||||||
if (retryCount < maxAttempts) {
|
e.errorsAppsString(key, value)));
|
||||||
var remainingSeconds = retryCount;
|
});
|
||||||
logs.add(
|
|
||||||
'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
|
|
||||||
var remainingToInstall = moveStrToEndMapEntryWithCount(
|
|
||||||
toInstall.sublist(i), MapEntry(appId, retryCount + 1));
|
|
||||||
AndroidAlarmManager.oneShot(
|
|
||||||
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck,
|
|
||||||
params: {
|
|
||||||
'toCheck': toCheck
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
'toInstall': remainingToInstall
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
|
// We don't expect to ever get here in any situation so no need to catch (but log it in case)
|
||||||
toInstall.removeAt(i);
|
logs.add('Fatal error in BG install task: ${e.toString()}');
|
||||||
i--;
|
rethrow;
|
||||||
notificationsProvider
|
|
||||||
.notify(ErrorCheckingUpdatesNotification(e.toString()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
logs.add('BG install task: Done installing updates.');
|
||||||
if (didCompleteInstalling || toInstall.isEmpty) {
|
|
||||||
logs.add('BG install task $taskId: Done.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
appsProvider.settingsProvider.lastCompletedBGCheckTime = DateTime.now();
|
||||||
}
|
}
|
||||||
|
56
lib/providers/installers_provider.dart
Normal file
56
lib/providers/installers_provider.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class Installers {
|
||||||
|
static const MethodChannel _channel = MethodChannel('installers');
|
||||||
|
static bool _callbacksApplied = false;
|
||||||
|
static int _resPermShizuku = -2; // not set
|
||||||
|
|
||||||
|
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 handleCalls(MethodCall call) async {
|
||||||
|
if (call.method == 'resPermShizuku') {
|
||||||
|
_resPermShizuku = call.arguments['res'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,8 @@ import 'package:shared_storage/shared_storage.dart' as saf;
|
|||||||
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
|
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
|
||||||
String obtainiumId = 'dev.imranr.obtainium';
|
String obtainiumId = 'dev.imranr.obtainium';
|
||||||
|
|
||||||
|
enum InstallMethodSettings { normal, shizuku, root }
|
||||||
|
|
||||||
enum ThemeSettings { system, light, dark }
|
enum ThemeSettings { system, light, dark }
|
||||||
|
|
||||||
enum ColourSettings { basic, materialYou }
|
enum ColourSettings { basic, materialYou }
|
||||||
@@ -49,6 +51,16 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstallMethodSettings get installMethod {
|
||||||
|
return InstallMethodSettings.values[
|
||||||
|
prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
set installMethod(InstallMethodSettings t) {
|
||||||
|
prefs?.setInt('installMethod', t.index);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
ThemeSettings get theme {
|
ThemeSettings get theme {
|
||||||
return ThemeSettings
|
return ThemeSettings
|
||||||
.values[prefs?.getInt('theme') ?? ThemeSettings.system.index];
|
.values[prefs?.getInt('theme') ?? ThemeSettings.system.index];
|
||||||
@@ -213,7 +225,8 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String? getSettingString(String settingId) {
|
String? getSettingString(String settingId) {
|
||||||
return prefs?.getString(settingId);
|
String? str = prefs?.getString(settingId);
|
||||||
|
return str?.isNotEmpty == true ? str : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSettingString(String settingId, String value) {
|
void setSettingString(String settingId, String value) {
|
||||||
@@ -332,15 +345,15 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime get lastBGCheckTime {
|
DateTime get lastCompletedBGCheckTime {
|
||||||
int? temp = prefs?.getInt('lastBGCheckTime');
|
int? temp = prefs?.getInt('lastCompletedBGCheckTime');
|
||||||
return temp != null
|
return temp != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(temp)
|
? DateTime.fromMillisecondsSinceEpoch(temp)
|
||||||
: DateTime.fromMillisecondsSinceEpoch(0);
|
: DateTime.fromMillisecondsSinceEpoch(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
set lastBGCheckTime(DateTime val) {
|
set lastCompletedBGCheckTime(DateTime val) {
|
||||||
prefs?.setInt('lastBGCheckTime', val.millisecondsSinceEpoch);
|
prefs?.setInt('lastCompletedBGCheckTime', val.millisecondsSinceEpoch);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,4 +428,22 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
prefs?.setBool('onlyCheckInstalledOrTrackOnlyApps', val);
|
prefs?.setBool('onlyCheckInstalledOrTrackOnlyApps', val);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get exportSettings {
|
||||||
|
return prefs?.getBool('exportSettings') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set exportSettings(bool val) {
|
||||||
|
prefs?.setBool('exportSettings', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get parallelDownloads {
|
||||||
|
return prefs?.getBool('parallelDownloads') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set parallelDownloads(bool val) {
|
||||||
|
prefs?.setBool('parallelDownloads', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -67,10 +67,11 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
|||||||
.reduce((value, element) => [...value, ...element]);
|
.reduce((value, element) => [...value, ...element]);
|
||||||
Map<String, dynamic> additionalSettings =
|
Map<String, dynamic> additionalSettings =
|
||||||
getDefaultValuesFromFormItems([formItems]);
|
getDefaultValuesFromFormItems([formItems]);
|
||||||
|
Map<String, dynamic> originalAdditionalSettings = {};
|
||||||
if (json['additionalSettings'] != null) {
|
if (json['additionalSettings'] != null) {
|
||||||
additionalSettings.addEntries(
|
originalAdditionalSettings =
|
||||||
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']))
|
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']));
|
||||||
.entries);
|
additionalSettings.addEntries(originalAdditionalSettings.entries);
|
||||||
}
|
}
|
||||||
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
|
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
|
||||||
if (json['additionalData'] != null) {
|
if (json['additionalData'] != null) {
|
||||||
@@ -134,6 +135,35 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
|||||||
if (additionalSettings['autoApkFilterByArch'] == null) {
|
if (additionalSettings['autoApkFilterByArch'] == null) {
|
||||||
additionalSettings['autoApkFilterByArch'] = false;
|
additionalSettings['autoApkFilterByArch'] = false;
|
||||||
}
|
}
|
||||||
|
if (source.runtimeType == HTML().runtimeType) {
|
||||||
|
// HTML 'fixed URL' support should be disabled if it previously did not exist
|
||||||
|
if (originalAdditionalSettings['supportFixedAPKURL'] == null) {
|
||||||
|
additionalSettings['supportFixedAPKURL'] = false;
|
||||||
|
}
|
||||||
|
// HTML key rename
|
||||||
|
if (originalAdditionalSettings['sortByFileNamesNotLinks'] != null) {
|
||||||
|
additionalSettings['sortByLastLinkSegment'] =
|
||||||
|
originalAdditionalSettings['sortByFileNamesNotLinks'];
|
||||||
|
}
|
||||||
|
// HTML single 'intermediate link' should be converted to multi-support version
|
||||||
|
if (originalAdditionalSettings['intermediateLinkRegex'] != null &&
|
||||||
|
additionalSettings['intermediateLinkRegex']?.isNotEmpty != true) {
|
||||||
|
additionalSettings['intermediateLink'] = [
|
||||||
|
{
|
||||||
|
'customLinkFilterRegex':
|
||||||
|
originalAdditionalSettings['intermediateLinkRegex'],
|
||||||
|
'filterByLinkText':
|
||||||
|
originalAdditionalSettings['intermediateLinkByText']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ((additionalSettings['intermediateLink']?.length ?? 0) > 0) {
|
||||||
|
additionalSettings['intermediateLink'] =
|
||||||
|
additionalSettings['intermediateLink'].where((e) {
|
||||||
|
return e['customLinkFilterRegex']?.isNotEmpty == true;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
json['additionalSettings'] = jsonEncode(additionalSettings);
|
json['additionalSettings'] = jsonEncode(additionalSettings);
|
||||||
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
||||||
// This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid)
|
// This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid)
|
||||||
@@ -448,7 +478,8 @@ abstract class AppSource {
|
|||||||
[
|
[
|
||||||
GeneratedFormSwitch('skipUpdateNotifications',
|
GeneratedFormSwitch('skipUpdateNotifications',
|
||||||
label: tr('skipUpdateNotifications'))
|
label: tr('skipUpdateNotifications'))
|
||||||
]
|
],
|
||||||
|
[GeneratedFormTextField('about', label: tr('about'), required: false)]
|
||||||
];
|
];
|
||||||
|
|
||||||
// Previous 2 variables combined into one at runtime for convenient usage
|
// Previous 2 variables combined into one at runtime for convenient usage
|
||||||
@@ -596,7 +627,7 @@ class SourceProvider {
|
|||||||
AppSource? source;
|
AppSource? source;
|
||||||
for (var s in sources.where((element) => element.host != null)) {
|
for (var s in sources.where((element) => element.host != null)) {
|
||||||
if (RegExp(
|
if (RegExp(
|
||||||
'://(${s.allowSubDomains ? '([^\\.]+\\.)*' : ''}|www\\.)${s.host}(/|\\z)?')
|
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}${s.host}(/|\\z)?')
|
||||||
.hasMatch(url)) {
|
.hasMatch(url)) {
|
||||||
source = s;
|
source = s;
|
||||||
break;
|
break;
|
||||||
@@ -677,8 +708,9 @@ class SourceProvider {
|
|||||||
name = name.isNotEmpty ? name : apk.names.name;
|
name = name.isNotEmpty ? name : apk.names.name;
|
||||||
App finalApp = App(
|
App finalApp = App(
|
||||||
currentApp?.id ??
|
currentApp?.id ??
|
||||||
((!source.appIdInferIsOptional ||
|
(!trackOnly &&
|
||||||
(source.appIdInferIsOptional && inferAppIdIfOptional))
|
(!source.appIdInferIsOptional ||
|
||||||
|
(source.appIdInferIsOptional && inferAppIdIfOptional))
|
||||||
? await source.tryInferringAppId(standardUrl,
|
? await source.tryInferringAppId(standardUrl,
|
||||||
additionalSettings: additionalSettings)
|
additionalSettings: additionalSettings)
|
||||||
: null) ??
|
: null) ??
|
||||||
@@ -698,15 +730,17 @@ class SourceProvider {
|
|||||||
changeLog: apk.changeLog,
|
changeLog: apk.changeLog,
|
||||||
overrideSource: overrideSource ?? currentApp?.overrideSource,
|
overrideSource: overrideSource ?? currentApp?.overrideSource,
|
||||||
allowIdChange: currentApp?.allowIdChange ??
|
allowIdChange: currentApp?.allowIdChange ??
|
||||||
source.appIdInferIsOptional &&
|
trackOnly ||
|
||||||
inferAppIdIfOptional // Optional ID inferring may be incorrect - allow correction on first install
|
(source.appIdInferIsOptional &&
|
||||||
|
inferAppIdIfOptional) // Optional ID inferring may be incorrect - allow correction on first install
|
||||||
);
|
);
|
||||||
return source.endOfGetAppChanges(finalApp);
|
return source.endOfGetAppChanges(finalApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns errors in [results, errors] instead of throwing them
|
// Returns errors in [results, errors] instead of throwing them
|
||||||
Future<List<dynamic>> getAppsByURLNaive(List<String> urls,
|
Future<List<dynamic>> getAppsByURLNaive(List<String> urls,
|
||||||
{List<String> alreadyAddedUrls = const []}) async {
|
{List<String> alreadyAddedUrls = const [],
|
||||||
|
AppSource? sourceOverride}) async {
|
||||||
List<App> apps = [];
|
List<App> apps = [];
|
||||||
Map<String, dynamic> errors = {};
|
Map<String, dynamic> errors = {};
|
||||||
for (var url in urls) {
|
for (var url in urls) {
|
||||||
@@ -714,7 +748,7 @@ class SourceProvider {
|
|||||||
if (alreadyAddedUrls.contains(url)) {
|
if (alreadyAddedUrls.contains(url)) {
|
||||||
throw ObtainiumError(tr('appAlreadyAdded'));
|
throw ObtainiumError(tr('appAlreadyAdded'));
|
||||||
}
|
}
|
||||||
var source = getSource(url);
|
var source = sourceOverride ?? getSource(url);
|
||||||
apps.add(await getApp(
|
apps.add(await getApp(
|
||||||
source,
|
source,
|
||||||
url,
|
url,
|
||||||
|
218
pubspec.lock
218
pubspec.lock
@@ -1,14 +1,6 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
android_alarm_manager_plus:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: android_alarm_manager_plus
|
|
||||||
sha256: "82fb28c867c4b3dd7e9157728e46426b8916362f977dbba46b949210f00099f4"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.3"
|
|
||||||
android_intent_plus:
|
android_intent_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -38,18 +30,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: animations
|
name: animations
|
||||||
sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70
|
sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.0.10"
|
||||||
|
app_links:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: app_links
|
||||||
|
sha256: "4e392b5eba997df356ca6021f28431ce1cfeb16758699553a94b13add874a3bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.5.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03"
|
sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.6"
|
version: "3.4.9"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -66,6 +66,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
|
background_fetch:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: background_fetch
|
||||||
|
sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -94,10 +102,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cli_util
|
name: cli_util
|
||||||
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
|
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -110,18 +118,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.2"
|
version: "1.18.0"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: connectivity_plus
|
name: connectivity_plus
|
||||||
sha256: b502a681ba415272ecc41400bd04fe543ed1a62632137dc84d25a91e7746f55f
|
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.2"
|
||||||
connectivity_plus_platform_interface:
|
connectivity_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -142,12 +150,12 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c"
|
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+6"
|
version: "0.3.3+8"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
@@ -174,18 +182,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
|
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.10"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419"
|
sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.0"
|
version: "9.1.1"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -198,10 +206,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dynamic_color
|
name: dynamic_color
|
||||||
sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f"
|
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.8"
|
version: "1.6.9"
|
||||||
easy_localization:
|
easy_localization:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -246,10 +254,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "903dd4ba13eae7cef64acc480e91bf54c3ddd23b5b90b639c170f3911e489620"
|
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -259,10 +267,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_archive
|
name: flutter_archive
|
||||||
sha256: aec85d1da65e5b33a529db00a86df0b8e92bda78088a7cfaeeba5187701d0d85
|
sha256: "004132780d382df5171589ab793e2efc9c3eef570fe72d78b4ccfbfbe52762ae"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "6.0.0"
|
||||||
flutter_fgbg:
|
flutter_fgbg:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -291,10 +299,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3"
|
sha256: "892ada16046d641263f30c72e7432397088810a84f34479f6677494802a2b535"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "16.1.0"
|
version: "16.3.0"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -320,10 +328,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: "8afc9a6aa6d8e8063523192ba837149dbf3d377a37c0b0fc579149a1fbd4a619"
|
sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.18"
|
version: "0.6.18+2"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -346,10 +354,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fluttertoast
|
name: fluttertoast
|
||||||
sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c"
|
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.2"
|
version: "8.2.4"
|
||||||
|
gtk:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gtk
|
||||||
|
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
hsluv:
|
hsluv:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -370,10 +386,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.2"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -450,10 +466,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -498,10 +514,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -538,50 +554,58 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8"
|
sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.1"
|
version: "11.1.0"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e
|
sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.1.0"
|
version: "12.0.1"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.4"
|
version: "9.2.0"
|
||||||
|
permission_handler_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_html
|
||||||
|
sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0+2"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.12.0"
|
version: "4.0.2"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_windows
|
name: permission_handler_windows
|
||||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.2.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.0"
|
version: "6.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -594,10 +618,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.6"
|
version: "2.1.7"
|
||||||
pointycastle:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -610,10 +634,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.5"
|
version: "6.1.1"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -674,10 +698,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
|
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -727,26 +751,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
|
sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.0+2"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -759,10 +783,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0+1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -775,10 +799,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.1"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -799,10 +823,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
|
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
version: "6.2.2"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -815,18 +839,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a"
|
sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.0"
|
version: "6.2.1"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd"
|
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -847,26 +871,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2"
|
sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.2"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc"
|
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: b715b8d3858b6fa9f68f87d20d98830283628014750c2b09b6f516c1da4af2a7
|
sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.2.2"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -879,10 +903,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4-beta"
|
version: "0.3.0"
|
||||||
webview_flutter:
|
webview_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -895,34 +919,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
sha256: "8326ee235f87605a2bfc444a4abc897f4abc78d83f054ba7d3d1074ce82b4fbf"
|
sha256: b54c89fe14a6d26a2a46e24880da0441cdd2bf1f6d01a5b3e1d39558feb1de0b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.12.1"
|
version: "3.13.1"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_platform_interface
|
name: webview_flutter_platform_interface
|
||||||
sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f"
|
sha256: dbe745ee459a16b6fec296f7565a8ef430d0d681001d8ae521898b9361854943
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.0"
|
version: "2.9.0"
|
||||||
webview_flutter_wkwebview:
|
webview_flutter_wkwebview:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_wkwebview
|
name: webview_flutter_wkwebview
|
||||||
sha256: af6f5ab05918070b33507b0d453ba9fb7d39338a3256c23cf9433dc68100774a
|
sha256: eebfabfa8a115b535b52031b8b26f7a4b58ceceab378bc9db8762b0fb46f7b5d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.9.3"
|
version: "3.10.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.9"
|
version: "5.1.1"
|
||||||
win32_registry:
|
win32_registry:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -943,10 +967,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.5.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -956,5 +980,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.0 <4.0.0"
|
dart: ">=3.2.0 <4.0.0"
|
||||||
flutter: ">=3.13.0"
|
flutter: ">=3.16.0"
|
||||||
|
@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.14.32+226 # When changing this, update the tag in main() accordingly
|
version: 0.15.2+238 # When changing this, update the tag in main() accordingly
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -57,15 +57,17 @@ dependencies:
|
|||||||
ref: main
|
ref: main
|
||||||
android_package_manager: ^0.6.0
|
android_package_manager: ^0.6.0
|
||||||
share_plus: ^7.0.0
|
share_plus: ^7.0.0
|
||||||
android_alarm_manager_plus: ^3.0.0
|
|
||||||
sqflite: ^2.2.0+3
|
sqflite: ^2.2.0+3
|
||||||
easy_localization: ^3.0.1
|
easy_localization: ^3.0.1
|
||||||
android_intent_plus: ^4.0.0
|
android_intent_plus: ^4.0.0
|
||||||
flutter_markdown: ^0.6.14
|
flutter_markdown: ^0.6.14
|
||||||
flutter_archive: ^5.0.0
|
flutter_archive: ^6.0.0
|
||||||
hsluv: ^1.1.3
|
hsluv: ^1.1.3
|
||||||
connectivity_plus: ^5.0.0
|
connectivity_plus: ^5.0.0
|
||||||
shared_storage: ^0.8.0
|
shared_storage: ^0.8.0
|
||||||
|
crypto: ^3.0.3
|
||||||
|
app_links: ^3.5.0
|
||||||
|
background_fetch: ^1.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user