Merge branch 'main' into room-entity

This commit is contained in:
LooKeR 2025-06-08 17:23:36 +05:30 committed by GitHub
commit 22a1b09d2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
146 changed files with 2237 additions and 2443 deletions

View File

@ -6,7 +6,6 @@ insert_final_newline = true
trim_trailing_whitespace = true
[*.{kt,kts}]
ktlint_code_style = android_studio
indent_size = 4
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true

View File

@ -48,9 +48,6 @@ jobs:
- name: Grant execution permission to Gradle Wrapper
run: chmod +x gradlew
- name: Format Code
run: ./gradlew ktlintFormat
- name: Build Debug APK
run: ./gradlew assembleDebug

View File

@ -56,12 +56,34 @@ jobs:
keyStorePassword: ${{ secrets.KEYSTORE_PASS }}
keyPassword: ${{ secrets.KEYSTORE_PASS }}
env:
BUILD_TOOLS_VERSION: "34.0.0"
BUILD_TOOLS_VERSION: "35.0.0"
- name: Extract Version Code
id: extract_version
run: |
VERSION_CODE=$(grep -oP '(?<=versionCode=)\d+' app/build.gradle.kts) # Adjust path to your build.gradle
echo "::set-output name=version_code::$VERSION_CODE"
echo "Version Code: $VERSION_CODE"
- name: Read Changelog
id: read_changelog
run: |
CHANGELOG_PATH="metadata/en-US/changelogs/${{ steps.extract_version.outputs.version_code }}.txt"
if [[ -f "$CHANGELOG_PATH" ]]; then
CHANGELOG=$(cat "$CHANGELOG_PATH")
echo "::set-output name=changelog::$CHANGELOG"
else
echo "::set-output name=changelog::No changelog found for this version."
echo "No changelog found at: $CHANGELOG_PATH"
fi
- uses: softprops/action-gh-release@v2
name: Create Release
id: publish_release
with:
body: ${{ steps.read_changelog.outputs.changelog }}
tag_name: ${{ github.ref }}
name: Release ${{ github.ref }}
files: ${{steps.sign_app.outputs.signedReleaseFile}}
draft: true
prerelease: false

View File

@ -7,7 +7,7 @@
[![Github Downloads](https://img.shields.io/github/downloads/Iamlooker/Droid-ify/total.svg?color=%23f5ad64&style=for-the-badge)](https://github.com/Iamlooker/Droid-ify/releases/)
[![Github Latest](https://img.shields.io/github/v/release/Iamlooker/Droid-ify?display_name=tag&color=%23f5ad64&style=for-the-badge)](https://github.com/Iamlooker/Droid-ify/releases/latest)
[![FDroid Latest](https://img.shields.io/f-droid/v/com.looker.droidify?color=%23f5ad64&style=for-the-badge)](https://f-droid.org/packages/com.looker.droidify)
</div>
<div align="left">
## Features
@ -22,8 +22,10 @@
<img src="metadata/en-US/images/phoneScreenshots/1.png" width="25%" /><img src="metadata/en-US/images/phoneScreenshots/2.png" width="25%" /><img src="metadata/en-US/images/phoneScreenshots/3.png" width="25%" /><img src="metadata/en-US/images/phoneScreenshots/4.png" width="25%" />
## Building and Installing
1. **Install Android Studio**:
- Download and install [Android Studio](https://developer.android.com/studio) on your computer if you haven't already.
- Download and install [Android Studio](https://developer.android.com/studio) on your computer
if you haven't already.
2. **Clone the Repository**:
- Open Android Studio and select "Project from Version Control."
@ -48,6 +50,7 @@
- Your PR will undergo review
## Translations
[![Translation status](https://hosted.weblate.org/widgets/droidify/-/horizontal-auto.svg)](https://hosted.weblate.org/engage/droidify/?utm_source=widget)
## License
@ -67,3 +70,5 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
</div>

View File

@ -10,7 +10,7 @@ plugins {
}
android {
val latestVersionName = "0.6.4"
val latestVersionName = "0.6.5"
namespace = "com.looker.droidify"
buildToolsVersion = "35.0.0"
compileSdk = 35
@ -18,7 +18,7 @@ android {
minSdk = 23
targetSdk = 35
applicationId = "com.looker.droidify"
versionCode = 640
versionCode = 650
versionName = latestVersionName
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner = "com.looker.droidify.TestRunner"
@ -32,7 +32,12 @@ android {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf("-Xcontext-receivers")
freeCompilerArgs = listOf(
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-Xcontext-receivers"
)
}
ksp {
@ -45,11 +50,11 @@ android {
}
buildTypes {
getByName("debug") {
debug {
applicationIdSuffix = ".debug"
resValue("string", "application_name", "Droid-ify-Debug")
}
getByName("release") {
release {
isMinifyEnabled = true
isShrinkResources = true
resValue("string", "application_name", "Droid-ify")
@ -79,7 +84,16 @@ android {
}
packaging {
resources {
excludes += listOf("/DebugProbesKt.bin")
excludes += listOf(
"/DebugProbesKt.bin",
"/kotlin/**.kotlin_builtins",
"/kotlin/**.kotlin_metadata",
"/META-INF/**.kotlin_module",
"/META-INF/**.pro",
"/META-INF/**.version",
"/META-INF/{AL2.0,LGPL2.1,LICENSE*}",
"/META-INF/versions/9/previous-**.bin",
)
}
}
buildFeatures {
@ -87,6 +101,10 @@ android {
viewBinding = true
buildConfig = true
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
}
dependencies {
@ -102,7 +120,7 @@ dependencies {
implementation(libs.sqlite.ktx)
implementation(libs.image.viewer)
implementation(libs.coil.kt)
implementation(libs.bundles.coil)
implementation(libs.datastore.core)
implementation(libs.datastore.proto)

65
app/proguard.pro vendored
View File

@ -7,68 +7,3 @@
-dontwarn kotlinx.serialization.KSerializer
-dontwarn kotlinx.serialization.Serializable
-dontwarn org.slf4j.impl.StaticLoggerBinder
#
#-dontwarn com.looker.core.common.BuildConfig
#-dontwarn com.looker.core.common.DeeplinkType$AddRepository
#-dontwarn com.looker.core.common.DeeplinkType$AppDetail
#-dontwarn com.looker.core.common.DeeplinkType
#-dontwarn com.looker.core.common.DeeplinksKt
#-dontwarn com.looker.core.common.Exporter
#-dontwarn com.looker.core.common.NotificationKt
#-dontwarn com.looker.core.common.PermissionsKt
#-dontwarn com.looker.core.common.Scroller
#-dontwarn com.looker.core.common.SdkCheck
#-dontwarn com.looker.core.common.Singleton
#-dontwarn com.looker.core.common.TextKt
#-dontwarn com.looker.core.common.cache.Cache
#-dontwarn com.looker.core.common.cache.Cache
#-dontwarn com.looker.core.common.device.Huawei
#-dontwarn com.looker.core.common.extension.ContextKt
#-dontwarn com.looker.core.common.extension.CursorKt
#-dontwarn com.looker.core.common.extension.DateTimeKt
#-dontwarn com.looker.core.common.extension.FingerprintKt
#-dontwarn com.looker.core.common.extension.FlowKt
#-dontwarn com.looker.core.common.extension.InsetsKt
#-dontwarn com.looker.core.common.extension.IntentKt
#-dontwarn com.looker.core.common.extension.Json
#-dontwarn com.looker.core.common.extension.JsonKt
#-dontwarn com.looker.core.common.extension.KeyToken
#-dontwarn com.looker.core.common.extension.LocaleKt
#-dontwarn com.looker.core.common.extension.NumberKt
#-dontwarn com.looker.core.common.extension.PackageInfoKt
#-dontwarn com.looker.core.common.extension.SQLiteDatabaseKt
#-dontwarn com.looker.core.common.extension.ServiceKt
#-dontwarn com.looker.core.common.extension.ViewKt
#-dontwarn com.looker.core.common.result.Result$Error
#-dontwarn com.looker.core.common.result.Result$Success
#-dontwarn com.looker.core.common.result.Result
#-dontwarn com.looker.core.common.signature.Hash
#-dontwarn com.looker.core.common.signature.HashCheckerKt
#
#-dontwarn com.looker.core.datastore.Settings
#-dontwarn com.looker.core.datastore.SettingsRepository
#-dontwarn com.looker.core.datastore.di.DatastoreModule_ProvidePreferenceDatastoreFactory
#-dontwarn com.looker.core.datastore.di.DatastoreModule_ProvideProtoDatastoreFactory
#-dontwarn com.looker.core.datastore.di.DatastoreModule_ProvideSettingsExporterFactory
#-dontwarn com.looker.core.datastore.di.DatastoreModule_ProvideSettingsRepositoryFactory
#-dontwarn com.looker.core.datastore.extension.PreferencesKt
#-dontwarn com.looker.core.datastore.model.AutoSync
#-dontwarn com.looker.core.datastore.model.InstallerType$Companion
#-dontwarn com.looker.core.datastore.model.InstallerType
#-dontwarn com.looker.core.datastore.model.ProxyPreference
#-dontwarn com.looker.core.datastore.model.ProxyType
#-dontwarn com.looker.core.datastore.model.SortOrder
#-dontwarn com.looker.core.datastore.model.Theme
#
#-dontwarn com.looker.installer.InstallManager
#-dontwarn com.looker.installer.InstallModule_ProvideRootPermissionHandlerFactory
#-dontwarn com.looker.installer.InstallModule_ProvideShizukuPermissionHandlerFactory
#-dontwarn com.looker.installer.InstallModule_ProvidesInstallerFactory
#-dontwarn com.looker.installer.installers.root.RootPermissionHandler
#-dontwarn com.looker.installer.installers.session.SessionInstallerReceiver_GeneratedInjector
#-dontwarn com.looker.installer.installers.shizuku.ShizukuPermissionHandler$State
#-dontwarn com.looker.installer.installers.shizuku.ShizukuPermissionHandler
#-dontwarn com.looker.installer.model.InstallItem
#-dontwarn com.looker.installer.model.InstallItemKt
#-dontwarn com.looker.installer.model.InstallState
#-dontwarn com.looker.installer.notification.InstallNotificationKt

View File

@ -1,7 +1,6 @@
package com.looker.droidify.sync.common
import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
import java.io.InputStream
fun assets(name: String): InputStream {

View File

@ -50,7 +50,6 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.SHOW_APP_INFO" />

View File

@ -13,10 +13,14 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import androidx.work.NetworkType
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.asImage
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.memory.MemoryCache
import coil3.request.crossfade
import com.looker.droidify.content.ProductPreferences
import com.looker.droidify.database.Database
import com.looker.droidify.datastore.SettingsRepository
@ -35,6 +39,7 @@ import com.looker.droidify.sync.toJobNetworkType
import com.looker.droidify.utility.common.Constants
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.cache.Cache
import com.looker.droidify.utility.common.extension.getDrawableCompat
import com.looker.droidify.utility.common.extension.getInstalledPackagesCompat
import com.looker.droidify.utility.common.extension.jobScheduler
import com.looker.droidify.utility.common.log
@ -55,7 +60,7 @@ import kotlin.time.Duration.Companion.INFINITE
import kotlin.time.Duration.Companion.hours
@HiltAndroidApp
class Droidify : Application(), ImageLoaderFactory, Configuration.Provider {
class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Provider {
private val parentJob = SupervisorJob()
private val appScope = CoroutineScope(Dispatchers.Default + parentJob)
@ -221,9 +226,14 @@ class Droidify : Application(), ImageLoaderFactory, Configuration.Provider {
override fun onReceive(context: Context, intent: Intent) = Unit
}
override fun newImageLoader(): ImageLoader {
val memoryCache = MemoryCache.Builder(this)
.maxSizePercent(0.25)
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override fun newImageLoader(context: PlatformContext): ImageLoader {
val memoryCache = MemoryCache.Builder()
.maxSizePercent(context, 0.25)
.build()
val diskCache = DiskCache.Builder()
@ -234,15 +244,10 @@ class Droidify : Application(), ImageLoaderFactory, Configuration.Provider {
return ImageLoader.Builder(this)
.memoryCache(memoryCache)
.diskCache(diskCache)
.error(R.drawable.ic_cannot_load)
.error(getDrawableCompat(R.drawable.ic_cannot_load).asImage())
.crossfade(350)
.build()
}
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
@RequiresApi(Build.VERSION_CODES.O)

View File

@ -2,9 +2,9 @@ package com.looker.droidify.content
import android.content.Context
import android.content.SharedPreferences
import com.looker.core.common.extension.Json
import com.looker.core.common.extension.parseDictionary
import com.looker.core.common.extension.writeDictionary
import com.looker.droidify.utility.common.extension.Json
import com.looker.droidify.utility.common.extension.parseDictionary
import com.looker.droidify.utility.common.extension.writeDictionary
import com.looker.droidify.model.ProductPreference
import com.looker.droidify.database.Database
import com.looker.droidify.utility.serialization.productPreference

View File

@ -12,28 +12,29 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
sealed class Request {
internal abstract val id: Int
data class ProductsAvailable(
class Available(
val searchQuery: String,
val section: ProductItem.Section,
val order: SortOrder
val order: SortOrder,
) : Request() {
override val id: Int
get() = 1
}
data class ProductsInstalled(
class Installed(
val searchQuery: String,
val section: ProductItem.Section,
val order: SortOrder
val order: SortOrder,
) : Request() {
override val id: Int
get() = 2
}
data class ProductsUpdates(
class Updates(
val searchQuery: String,
val section: ProductItem.Section,
val order: SortOrder
val order: SortOrder,
val skipSignatureCheck: Boolean,
) : Request() {
override val id: Int
get() = 3
@ -52,7 +53,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
private data class ActiveRequest(
val request: Request,
val callback: Callback?,
val cursor: Cursor?
val cursor: Cursor?,
)
init {
@ -93,7 +94,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
val request = activeRequests[id]!!.request
return QueryLoader(requireContext()) {
when (request) {
is Request.ProductsAvailable ->
is Request.Available ->
Database.ProductAdapter
.query(
installed = false,
@ -101,10 +102,10 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
searchQuery = request.searchQuery,
section = request.section,
order = request.order,
signal = it
signal = it,
)
is Request.ProductsInstalled ->
is Request.Installed ->
Database.ProductAdapter
.query(
installed = true,
@ -112,10 +113,10 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
searchQuery = request.searchQuery,
section = request.section,
order = request.order,
signal = it
signal = it,
)
is Request.ProductsUpdates ->
is Request.Updates ->
Database.ProductAdapter
.query(
installed = true,
@ -123,7 +124,8 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
searchQuery = request.searchQuery,
section = request.section,
order = request.order,
signal = it
signal = it,
skipSignatureCheck = request.skipSignatureCheck,
)
is Request.Repositories -> Database.RepositoryAdapter.query(it)

View File

@ -9,18 +9,18 @@ import android.os.CancellationSignal
import androidx.core.database.sqlite.transaction
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.looker.core.common.extension.Json
import com.looker.droidify.utility.common.extension.asSequence
import com.looker.droidify.utility.common.extension.firstOrNull
import com.looker.core.common.extension.parseDictionary
import com.looker.core.common.extension.writeDictionary
import com.looker.droidify.utility.common.log
import com.looker.droidify.BuildConfig
import com.looker.droidify.datastore.model.SortOrder
import com.looker.droidify.model.InstalledItem
import com.looker.droidify.model.Product
import com.looker.droidify.model.ProductItem
import com.looker.droidify.model.Repository
import com.looker.droidify.BuildConfig
import com.looker.droidify.utility.common.extension.Json
import com.looker.droidify.utility.common.extension.asSequence
import com.looker.droidify.utility.common.extension.firstOrNull
import com.looker.droidify.utility.common.extension.parseDictionary
import com.looker.droidify.utility.common.extension.writeDictionary
import com.looker.droidify.utility.common.log
import com.looker.droidify.utility.serialization.product
import com.looker.droidify.utility.serialization.productItem
import com.looker.droidify.utility.serialization.repository
@ -71,14 +71,20 @@ object Database {
get() = "$databasePrefix$innerName"
fun formatCreateTable(name: String): String {
return "CREATE TABLE $name (${QueryBuilder.trimQuery(createTable)})"
return buildString(128) {
append("CREATE TABLE ")
append(name)
append(" (")
trimAndJoin(createTable)
append(")")
}
}
val createIndexPairFormatted: Pair<String, String>?
get() = createIndex?.let {
Pair(
"CREATE INDEX ${innerName}_index ON $innerName ($it)",
"CREATE INDEX ${name}_index ON $innerName ($it)"
"CREATE INDEX ${name}_index ON $innerName ($it)",
)
}
}
@ -214,7 +220,7 @@ object Database {
Schema.Product,
Schema.Category,
Schema.Installed,
Schema.Lock
Schema.Lock,
)
dropOldTables(db, Schema.Repository, Schema.Product, Schema.Category)
this.created = this.created || create
@ -227,7 +233,7 @@ object Database {
val sql = db.query(
"${table.databasePrefix}sqlite_master",
columns = arrayOf("sql"),
selection = Pair("type = ? AND name = ?", arrayOf("table", table.innerName))
selection = Pair("type = ? AND name = ?", arrayOf("table", table.innerName)),
).use { it.firstOrNull()?.getString(0) }.orEmpty()
table.formatCreateTable(table.innerName) != sql
}
@ -261,7 +267,7 @@ object Database {
val sqls = db.query(
"${table.databasePrefix}sqlite_master",
columns = arrayOf("name", "sql"),
selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", table.innerName))
selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", table.innerName)),
)
.use { cursor ->
cursor.asSequence()
@ -289,7 +295,7 @@ object Database {
val tables = db.query(
"sqlite_master",
columns = arrayOf("name"),
selection = Pair("type = ?", arrayOf("table"))
selection = Pair("type = ?", arrayOf("table")),
)
.use { cursor -> cursor.asSequence().mapNotNull { it.getString(0) }.toList() }
.filter { !it.startsWith("sqlite_") && !it.startsWith("android_") }
@ -345,7 +351,7 @@ object Database {
private fun SQLiteDatabase.insertOrReplace(
replace: Boolean,
table: String,
contentValues: ContentValues
contentValues: ContentValues,
): Long {
return if (replace) {
replace(table, null, contentValues)
@ -353,7 +359,7 @@ object Database {
insert(
table,
null,
contentValues
contentValues,
)
}
}
@ -363,7 +369,7 @@ object Database {
columns: Array<String>? = null,
selection: Pair<String, Array<String>>? = null,
orderBy: String? = null,
signal: CancellationSignal? = null
signal: CancellationSignal? = null,
): Cursor {
return query(
false,
@ -375,7 +381,7 @@ object Database {
null,
orderBy,
null,
signal
signal,
)
}
@ -397,7 +403,7 @@ object Database {
internal fun putWithoutNotification(
repository: Repository,
shouldReplace: Boolean,
database: SQLiteDatabase
database: SQLiteDatabase,
): Long {
return database.insertOrReplace(
shouldReplace,
@ -409,7 +415,7 @@ object Database {
put(Schema.Repository.ROW_ENABLED, if (repository.enabled) 1 else 0)
put(Schema.Repository.ROW_DELETED, 0)
put(Schema.Repository.ROW_DATA, jsonGenerate(repository::serialize))
}
},
)
}
@ -442,8 +448,8 @@ object Database {
Schema.Repository.name,
selection = Pair(
"${Schema.Repository.ROW_ID} = ? AND ${Schema.Repository.ROW_DELETED} == 0",
arrayOf(id.toString())
)
arrayOf(id.toString()),
),
).use { it.firstOrNull()?.let(::transform) }
}
@ -463,9 +469,9 @@ object Database {
selection = Pair(
"${Schema.Repository.ROW_ENABLED} != 0 AND " +
"${Schema.Repository.ROW_DELETED} == 0",
emptyArray()
emptyArray(),
),
signal = null
signal = null,
).use { it.asSequence().map(::transform).toList() }
}
@ -473,7 +479,7 @@ object Database {
return db.query(
Schema.Repository.name,
selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()),
signal = null
signal = null,
).use { it.asSequence().map(::transform).toList() }
}
@ -489,9 +495,9 @@ object Database {
selection = Pair(
"${Schema.Repository.ROW_ENABLED} == 0 OR " +
"${Schema.Repository.ROW_DELETED} != 0",
emptyArray()
emptyArray(),
),
signal = null
signal = null,
).use { parentCursor ->
parentCursor.asSequence().associate {
val idIndex = it.getColumnIndexOrThrow(Schema.Repository.ROW_ID)
@ -508,7 +514,7 @@ object Database {
put(Schema.Repository.ROW_DELETED, 1)
},
"${Schema.Repository.ROW_ID} = ?",
arrayOf(id.toString())
arrayOf(id.toString()),
)
notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products)
}
@ -519,18 +525,18 @@ object Database {
val productsCount = db.delete(
Schema.Product.name,
"${Schema.Product.ROW_REPOSITORY_ID} IN ($idsString)",
null
null,
)
val categoriesCount = db.delete(
Schema.Category.name,
"${Schema.Category.ROW_REPOSITORY_ID} IN ($idsString)",
null
null,
)
if (isDeleted) {
db.delete(
Schema.Repository.name,
"${Schema.Repository.ROW_ID} IN ($id)",
null
null,
)
}
productsCount != 0 || categoriesCount != 0
@ -555,7 +561,7 @@ object Database {
Schema.Repository.name,
selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()),
orderBy = "${Schema.Repository.ROW_ENABLED} DESC",
signal = signal
signal = signal,
).observable(Subject.Repositories)
}
@ -577,14 +583,16 @@ object Database {
.map { get(packageName, null) }
.flowOn(Dispatchers.IO)
suspend fun getUpdates(): List<ProductItem> = withContext(Dispatchers.IO) {
suspend fun getUpdates(skipSignatureCheck: Boolean): List<ProductItem> =
withContext(Dispatchers.IO) {
query(
installed = true,
updates = true,
searchQuery = "",
skipSignatureCheck = skipSignatureCheck,
section = ProductItem.Section.All,
order = SortOrder.NAME,
signal = null
signal = null,
).use {
it.asSequence()
.map(ProductAdapter::transformItem)
@ -592,11 +600,11 @@ object Database {
}
}
fun getUpdatesStream(): Flow<List<ProductItem>> = flowOf(Unit)
fun getUpdatesStream(skipSignatureCheck: Boolean): Flow<List<ProductItem>> = flowOf(Unit)
.onCompletion { if (it == null) emitAll(flowCollection(Subject.Products)) }
// Crashes due to immediate retrieval of data?
.onEach { delay(50) }
.map { getUpdates() }
.map { getUpdates(skipSignatureCheck) }
.flowOn(Dispatchers.IO)
fun getAll(): List<Product> {
@ -618,10 +626,10 @@ object Database {
columns = arrayOf(
Schema.Product.ROW_REPOSITORY_ID,
Schema.Product.ROW_DESCRIPTION,
Schema.Product.ROW_DATA
Schema.Product.ROW_DATA,
),
selection = Pair("${Schema.Product.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)),
signal = signal
signal = signal,
).use { it.asSequence().map(::transform).toList() }
}
@ -636,22 +644,24 @@ object Database {
columns = arrayOf("COUNT (*)"),
selection = Pair(
"${Schema.Product.ROW_REPOSITORY_ID} = ?",
arrayOf(repositoryId.toString())
)
arrayOf(repositoryId.toString()),
),
).use { it.firstOrNull()?.getInt(0) ?: 0 }
}
fun query(
installed: Boolean,
updates: Boolean,
skipSignatureCheck: Boolean = false,
searchQuery: String,
section: ProductItem.Section,
order: SortOrder,
signal: CancellationSignal?
signal: CancellationSignal?,
): Cursor {
val builder = QueryBuilder()
val signatureMatches = """installed.${Schema.Installed.ROW_SIGNATURE} IS NOT NULL AND
val signatureMatches = if (skipSignatureCheck) "1"
else """installed.${Schema.Installed.ROW_SIGNATURE} IS NOT NULL AND
product.${Schema.Product.ROW_SIGNATURES} LIKE ('%.' || installed.${Schema.Installed.ROW_SIGNATURE} || '.%') AND
product.${Schema.Product.ROW_SIGNATURES} != ''"""
@ -741,6 +751,10 @@ object Database {
}
}
fun transformPackageName(cursor: Cursor): String {
return cursor.getString(cursor.getColumnIndexOrThrow(Schema.Product.ROW_PACKAGE_NAME))
}
fun transformItem(cursor: Cursor): ProductItem {
return cursor.getBlob(cursor.getColumnIndexOrThrow(Schema.Product.ROW_DATA_ITEM))
.jsonParse {
@ -806,10 +820,10 @@ object Database {
Schema.Installed.ROW_PACKAGE_NAME,
Schema.Installed.ROW_VERSION,
Schema.Installed.ROW_VERSION_CODE,
Schema.Installed.ROW_SIGNATURE
Schema.Installed.ROW_SIGNATURE,
),
selection = Pair("${Schema.Installed.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)),
signal = signal
signal = signal,
).use { it.firstOrNull()?.let(::transform) }
}
@ -822,7 +836,7 @@ object Database {
put(Schema.Installed.ROW_VERSION, installedItem.version)
put(Schema.Installed.ROW_VERSION_CODE, installedItem.versionCode)
put(Schema.Installed.ROW_SIGNATURE, installedItem.signature)
}
},
)
if (notify) {
notifyChanged(Subject.Products)
@ -842,7 +856,7 @@ object Database {
val count = db.delete(
Schema.Installed.name,
"${Schema.Installed.ROW_PACKAGE_NAME} = ?",
arrayOf(packageName)
arrayOf(packageName),
)
if (count > 0) {
notifyChanged(Subject.Products)
@ -854,7 +868,7 @@ object Database {
cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_PACKAGE_NAME)),
cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_VERSION)),
cursor.getLong(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_VERSION_CODE)),
cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_SIGNATURE))
cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_SIGNATURE)),
)
}
}
@ -867,7 +881,7 @@ object Database {
ContentValues().apply {
put(Schema.Lock.ROW_PACKAGE_NAME, lock.first)
put(Schema.Lock.ROW_VERSION_CODE, lock.second)
}
},
)
if (notify) {
notifyChanged(Subject.Products)
@ -923,9 +937,9 @@ object Database {
put(Schema.Product.ROW_DATA, jsonGenerate(product::serialize))
put(
Schema.Product.ROW_DATA_ITEM,
jsonGenerate(product.item()::serialize)
jsonGenerate(product.item()::serialize),
)
}
},
)
for (category in product.categories) {
db.insertOrReplace(
@ -935,7 +949,7 @@ object Database {
put(Schema.Category.ROW_REPOSITORY_ID, product.repositoryId)
put(Schema.Category.ROW_PACKAGE_NAME, product.packageName)
put(Schema.Category.ROW_NAME, category)
}
},
)
}
}
@ -948,20 +962,20 @@ object Database {
db.delete(
Schema.Product.name,
"${Schema.Product.ROW_REPOSITORY_ID} = ?",
arrayOf(repository.id.toString())
arrayOf(repository.id.toString()),
)
db.delete(
Schema.Category.name,
"${Schema.Category.ROW_REPOSITORY_ID} = ?",
arrayOf(repository.id.toString())
arrayOf(repository.id.toString()),
)
db.execSQL(
"INSERT INTO ${Schema.Product.name} SELECT * " +
"FROM ${Schema.Product.temporaryName}"
"FROM ${Schema.Product.temporaryName}",
)
db.execSQL(
"INSERT INTO ${Schema.Category.name} SELECT * " +
"FROM ${Schema.Category.temporaryName}"
"FROM ${Schema.Category.temporaryName}",
)
RepositoryAdapter.putWithoutNotification(repository, true, db)
db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}")
@ -970,7 +984,7 @@ object Database {
notifyChanged(
Subject.Repositories,
Subject.Repository(repository.id),
Subject.Products
Subject.Products,
)
} else {
db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}")

View File

@ -3,26 +3,20 @@ package com.looker.droidify.database
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.os.CancellationSignal
import com.looker.droidify.BuildConfig
import com.looker.droidify.utility.common.extension.asSequence
import com.looker.droidify.utility.common.log
import com.looker.droidify.BuildConfig
class QueryBuilder {
companion object {
fun trimQuery(query: String): String {
return query.lines().map { it.trim() }.filter { it.isNotEmpty() }
.joinToString(separator = " ")
}
}
private val builder = StringBuilder()
private val builder = StringBuilder(256)
private val arguments = mutableListOf<String>()
operator fun plusAssign(query: String) {
if (builder.isNotEmpty()) {
builder.append(" ")
}
builder.append(trimQuery(query))
builder.trimAndJoin(query)
}
operator fun remAssign(argument: String) {
@ -48,3 +42,53 @@ class QueryBuilder {
return db.rawQuery(query, arguments, signal)
}
}
fun StringBuilder.trimAndJoin(
input: String,
) {
var isFirstLine = true
var startOfLine = 0
for (i in input.indices) {
val char = input[i]
when {
char == '\n' -> {
trimAndAppendLine(input, startOfLine, i, this, isFirstLine)
isFirstLine = false
startOfLine = i + 1
}
else -> {
if (i == input.lastIndex) {
trimAndAppendLine(input, startOfLine, i + 1, this, isFirstLine)
}
}
}
}
}
private fun trimAndAppendLine(
input: String,
start: Int,
end: Int,
builder: StringBuilder,
isFirstLine: Boolean,
) {
var lineStart = start
var lineEnd = end - 1
while (lineStart <= lineEnd && input[lineStart].isWhitespace()) {
lineStart++
}
while (lineEnd >= lineStart && input[lineEnd].isWhitespace()) {
lineEnd--
}
if (lineStart <= lineEnd) {
if (!isFirstLine) {
builder.append(' ')
}
builder.append(input, lineStart, lineEnd + 1)
}
}

View File

@ -4,12 +4,12 @@ import android.content.Context
import android.net.Uri
import com.fasterxml.jackson.core.JsonToken
import com.looker.droidify.utility.common.Exporter
import com.looker.core.common.extension.Json
import com.looker.core.common.extension.forEach
import com.looker.core.common.extension.forEachKey
import com.looker.core.common.extension.parseDictionary
import com.looker.core.common.extension.writeArray
import com.looker.core.common.extension.writeDictionary
import com.looker.droidify.utility.common.extension.Json
import com.looker.droidify.utility.common.extension.forEach
import com.looker.droidify.utility.common.extension.forEachKey
import com.looker.droidify.utility.common.extension.parseDictionary
import com.looker.droidify.utility.common.extension.writeArray
import com.looker.droidify.utility.common.extension.writeDictionary
import com.looker.droidify.di.ApplicationScope
import com.looker.droidify.di.IoDispatcher
import com.looker.droidify.model.Repository

View File

@ -14,6 +14,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import com.looker.droidify.datastore.model.AutoSync
import com.looker.droidify.datastore.model.InstallerType
import com.looker.droidify.datastore.model.LegacyInstallerComponent
import com.looker.droidify.datastore.model.ProxyPreference
import com.looker.droidify.datastore.model.ProxyType
import com.looker.droidify.datastore.model.SortOrder
@ -85,6 +86,31 @@ class PreferenceSettingsRepository(
override suspend fun setInstallerType(installerType: InstallerType) =
INSTALLER_TYPE.update(installerType.name)
override suspend fun setLegacyInstallerComponent(component: LegacyInstallerComponent?) {
when (component) {
null -> {
LEGACY_INSTALLER_COMPONENT_TYPE.update("")
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
}
is LegacyInstallerComponent.Component -> {
LEGACY_INSTALLER_COMPONENT_TYPE.update("component")
LEGACY_INSTALLER_COMPONENT_CLASS.update(component.clazz)
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update(component.activity)
}
LegacyInstallerComponent.Unspecified -> {
LEGACY_INSTALLER_COMPONENT_TYPE.update("unspecified")
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
}
LegacyInstallerComponent.AlwaysChoose -> {
LEGACY_INSTALLER_COMPONENT_TYPE.update("always_choose")
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
}
}
}
override suspend fun setAutoUpdate(allow: Boolean) =
AUTO_UPDATE.update(allow)
@ -125,6 +151,18 @@ class PreferenceSettingsRepository(
private fun mapSettings(preferences: Preferences): Settings {
val installerType =
InstallerType.valueOf(preferences[INSTALLER_TYPE] ?: InstallerType.Default.name)
val legacyInstallerComponent = when (preferences[LEGACY_INSTALLER_COMPONENT_TYPE]) {
"component" -> {
preferences[LEGACY_INSTALLER_COMPONENT_CLASS]?.takeIf { it.isNotBlank() }?.let { cls ->
preferences[LEGACY_INSTALLER_COMPONENT_ACTIVITY]?.takeIf { it.isNotBlank() }?.let { act ->
LegacyInstallerComponent.Component(cls, act)
}
}
}
"unspecified" -> LegacyInstallerComponent.Unspecified
"always_choose" -> LegacyInstallerComponent.AlwaysChoose
else -> null
}
val language = preferences[LANGUAGE] ?: "system"
val incompatibleVersions = preferences[INCOMPATIBLE_VERSIONS] ?: false
@ -154,6 +192,7 @@ class PreferenceSettingsRepository(
theme = theme,
dynamicTheme = dynamicTheme,
installerType = installerType,
legacyInstallerComponent = legacyInstallerComponent,
autoUpdate = autoUpdate,
autoSync = autoSync,
sortOrder = sortOrder,
@ -185,6 +224,9 @@ class PreferenceSettingsRepository(
val LAST_CLEAN_UP = longPreferencesKey("key_last_clean_up_time")
val FAVOURITE_APPS = stringSetPreferencesKey("key_favourite_apps")
val HOME_SCREEN_SWIPING = booleanPreferencesKey("key_home_swiping")
val LEGACY_INSTALLER_COMPONENT_CLASS = stringPreferencesKey("key_legacy_installer_component_class")
val LEGACY_INSTALLER_COMPONENT_ACTIVITY = stringPreferencesKey("key_legacy_installer_component_activity")
val LEGACY_INSTALLER_COMPONENT_TYPE = stringPreferencesKey("key_legacy_installer_component_type")
// Enums
val THEME = stringPreferencesKey("key_theme")
@ -200,6 +242,28 @@ class PreferenceSettingsRepository(
set(UNSTABLE_UPDATES, settings.unstableUpdate)
set(THEME, settings.theme.name)
set(DYNAMIC_THEME, settings.dynamicTheme)
when (settings.legacyInstallerComponent) {
is LegacyInstallerComponent.Component -> {
set(LEGACY_INSTALLER_COMPONENT_TYPE, "component")
set(LEGACY_INSTALLER_COMPONENT_CLASS, settings.legacyInstallerComponent.clazz)
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, settings.legacyInstallerComponent.activity)
}
LegacyInstallerComponent.Unspecified -> {
set(LEGACY_INSTALLER_COMPONENT_TYPE, "unspecified")
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
}
LegacyInstallerComponent.AlwaysChoose -> {
set(LEGACY_INSTALLER_COMPONENT_TYPE, "always_choose")
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
}
null -> {
set(LEGACY_INSTALLER_COMPONENT_TYPE, "")
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
}
}
set(INSTALLER_TYPE, settings.installerType.name)
set(AUTO_UPDATE, settings.autoUpdate)
set(AUTO_SYNC, settings.autoSync.name)

View File

@ -3,6 +3,7 @@ package com.looker.droidify.datastore
import androidx.datastore.core.Serializer
import com.looker.droidify.datastore.model.AutoSync
import com.looker.droidify.datastore.model.InstallerType
import com.looker.droidify.datastore.model.LegacyInstallerComponent
import com.looker.droidify.datastore.model.ProxyPreference
import com.looker.droidify.datastore.model.SortOrder
import com.looker.droidify.datastore.model.Theme
@ -29,6 +30,7 @@ data class Settings(
val theme: Theme = Theme.SYSTEM,
val dynamicTheme: Boolean = false,
val installerType: InstallerType = InstallerType.Default,
val legacyInstallerComponent: LegacyInstallerComponent? = null,
val autoUpdate: Boolean = false,
val autoSync: AutoSync = AutoSync.WIFI_ONLY,
val sortOrder: SortOrder = SortOrder.UPDATED,

View File

@ -3,6 +3,7 @@ package com.looker.droidify.datastore
import android.net.Uri
import com.looker.droidify.datastore.model.AutoSync
import com.looker.droidify.datastore.model.InstallerType
import com.looker.droidify.datastore.model.LegacyInstallerComponent
import com.looker.droidify.datastore.model.ProxyType
import com.looker.droidify.datastore.model.SortOrder
import com.looker.droidify.datastore.model.Theme
@ -37,6 +38,8 @@ interface SettingsRepository {
suspend fun setInstallerType(installerType: InstallerType)
suspend fun setLegacyInstallerComponent(component: LegacyInstallerComponent?)
suspend fun setAutoUpdate(allow: Boolean)
suspend fun setAutoSync(autoSync: AutoSync)

View File

@ -0,0 +1,26 @@
package com.looker.droidify.datastore.model
import kotlinx.serialization.Serializable
@Serializable
sealed class LegacyInstallerComponent {
@Serializable
object Unspecified : LegacyInstallerComponent()
@Serializable
object AlwaysChoose : LegacyInstallerComponent()
@Serializable
data class Component(
val clazz: String,
val activity: String,
) : LegacyInstallerComponent() {
fun update(
newClazz: String? = null,
newActivity: String? = null,
): Component = copy(
clazz = newClazz ?: clazz,
activity = newActivity ?: activity
)
}
}

View File

@ -1,5 +1,3 @@
@file:Suppress("unused")
package com.looker.droidify.di
import dagger.Module

View File

@ -22,9 +22,6 @@ data class Repo(
}
}
val String.isOnion: Boolean
get() = endsWith(".onion")
data class AntiFeature(
val id: Long,
val name: String,

View File

@ -51,7 +51,6 @@ open class DrawableWrapper(val drawable: Drawable) : Drawable() {
drawable.colorFilter = colorFilter
}
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun getOpacity(): Int = drawable.opacity
}

View File

@ -3,10 +3,10 @@ package com.looker.droidify.index
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import com.fasterxml.jackson.core.JsonToken
import com.looker.core.common.extension.Json
import com.looker.droidify.utility.common.extension.Json
import com.looker.droidify.utility.common.extension.asSequence
import com.looker.core.common.extension.collectNotNull
import com.looker.core.common.extension.writeDictionary
import com.looker.droidify.utility.common.extension.collectNotNull
import com.looker.droidify.utility.common.extension.writeDictionary
import com.looker.droidify.model.Product
import com.looker.droidify.model.Release
import com.looker.droidify.utility.serialization.product
@ -84,7 +84,7 @@ class IndexMerger(file: File) : Closeable {
"""SELECT product.description, product.data AS pd, releases.data AS rd FROM product
LEFT JOIN releases ON product.package_name = releases.package_name""",
null
)?.use { cursor ->
).use { cursor ->
cursor.asSequence().map { currentCursor ->
val description = currentCursor.getString(0)
val product = Json.factory.createParser(currentCursor.getBlob(1)).use {

View File

@ -5,16 +5,27 @@ import androidx.core.os.ConfigurationCompat.getLocales
import androidx.core.os.LocaleListCompat
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.looker.droidify.utility.common.SdkCheck
import com.looker.core.common.extension.Json
import com.looker.core.common.extension.collectDistinctNotEmptyStrings
import com.looker.core.common.extension.collectNotNull
import com.looker.core.common.extension.forEach
import com.looker.core.common.extension.forEachKey
import com.looker.core.common.extension.illegal
import com.looker.droidify.utility.common.nullIfEmpty
import com.looker.droidify.utility.common.extension.Json
import com.looker.droidify.utility.common.extension.collectDistinctNotEmptyStrings
import com.looker.droidify.utility.common.extension.collectNotNull
import com.looker.droidify.utility.common.extension.forEach
import com.looker.droidify.utility.common.extension.forEachKey
import com.looker.droidify.utility.common.extension.illegal
import com.looker.droidify.model.Product
import com.looker.droidify.model.Product.Donate.Bitcoin
import com.looker.droidify.model.Product.Donate.Liberapay
import com.looker.droidify.model.Product.Donate.Litecoin
import com.looker.droidify.model.Product.Donate.OpenCollective
import com.looker.droidify.model.Product.Donate.Regular
import com.looker.droidify.model.Product.Screenshot.Type.LARGE_TABLET
import com.looker.droidify.model.Product.Screenshot.Type.PHONE
import com.looker.droidify.model.Product.Screenshot.Type.SMALL_TABLET
import com.looker.droidify.model.Product.Screenshot.Type.TV
import com.looker.droidify.model.Product.Screenshot.Type.VIDEO
import com.looker.droidify.model.Product.Screenshot.Type.WEAR
import com.looker.droidify.model.Release
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.nullIfEmpty
import java.io.InputStream
object IndexV1Parser {
@ -32,9 +43,12 @@ object IndexV1Parser {
}
private class Screenshots(
val video: List<String>,
val phone: List<String>,
val smallTablet: List<String>,
val largeTablet: List<String>
val largeTablet: List<String>,
val wear: List<String>,
val tv: List<String>,
)
private class Localized(
@ -90,10 +104,9 @@ object IndexV1Parser {
}
private fun <T> Map<String, Localized>.find(callback: (String, Localized) -> T?): T? {
return getAndCall("en-US", callback) ?: getAndCall("en_US", callback) ?: getAndCall(
"en",
callback
)
return getAndCall("en-US", callback)
?: getAndCall("en_US", callback)
?: getAndCall("en", callback)
}
private fun <T> Map<String, Localized>.findLocalized(callback: (Localized) -> T?): T? {
@ -122,12 +135,11 @@ object IndexV1Parser {
internal object DonateComparator : Comparator<Product.Donate> {
private val classes = listOf(
Product.Donate.Regular::class,
Product.Donate.Bitcoin::class,
Product.Donate.Litecoin::class,
Product.Donate.Flattr::class,
Product.Donate.Liberapay::class,
Product.Donate.OpenCollective::class
Regular::class,
Bitcoin::class,
Litecoin::class,
Liberapay::class,
OpenCollective::class
)
override fun compare(donate1: Product.Donate, donate2: Product.Donate): Int {
@ -236,14 +248,17 @@ object IndexV1Parser {
private const val KEY_PRODUCT_LICENSE = "license"
private const val KEY_PRODUCT_DONATE = "donate"
private const val KEY_PRODUCT_BITCOIN = "bitcoin"
private const val KEY_PRODUCT_FLATTRID = "flattrID"
private const val KEY_PRODUCT_LIBERAPAYID = "liberapayID"
private const val KEY_PRODUCT_LIBERAPAYID = "liberapay"
private const val KEY_PRODUCT_LITECOIN = "litecoin"
private const val KEY_PRODUCT_OPENCOLLECTIVE = "openCollective"
private const val KEY_PRODUCT_LOCALIZED = "localized"
private const val KEY_PRODUCT_WHATSNEW = "whatsNew"
private const val KEY_PRODUCT_PHONESCREENSHOTS = "phoneScreenshots"
private const val KEY_PRODUCT_SEVENINCHSCREENSHOTS = "sevenInchScreenshots"
private const val KEY_PRODUCT_TENINCHSCREENSHOTS = "tenInchScreenshots"
private const val KEY_PRODUCT_PHONE_SCREENSHOTS = "phoneScreenshots"
private const val KEY_PRODUCT_SEVEN_INCH_SCREENSHOTS = "sevenInchScreenshots"
private const val KEY_PRODUCT_TEN_INCH_SCREENSHOTS = "tenInchScreenshots"
private const val KEY_PRODUCT_WEAR_SCREENSHOTS = "wearScreenshots"
private const val KEY_PRODUCT_TV_SCREENSHOTS = "tvScreenshots"
private const val KEY_PRODUCT_VIDEO = "video"
private fun JsonParser.parseProduct(repositoryId: Long): Product {
var packageName = ""
@ -293,16 +308,11 @@ object IndexV1Parser {
key.string(KEY_PRODUCT_LICENSE) -> licenses += valueAsString.split(',')
.filter { it.isNotEmpty() }
key.string(KEY_PRODUCT_DONATE) -> donates += Product.Donate.Regular(valueAsString)
key.string(KEY_PRODUCT_BITCOIN) -> donates += Product.Donate.Bitcoin(valueAsString)
key.string(KEY_PRODUCT_FLATTRID) -> donates += Product.Donate.Flattr(valueAsString)
key.string(KEY_PRODUCT_LIBERAPAYID) -> donates += Product.Donate.Liberapay(
valueAsString
)
key.string(KEY_PRODUCT_OPENCOLLECTIVE) -> donates += Product.Donate.OpenCollective(
valueAsString
)
key.string(KEY_PRODUCT_DONATE) -> donates += Regular(valueAsString)
key.string(KEY_PRODUCT_BITCOIN) -> donates += Bitcoin(valueAsString)
key.string(KEY_PRODUCT_LIBERAPAYID) -> donates += Liberapay(valueAsString)
key.string(KEY_PRODUCT_LITECOIN) -> donates += Litecoin(valueAsString)
key.string(KEY_PRODUCT_OPENCOLLECTIVE) -> donates += OpenCollective(valueAsString)
key.dictionary(KEY_PRODUCT_LOCALIZED) -> forEachKey { localizedKey ->
if (localizedKey.token == JsonToken.START_OBJECT) {
@ -315,6 +325,9 @@ object IndexV1Parser {
var phone = emptyList<String>()
var smallTablet = emptyList<String>()
var largeTablet = emptyList<String>()
var wear = emptyList<String>()
var tv = emptyList<String>()
var video = emptyList<String>()
forEachKey {
when {
it.string(KEY_PRODUCT_NAME) -> name = valueAsString
@ -322,39 +335,42 @@ object IndexV1Parser {
it.string(KEY_PRODUCT_DESCRIPTION) -> description = valueAsString
it.string(KEY_PRODUCT_WHATSNEW) -> whatsNew = valueAsString
it.string(KEY_PRODUCT_ICON) -> metadataIcon = valueAsString
it.array(KEY_PRODUCT_PHONESCREENSHOTS) ->
phone =
collectDistinctNotEmptyStrings()
it.string(KEY_PRODUCT_VIDEO) -> video = listOf(valueAsString)
it.array(KEY_PRODUCT_PHONE_SCREENSHOTS) ->
phone = collectDistinctNotEmptyStrings()
it.array(KEY_PRODUCT_SEVENINCHSCREENSHOTS) ->
smallTablet =
collectDistinctNotEmptyStrings()
it.array(KEY_PRODUCT_SEVEN_INCH_SCREENSHOTS) ->
smallTablet = collectDistinctNotEmptyStrings()
it.array(KEY_PRODUCT_TENINCHSCREENSHOTS) ->
largeTablet =
collectDistinctNotEmptyStrings()
it.array(KEY_PRODUCT_TEN_INCH_SCREENSHOTS) ->
largeTablet = collectDistinctNotEmptyStrings()
it.array(KEY_PRODUCT_WEAR_SCREENSHOTS) ->
wear = collectDistinctNotEmptyStrings()
it.array(KEY_PRODUCT_TV_SCREENSHOTS) ->
tv = collectDistinctNotEmptyStrings()
else -> skipChildren()
}
}
val isScreenshotEmpty =
arrayOf(video, phone, smallTablet, largeTablet, wear, tv)
.any { it.isNotEmpty() }
val screenshots =
if (sequenceOf(
phone,
smallTablet,
largeTablet
).any { it.isNotEmpty() }
) {
Screenshots(phone, smallTablet, largeTablet)
if (isScreenshotEmpty) {
Screenshots(video, phone, smallTablet, largeTablet, wear, tv)
} else {
null
}
localizedMap[locale] = Localized(
name,
summary,
description,
whatsNew,
metadataIcon.nullIfEmpty()?.let { "$locale/$it" }.orEmpty(),
screenshots
name = name,
summary = summary,
description = description,
whatsNew = whatsNew,
metadataIcon = metadataIcon.nullIfEmpty()?.let { "$locale/$it" }
.orEmpty(),
screenshots = screenshots,
)
} else {
skipChildren()
@ -377,28 +393,14 @@ object IndexV1Parser {
}
val screenshotPairs =
localizedMap.find { key, localized -> localized.screenshots?.let { Pair(key, it) } }
val screenshots = screenshotPairs
?.let { (key, screenshots) ->
screenshots.phone.asSequence()
.map { Product.Screenshot(key, Product.Screenshot.Type.PHONE, it) } +
screenshots.smallTablet.asSequence()
.map {
Product.Screenshot(
key,
Product.Screenshot.Type.SMALL_TABLET,
it
)
} +
screenshots.largeTablet.asSequence()
.map {
Product.Screenshot(
key,
Product.Screenshot.Type.LARGE_TABLET,
it
)
}
}
.orEmpty().toList()
val screenshots = screenshotPairs?.let { (key, screenshots) ->
screenshots.video.map { Product.Screenshot(key, VIDEO, it) } +
screenshots.phone.map { Product.Screenshot(key, PHONE, it) } +
screenshots.smallTablet.map { Product.Screenshot(key, SMALL_TABLET, it) } +
screenshots.largeTablet.map { Product.Screenshot(key, LARGE_TABLET, it) } +
screenshots.wear.map { Product.Screenshot(key, WEAR, it) } +
screenshots.tv.map { Product.Screenshot(key, TV, it) }
}.orEmpty()
return Product(
repositoryId = repositoryId,
packageName = packageName,

View File

@ -16,6 +16,8 @@ import com.looker.droidify.installer.installers.session.SessionInstaller
import com.looker.droidify.installer.installers.shizuku.ShizukuInstaller
import com.looker.droidify.installer.model.InstallItem
import com.looker.droidify.installer.model.InstallState
import com.looker.droidify.installer.notification.createInstallNotification
import com.looker.droidify.installer.notification.installNotification
import com.looker.droidify.installer.notification.removeInstallNotification
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@ -30,7 +32,7 @@ import kotlinx.coroutines.sync.withLock
class InstallManager(
private val context: Context,
settingsRepository: SettingsRepository
private val settingsRepository: SettingsRepository
) {
private val installItems = Channel<InstallItem>()
@ -87,6 +89,13 @@ class InstallManager(
}.consumeEach { item ->
if (state.value.containsKey(item.packageName)) {
updateState { put(item.packageName, InstallState.Installing) }
context.notificationManager?.installNotification(
packageName = item.packageName.name,
notification = context.createInstallNotification(
appName = item.packageName.name,
state = InstallState.Installing,
)
)
val success = installer.use {
it.install(item)
}
@ -106,7 +115,7 @@ class InstallManager(
private suspend fun setInstaller(installerType: InstallerType) {
lock.withLock {
_installer = when (installerType) {
InstallerType.LEGACY -> LegacyInstaller(context)
InstallerType.LEGACY -> LegacyInstaller(context, settingsRepository)
InstallerType.SESSION -> SessionInstaller(context)
InstallerType.SHIZUKU -> ShizukuInstaller(context)
InstallerType.ROOT -> RootInstaller(context)

View File

@ -1,50 +1,81 @@
package com.looker.droidify.installer.installers
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.util.AndroidRuntimeException
import androidx.core.net.toUri
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.cache.Cache
import com.looker.droidify.R
import com.looker.droidify.datastore.SettingsRepository
import com.looker.droidify.datastore.get
import com.looker.droidify.datastore.model.LegacyInstallerComponent
import com.looker.droidify.domain.model.PackageName
import com.looker.droidify.installer.model.InstallItem
import com.looker.droidify.installer.model.InstallState
import kotlin.coroutines.resume
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.cache.Cache
import com.looker.droidify.utility.common.extension.intent
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
@Suppress("DEPRECATION")
internal class LegacyInstaller(private val context: Context) : Installer {
class LegacyInstaller(
private val context: Context,
private val settingsRepository: SettingsRepository
) : Installer {
companion object {
private const val APK_MIME = "application/vnd.android.package-archive"
}
override suspend fun install(
installItem: InstallItem
): InstallState = suspendCancellableCoroutine { cont ->
val (uri, flags) = if (SdkCheck.isNougat) {
Cache.getReleaseUri(
context,
installItem.installFileName
) to Intent.FLAG_GRANT_READ_URI_PERMISSION
installItem: InstallItem,
): InstallState {
val installFlag = if (SdkCheck.isNougat) Intent.FLAG_GRANT_READ_URI_PERMISSION else 0
val fileUri = if (SdkCheck.isNougat) {
Cache.getReleaseUri(context, installItem.installFileName)
} else {
val file = Cache.getReleaseFile(context, installItem.installFileName)
file.toUri() to 0
Cache.getReleaseFile(context, installItem.installFileName).toUri()
}
val comp = settingsRepository.get { legacyInstallerComponent }.firstOrNull()
return suspendCancellableCoroutine { cont ->
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
setDataAndType(fileUri, APK_MIME)
flags = installFlag
when (comp) {
is LegacyInstallerComponent.Component -> {
component = ComponentName(comp.clazz, comp.activity)
}
else -> {
// For Unspecified and AlwaysChoose, don't set component
}
}
}
val installIntent = when (comp) {
LegacyInstallerComponent.AlwaysChoose -> Intent.createChooser(intent, context.getString(
R.string.select_installer))
else -> intent
}
try {
context.startActivity(
Intent(Intent.ACTION_INSTALL_PACKAGE).setDataAndType(uri, APK_MIME).setFlags(flags)
)
context.startActivity(installIntent)
cont.resume(InstallState.Installed)
} catch (e: AndroidRuntimeException) {
context.startActivity(
Intent(Intent.ACTION_INSTALL_PACKAGE).setDataAndType(uri, APK_MIME)
.setFlags(flags or Intent.FLAG_ACTIVITY_NEW_TASK)
)
installIntent.flags = installFlag or Intent.FLAG_ACTIVITY_NEW_TASK
try {
context.startActivity(installIntent)
cont.resume(InstallState.Installed)
} catch (e: Exception) {
cont.resume(InstallState.Failed)
}
} catch (e: Exception) {
cont.resume(InstallState.Failed)
}
}
}
override suspend fun uninstall(packageName: PackageName) =
@ -53,14 +84,14 @@ internal class LegacyInstaller(private val context: Context) : Installer {
override fun close() {}
}
internal suspend fun Context.uninstallPackage(packageName: PackageName) =
suspend fun Context.uninstallPackage(packageName: PackageName) =
suspendCancellableCoroutine { cont ->
try {
startActivity(
Intent(
Intent.ACTION_UNINSTALL_PACKAGE,
"package:${packageName.name}".toUri()
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent(Intent.ACTION_UNINSTALL_PACKAGE) {
data = "package:${packageName.name}".toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
)
cont.resume(Unit)
} catch (e: Exception) {

View File

@ -1,70 +1,34 @@
package com.looker.droidify.installer.installers.root
import android.content.Context
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.cache.Cache
import com.looker.droidify.domain.model.PackageName
import com.looker.droidify.installer.installers.Installer
import com.looker.droidify.installer.installers.uninstallPackage
import com.looker.droidify.installer.model.InstallItem
import com.looker.droidify.installer.model.InstallState
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.cache.Cache
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.File
import kotlin.coroutines.resume
internal class RootInstaller(private val context: Context) : Installer {
private companion object {
const val ROOT_INSTALL_PACKAGE = "cat %s | pm install --user %s -t -r -S %s"
const val DELETE_PACKAGE = "%s rm %s"
val getCurrentUserState: String
get() = if (SdkCheck.isOreo) {
com.topjohnwu.superuser.Shell.cmd("am get-current-user").exec().out[0]
} else {
com.topjohnwu.superuser.Shell.cmd("dumpsys activity | grep -E \"mUserLru\"")
.exec().out[0].trim()
.removePrefix("mUserLru: [").removeSuffix("]")
}
val String.quote
get() = "\"${this.replace(Regex("""[\\$"`]""")) { c -> "\\${c.value}" }}\""
val getUtilBoxPath: String
get() {
listOf("toybox", "busybox").forEach {
val shellResult = com.topjohnwu.superuser.Shell.cmd("which $it").exec()
if (shellResult.out.isNotEmpty()) {
val utilBoxPath = shellResult.out.joinToString("")
if (utilBoxPath.isNotEmpty()) return utilBoxPath.quote
}
}
return ""
}
fun installCmd(file: File): String = String.format(
ROOT_INSTALL_PACKAGE,
file.absolutePath,
getCurrentUserState,
file.length()
)
fun deleteCmd(file: File): String = String.format(
DELETE_PACKAGE,
getUtilBoxPath,
file.absolutePath.quote
)
}
class RootInstaller(private val context: Context) : Installer {
override suspend fun install(
installItem: InstallItem
installItem: InstallItem,
): InstallState = suspendCancellableCoroutine { cont ->
val releaseFile = Cache.getReleaseFile(context, installItem.installFileName)
com.topjohnwu.superuser.Shell.cmd(installCmd(releaseFile)).submit { shellResult ->
val installCommand = INSTALL_COMMAND.format(
releaseFile.absolutePath,
currentUser(),
releaseFile.length(),
)
Shell.cmd(installCommand).submit { shellResult ->
val result = if (shellResult.isSuccess) InstallState.Installed
else InstallState.Failed
cont.resume(result)
com.topjohnwu.superuser.Shell.cmd(deleteCmd(releaseFile)).submit()
val deleteCommand = DELETE_COMMAND.format(utilBox(), releaseFile.absolutePath)
Shell.cmd(deleteCommand).submit()
}
}
@ -72,4 +36,34 @@ internal class RootInstaller(private val context: Context) : Installer {
context.uninstallPackage(packageName)
override fun close() {}
}
private const val INSTALL_COMMAND = "cat %s | pm install --user %s -t -r -S %s"
private const val DELETE_COMMAND = "%s rm %s"
/** Returns the path of either toybox or busybox, or empty string if not found. */
private fun utilBox(): String {
listOf("toybox", "busybox").forEach {
// Returns the path of the requested [command], or empty string if not found
val out = Shell.cmd("which $it").exec().out
if (out.isEmpty()) return ""
if (out.first().contains("not found")) return ""
return out.first()
}
return ""
}
/** Returns the current user of the device. */
private fun currentUser() = if (SdkCheck.isOreo) {
Shell.cmd("am get-current-user")
.exec()
.out[0]
} else {
Shell.cmd("dumpsys activity | grep -E \"mUserLru\"")
.exec()
.out[0]
.trim()
.removePrefix("mUserLru: [")
.removeSuffix("]")
}

View File

@ -20,7 +20,7 @@ import com.looker.droidify.installer.model.InstallState
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
internal class SessionInstaller(private val context: Context) : Installer {
class SessionInstaller(private val context: Context) : Installer {
private val installer = context.packageManager.packageInstaller
private val intent = Intent(context, SessionInstallerReceiver::class.java)

View File

@ -14,7 +14,7 @@ import java.io.BufferedReader
import java.io.InputStream
import kotlin.coroutines.resume
internal class ShizukuInstaller(private val context: Context) : Installer {
class ShizukuInstaller(private val context: Context) : Installer {
companion object {
private val SESSION_ID_REGEX = Regex("(?<=\\[).+?(?=])")

View File

@ -3,7 +3,7 @@ package com.looker.droidify.installer.model
import com.looker.droidify.domain.model.PackageName
import com.looker.droidify.domain.model.toPackageName
data class InstallItem(
class InstallItem(
val packageName: PackageName,
val installFileName: String
)

View File

@ -1,7 +1,9 @@
package com.looker.droidify.model
import com.looker.droidify.domain.model.Donation
import com.looker.droidify.domain.model.Screenshots
import android.content.Context
import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.videoPlaceHolder
import com.google.android.material.R as MaterialR
data class Product(
var repositoryId: Long,
@ -33,29 +35,38 @@ data class Product(
data class Regular(val url: String) : Donate()
data class Bitcoin(val address: String) : Donate()
data class Litecoin(val address: String) : Donate()
data class Flattr(val id: String) : Donate()
data class Liberapay(val id: String) : Donate()
data class OpenCollective(val id: String) : Donate()
}
class Screenshot(val locale: String, val type: Type, val path: String) {
enum class Type(val jsonName: String) {
VIDEO("video"),
PHONE("phone"),
SMALL_TABLET("smallTablet"),
LARGE_TABLET("largeTablet")
LARGE_TABLET("largeTablet"),
WEAR("wear"),
TV("tv")
}
val identifier: String
get() = "$locale.${type.name}.$path"
fun url(
context: Context,
repository: Repository,
packageName: String
): String {
): Any {
if (type == Type.VIDEO) return context.videoPlaceHolder.apply {
setTintList(context.getColorFromAttr(MaterialR.attr.colorOnSurfaceInverse))
}
val phoneType = when (type) {
Type.PHONE -> "phoneScreenshots"
Type.SMALL_TABLET -> "sevenInchScreenshots"
Type.LARGE_TABLET -> "tenInchScreenshots"
Type.WEAR -> "wearScreenshots"
Type.TV -> "tvScreenshots"
else -> error("Should not be here, video url already returned")
}
return "${repository.address}/$packageName/$locale/$phoneType/$path"
}
@ -115,48 +126,3 @@ fun List<Pair<Product, Repository>>.findSuggested(
}
)
)
//
//fun App.toProduct() = Product(
// packageName = metadata.packageName.name,
// name = metadata.name,
// summary = metadata.summary,
// description = metadata.description,
// whatsNew = metadata.whatsNew,
// icon = metadata.icon,
// metadataIcon = "",
// author = Product.Author(
// name = author.name,
// email = author.email,
// web = author.web,
// ),
// source = links.sourceCode,
// changelog = links.changelog,
// web = links.webSite,
// tracker = links.issueTracker,
// added = metadata.added,
// updated = metadata.lastUpdated,
// suggestedVersionCode = metadata.suggestedVersionCode,
// categories = categories,
// antiFeatures = metadata.antiFeatures,
// licenses = listOf(metadata.license),
// donates = donation.toLegacy(),
// screenshots = screenshots.toLegacy(),
// releases = packages
//)
fun Donation.toLegacy() = buildList {
regularUrl?.let { it.forEach { add(Product.Donate.Regular(it)) } }
bitcoinAddress?.let { add(Product.Donate.Bitcoin(it)) }
flattrId?.let { add(Product.Donate.Flattr(it)) }
litecoinAddress?.let { add(Product.Donate.Litecoin(it)) }
openCollectiveId?.let { add(Product.Donate.OpenCollective(it)) }
liberapayId?.let { add(Product.Donate.Liberapay(it)) }
}
fun Screenshots.toLegacy() = buildList {
phone.forEach { add(Product.Screenshot("en-US", Product.Screenshot.Type.PHONE, it)) }
sevenInch.forEach { add(Product.Screenshot("en-US", Product.Screenshot.Type.SMALL_TABLET, it)) }
tenInch.forEach { add(Product.Screenshot("en-US", Product.Screenshot.Type.LARGE_TABLET, it)) }
tv.forEach { add(Product.Screenshot("en-US", Product.Screenshot.Type.PHONE, it)) }
wear.forEach { add(Product.Screenshot("en-US", Product.Screenshot.Type.PHONE, it)) }
}

View File

@ -18,16 +18,16 @@ data class ProductItem(
var canUpdate: Boolean,
var matchRank: Int
) {
sealed class Section : Parcelable {
sealed interface Section : Parcelable {
@Parcelize
data object All : Section()
object All : Section
@Parcelize
data class Category(val name: String) : Section()
class Category(val name: String) : Section
@Parcelize
data class Repository(val id: Long, val name: String) : Section()
class Repository(val id: Long, val name: String) : Section
}
private val supportedDpi = intArrayOf(120, 160, 240, 320, 480, 640)

View File

@ -31,7 +31,7 @@ data class Release(
object MinSdk : Incompatibility()
object MaxSdk : Incompatibility()
object Platform : Incompatibility()
data class Feature(val feature: String) : Incompatibility()
class Feature(val feature: String) : Incompatibility()
}
val identifier: String

View File

@ -15,7 +15,7 @@ data class Repository(
val entityTag: String,
val updated: Long,
val timestamp: Long,
val authentication: String
val authentication: String,
) {
fun edit(address: String, fingerprint: String, authentication: String): Repository {
@ -38,7 +38,7 @@ data class Repository(
version: Int,
lastModified: String,
entityTag: String,
timestamp: Long
timestamp: Long,
): Repository {
return copy(
mirrors = mirrors,
@ -62,7 +62,7 @@ data class Repository(
fun newRepository(
address: String,
fingerprint: String,
authentication: String
authentication: String,
): Repository {
val name = try {
URL(address).let { "${it.host}${it.path}" }
@ -79,7 +79,7 @@ data class Repository(
version: Int = 21,
enabled: Boolean = false,
fingerprint: String,
authentication: String = ""
authentication: String = "",
): Repository {
return Repository(
-1, address, emptyList(), name, description, version, enabled,
@ -150,14 +150,6 @@ data class Repository(
" by Netsyms Technologies.",
fingerprint = "2581BA7B32D3AB443180C4087CAB6A7E8FB258D3A6E98870ECB3C675E4D64489"
),
defaultRepository(
address = "https://fdroid.bromite.org/fdroid/repo",
name = "Bromite",
description = "The official repository for Bromite. " +
"Bromite is a Chromium with ad blocking and enhanced p" +
"rivacy.",
fingerprint = "E1EE5CD076D7B0DC84CB2B45FB78B86DF2EB39A3B6C56BA3DC292A5E0C3B9504"
),
defaultRepository(
address = "https://molly.im/fdroid/foss/fdroid/repo",
name = "Molly",
@ -217,7 +209,7 @@ data class Repository(
name = "Kali Nethunter",
description = "Kali Nethunter's official selection of original b" +
"inaries.",
fingerprint = "7E418D34C3AD4F3C37D7E6B0FACE13332364459C862134EB099A3BDA2CCF4494"
fingerprint = "FE7A23DFC003A1CF2D2ADD2469B9C0C49B206BA5DC9EDD6563B3B7EB6A8F5FAB"
),
defaultRepository(
address = "https://secfirst.org/fdroid/repo",
@ -265,14 +257,14 @@ data class Repository(
name = "Threema Libre",
description = "The official repository for Threema Libre. R" +
"equires Threema Shop license. Threema Libre is an open" +
"-source messanger focused on security and privacy.",
"-source messenger focused on security and privacy.",
fingerprint = "5734E753899B25775D90FE85362A49866E05AC4F83C05BEF5A92880D2910639E"
),
defaultRepository(
address = "https://fdroid.getsession.org/fdroid/repo",
name = "Session",
description = "The official repository for Session. Session" +
" is an open-source messanger focused on security and privacy.",
" is an open-source messenger focused on security and privacy.",
fingerprint = "DB0E5297EB65CC22D6BD93C869943BDCFCB6A07DC69A48A0DD8C7BA698EC04E6"
),
defaultRepository(
@ -398,7 +390,7 @@ data class Repository(
),
)
val newlyAdded = listOf<Repository>(
val newlyAdded: List<Repository> = listOf(
defaultRepository(
address = "https://fdroid.ironfoxoss.org/fdroid/repo",
name = "IronFox",
@ -412,6 +404,14 @@ data class Repository(
description = "The official repository for Total Commander",
fingerprint = "3576596CECDD70488D61CFD90799A49B7FFD26A81A8FEF1BADEC88D069FA72C1"
),
defaultRepository(
address = "https://www.cromite.org/fdroid/repo",
name = "Cromite",
description = "The official repository for Cromite. " +
"Cromite is a Chromium fork based on Bromite with " +
"built-in support for ad blocking and an eye for privacy.",
fingerprint = "49F37E74DEE483DCA2B991334FB5A0200787430D0B5F9A783DD5F13695E9517B"
)
)
}
}

View File

@ -136,7 +136,7 @@ internal class KtorDownloader(
private const val CONNECTION_TIMEOUT = 30_000L
private const val SOCKET_TIMEOUT = 15_000L
private const val USER_AGENT = "Droid-ify, ${BuildConfig.VERSION_NAME}"
private const val USER_AGENT = "Droid-ify/${BuildConfig.VERSION_NAME}-${BuildConfig.BUILD_TYPE}"
private fun HttpClientConfig<*>.userAgentConfig() = install(UserAgent) {
agent = USER_AGENT

View File

@ -22,7 +22,6 @@ import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.notificationManager
import com.looker.droidify.utility.common.extension.startServiceCompat
import com.looker.droidify.utility.common.extension.stopForegroundCompat
import com.looker.droidify.utility.common.log
import com.looker.droidify.utility.common.result.Result
import com.looker.droidify.utility.common.sdkAbove
import com.looker.droidify.datastore.SettingsRepository
@ -53,6 +52,8 @@ import kotlinx.coroutines.withContext
import java.lang.ref.WeakReference
import javax.inject.Inject
import com.looker.droidify.R
import kotlinx.coroutines.FlowPreview
import kotlin.math.roundToInt
import android.R as AndroidR
import com.looker.droidify.R.string as stringRes
import com.looker.droidify.R.style as styleRes
@ -69,15 +70,16 @@ class SyncService : ConnectionService<SyncService.Binder>() {
private const val MAX_UPDATE_NOTIFICATION = 5
private const val ACTION_CANCEL = "${BuildConfig.APPLICATION_ID}.intent.action.CANCEL"
private val syncState = MutableSharedFlow<State>()
val syncState = MutableSharedFlow<State>()
}
@Inject
lateinit var settingsRepository: SettingsRepository
sealed class State(val name: String) {
data class Connecting(val appName: String) : State(appName)
data class Syncing(
class Connecting(appName: String) : State(appName)
class Syncing(
val appName: String,
val stage: RepositoryUpdater.Stage,
val read: DataSize,
@ -85,6 +87,18 @@ class SyncService : ConnectionService<SyncService.Binder>() {
) : State(appName)
data object Finish : State("")
val progress: Int
get() = when (this) {
is Connecting -> Int.MIN_VALUE
Finish -> Int.MAX_VALUE
is Syncing -> when(stage) {
RepositoryUpdater.Stage.DOWNLOAD -> ((read percentBy total) * 0.4F).roundToInt()
RepositoryUpdater.Stage.PROCESS -> 50
RepositoryUpdater.Stage.MERGE -> 75
RepositoryUpdater.Stage.COMMIT -> 90
}
}
}
private class Task(val repositoryId: Long, val manual: Boolean)
@ -145,7 +159,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
}
suspend fun updateAllApps() {
updateAllAppsInternal()
val skipSignature = settingsRepository.getInitial().ignoreSignature
updateAllAppsInternal(skipSignature)
}
fun setUpdateNotificationBlocker(fragment: Fragment?) {
@ -198,6 +213,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
private val binder = Binder()
override fun onBind(intent: Intent): Binder = binder
@OptIn(FlowPreview::class)
override fun onCreate() {
super.onCreate()
@ -389,7 +405,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
handleUpdates(
hasUpdates = hasUpdates,
notifyUpdates = setting.notifyUpdate,
autoUpdate = setting.autoUpdate
autoUpdate = setting.autoUpdate,
skipSignature = setting.ignoreSignature,
)
}
}
@ -470,7 +487,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
private suspend fun handleUpdates(
hasUpdates: Boolean,
notifyUpdates: Boolean,
autoUpdate: Boolean
autoUpdate: Boolean,
skipSignature: Boolean,
) {
try {
if (!hasUpdates) {
@ -481,15 +499,16 @@ class SyncService : ConnectionService<SyncService.Binder>() {
return
}
val blocked = updateNotificationBlockerFragment?.get()?.isAdded == true
val updates = Database.ProductAdapter.getUpdates()
val updates = Database.ProductAdapter.getUpdates(skipSignature)
if (!blocked && updates.isNotEmpty()) {
if (notifyUpdates) displayUpdatesNotification(updates)
if (autoUpdate) updateAllAppsInternal()
if (autoUpdate) updateAllAppsInternal(skipSignature)
}
handleUpdates(
hasUpdates = false,
notifyUpdates = notifyUpdates,
autoUpdate = autoUpdate
autoUpdate = autoUpdate,
skipSignature = skipSignature,
)
} finally {
withContext(NonCancellable) {
@ -499,10 +518,9 @@ class SyncService : ConnectionService<SyncService.Binder>() {
}
}
private suspend fun updateAllAppsInternal() {
log("Check Running", "Syncing")
private suspend fun updateAllAppsInternal(skipSignature: Boolean) {
Database.ProductAdapter
.getUpdates()
.getUpdates(skipSignature)
// Update Droid-ify the last
.sortedBy { if (it.packageName == packageName) 1 else -1 }
.map {

View File

@ -40,7 +40,7 @@ import androidx.core.text.util.LinkifyCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil3.load
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.imageview.ShapeableImageView
@ -56,6 +56,7 @@ import com.looker.droidify.model.Release
import com.looker.droidify.model.Repository
import com.looker.droidify.model.findSuggested
import com.looker.droidify.network.DataSize
import com.looker.droidify.network.percentBy
import com.looker.droidify.utility.PackageItemResolver
import com.looker.droidify.utility.common.extension.authentication
import com.looker.droidify.utility.common.extension.copyToClipboard
@ -69,6 +70,7 @@ import com.looker.droidify.utility.common.extension.inflate
import com.looker.droidify.utility.common.extension.open
import com.looker.droidify.utility.common.extension.setTextSizeScaled
import com.looker.droidify.utility.common.nullIfEmpty
import com.looker.droidify.utility.common.sdkName
import com.looker.droidify.utility.extension.android.Android
import com.looker.droidify.utility.extension.resources.TypefaceExtra
import com.looker.droidify.utility.extension.resources.sizeScaled
@ -101,7 +103,7 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
fun onFavouriteClicked()
fun onPreferenceChanged(preference: ProductPreference)
fun onPermissionsClick(group: String?, permissions: List<String>)
fun onScreenshotClick(screenshot: Product.Screenshot, parentView: ImageView)
fun onScreenshotClick(position: Int)
fun onReleaseClick(release: Release)
fun onRequestAddRepository(address: String)
fun onUriClick(uri: Uri, shouldConfirm: Boolean): Boolean
@ -313,7 +315,6 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
is Product.Donate.Regular -> drawableRes.ic_donate
is Product.Donate.Bitcoin -> drawableRes.ic_donate_bitcoin
is Product.Donate.Litecoin -> drawableRes.ic_donate_litecoin
is Product.Donate.Flattr -> drawableRes.ic_donate_flattr
is Product.Donate.Liberapay -> drawableRes.ic_donate_liberapay
is Product.Donate.OpenCollective -> drawableRes.ic_donate_opencollective
}
@ -322,7 +323,6 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
is Product.Donate.Regular -> context.getString(stringRes.website)
is Product.Donate.Bitcoin -> "Bitcoin"
is Product.Donate.Litecoin -> "Litecoin"
is Product.Donate.Flattr -> "Flattr"
is Product.Donate.Liberapay -> "Liberapay"
is Product.Donate.OpenCollective -> "Open Collective"
}
@ -331,12 +331,8 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
is Product.Donate.Regular -> Uri.parse(donate.url)
is Product.Donate.Bitcoin -> Uri.parse("bitcoin:${donate.address}")
is Product.Donate.Litecoin -> Uri.parse("litecoin:${donate.address}")
is Product.Donate.Flattr -> Uri.parse(
"https://flattr.com/thing/${donate.id}"
)
is Product.Donate.Liberapay -> Uri.parse(
"https://liberapay.com/~${donate.id}"
"https://liberapay.com/${donate.id}"
)
is Product.Donate.OpenCollective -> Uri.parse(
@ -561,6 +557,7 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
val size = itemView.findViewById<TextView>(R.id.size)!!
val signature = itemView.findViewById<TextView>(R.id.signature)!!
val compatibility = itemView.findViewById<TextView>(R.id.compatibility)!!
val sdkVer = itemView.findViewById<TextView>(R.id.sdk_ver)!!
val statefulViews: Sequence<View>
get() = sequenceOf(
@ -571,7 +568,8 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
added,
size,
signature,
compatibility
compatibility,
sdkVer,
)
}
@ -1365,12 +1363,10 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
)
holder.progress.isIndeterminate = status.total == null
if (status.total != null) {
holder.progress.progress =
(
holder.progress.max.toFloat() *
status.read.value /
status.total.value
).roundToInt()
holder.progress.setProgressCompat(
status.read.value percentBy status.total.value,
true
)
}
}
@ -1430,15 +1426,13 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
holder as ScreenShotViewHolder
item as Item.ScreenshotItem
holder.screenshotsRecycler.run {
setHasFixedSize(true)
isNestedScrollingEnabled = false
clipToPadding = false
setPadding(8.dp, 8.dp, 8.dp, 8.dp)
layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter =
ScreenshotsAdapter { screenshot, view ->
callbacks.onScreenshotClick(screenshot, view)
}.apply {
adapter = ScreenshotsAdapter(callbacks::onScreenshotClick).apply {
setScreenshots(item.repository, item.packageName, item.screenshots)
}
}
@ -1626,7 +1620,7 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
holder.version.text =
context.getString(stringRes.version_FORMAT, item.release.version)
holder.status.apply {
with(holder.status) {
isVisible = installed || suggested
setText(
when {
@ -1637,14 +1631,15 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
)
background = context.corneredBackground
setPadding(15, 15, 15, 15)
val (background, foreground) = if (installed) {
MaterialR.attr.colorSecondaryContainer to MaterialR.attr.colorOnSecondaryContainer
} else {
MaterialR.attr.colorPrimaryContainer to MaterialR.attr.colorOnPrimaryContainer
}
if (installed) {
backgroundTintList =
context.getColorFromAttr(background)
setTextColor(context.getColorFromAttr(foreground))
context.getColorFromAttr(MaterialR.attr.colorSecondaryContainer)
setTextColor(context.getColorFromAttr(MaterialR.attr.colorOnSecondaryContainer))
} else {
backgroundTintList =
context.getColorFromAttr(MaterialR.attr.colorPrimaryContainer)
setTextColor(context.getColorFromAttr(MaterialR.attr.colorOnPrimaryContainer))
}
}
holder.source.text =
context.getString(stringRes.provided_by_FORMAT, item.repository.name)
@ -1688,15 +1683,13 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
}
holder.signature.text = builder
}
holder.compatibility.isVisible = incompatibility != null || singlePlatform != null
with(holder.compatibility) {
isVisible = incompatibility != null || singlePlatform != null
if (incompatibility != null) {
holder.compatibility.setTextColor(
context.getColorFromAttr(MaterialR.attr.colorError)
)
holder.compatibility.text = when (incompatibility) {
setTextColor(context.getColorFromAttr(MaterialR.attr.colorError))
text = when (incompatibility) {
is Release.Incompatibility.MinSdk,
is Release.Incompatibility.MaxSdk
-> context.getString(
is Release.Incompatibility.MaxSdk -> context.getString(
stringRes.incompatible_with_FORMAT,
Android.name
)
@ -1712,11 +1705,29 @@ class AppDetailAdapter(private val callbacks: Callbacks) :
)
}
} else if (singlePlatform != null) {
holder.compatibility.setTextColor(
context.getColorFromAttr(android.R.attr.textColorSecondary)
setTextColor(context.getColorFromAttr(android.R.attr.textColorSecondary))
text = context.getString(
stringRes.only_compatible_with_FORMAT,
singlePlatform,
)
holder.compatibility.text =
context.getString(stringRes.only_compatible_with_FORMAT, singlePlatform)
}
}
with(holder.sdkVer) {
val targetSdkVersion = sdkName.getOrDefault(
item.release.targetSdkVersion,
context.getString(
stringRes.label_unknown_sdk,
item.release.targetSdkVersion,
),
)
val minSdkVersion = sdkName.getOrDefault(
item.release.minSdkVersion,
context.getString(
stringRes.label_unknown_sdk,
item.release.minSdkVersion,
),
)
text = context.getString(stringRes.label_sdk_version, targetSdkVersion, minSdkVersion)
}
val enabled = status == Status.Idle
holder.statefulViews.forEach { it.isEnabled = enabled }

View File

@ -1,5 +1,6 @@
package com.looker.droidify.ui.appDetail
import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Intent
@ -8,7 +9,6 @@ import android.os.Bundle
import android.provider.Settings
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.core.os.bundleOf
@ -20,16 +20,13 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import coil.load
import coil3.load
import coil3.request.allowHardware
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.looker.droidify.utility.common.cache.Cache
import com.looker.droidify.utility.common.extension.getLauncherActivities
import com.looker.droidify.utility.common.extension.getMutatedIcon
import com.looker.droidify.utility.common.extension.isFirstItemVisible
import com.looker.droidify.utility.common.extension.isSystemApplication
import com.looker.droidify.utility.common.extension.systemBarsPadding
import com.looker.droidify.utility.common.extension.updateAsMutable
import com.looker.droidify.content.ProductPreferences
import com.looker.droidify.installer.installers.launchShizuku
import com.looker.droidify.installer.model.InstallState
import com.looker.droidify.installer.model.isCancellable
import com.looker.droidify.model.InstalledItem
import com.looker.droidify.model.Product
import com.looker.droidify.model.ProductPreference
@ -43,11 +40,15 @@ import com.looker.droidify.ui.MessageDialog
import com.looker.droidify.ui.ScreenFragment
import com.looker.droidify.ui.appDetail.AppDetailViewModel.Companion.ARG_PACKAGE_NAME
import com.looker.droidify.ui.appDetail.AppDetailViewModel.Companion.ARG_REPO_ADDRESS
import com.looker.droidify.utility.extension.screenActivity
import com.looker.droidify.utility.common.cache.Cache
import com.looker.droidify.utility.common.extension.getLauncherActivities
import com.looker.droidify.utility.common.extension.getMutatedIcon
import com.looker.droidify.utility.common.extension.isFirstItemVisible
import com.looker.droidify.utility.common.extension.isSystemApplication
import com.looker.droidify.utility.common.extension.systemBarsPadding
import com.looker.droidify.utility.common.extension.updateAsMutable
import com.looker.droidify.utility.extension.mainActivity
import com.looker.droidify.utility.extension.startUpdate
import com.looker.droidify.installer.installers.launchShizuku
import com.looker.droidify.installer.model.InstallState
import com.looker.droidify.installer.model.isCancellable
import com.stfalcon.imageviewer.StfalconImageViewer
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
@ -89,6 +90,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
private val viewModel: AppDetailViewModel by viewModels()
@SuppressLint("RestrictedApi")
private var layoutManagerState: LinearLayoutManager.SavedState? = null
private var actions = Pair(emptySet<Action>(), null as Action?)
@ -99,6 +101,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
private var recyclerView: RecyclerView? = null
private var detailAdapter: AppDetailAdapter? = null
private var imageViewer: StfalconImageViewer.Builder<Product.Screenshot>? = null
private val downloadConnection = Connection(
serviceClass = DownloadService::class.java,
@ -109,11 +112,12 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
}
)
@SuppressLint("RestrictedApi")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
detailAdapter = AppDetailAdapter(this@AppDetailFragment)
screenActivity.onToolbarCreated(toolbar)
mainActivity.onToolbarCreated(toolbar)
toolbar.menu.apply {
Action.entries.forEach { action ->
add(0, action.id, 0, action.adapterAction.titleResId)
@ -205,10 +209,12 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
super.onDestroyView()
recyclerView = null
detailAdapter = null
imageViewer = null
downloadConnection.unbind(requireContext())
}
@SuppressLint("RestrictedApi")
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
@ -446,20 +452,27 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
.show(childFragmentManager)
}
override fun onScreenshotClick(screenshot: Product.Screenshot, parentView: ImageView) {
val product = products
.firstOrNull { (product, _) ->
product.screenshots.find { it === screenshot }?.identifier != null
override fun onScreenshotClick(position: Int) {
if (imageViewer == null) {
val productRepository = products.findSuggested(installed?.installedItem) ?: return
val screenshots = productRepository.first.screenshots.mapNotNull {
if (it.type == Product.Screenshot.Type.VIDEO) null
else it
}
?: return
val screenshots = product.first.screenshots
val position = screenshots.indexOfFirst { screenshot.identifier == it.identifier }
StfalconImageViewer
imageViewer = StfalconImageViewer
.Builder(context, screenshots) { view, current ->
view.load(current.url(product.second, viewModel.packageName))
val screenshotUrl = current.url(
context = requireContext(),
repository = productRepository.second,
packageName = viewModel.packageName
)
view.load(screenshotUrl) {
allowHardware(false)
}
.withStartPosition(position)
.show()
}
}
imageViewer?.withStartPosition(position)
imageViewer?.show()
}
override fun onReleaseClick(release: Release) {
@ -517,7 +530,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
}
override fun onRequestAddRepository(address: String) {
screenActivity.navigateAddRepository(address)
mainActivity.navigateAddRepository(address)
}
override fun onUriClick(uri: Uri, shouldConfirm: Boolean): Boolean {

View File

@ -5,55 +5,71 @@ import android.graphics.drawable.Drawable
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import coil.dispose
import coil.load
import coil.size.Dimension
import coil.size.Scale
import coil3.asImage
import coil3.dispose
import coil3.load
import coil3.request.placeholder
import coil3.size.Scale
import com.google.android.material.imageview.ShapeableImageView
import com.looker.droidify.databinding.VideoButtonBinding
import com.looker.droidify.graphics.PaddingDrawable
import com.looker.droidify.model.Product
import com.looker.droidify.model.Repository
import com.looker.droidify.utility.common.extension.aspectRatio
import com.looker.droidify.utility.common.extension.authentication
import com.looker.droidify.utility.common.extension.camera
import com.looker.droidify.utility.common.extension.dp
import com.looker.droidify.utility.common.extension.dpToPx
import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.layoutInflater
import com.looker.droidify.utility.common.extension.openLink
import com.looker.droidify.utility.common.extension.selectableBackground
import com.looker.droidify.graphics.PaddingDrawable
import com.looker.droidify.model.Product
import com.looker.droidify.model.Repository
import com.looker.droidify.widget.StableRecyclerAdapter
import com.google.android.material.R as MaterialR
import com.looker.droidify.R.dimen as dimenRes
class ScreenshotsAdapter(private val onClick: (Product.Screenshot, ImageView) -> Unit) :
class ScreenshotsAdapter(private val onClick: (position: Int) -> Unit) :
StableRecyclerAdapter<ScreenshotsAdapter.ViewType, RecyclerView.ViewHolder>() {
enum class ViewType { SCREENSHOT }
enum class ViewType { SCREENSHOT, VIDEO }
private val items = mutableListOf<Item.ScreenshotItem>()
private val items = mutableListOf<Item>()
private class ViewHolder(context: Context) :
RecyclerView.ViewHolder(FrameLayout(context)) {
val image: ShapeableImageView = object : ShapeableImageView(context) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measuredWidth, measuredHeight)
private inner class VideoViewHolder(
binding: VideoButtonBinding,
) : RecyclerView.ViewHolder(binding.root) {
val button = binding.videoButton
init {
with(button) {
layoutParams = RecyclerView.LayoutParams(
RecyclerView.LayoutParams.WRAP_CONTENT,
150.dp,
)
setOnClickListener {
val item = items[absoluteAdapterPosition] as Item.VideoItem
it.context?.openLink(item.videoUrl)
}
}
}
}
private inner class ScreenshotViewHolder(
context: Context,
) : RecyclerView.ViewHolder(FrameLayout(context)) {
val image = ShapeableImageView(context)
val placeholderColor = context.getColorFromAttr(MaterialR.attr.colorPrimaryContainer)
val radius = context.resources.getDimension(dimenRes.shape_small_corner)
val imageShapeModel = image.shapeAppearanceModel.toBuilder()
.setAllCornerSizes(radius)
.build()
val cameraIcon = context.camera
.apply { setTintList(placeholderColor) }
val cameraIcon = context.camera.apply { setTintList(placeholderColor) }
val placeholder: Drawable = PaddingDrawable(cameraIcon, 3f, context.aspectRatio)
init {
with(image) {
layout(0, 0, 0, 0)
adjustViewBounds = true
shapeAppearanceModel = imageShapeModel
background = context.selectableBackground
isFocusable = true
@ -68,6 +84,14 @@ class ScreenshotsAdapter(private val onClick: (Product.Screenshot, ImageView) ->
marginEnd = radius.toInt()
}
foregroundGravity = Gravity.CENTER
setOnClickListener {
val position = if (items.any { it.viewType == ViewType.VIDEO }) {
absoluteAdapterPosition - 1
} else {
absoluteAdapterPosition
}
onClick(position)
}
}
}
}
@ -75,67 +99,73 @@ class ScreenshotsAdapter(private val onClick: (Product.Screenshot, ImageView) ->
fun setScreenshots(
repository: Repository,
packageName: String,
screenshots: List<Product.Screenshot>
screenshots: List<Product.Screenshot>,
) {
items.clear()
items += screenshots.map { Item.ScreenshotItem(repository, packageName, it) }
items += screenshots.map {
if (it.type == Product.Screenshot.Type.VIDEO) Item.VideoItem(it.path)
else Item.ScreenshotItem(repository, packageName, it)
}
notifyItemRangeInserted(0, screenshots.size)
}
override val viewTypeClass: Class<ViewType>
get() = ViewType::class.java
override fun getItemEnumViewType(position: Int): ViewType {
return ViewType.SCREENSHOT
}
override val viewTypeClass: Class<ViewType> get() = ViewType::class.java
override fun getItemCount(): Int = items.size
override fun getItemEnumViewType(position: Int) = items[position].viewType
override fun getItemDescriptor(position: Int): String = items[position].descriptor
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: ViewType
viewType: ViewType,
): RecyclerView.ViewHolder {
return ViewHolder(parent.context).apply {
image.setOnClickListener {
onClick(
items[absoluteAdapterPosition].screenshot,
it as ImageView
)
return when (viewType) {
ViewType.VIDEO -> VideoViewHolder(VideoButtonBinding.inflate(parent.context.layoutInflater))
ViewType.SCREENSHOT -> ScreenshotViewHolder(parent.context)
}
}
}
override fun getItemDescriptor(position: Int): String = items[position].descriptor
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as ViewHolder
val item = items[position]
when (getItemEnumViewType(position)) {
ViewType.SCREENSHOT -> {
holder as ScreenshotViewHolder
val item = items[position] as Item.ScreenshotItem
with(holder.image) {
load(item.screenshot.url(item.repository, item.packageName)) {
size(Dimension.Undefined, Dimension(150.dp.dpToPx.toInt()))
scale(Scale.FIT)
placeholder(holder.placeholder)
error(holder.placeholder)
load(item.screenshot.url(context, item.repository, item.packageName)) {
authentication(item.repository.authentication)
scale(Scale.FILL)
placeholder(holder.placeholder)
error(holder.placeholder.asImage())
}
}
}
ViewType.VIDEO -> {}
}
}
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
super.onViewRecycled(holder)
holder as ViewHolder
holder.image.dispose()
if (holder is ScreenshotViewHolder) holder.image.dispose()
}
private sealed class Item {
abstract val descriptor: String
private sealed interface Item {
val descriptor: String
val viewType: ViewType
class ScreenshotItem(
val repository: Repository,
val packageName: String,
val screenshot: Product.Screenshot
) : Item() {
val screenshot: Product.Screenshot,
) : Item {
override val viewType: ViewType get() = ViewType.SCREENSHOT
override val descriptor: String
get() = "screenshot.${repository.id}.${screenshot.identifier}"
}
class VideoItem(val videoUrl: String) : Item {
override val viewType: ViewType get() = ViewType.VIDEO
override val descriptor: String get() = "video"
}
}
}

View File

@ -2,6 +2,7 @@ package com.looker.droidify.ui.appList
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
@ -9,7 +10,7 @@ import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil3.load
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.looker.droidify.R
@ -22,23 +23,31 @@ import com.looker.droidify.utility.common.extension.dp
import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.inflate
import com.looker.droidify.utility.common.extension.setTextSizeScaled
import com.looker.droidify.utility.common.log
import com.looker.droidify.utility.common.nullIfEmpty
import com.looker.droidify.utility.extension.resources.TypefaceExtra
import com.looker.droidify.widget.CursorRecyclerAdapter
import kotlin.system.measureTimeMillis
import com.google.android.material.R as MaterialR
class AppListAdapter(
private val source: AppListFragment.Source,
private val onClick: (ProductItem) -> Unit
private val onClick: (packageName: String) -> Unit,
) : CursorRecyclerAdapter<AppListAdapter.ViewType, RecyclerView.ViewHolder>() {
enum class ViewType { PRODUCT, LOADING, EMPTY }
private class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private inner class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val name = itemView.findViewById<TextView>(R.id.name)!!
val status = itemView.findViewById<TextView>(R.id.status)!!
val summary = itemView.findViewById<TextView>(R.id.summary)!!
val icon = itemView.findViewById<ShapeableImageView>(R.id.icon)!!
init {
itemView.setOnClickListener {
log(measureTimeMillis { onClick(getPackageName(absoluteAdapterPosition)) }, "Bench")
}
}
}
private class LoadingViewHolder(context: Context) :
@ -82,10 +91,12 @@ class AppListAdapter(
}
}
var repositories: Map<Long, Repository> = emptyMap()
@SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
private val repositories: HashMap<Long, Repository> = HashMap()
fun updateRepos(repos: List<Repository>) {
repos.forEach {
repositories[it.id] = it
}
notifyDataSetChanged()
}
@ -117,24 +128,31 @@ class AppListAdapter(
}
}
private fun getPackageName(position: Int): String {
return Database.ProductAdapter.transformPackageName(moveTo(position.coerceAtLeast(0)))
}
private fun getProductItem(position: Int): ProductItem {
return Database.ProductAdapter.transformItem(moveTo(position.coerceAtLeast(0)))
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: ViewType
viewType: ViewType,
): RecyclerView.ViewHolder {
return when (viewType) {
ViewType.PRODUCT -> ProductViewHolder(parent.inflate(R.layout.product_item)).apply {
itemView.setOnClickListener { onClick(getProductItem(absoluteAdapterPosition)) }
}
ViewType.PRODUCT -> ProductViewHolder(parent.inflate(R.layout.product_item))
ViewType.LOADING -> LoadingViewHolder(parent.context)
ViewType.EMPTY -> EmptyViewHolder(parent.context)
}
}
private var updateBackground: ColorStateList? = null
private var updateForeground: ColorStateList? = null
private var installedBackground: ColorStateList? = null
private var installedForeground: ColorStateList? = null
private var defaultForeground: ColorStateList? = null
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemEnumViewType(position)) {
ViewType.PRODUCT -> {
@ -142,9 +160,9 @@ class AppListAdapter(
val productItem = getProductItem(position)
holder.name.text = productItem.name
holder.summary.text = productItem.summary
holder.summary.isVisible =
productItem.summary.isNotEmpty() && productItem.name != productItem.summary
val repository: Repository? = repositories[productItem.repositoryId]
holder.summary.isVisible = productItem.summary.isNotEmpty()
&& productItem.name != productItem.summary
val repository = repositories[productItem.repositoryId]
if (repository != null) {
val iconUrl = productItem.icon(view = holder.icon, repository = repository)
holder.icon.load(iconUrl) {
@ -161,28 +179,38 @@ class AppListAdapter(
val isInstalled = productItem.installedVersion.nullIfEmpty() != null
when {
productItem.canUpdate -> {
backgroundTintList =
if (updateBackground == null) {
updateBackground =
context.getColorFromAttr(MaterialR.attr.colorTertiaryContainer)
setTextColor(
}
if (updateForeground == null) {
updateForeground =
context.getColorFromAttr(MaterialR.attr.colorOnTertiaryContainer)
)
}
backgroundTintList = updateBackground
setTextColor(updateForeground)
}
isInstalled -> {
backgroundTintList =
if (installedBackground == null) {
installedBackground =
context.getColorFromAttr(MaterialR.attr.colorSecondaryContainer)
setTextColor(
}
if (installedForeground == null) {
installedForeground =
context.getColorFromAttr(MaterialR.attr.colorOnSecondaryContainer)
)
}
backgroundTintList = installedBackground
setTextColor(installedForeground)
}
else -> {
setPadding(0, 0, 0, 0)
setTextColor(
holder.status.context.getColorFromAttr(
MaterialR.attr.colorOnBackground
)
)
if (defaultForeground == null) {
defaultForeground =
context.getColorFromAttr(MaterialR.attr.colorOnBackground)
}
setTextColor(defaultForeground)
background = null
return@with
}
@ -191,9 +219,9 @@ class AppListAdapter(
6.dp.let { setPadding(it, it, it, it) }
}
val enabled = productItem.compatible || productItem.installedVersion.isNotEmpty()
sequenceOf(holder.name, holder.status, holder.summary).forEach {
it.isEnabled = enabled
}
holder.name.isEnabled = enabled
holder.status.isEnabled = enabled
holder.summary.isEnabled = enabled
}
ViewType.LOADING -> {
@ -204,6 +232,6 @@ class AppListAdapter(
holder as EmptyViewHolder
holder.text.text = emptyText
}
}::class
}
}
}

View File

@ -14,19 +14,19 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.looker.droidify.utility.common.Scroller
import com.looker.droidify.R
import com.looker.droidify.R.string as stringRes
import com.looker.droidify.database.CursorOwner
import com.looker.droidify.databinding.RecyclerViewWithFabBinding
import com.looker.droidify.model.ProductItem
import com.looker.droidify.utility.common.Scroller
import com.looker.droidify.utility.common.extension.dp
import com.looker.droidify.utility.common.extension.isFirstItemVisible
import com.looker.droidify.utility.common.extension.systemBarsMargin
import com.looker.droidify.utility.common.extension.systemBarsPadding
import com.looker.droidify.model.ProductItem
import com.looker.droidify.database.CursorOwner
import com.looker.droidify.databinding.RecyclerViewWithFabBinding
import com.looker.droidify.utility.extension.screenActivity
import com.looker.droidify.utility.extension.mainActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import com.looker.droidify.R.string as stringRes
@AndroidEntryPoint
class AppListFragment() : Fragment(), CursorOwner.Callback {
@ -46,7 +46,7 @@ class AppListFragment() : Fragment(), CursorOwner.Callback {
val titleResId: Int,
val sections: Boolean,
val order: Boolean,
val updateAll: Boolean
val updateAll: Boolean,
) {
AVAILABLE(stringRes.available, true, true, false),
INSTALLED(stringRes.installed, false, true, false),
@ -63,14 +63,15 @@ class AppListFragment() : Fragment(), CursorOwner.Callback {
get() = requireArguments().getString(EXTRA_SOURCE)!!.let(Source::valueOf)
private lateinit var recyclerView: RecyclerView
private lateinit var recyclerViewAdapter: AppListAdapter
private lateinit var appListAdapter: AppListAdapter
private var scroller: Scroller? = null
private var shortAnimationDuration: Int = 0
private var layoutManagerState: Parcelable? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
): View {
_binding = RecyclerViewWithFabBinding.inflate(inflater, container, false)
@ -83,10 +84,8 @@ class AppListFragment() : Fragment(), CursorOwner.Callback {
isMotionEventSplittingEnabled = false
setHasFixedSize(true)
recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
recyclerViewAdapter = AppListAdapter(source) {
screenActivity.navigateProduct(it.packageName)
}
adapter = recyclerViewAdapter
appListAdapter = AppListAdapter(source, mainActivity::navigateProduct)
adapter = appListAdapter
systemBarsPadding()
}
val fab = binding.scrollUp
@ -103,11 +102,13 @@ class AppListFragment() : Fragment(), CursorOwner.Callback {
}
systemBarsMargin(16.dp)
} else {
text = ""
text = null
setIconResource(R.drawable.arrow_up)
setOnClickListener {
val scroller = Scroller(requireContext())
scroller.targetPosition = 0
if (scroller == null) {
scroller = Scroller(requireContext())
}
scroller!!.targetPosition = 0
recyclerView.layoutManager?.startSmoothScroll(scroller)
}
alpha = 0f
@ -138,7 +139,7 @@ class AppListFragment() : Fragment(), CursorOwner.Callback {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
launch {
viewModel.reposStream.collect { repos ->
recyclerViewAdapter.repositories = repos.associateBy { it.id }
appListAdapter.updateRepos(repos)
}
}
launch {
@ -160,12 +161,13 @@ class AppListFragment() : Fragment(), CursorOwner.Callback {
super.onDestroyView()
viewModel.syncConnection.unbind(requireContext())
_binding = null
screenActivity.cursorOwner.detach(this)
scroller = null
mainActivity.cursorOwner.detach(this)
}
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {
recyclerViewAdapter.cursor = cursor
recyclerViewAdapter.emptyText = when {
appListAdapter.cursor = cursor
appListAdapter.emptyText = when {
cursor == null -> ""
viewModel.searchQuery.value.isNotEmpty() -> {
getString(stringRes.no_matching_applications_found)
@ -197,7 +199,7 @@ class AppListFragment() : Fragment(), CursorOwner.Callback {
private fun updateRequest() {
if (view != null) {
screenActivity.cursorOwner.attach(this, viewModel.request(source))
mainActivity.cursorOwner.attach(this, viewModel.request(source))
}
}
}

View File

@ -2,40 +2,52 @@ package com.looker.droidify.ui.appList
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.looker.droidify.utility.common.extension.asStateFlow
import com.looker.droidify.database.CursorOwner
import com.looker.droidify.database.CursorOwner.Request.Available
import com.looker.droidify.database.CursorOwner.Request.Installed
import com.looker.droidify.database.CursorOwner.Request.Updates
import com.looker.droidify.database.Database
import com.looker.droidify.datastore.SettingsRepository
import com.looker.droidify.datastore.get
import com.looker.droidify.datastore.model.SortOrder
import com.looker.droidify.model.ProductItem
import com.looker.droidify.model.ProductItem.Section.All
import com.looker.droidify.database.CursorOwner
import com.looker.droidify.database.Database
import com.looker.droidify.service.Connection
import com.looker.droidify.service.SyncService
import com.looker.droidify.utility.common.extension.asStateFlow
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class AppListViewModel
@Inject constructor(
settingsRepository: SettingsRepository
settingsRepository: SettingsRepository,
) : ViewModel() {
private val skipSignatureStream = settingsRepository
.get { ignoreSignature }
.asStateFlow(false)
val sortOrderFlow = settingsRepository
.get { sortOrder }
.asStateFlow(SortOrder.UPDATED)
val reposStream = Database.RepositoryAdapter
.getAllStream()
.asStateFlow(emptyList())
val showUpdateAllButton = Database.ProductAdapter
.getUpdatesStream()
@OptIn(ExperimentalCoroutinesApi::class)
val showUpdateAllButton = skipSignatureStream.flatMapConcat { skip ->
Database.ProductAdapter
.getUpdatesStream(skip)
.map { it.isNotEmpty() }
.asStateFlow(false)
val sortOrderFlow = settingsRepository.get { sortOrder }
.asStateFlow(SortOrder.UPDATED)
}.asStateFlow(false)
private val sections = MutableStateFlow<ProductItem.Section>(All)
@ -51,22 +63,23 @@ class AppListViewModel
fun request(source: AppListFragment.Source): CursorOwner.Request {
return when (source) {
AppListFragment.Source.AVAILABLE -> CursorOwner.Request.ProductsAvailable(
searchQuery.value,
sections.value,
sortOrderFlow.value
AppListFragment.Source.AVAILABLE -> Available(
searchQuery = searchQuery.value,
section = sections.value,
order = sortOrderFlow.value,
)
AppListFragment.Source.INSTALLED -> CursorOwner.Request.ProductsInstalled(
searchQuery.value,
sections.value,
sortOrderFlow.value
AppListFragment.Source.INSTALLED -> Installed(
searchQuery = searchQuery.value,
section = sections.value,
order = sortOrderFlow.value,
)
AppListFragment.Source.UPDATES -> CursorOwner.Request.ProductsUpdates(
searchQuery.value,
sections.value,
sortOrderFlow.value
AppListFragment.Source.UPDATES -> Updates(
searchQuery = searchQuery.value,
section = sections.value,
order = sortOrderFlow.value,
skipSignatureCheck = skipSignatureStream.value,
)
}
}

View File

@ -4,7 +4,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil3.load
import com.looker.droidify.databinding.ProductItemBinding
import com.looker.droidify.model.Product
import com.looker.droidify.model.Repository

View File

@ -15,7 +15,7 @@ import com.looker.droidify.R
import com.looker.droidify.utility.common.extension.systemBarsPadding
import com.looker.droidify.database.Database
import com.looker.droidify.ui.ScreenFragment
import com.looker.droidify.utility.extension.screenActivity
import com.looker.droidify.utility.extension.mainActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@ -43,7 +43,7 @@ class FavouritesFragment : ScreenFragment() {
isVerticalScrollBarEnabled = false
setHasFixedSize(true)
recyclerViewAdapter =
FavouriteFragmentAdapter { screenActivity.navigateProduct(it) }
FavouriteFragmentAdapter { mainActivity.navigateProduct(it) }
this.adapter = recyclerViewAdapter
systemBarsPadding(includeFab = false)
recyclerView = this
@ -74,6 +74,6 @@ class FavouritesFragment : ScreenFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
screenActivity.onToolbarCreated(toolbar)
mainActivity.onToolbarCreated(toolbar)
}
}

View File

@ -1,21 +1,19 @@
package com.looker.droidify.ui.favourites
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.looker.droidify.utility.common.extension.asStateFlow
import com.looker.droidify.database.Database
import com.looker.droidify.datastore.SettingsRepository
import com.looker.droidify.datastore.get
import com.looker.droidify.model.Product
import com.looker.droidify.database.Database
import com.looker.droidify.utility.common.extension.asStateFlow
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class FavouritesViewModel @Inject constructor(
private val settingsRepository: SettingsRepository
settingsRepository: SettingsRepository,
) : ViewModel() {
val favouriteApps: StateFlow<List<List<Product>>> =
@ -27,9 +25,4 @@ class FavouritesViewModel @Inject constructor(
}
}.asStateFlow(emptyList())
fun updateFavourites(packageName: String) {
viewModelScope.launch {
settingsRepository.toggleFavourites(packageName)
}
}
}

View File

@ -15,26 +15,25 @@ import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.looker.droidify.utility.common.extension.clipboardManager
import com.looker.droidify.utility.common.extension.get
import com.looker.droidify.utility.common.extension.getMutatedIcon
import com.looker.droidify.utility.common.nullIfEmpty
import com.looker.droidify.model.Repository
import com.looker.droidify.R
import com.looker.droidify.database.Database
import com.looker.droidify.databinding.EditRepositoryBinding
import com.looker.droidify.model.Repository
import com.looker.droidify.network.Downloader
import com.looker.droidify.network.NetworkResponse
import com.looker.droidify.service.Connection
import com.looker.droidify.service.SyncService
import com.looker.droidify.ui.Message
import com.looker.droidify.ui.MessageDialog
import com.looker.droidify.ui.ScreenFragment
import com.looker.droidify.utility.extension.screenActivity
import com.looker.droidify.network.Downloader
import com.looker.droidify.network.NetworkResponse
import com.looker.droidify.utility.common.extension.clipboardManager
import com.looker.droidify.utility.common.extension.get
import com.looker.droidify.utility.common.extension.getMutatedIcon
import com.looker.droidify.utility.common.nullIfEmpty
import com.looker.droidify.utility.extension.mainActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import java.net.URI
@ -44,7 +43,6 @@ import java.nio.charset.Charset
import java.util.Locale
import javax.inject.Inject
import kotlin.math.min
import com.looker.droidify.R
import com.looker.droidify.R.string as stringRes
@AndroidEntryPoint
@ -82,11 +80,9 @@ class EditRepositoryFragment() : ScreenFragment() {
syncConnection.bind(requireContext())
screenActivity.onToolbarCreated(toolbar)
mainActivity.onToolbarCreated(toolbar)
toolbar.title =
getString(
if (repoId != null) stringRes.edit_repository else stringRes.add_repository
)
getString(if (repoId != null) stringRes.edit_repository else stringRes.add_repository)
saveMenuItem = toolbar.menu.add(stringRes.save)
.setIcon(toolbar.context.getMutatedIcon(R.drawable.ic_save))
@ -240,6 +236,8 @@ class EditRepositoryFragment() : ScreenFragment() {
saveMenuItem = null
syncConnection.unbind(requireContext())
checkJob?.cancel()
checkJob = null
_binding = null
}
@ -394,22 +392,22 @@ class EditRepositoryFragment() : ScreenFragment() {
}
private suspend fun checkAddress(
address: String,
rawAddress: String,
authentication: String
): String? = coroutineScope {
checkInProgress = true
invalidateState()
val allAddresses = addressSuffixes.map { "$address/$it" } + address
val pathCheck = allAddresses.map {
async {
downloader.headCall(
url = "$it/index-v1.jar",
val allAddresses = addressSuffixes.map { "$rawAddress/$it" } + rawAddress
allAddresses
.sortedBy { it.length }
.forEach { address ->
val response = downloader.headCall(
url = "$address/index-v1.jar",
headers = { authentication(authentication) }
) is NetworkResponse.Success
)
if (response is NetworkResponse.Success) return@coroutineScope address
}
}
val indexOfValidAddress = pathCheck.awaitAll().indexOf(true)
allAddresses[indexOfValidAddress].nullIfEmpty()
null
}
private fun onSaveRepositoryProceedInvalidate(
@ -431,7 +429,7 @@ class EditRepositoryFragment() : ScreenFragment() {
if (repositoryId == null && changedRepository.enabled) {
binder.sync(changedRepository)
}
screenActivity.onBackPressedDispatcher.onBackPressed()
mainActivity.onBackPressedDispatcher.onBackPressed()
}
} else {
invalidateState()
@ -470,6 +468,6 @@ class EditRepositoryFragment() : ScreenFragment() {
const val EXTRA_REPOSITORY_ID = "repositoryId"
const val EXTRA_REPOSITORY_ADDRESS = "repositoryAddress"
val addressSuffixes = listOf("fdroid/repo", "repo")
val addressSuffixes = arrayOf("fdroid/repo", "repo")
}
}

View File

@ -15,7 +15,7 @@ import com.looker.droidify.databinding.RecyclerViewWithFabBinding
import com.looker.droidify.service.Connection
import com.looker.droidify.service.SyncService
import com.looker.droidify.ui.ScreenFragment
import com.looker.droidify.utility.extension.screenActivity
import com.looker.droidify.utility.extension.mainActivity
import com.looker.droidify.widget.addDivider
class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
@ -36,7 +36,7 @@ class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
binding.scrollUp.apply {
setIconResource(R.drawable.ic_add)
setText(R.string.add_repository)
setOnClickListener { screenActivity.navigateAddRepository() }
setOnClickListener { mainActivity.navigateAddRepository() }
systemBarsMargin(16.dp)
}
binding.recyclerView.apply {
@ -44,7 +44,7 @@ class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
isMotionEventSplittingEnabled = false
setHasFixedSize(true)
adapter = RepositoriesAdapter(
navigate = { screenActivity.navigateRepository(it.id) }
navigate = { mainActivity.navigateRepository(it.id) }
) { repository, isEnabled ->
repository.enabled != isEnabled &&
syncConnection.binder?.setEnabled(repository, isEnabled) == true
@ -79,8 +79,8 @@ class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
super.onViewCreated(view, savedInstanceState)
syncConnection.bind(requireContext())
screenActivity.cursorOwner.attach(this, CursorOwner.Request.Repositories)
screenActivity.onToolbarCreated(toolbar)
mainActivity.cursorOwner.attach(this, CursorOwner.Request.Repositories)
mainActivity.onToolbarCreated(toolbar)
toolbar.title = getString(R.string.repositories)
}
@ -89,7 +89,7 @@ class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
_binding = null
syncConnection.unbind(requireContext())
screenActivity.cursorOwner.detach(this)
mainActivity.cursorOwner.detach(this)
}
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {

View File

@ -22,7 +22,7 @@ import com.looker.droidify.databinding.RepositoryPageBinding
import com.looker.droidify.ui.Message
import com.looker.droidify.ui.MessageDialog
import com.looker.droidify.ui.ScreenFragment
import com.looker.droidify.utility.extension.screenActivity
import com.looker.droidify.utility.extension.mainActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@ -53,7 +53,7 @@ class RepositoryFragment() : ScreenFragment() {
super.onCreateView(inflater, container, savedInstanceState)
_binding = RepositoryPageBinding.inflate(inflater, container, false)
viewModel.bindService(requireContext())
screenActivity.onToolbarCreated(toolbar)
mainActivity.onToolbarCreated(toolbar)
toolbar.title = getString(stringRes.repository)
val scroll = NestedScrollView(binding.root.context)
scroll.addView(binding.root)
@ -149,7 +149,7 @@ class RepositoryFragment() : ScreenFragment() {
}
editRepoButton.setOnClickListener {
screenActivity.navigateEditRepository(viewModel.id)
mainActivity.navigateEditRepository(viewModel.id)
}
deleteRepoButton.setOnClickListener {

View File

@ -2,6 +2,7 @@ package com.looker.droidify.ui.settings
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
@ -23,13 +24,11 @@ import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputEditText
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.homeAsUp
import com.looker.droidify.utility.common.extension.systemBarsPadding
import com.looker.droidify.utility.common.extension.updateAsMutable
import com.looker.droidify.utility.common.isIgnoreBatteryEnabled
import com.looker.droidify.utility.common.requestBatteryFreedom
import com.looker.droidify.BuildConfig
import com.looker.droidify.R
import com.looker.droidify.databinding.EnumTypeBinding
import com.looker.droidify.databinding.SettingsPageBinding
import com.looker.droidify.databinding.SwitchTypeBinding
import com.looker.droidify.datastore.Settings
import com.looker.droidify.datastore.extension.autoSyncName
import com.looker.droidify.datastore.extension.installerName
@ -38,22 +37,25 @@ import com.looker.droidify.datastore.extension.themeName
import com.looker.droidify.datastore.extension.toTime
import com.looker.droidify.datastore.model.AutoSync
import com.looker.droidify.datastore.model.InstallerType
import com.looker.droidify.datastore.model.LegacyInstallerComponent
import com.looker.droidify.datastore.model.ProxyType
import com.looker.droidify.datastore.model.Theme
import com.looker.droidify.BuildConfig
import com.looker.droidify.databinding.EnumTypeBinding
import com.looker.droidify.databinding.SettingsPageBinding
import com.looker.droidify.databinding.SwitchTypeBinding
import com.looker.droidify.utility.common.SdkCheck
import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.homeAsUp
import com.looker.droidify.utility.common.extension.systemBarsPadding
import com.looker.droidify.utility.common.extension.updateAsMutable
import com.looker.droidify.utility.common.isIgnoreBatteryEnabled
import com.looker.droidify.utility.common.requestBatteryFreedom
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.util.Locale
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import com.google.android.material.R as MaterialR
import com.looker.droidify.R
import androidx.core.net.toUri
@AndroidEntryPoint
class SettingsFragment : Fragment() {
@ -119,9 +121,7 @@ class SettingsFragment : Fragment() {
): View {
_binding = SettingsPageBinding.inflate(inflater, container, false)
binding.nestedScrollView.systemBarsPadding()
if (requireContext().isIgnoreBatteryEnabled()) {
viewModel.allowBackground()
}
viewModel.toggleBackgroundAccess(requireContext().isIgnoreBatteryEnabled())
val toolbar = binding.toolbar
toolbar.navigationIcon = toolbar.context.homeAsUp
toolbar.setNavigationOnClickListener { activity?.onBackPressedDispatcher?.onBackPressed() }
@ -233,6 +233,56 @@ class SettingsFragment : Fragment() {
onClick = { viewModel.setInstaller(requireContext(), it) }
)
}
val pm = requireContext().packageManager
legacyInstallerComponent.connect(
titleText = getString(R.string.legacyInstallerComponent),
setting = viewModel.getSetting { legacyInstallerComponent },
map = {
when (it) {
is LegacyInstallerComponent.Component -> {
val component = it
val appLabel = runCatching {
val info = pm.getApplicationInfo(component.clazz, 0)
pm.getApplicationLabel(info).toString()
}.getOrElse { component.clazz }
"$appLabel (${component.activity})"
}
LegacyInstallerComponent.Unspecified -> getString(R.string.unspecified)
LegacyInstallerComponent.AlwaysChoose -> getString(R.string.always_choose)
null -> getString(R.string.unspecified)
}
},
) { component, valueToString ->
val installerOptions = run {
var contentProtocol = "content://"
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
setDataAndType(contentProtocol.toUri(), "application/vnd.android.package-archive")
}
val activities = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
listOf(
LegacyInstallerComponent.Unspecified,
LegacyInstallerComponent.AlwaysChoose
) + activities.map {
LegacyInstallerComponent.Component(
clazz = it.activityInfo.packageName,
activity = it.activityInfo.name,
)
}
}
addSingleCorrectDialog(
initialValue = component ?: LegacyInstallerComponent.Unspecified,
values = installerOptions,
title = R.string.legacyInstallerComponent,
iconRes = R.drawable.ic_apk_install,
valueToString = valueToString,
onClick = { viewModel.setLegacyInstallerComponentComponent(it) },
)
}
incompatibleUpdates.connect(
titleText = getString(R.string.incompatible_versions),
contentText = getString(R.string.incompatible_versions_summary),
setting = viewModel.getInitialSetting { incompatibleVersions },
)
proxyType.connect(
titleText = getString(R.string.proxy_type),
setting = viewModel.getSetting { proxy.type },
@ -283,6 +333,7 @@ class SettingsFragment : Fragment() {
exportRepos.title.text = getString(R.string.export_repos_title)
exportRepos.content.text = getString(R.string.export_repos_DESC)
allowBackgroundWork.root.isVisible = false
allowBackgroundWork.title.text = getString(R.string.require_background_access)
allowBackgroundWork.content.text =
getString(R.string.require_background_access_DESC)
@ -315,8 +366,8 @@ class SettingsFragment : Fragment() {
launch {
viewModel.settingsFlow.collect { setting ->
updateSettings(setting)
binding.allowBackgroundWork.root.isVisible = !viewModel.backgroundTask.first()
&& setting.autoSync != AutoSync.NEVER
binding.allowBackgroundWork.root.isVisible =
!viewModel.isBackgroundAllowed && setting.autoSync != AutoSync.NEVER
}
}
}
@ -326,9 +377,7 @@ class SettingsFragment : Fragment() {
override fun onResume() {
super.onResume()
if (requireContext().isIgnoreBatteryEnabled()) {
viewModel.allowBackground()
}
viewModel.toggleBackgroundAccess(requireContext().isIgnoreBatteryEnabled())
}
override fun onDestroyView() {
@ -376,9 +425,7 @@ class SettingsFragment : Fragment() {
}
allowBackgroundWork.root.setOnClickListener {
requireContext().requestBatteryFreedom()
if (requireContext().isIgnoreBatteryEnabled()) {
viewModel.allowBackground()
}
viewModel.toggleBackgroundAccess(requireContext().isIgnoreBatteryEnabled())
}
creditFoxy.root.setOnClickListener {
openLink(FOXY_DROID_URL)
@ -395,6 +442,9 @@ class SettingsFragment : Fragment() {
proxyHost.root.isVisible = allowProxies
proxyPort.root.isVisible = allowProxies
forceCleanUp.root.isVisible = settings.cleanUpInterval == Duration.INFINITE
val useLegacyInstaller = settings.installerType == InstallerType.LEGACY
legacyInstallerComponent.root.isVisible = useLegacyInstaller
}
}

View File

@ -7,35 +7,35 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.looker.droidify.R
import com.looker.droidify.database.Database
import com.looker.droidify.database.RepositoryExporter
import com.looker.droidify.datastore.Settings
import com.looker.droidify.datastore.SettingsRepository
import com.looker.droidify.datastore.get
import com.looker.droidify.datastore.model.AutoSync
import com.looker.droidify.datastore.model.InstallerType
import com.looker.droidify.datastore.model.InstallerType.*
import com.looker.droidify.datastore.model.InstallerType.ROOT
import com.looker.droidify.datastore.model.InstallerType.SHIZUKU
import com.looker.droidify.datastore.model.LegacyInstallerComponent
import com.looker.droidify.datastore.model.ProxyType
import com.looker.droidify.datastore.model.Theme
import com.looker.droidify.database.Database
import com.looker.droidify.database.RepositoryExporter
import com.looker.droidify.work.CleanUpWorker
import com.looker.droidify.installer.installers.isMagiskGranted
import com.looker.droidify.installer.installers.isShizukuAlive
import com.looker.droidify.installer.installers.isShizukuGranted
import com.looker.droidify.installer.installers.isShizukuInstalled
import com.looker.droidify.installer.installers.requestPermissionListener
import com.looker.droidify.work.CleanUpWorker
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.Locale
import javax.inject.Inject
import kotlin.time.Duration
import com.looker.droidify.R
@HiltViewModel
class SettingsViewModel
@ -49,8 +49,8 @@ class SettingsViewModel
}
val settingsFlow get() = settingsRepository.data
private val _backgroundTask = MutableStateFlow(false)
val backgroundTask = _backgroundTask.asStateFlow()
var isBackgroundAllowed = true
private set
private val _snackbarStringId = MutableSharedFlow<Int>()
val snackbarStringId = _snackbarStringId.asSharedFlow()
@ -59,10 +59,8 @@ class SettingsViewModel
fun <T> getInitialSetting(block: Settings.() -> T): Flow<T> = initialSetting.map { it.block() }
fun allowBackground() {
viewModelScope.launch {
_backgroundTask.emit(true)
}
fun toggleBackgroundAccess(enable: Boolean) {
isBackgroundAllowed = enable
}
fun setLanguage(language: String) {
@ -172,7 +170,7 @@ class SettingsViewModel
} else if (isShizukuGranted()) {
settingsRepository.setInstallerType(installerType)
} else if (!isShizukuGranted()) {
if(requestPermissionListener()) {
if (requestPermissionListener()) {
settingsRepository.setInstallerType(installerType)
}
}
@ -194,6 +192,12 @@ class SettingsViewModel
}
}
fun setLegacyInstallerComponentComponent(component: LegacyInstallerComponent?) {
viewModelScope.launch {
settingsRepository.setLegacyInstallerComponent(component)
}
}
fun exportSettings(file: Uri) {
viewModelScope.launch {
settingsRepository.export(file)

View File

@ -14,6 +14,7 @@ import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SearchView
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@ -28,24 +29,24 @@ import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.tabs.TabLayoutMediator
import com.looker.droidify.R
import com.looker.droidify.databinding.TabsToolbarBinding
import com.looker.droidify.datastore.model.supportedSortOrders
import com.looker.droidify.datastore.extension.sortOrderName
import com.looker.droidify.datastore.model.SortOrder
import com.looker.droidify.model.ProductItem
import com.looker.droidify.service.Connection
import com.looker.droidify.service.SyncService
import com.looker.droidify.ui.ScreenFragment
import com.looker.droidify.ui.appList.AppListFragment
import com.looker.droidify.utility.common.device.Huawei
import com.looker.droidify.utility.common.extension.dp
import com.looker.droidify.utility.common.extension.getMutatedIcon
import com.looker.droidify.utility.common.extension.selectableBackground
import com.looker.droidify.utility.common.extension.systemBarsPadding
import com.looker.droidify.utility.common.sdkAbove
import com.looker.droidify.datastore.extension.sortOrderName
import com.looker.droidify.datastore.model.SortOrder
import com.looker.droidify.R
import com.looker.droidify.databinding.TabsToolbarBinding
import com.looker.droidify.datastore.model.supportedSortOrders
import com.looker.droidify.model.ProductItem
import com.looker.droidify.service.Connection
import com.looker.droidify.service.SyncService
import com.looker.droidify.ui.ScreenFragment
import com.looker.droidify.ui.appList.AppListFragment
import com.looker.droidify.utility.extension.resources.sizeScaled
import com.looker.droidify.utility.extension.screenActivity
import com.looker.droidify.utility.extension.mainActivity
import com.looker.droidify.widget.DividerConfiguration
import com.looker.droidify.widget.FocusSearchView
import com.looker.droidify.widget.StableRecyclerAdapter
@ -152,7 +153,7 @@ class TabsFragment : ScreenFragment() {
}
}
screenActivity.onToolbarCreated(toolbar)
mainActivity.onToolbarCreated(toolbar)
toolbar.title = getString(R.string.application_name)
// Move focus from SearchView to Toolbar
toolbar.isFocusable = true
@ -204,7 +205,7 @@ class TabsFragment : ScreenFragment() {
syncRepositoriesMenuItem = add(0, 0, 0, stringRes.sync_repositories)
.setIcon(toolbar.context.getMutatedIcon(R.drawable.ic_sync))
.setOnMenuItemClickListener {
viewModel.sync()
syncConnection.binder?.sync(SyncService.SyncRequest.MANUAL)
true
}
@ -226,19 +227,19 @@ class TabsFragment : ScreenFragment() {
favouritesItem = add(1, 0, 0, stringRes.favourites)
.setIcon(toolbar.context.getMutatedIcon(R.drawable.ic_favourite_checked))
.setOnMenuItemClickListener {
view.post { screenActivity.navigateFavourites() }
view.post { mainActivity.navigateFavourites() }
true
}
add(1, 0, 0, stringRes.repositories)
.setOnMenuItemClickListener {
view.post { screenActivity.navigateRepositories() }
view.post { mainActivity.navigateRepositories() }
true
}
add(1, 0, 0, stringRes.settings)
.setOnMenuItemClickListener {
view.post { screenActivity.navigatePreferences() }
view.post { mainActivity.navigatePreferences() }
true
}
}
@ -295,6 +296,25 @@ class TabsFragment : ScreenFragment() {
onBackPressedCallback?.isEnabled = it != BackAction.None
}
}
launch {
SyncService.syncState.collect {
when (it) {
is SyncService.State.Connecting -> {
tabsBinding.syncState.isVisible = true
tabsBinding.syncState.isIndeterminate = true
}
SyncService.State.Finish -> {
tabsBinding.syncState.isGone = true
}
is SyncService.State.Syncing -> {
tabsBinding.syncState.isVisible = true
tabsBinding.syncState.setProgressCompat(it.progress, true)
}
}
}
}
}
}

View File

@ -3,17 +3,12 @@ package com.looker.droidify.ui.tabsFragment
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.looker.droidify.data.local.dao.IndexDao
import com.looker.droidify.data.local.model.RepoEntity
import com.looker.droidify.data.local.model.toRepo
import com.looker.droidify.database.Database
import com.looker.droidify.datastore.SettingsRepository
import com.looker.droidify.datastore.get
import com.looker.droidify.datastore.model.SortOrder
import com.looker.droidify.domain.model.Fingerprint
import com.looker.droidify.model.ProductItem
import com.looker.droidify.sync.Syncable
import com.looker.droidify.sync.v2.model.IndexV2
import com.looker.droidify.ui.tabsFragment.TabsFragment.BackAction
import com.looker.droidify.utility.common.extension.asStateFlow
import dagger.hilt.android.lifecycle.HiltViewModel
@ -68,9 +63,15 @@ class TabsViewModel @Inject constructor(
val backAction = combine(
currentSection,
isSearchActionItemExpanded,
showSections,
::calcBackAction,
).asStateFlow(BackAction.None)
showSections
) { currentSection, isSearchActionItemExpanded, showSections ->
when {
currentSection != ProductItem.Section.All -> BackAction.ProductAll
isSearchActionItemExpanded -> BackAction.CollapseSearchView
showSections -> BackAction.HideSections
else -> BackAction.None
}
}.asStateFlow(BackAction.None)
fun setSection(section: ProductItem.Section) {
savedStateHandle[STATE_SECTION] = section
@ -82,33 +83,6 @@ class TabsViewModel @Inject constructor(
}
}
fun sync() {
viewModelScope.launch {
val repo = RepoEntity(
id = 1,
address = "https://apt.izzysoft.de/fdroid/repo",
name = mapOf("en-US" to "IzzyOnDroid F-Droid Repo"),
description = emptyMap(),
fingerprint = Fingerprint("3BF0D6ABFEAE2F401707B6D966BE743BF0EEE49C2561B9BA39073711F628937A"),
timestamp = 0L,
icon = emptyMap(),
)
val (_, index) = syncable.sync(
repo.toRepo(
locale = "en-US",
mirrors = emptyList(),
enabled = true,
),
)
requireNotNull(index)
indexDao.insertIndex(
fingerprint = repo.fingerprint,
index = index,
expectedRepoId = repo.id,
)
}
}
private fun calcBackAction(
currentSection: ProductItem.Section,
isSearchActionItemExpanded: Boolean,

View File

@ -12,7 +12,7 @@ private val supportedExternalHosts = arrayOf(
"f-droid.org",
"www.f-droid.org",
"staging.f-droid.org",
"apt.izzysoft.de"
"apt.izzysoft.de",
)
val Intent.deeplinkType: DeeplinkType?
@ -77,7 +77,7 @@ private inline fun invalidDeeplink(message: String): Nothing = throw InvalidDeep
sealed interface DeeplinkType {
data class AddRepository(val address: String) : DeeplinkType
class AddRepository(val address: String) : DeeplinkType
data class AppDetail(val packageName: String, val repoAddress: String? = null) : DeeplinkType
class AppDetail(val packageName: String, val repoAddress: String? = null) : DeeplinkType
}

View File

@ -33,3 +33,28 @@ object SdkCheck {
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N)
val isNougat: Boolean get() = sdk >= Build.VERSION_CODES.N
}
val sdkName by lazy {
mapOf(
16 to "4.1",
17 to "4.2",
18 to "4.3",
19 to "4.4",
21 to "5.0",
22 to "5.1",
23 to "6",
24 to "7.0",
25 to "7.1",
26 to "8.0",
27 to "8.1",
28 to "9",
29 to "10",
30 to "11",
31 to "12",
32 to "12L",
33 to "13",
34 to "14",
35 to "15",
36 to "16",
)
}

View File

@ -46,13 +46,6 @@ object Cache {
}
}
private fun subPath(dir: File, file: File): String {
val dirPath = "${dir.path}/"
val filePath = file.path
filePath.startsWith(dirPath) || throw RuntimeException()
return filePath.substring(dirPath.length)
}
fun getEmptySpace(context: Context): Long {
val dir = context.cacheDir
return min(dir.usableSpace, dir.freeSpace)
@ -187,7 +180,7 @@ object Cache {
projection: Array<String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
sortOrder: String?,
): Cursor {
val file = getFileAndTypeForUri(uri).first
val columns = (projection ?: defaultColumns).mapNotNull {
@ -217,7 +210,7 @@ object Cache {
uri: Uri,
contentValues: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
selectionArgs: Array<out String>?,
): Int = unsupported
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {

View File

@ -5,9 +5,9 @@ import android.app.job.JobScheduler
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.os.PowerManager
import android.view.inputmethod.InputMethodManager
import androidx.annotation.AttrRes
@ -15,14 +15,12 @@ import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.looker.droidify.R
inline val Context.clipboardManager: ClipboardManager?
get() = getSystemService()
inline val Context.connectivityManager: ConnectivityManager?
get() = getSystemService()
inline val Context.inputManager: InputMethodManager?
get() = getSystemService()
@ -39,6 +37,13 @@ fun Context.copyToClipboard(clip: String) {
clipboardManager?.setPrimaryClip(ClipData.newPlainText(null, clip))
}
fun Context.openLink(url: String) {
val intent = intent(Intent.ACTION_VIEW) {
setData(url.toUri())
}
startActivity(intent)
}
val Context.corneredBackground: Drawable
get() = getDrawableCompat(R.drawable.background_border)
@ -57,6 +62,9 @@ val Context.selectableBackground: Drawable
val Context.camera: Drawable
get() = getDrawableCompat(R.drawable.ic_image)
val Context.videoPlaceHolder: Drawable
get() = getDrawableCompat(R.drawable.ic_video)
val Context.aspectRatio: Float
get() = with(resources.displayMetrics) {
(heightPixels / widthPixels).toFloat()
@ -75,14 +83,21 @@ private fun Context.getDrawableFromAttr(attrResId: Int): Drawable {
}
fun Context.getDrawableCompat(@DrawableRes resId: Int = R.drawable.background_border): Drawable =
requireNotNull(AppCompatResources.getDrawable(this, resId)) { "Cannot find drawable, ID: $resId" }
requireNotNull(
AppCompatResources.getDrawable(
this,
resId
)
) { "Cannot find drawable, ID: $resId" }
fun Context.getColorFromAttr(@AttrRes attrResId: Int): ColorStateList {
val typedArray = obtainStyledAttributes(intArrayOf(attrResId))
val (colorStateList, resId) = try {
Pair(typedArray.getColorStateList(0), typedArray.getResourceId(0, 0))
return try {
typedArray.getColorStateList(0) ?: run {
val resourceId = typedArray.getResourceId(0, 0)
ContextCompat.getColorStateList(this, resourceId)!!
}
} finally {
typedArray.recycle()
}
return colorStateList ?: ContextCompat.getColorStateList(this, resId)!!
}

View File

@ -1,4 +1,4 @@
package com.looker.core.common.extension
package com.looker.droidify.utility.common.extension
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonGenerator

View File

@ -74,7 +74,6 @@ fun PackageManager.getApplicationInfoCompat(
PackageManager.ApplicationInfoFlags.of(0L)
)
} else {
@Suppress("DEPRECATION")
getApplicationInfo(filePath, 0)
}
@ -98,7 +97,6 @@ fun PackageManager.getPackageInfoCompat(
PackageManager.PackageInfoFlags.of(signatureFlag.toLong())
)
} else {
@Suppress("DEPRECATION")
getPackageInfo(packageName, signatureFlag)
}
} catch (e: Exception) {
@ -131,7 +129,6 @@ fun PackageManager.getPackageArchiveInfoCompat(
PackageManager.PackageInfoFlags.of(signatureFlag.toLong())
)
} else {
@Suppress("DEPRECATION")
getPackageArchiveInfo(filePath, signatureFlag)
}
} catch (e: Exception) {
@ -144,7 +141,6 @@ fun PackageManager.getInstalledPackagesCompat(
if (SdkCheck.isTiramisu) {
getInstalledPackages(PackageManager.PackageInfoFlags.of(signatureFlag.toLong()))
} else {
@Suppress("DEPRECATION")
getInstalledPackages(signatureFlag)
}
} catch (e: Exception) {

View File

@ -1,5 +1,6 @@
package com.looker.droidify.utility.common.extension
import android.content.Context
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
@ -7,7 +8,9 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.request.ImageRequest
import coil3.network.NetworkHeaders
import coil3.network.httpHeaders
import coil3.request.ImageRequest
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
@ -17,8 +20,13 @@ import kotlinx.coroutines.flow.map
import kotlin.math.min
import kotlin.math.roundToInt
private val networkHeader by lazy { NetworkHeaders.Builder() }
fun ImageRequest.Builder.authentication(base64: String) {
addHeader("Authorization", base64)
if (base64.isNotEmpty()) {
networkHeader["Authorization"] = base64
httpHeaders(networkHeader.build())
}
}
fun TextView.setTextSizeScaled(size: Int) {
@ -26,6 +34,9 @@ fun TextView.setTextSizeScaled(size: Int) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, realSize.toFloat())
}
val Context.layoutInflater: LayoutInflater
get() = LayoutInflater.from(this)
fun ViewGroup.inflate(layoutResId: Int): View {
return LayoutInflater.from(context).inflate(layoutResId, this, false)
}

View File

@ -34,7 +34,7 @@ suspend fun File.calculateHash(hashType: String): String? {
}
private suspend fun MessageDigest.readBytesFrom(
file: File
file: File,
): ByteArray? = withContext(Dispatchers.IO) {
try {
if (file.length() < DIRECT_READ_LIMIT) return@withContext digest(file.readBytes())
@ -57,16 +57,9 @@ private suspend fun MessageDigest.readBytesFrom(
// 25 MB
private const val DIRECT_READ_LIMIT = 25 * 1024 * 1024
@Suppress("FunctionName")
data class Hash(
val type: String,
val hash: String
val hash: String,
) {
companion object {
fun SHA256(hash: String) = Hash(type = "sha256", hash)
fun MD5(hash: String) = Hash(type = "md5", hash)
}
fun isValid(): Boolean = type.isNotBlank() && hash.isNotBlank()
}

View File

@ -3,5 +3,5 @@ package com.looker.droidify.utility.extension
import androidx.fragment.app.Fragment
import com.looker.droidify.MainActivity
inline val Fragment.screenActivity: MainActivity
inline val Fragment.mainActivity: MainActivity
get() = requireActivity() as MainActivity

View File

@ -2,7 +2,7 @@ package com.looker.droidify.utility.serialization
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.looker.core.common.extension.forEachKey
import com.looker.droidify.utility.common.extension.forEachKey
import com.looker.droidify.model.ProductItem
fun ProductItem.serialize(generator: JsonGenerator) {

View File

@ -2,7 +2,7 @@ package com.looker.droidify.utility.serialization
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.looker.core.common.extension.forEachKey
import com.looker.droidify.utility.common.extension.forEachKey
import com.looker.droidify.model.ProductPreference
fun ProductPreference.serialize(generator: JsonGenerator) {

View File

@ -3,11 +3,11 @@ package com.looker.droidify.utility.serialization
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.looker.core.common.extension.collectNotNull
import com.looker.core.common.extension.collectNotNullStrings
import com.looker.core.common.extension.forEachKey
import com.looker.core.common.extension.writeArray
import com.looker.core.common.extension.writeDictionary
import com.looker.droidify.utility.common.extension.collectNotNull
import com.looker.droidify.utility.common.extension.collectNotNullStrings
import com.looker.droidify.utility.common.extension.forEachKey
import com.looker.droidify.utility.common.extension.writeArray
import com.looker.droidify.utility.common.extension.writeDictionary
import com.looker.droidify.model.Product
import com.looker.droidify.model.Release
@ -53,11 +53,6 @@ fun Product.serialize(generator: JsonGenerator) {
writeStringField(ADDRESS, it.address)
}
is Product.Donate.Flattr -> {
writeStringField(TYPE, DONATION_FLATTR)
writeStringField(ID, it.id)
}
is Product.Donate.Liberapay -> {
writeStringField(TYPE, DONATION_LIBERAPAY)
writeStringField(ID, it.id)
@ -149,7 +144,6 @@ fun JsonParser.product(): Product {
DONATION_EMPTY -> Product.Donate.Regular(url)
DONATION_BITCOIN -> Product.Donate.Bitcoin(address)
DONATION_LITECOIN -> Product.Donate.Litecoin(address)
DONATION_FLATTR -> Product.Donate.Flattr(id)
DONATION_LIBERAPAY -> Product.Donate.Liberapay(id)
DONATION_OPENCOLLECTIVE -> Product.Donate.OpenCollective(id)
else -> null
@ -243,6 +237,5 @@ private const val KEY_EMPTY = ""
private const val DONATION_EMPTY = ""
private const val DONATION_BITCOIN = "bitcoin"
private const val DONATION_LITECOIN = "litecoin"
private const val DONATION_FLATTR = "flattr"
private const val DONATION_LIBERAPAY = "liberapay"
private const val DONATION_OPENCOLLECTIVE = "openCollective"

View File

@ -3,11 +3,11 @@ package com.looker.droidify.utility.serialization
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.looker.core.common.extension.collectNotNull
import com.looker.core.common.extension.collectNotNullStrings
import com.looker.core.common.extension.forEachKey
import com.looker.core.common.extension.writeArray
import com.looker.core.common.extension.writeDictionary
import com.looker.droidify.utility.common.extension.collectNotNull
import com.looker.droidify.utility.common.extension.collectNotNullStrings
import com.looker.droidify.utility.common.extension.forEachKey
import com.looker.droidify.utility.common.extension.writeArray
import com.looker.droidify.utility.common.extension.writeDictionary
import com.looker.droidify.model.Release
fun Release.serialize(generator: JsonGenerator) {

View File

@ -2,9 +2,9 @@ package com.looker.droidify.utility.serialization
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.looker.core.common.extension.collectNotNullStrings
import com.looker.core.common.extension.forEachKey
import com.looker.core.common.extension.writeArray
import com.looker.droidify.utility.common.extension.collectNotNullStrings
import com.looker.droidify.utility.common.extension.forEachKey
import com.looker.droidify.utility.common.extension.writeArray
import com.looker.droidify.model.Repository
fun Repository.serialize(generator: JsonGenerator) {

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorOnSurfaceInverse" android:state_enabled="false" />
<item android:color="?attr/colorPrimary" android:state_checked="true" />
<item android:color="?attr/colorOutline" />
</selector>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="@dimen/material_emphasis_disabled_background" android:color="?attr/colorOnSurface" android:state_enabled="false" />
<item android:color="?attr/colorPrimaryContainer" android:state_checked="true" />
<item android:color="?attr/colorSurfaceVariant" />
</selector>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorOnSurface"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M9.461 3C5.183 3 3 5.464 3 10.064v3.213 6.438L7.19 15.52v-4.902c0-1.906 .505-3.118 2.199-3.391
.592-.116 1.824-.075 2.607-.075v2.911c0 .027 .004 .074 .01 .098 .033 .118 .139 .204 .266 .204 .071 0 .138-.037
.207-.105l7.262-7.259-4.875-.001zM21 4.285L16.81 8.48v4.902c0 1.906-.505 3.118-2.199 3.391-.592 .116-1.824 .075
-2.607 .075v-2.911c0-.026-.004-.074-.01-.098-.033-.118-.139-.204-.266-.204-.071 0-.138 .037-.207 .105L4.258 20.999
9.133 21H14.539C18.817 21 21 18.536 21 13.936v-3.214z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="?android:textColor"
android:pathData="M494.07,281.6l-25.18,-78.08a11,11 0,0 0,-0.61 -2.1L417.78,44.48a20.08,20.08 0,0 0,-19.17 -13.82A19.77,19.77 0,0 0,379.66 44.6L331.52,194.15h-152L131.34,44.59a19.76,19.76 0,0 0,-18.86 -13.94h-0.11a20.15,20.15 0,0 0,-19.12 14L42.7,201.73c0,0.14 -0.11,0.26 -0.16,0.4L16.91,281.61a29.15,29.15 0,0 0,10.44 32.46L248.79,476.48a11.25,11.25 0,0 0,13.38 -0.07L483.65,314.07a29.13,29.13 0,0 0,10.42 -32.47m-331,-64.51L224.8,408.85 76.63,217.09m209.64,191.8 l59.19,-183.84 2.55,-8h86.52L300.47,390.44M398.8,59.31l43.37,134.83H355.35M324.16,217l-43,133.58L255.5,430.14 186.94,217M112.27,59.31l43.46,134.83H69M40.68,295.58a6.19,6.19 0,0 1,-2.21 -6.9l19,-59L197.08,410.27M470.34,295.58 L313.92,410.22l0.52,-0.69L453.5,229.64l19,59a6.2,6.2 0,0 1,-2.19 6.92" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:textColor"
android:pathData="M13.8521,0.7714C12.3162,0.2228 11.0572,0.3718 9.89,0.7524L9.9406,16.1462c0.8343,0.7743 3.0648,0.8543 3.8984,0.0768V9.216l5.2569,7.0811c0.9244,0.5188 4.1768,0.2319 4.162,-1.317L17.873,8.196 23.352,1.146C22.8842,0.3544 19.6824,0.1619 18.7887,1.003L13.84,7.22ZM4.834,4.005C4.7874,4.0099 4.744,4.0307 4.711,4.064L3.145,5.63C3.0793,5.696 3.0669,5.7982 3.115,5.878L4.949,8.9C4.6205,9.4525 4.3613,10.0432 4.177,10.659l-3.367,0.7c-0.0944,0.0195 -0.1621,0.1026 -0.162,0.199v2.215c0,0.093 0.064,0.174 0.155,0.196l3.268,0.8c0.1709,0.7107 0.4405,1.394 0.801,2.03L2.98,19.683c-0.0521,0.0804 -0.0408,0.1863 0.027,0.254l1.566,1.567c0.0663,0.0659 0.1689,0.0782 0.249,0.03l2.964,-1.8c0.582,0.336 1.21,0.6 1.874,0.78l0.692,3.325C10.372,23.933 10.454,24 10.55,24h2.215c0.0937,0.0003 0.1752,-0.0639 0.197,-0.155l0.815,-3.332c0.6758,-0.1827 1.3239,-0.4555 1.927,-0.811l2.922,1.915c0.08,0.053 0.186,0.042 0.254,-0.026l1.567,-1.566c0.0661,-0.0658 0.0784,-0.1683 0.03,-0.248L19.41,18.019C18.9297,17.2055 18.038,15.026 17.371,15.8 15.3819,19.6985 10.1369,20.4613 7.1197,17.291 4.1025,14.1206 5.1238,8.9198 9.116,7.126 9.5319,6.8182 7.957,5.999 7.957,5.999L7.956,5.997 4.966,4.037C4.9271,4.0111 4.8805,3.9995 4.834,4.004Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M380,660L660,480L380,300L380,660ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,240Q800,240 800,240Q800,240 800,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720ZM160,720Q160,720 160,720Q160,720 160,720L160,240Q160,240 160,240Q160,240 160,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720Z"/>
</vector>

View File

@ -90,7 +90,6 @@
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider1"
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_marginVertical="12dp" />
@ -120,7 +119,6 @@
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider2"
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_marginVertical="12dp" />

View File

@ -10,13 +10,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
style="?materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginTop="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -79,8 +72,6 @@
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.core.widget.NestedScrollView>
<LinearLayout
@ -90,6 +81,7 @@
android:background="?android:attr/colorBackground"
android:clickable="true"
android:gravity="center"
android:visibility="gone"
android:orientation="vertical"
tools:ignore="KeyboardInaccessibleWidget">

View File

@ -20,7 +20,7 @@
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
@ -96,6 +96,13 @@
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
<TextView
android:id="@+id/sdk_ver"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -2,7 +2,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:orientation="vertical"
android:paddingBottom="20dp">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/repo_switch"
@ -65,9 +66,4 @@
android:text="@string/delete"
android:textColor="?colorOnError" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="20dp"/>
</LinearLayout>

View File

@ -145,6 +145,9 @@
<include
android:id="@+id/installer"
layout="@layout/enum_type" />
<include
android:id="@+id/legacy_installer_component"
layout="@layout/enum_type" />
<TextView
android:layout_width="match_parent"

View File

@ -44,8 +44,12 @@
android:layout_marginEnd="20dp"
android:src="@drawable/ic_arrow_down"
tools:ignore="ContentDescription" />
</LinearLayout>
</FrameLayout>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/sync_state"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/video_button"
style="?materialIconButtonFilledTonalStyle"
android:text="@string/label_open_video"
android:layout_width="match_parent"
android:layout_height="150dp"
android:paddingHorizontal="24dp"
app:icon="@drawable/ic_video"
app:iconGravity="textTop"
app:iconPadding="6dp"
app:iconSize="24dp" />

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/exploreTab"
android:icon="@drawable/ic_public"
android:title="@string/explore" />
<item
android:id="@+id/latestTab"
android:icon="@drawable/ic_new_releases"
android:title="@string/latest" />
<item
android:id="@+id/installedTab"
android:icon="@drawable/ic_launch"
android:title="@string/installed" />
</menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -6,17 +6,13 @@
<string name="description">الوصف</string>
<string name="incompatible_api_min_DESC_FORMAT">الحد الأدنى لإصدار واجهة برمجة التطبيقات هو %d.</string>
<string name="light">فاتح</string>
<string name="list_animation">تحريكات القائمة</string>
<string name="ok">حسنًا</string>
<string name="repository_unsigned_DESC">ليس موقعًا. تعذر التحقق من قائمة التطبيقات. كن حذرًا عند تنزيل التطبيقات من مستودعات غير موقعة.</string>
<string name="sync_repositories">زامن المستودعات</string>
<string name="syncing">جاري المزامنة</string>
<string name="unknown_FORMAT">غير معروف: %s</string>
<string name="unsigned">لم يُوقع</string>
<string name="unstable_updates">تحديثات غير مستقرة</string>
<string name="show_less">اعرض أقل</string>
<string name="latest">الأحدث</string>
<string name="explore">استكشف</string>
<string name="action_failed">فشل الإجراء</string>
<string name="add_repository">أضف مستودعًا</string>
<string name="all_applications">كل التطبيقات</string>
@ -90,7 +86,6 @@
<string name="license_FORMAT">رخصة %s</string>
<string name="link_copied_to_clipboard">تم نسخ الرابط</string>
<string name="links">الروابط</string>
<string name="list_animation_description">أظهر تحريكات القائمة على الصفحة الرئيسة</string>
<string name="merging_FORMAT">جاري الدمج %s</string>
<string name="name">الاسم</string>
<string name="network_error_DESC">خطأ في الشبكة</string>
@ -135,12 +130,8 @@
<string name="requires_FORMAT">يتطلب %s</string>
<string name="repositories">المستودعات</string>
<string name="repository">المستودع</string>
<string name="repository_not_used_DESC">هذا المستودع لم يُستخدم بعد. مكنه لعرض التطبيقات التي يحتويها.</string>
<string name="root_permission">تثبيت صامت</string>
<string name="root_permission_description">اسمح لصلاحية الجذر لتمكين التثبيت الصامت</string>
<string name="save">احفظ</string>
<string name="saving_details">جارٍ حفظ التفاصيل…</string>
<string name="screenshots">لقطات الشاشة</string>
<string name="search">ابحث</string>
<string name="select_mirror">اختر مرآة</string>
<string name="share">شارك</string>
@ -159,12 +150,10 @@
<string name="syncing_FORMAT">جاري مزامنة %s…</string>
<string name="system">النظام</string>
<string name="tap_to_install_DESC">اضغط للتثبيت.</string>
<string name="target">الهدف</string>
<string name="theme">السمة</string>
<string name="themes">السمات</string>
<string name="tracks_or_reports_your_activity">يتعقب أو يرفق نشاطك</string>
<string name="uninstall">إلغاء التثبيت</string>
<string name="unverified">لم يُتحقق منه</string>
<string name="update">حدث</string>
<string name="updates">التحديثات</string>
<string name="unknown">غير معروف</string>
@ -183,10 +172,6 @@
<string name="prefs_language_title">اللغة</string>
<string name="prefs_personalization">التفضيلات</string>
<string name="update_all">حدث الكل</string>
<string name="installed_applications">التطبيقات المثبَّتة</string>
<string name="sort_filter">افرز وصفِّ</string>
<string name="new_applications">التطبيقات الجديدة</string>
<string name="cleanup_description">الزمن المار قبل فحص وإزالة الملفات المنزَّلة</string>
<string name="cleanup_title">مدة تنظيف ملفات APK</string>
<plurals name="days">
<item quantity="zero">أقل من يوم</item>
@ -206,9 +191,6 @@
</plurals>
<string name="only_on_wifi_with_charging">فقط على شبكة واي-فاي وعند الشحن</string>
<string name="io_error_DESC">تعذر أداء بعض الإجراءات.</string>
<string name="no_internet">ليس لديك اتصال بالإنترنت</string>
<string name="allow_collapsing_toolbar">اسمح بتوسيع شريط التطبيقات العلوي</string>
<string name="allow_collapsing_toolbar_DESC">اسمح لشريط التطبيقات العلوي بالتوسع والطي</string>
<string name="material_you">لون النظام ( Material You )</string>
<string name="material_you_desc">استخدم سمة ألوان النظام</string>
<string name="favourites">المفضَّلات</string>
@ -216,7 +198,6 @@
<string name="force_clean_up">افرض التنظيف</string>
<string name="force_clean_up_DESC">ينظِّف الملفَّات المتكرِّرة</string>
<string name="enable_repo">مكِّن المستودع</string>
<string name="restart_app">أعد تشغيل Droid-ify لرؤية التغييرات</string>
<string name="installing">يثبّت</string>
<string name="waiting_to_start_installation">في انتظار بدء التثبيت…</string>
<string name="auto_update">حدِّث التطبيقات تلقائيًّا</string>
@ -230,7 +211,6 @@
<string name="special_credits">شكر خاص</string>
<string name="home_screen_swiping">ايماءات الشاشة الرئيسة</string>
<string name="home_screen_swiping_DESC">اسمح للمستخدم بالتمرير بين الصفحات في الشاشة الرئيسة</string>
<string name="label_copy">انسخ</string>
<string name="proxy_port_error_not_int">يجب أن يكون منفذ الوكيل رقمًا صحيحًا</string>
<string name="repository_not_found">لم يُعثَر على المستودع</string>
<string name="import_settings_title">استيراد الإعدادات</string>
@ -254,4 +234,14 @@
<string name="ignore_signature">تجاهل التوقيع</string>
<string name="insufficient_storage">مساحة غير كافية</string>
<string name="insufficient_storage_DESC">لا توجد مساحة خالية كافية على الجهاز لتثبيت هذا التطبيق. حاول إفراغ بعض المساحة</string>
<string name="error_shizuku_not_granted">إذن Shizuku مفقود</string>
<string name="error_shizuku_not_granted_DESC">لم يتم منح إذن خدمة Shizuku. يُرجى التحقق من تطبيق Shizuku</string>
<string name="error_shizuku_not_installed_DESC">لا يبدو أن Shizuku مثبت</string>
<string name="open_shizuku">افتح Shizuku</string>
<string name="label_unknown_sdk">غير معروف (%d)</string>
<string name="error_shizuku_not_installed">Shizuku غير مثبت</string>
<string name="error_shizuku_not_running_DESC">خدمة Shizuku لا تعمل. يُرجى التحقق من تطبيق Shizuku</string>
<string name="label_open_video">فيديو</string>
<string name="error_shizuku_service_unavailable">Shizuku لا يعمل</string>
<string name="switch_to_default_installer">بدّل إلى الافتراضي</string>
</resources>

View File

@ -3,8 +3,6 @@
<string name="add_repository">Yeni qaynaq əlavə et</string>
<string name="address">Ünvan</string>
<string name="all_applications">Bütün applikasiyalar</string>
<string name="allow_collapsing_toolbar">Üst çubuğun genişlədilməsinə izn ver</string>
<string name="allow_collapsing_toolbar_DESC">Üst çubuğun genişləndirilməsinə və sıxılmasına izn ver</string>
<string name="already_exists">Zatən mövcuddur</string>
<string name="always">Hər zaman</string>
<string name="anti_features">Anti-özəlliklər</string>
@ -20,7 +18,6 @@
<string name="changelog">Dəyişim günlüyü</string>
<string name="changes">Dəyişikliklər</string>
<string name="cleanup_title">APK cleanup interval</string>
<string name="cleanup_description">Period to check and remove downloaded files</string>
<string name="compiled_for_debugging">Xəta tapmaq üçün kompayl edildi</string>
<string name="confirmation">Təsdiq</string>
<string name="connecting">Bağlanılır…</string>
@ -78,8 +75,6 @@
<string name="license_FORMAT">%s lisenziyası</string>
<string name="light">ıq</string>
<string name="links">Linklər</string>
<string name="list_animation">Siyahı Animasiyaları</string>
<string name="list_animation_description">Siyahı canlandırmasını ana səhifədə göstər</string>
<string name="material_you">Material You</string>
<string name="material_you_desc">Material you rəng temasını istifadə et</string>
<string name="merging_FORMAT">%s birləşdirilir</string>
@ -92,7 +87,6 @@
</plurals>
<string name="no_applications_available">İstifadə edilə bilən applikasiya yoxdur</string>
<string name="no_description_available_DESC">ıqlama yoxdur</string>
<string name="no_internet">İnternet bağlantınız yoxdur</string>
<string name="no_proxy">Proxy yoxdur</string>
<string name="notify_about_updates">Applikasiyanın yeni versiyaları haqqında bilgiləndir</string>
<string name="number_of_applications">Applikasiya sayı</string>
@ -144,16 +138,11 @@
<string name="recently_updated">Bu yaxınlarda yeniləndi</string>
<string name="repositories">Anbarlar</string>
<string name="repository">Repozitoriya</string>
<string name="repository_not_used_DESC">Bu anbardan hələ istifadə olunmayıb. İçindəki proqramlara baxmaq üçün onu yandırın.</string>
<string name="repository_unsigned_DESC">İmzasız. Tətbiq siyahısını yoxlamaq mümkün olmadı. İmzasız depolardan proqramları endirərkən diqqətli olun.</string>
<string name="repository_unreachable">Repozitoriya əlçatmazdır</string>
<string name="requires_FORMAT">%s tələb edir</string>
<string name="restart_app">Dəyişiklikləri görmək üçün Droid-ify-ı yenidən başladın</string>
<string name="root_permission">Səssiz Quraşdırma</string>
<string name="root_permission_description">Səssiz quraşdırmalar üçün kök icazəsinə icazə verin</string>
<string name="save">Yadda saxla</string>
<string name="saving_details">Detallar yadda saxlanılır…</string>
<string name="screenshots">Ekran görüntüləri</string>
<string name="search">Axtar</string>
<string name="select_mirror">Güzgü seçin</string>
<string name="settings">Parametrlər</string>
@ -173,17 +162,14 @@
<string name="syncing">Sinxronizasiya</string>
<string name="syncing_FORMAT">%s sinxronizasiya edilir…</string>
<string name="system">Sistem</string>
<string name="target">Hədəf</string>
<string name="theme">Mövzu</string>
<string name="themes">Mövzular</string>
<string name="tracks_or_reports_your_activity">Fəaliyyətinizi izləyir və ya hesabat verir</string>
<string name="unknown">Naməlum</string>
<string name="unknown_error_DESC">Naməlum xəta.</string>
<string name="unknown_FORMAT">Naməlum: %s</string>
<string name="unsigned">İmzasız</string>
<string name="unstable_updates">Qeyri-sabit yeniləmələr</string>
<string name="unstable_updates_summary">Qeyri-sabit versiyaların quraşdırılmasını təklif edin</string>
<string name="unverified">Doğrulanmamış</string>
<string name="update">Yeniləyin</string>
<string name="updates">Yeniləmələr</string>
<string name="username">İstifadəçi adı</string>
@ -197,12 +183,7 @@
<string name="website">Veb sayt</string>
<string name="prefs_language_title">Dil</string>
<string name="prefs_personalization">Fərdiləşdirmə</string>
<string name="latest">Ən son</string>
<string name="explore">Araşdırın</string>
<string name="update_all">Hamısını yeniləyin</string>
<string name="installed_applications">Quraşdırılmış proqramlar</string>
<string name="sort_filter">Çeşidləyin və Filtr edin</string>
<string name="new_applications">Yeni tətbiqlər</string>
<string name="show_less">Daha az göstər</string>
<string name="sync_repositories_automatically">Repozitoriyaları avtomatik sinxronlaşdırın</string>
<string name="uninstall">Silin</string>

View File

@ -1,14 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_failed">Не атрымалася выканаць дзеянне</string>
<string name="allow_collapsing_toolbar">Дазволіць верхняй панэлі праграм пашырацца</string>
<string name="application_not_found">Не ўдалося знайсці гэтую праграму</string>
<string name="author_website">Сайт аўтара</string>
<string name="auto_update">Аўтаабнаўленне праграм</string>
<string name="add_repository">Дадаць рэпазіторый</string>
<string name="address">Адрас</string>
<string name="all_applications">Ўсе праграмы</string>
<string name="allow_collapsing_toolbar_DESC">Дазволіць верхнюю панэль праграмы пашыраць і згортваць</string>
<string name="already_exists">Ўжо існуе</string>
<string name="always">Заўсёды</string>
<string name="amoled">Чорная</string>
@ -21,7 +19,6 @@
<string name="cancel">Скасаваць</string>
<string name="all_applications_up_to_date">Усе вашыя праграмы абноўлены</string>
<string name="delete">Выдаліць</string>
<string name="cleanup_description">Перыяд для праверкі і выдалення спампаваных файлаў</string>
<string name="description">Апісанне</string>
<string name="details">Дэталі</string>
<string name="downloading_FORMAT">Ідзе загрузка %s…</string>
@ -80,7 +77,6 @@
<string name="installing">Ўсталяванне</string>
<string name="incompatible_platforms_DESC_FORMAT">Ваша платформа %1$s не падтрымліваецца. Падтрымліваюцца платформы: %2$s.</string>
<string name="repository_unreachable">Рэпазіторый недаступны</string>
<string name="restart_app">Перазапусціце Droid-ify, каб убачыць змены</string>
<string name="incompatible_features_DESC">Адсутныя функцыі.</string>
<string name="no_applications_available">Няма даступных праграм</string>
<string name="password_missing">Пароль адсутнічае</string>
@ -96,8 +92,6 @@
<string name="other">Іншае</string>
<string name="link_copied_to_clipboard">Спасылка скапіявана</string>
<string name="processing_FORMAT">Апрацоўваецца %1$s…</string>
<string name="label_copy">Капіяваць</string>
<string name="no_internet">У вас няма падключэння да інтэрнэту</string>
<string name="proxy_port_error_not_int">Порт проксі можа быць толькі цэлым лікам</string>
<string name="number_of_applications">Колькасць праграм</string>
<string name="incompatible_with_FORMAT">Несумяшчальна з %s</string>
@ -158,19 +152,15 @@
<string name="plus_more_FORMAT">+%d больш</string>
<string name="import_settings_title">Налады імпарту</string>
<string name="incompatible_api_DESC_FORMAT">Ваш %1$s (версія API %2$d) не падтрымліваецца. %3$s</string>
<string name="unsigned">Без подпісу</string>
<string name="contains_nsfw">Змяшчае NSFW кантэнт</string>
<string name="select_mirror">Выберыце люстэрка</string>
<string name="list_animation_description">Паказаць анімацыю спісу на галоўнай старонцы</string>
<string name="syncing_FORMAT">Сінхранізацыя %s…</string>
<string name="import_export">Імпарт/Экспарт</string>
<string name="tracks_or_reports_your_activity">Адсочвае або паведамляе аб вашай дзейнасці</string>
<string name="website">Вэб-сайт</string>
<string name="merging_FORMAT">Аб\'яднанне %s</string>
<string name="waiting_to_start_download">Чаканне пачатку загрузкі…</string>
<string name="explore">Агляд</string>
<string name="suggested">Рэкамендуецца</string>
<string name="sort_filter">Сартаваць і фільтраваць</string>
<string name="open_DESC_FORMAT">Адкрыць %s?</string>
<string name="validation_index_error_DESC">Не ўдалося праверыць індэкс.</string>
<string name="shizuku_not_alive">Shizuku не працуе</string>
@ -181,14 +171,12 @@
<string name="theme">Тэма</string>
<string name="socks_proxy">SOCKS проксі</string>
<string name="material_you">Material You</string>
<string name="target">Мэтавы</string>
<string name="waiting_to_start_installation">Чаканне пачатку ўстаноўкі…</string>
<string name="signed_using_unsafe_algorithm">Падпісана з выкарыстаннем небяспечнага алгарытму</string>
<string name="source_code">Зыходны код</string>
<string name="username">Імя карыстальніка</string>
<string name="version_FORMAT">Версія %s</string>
<string name="light">Светлая</string>
<string name="installed_applications">Ўсталяваныя праграмы</string>
<string name="import_settings_DESC">Імпарт налад і абранага з файла</string>
<string name="repository">Рэпазіторый</string>
<string name="unstable_updates_summary">Прапанаваць усталёўваць нестабільныя версіі</string>
@ -196,17 +184,13 @@
<string name="prefs_personalization">Персаналізацыя</string>
<string name="signature_FORMAT">Подпіс %s</string>
<string name="shizuku_installer">Shizuku устаноўшчык</string>
<string name="new_applications">Новыя праграмы</string>
<string name="uninstall">Выдаліць</string>
<string name="skip">Прапусціць</string>
<string name="export_settings_title">Налады экспарту</string>
<string name="export_repos_DESC">Экспарт усіх рэпазіторыяў з файла</string>
<string name="import_repos_title">Імпарт рэпазіторыяў</string>
<string name="screenshots">Скрыншоты</string>
<string name="recently_updated">Нядаўна абноўлены</string>
<string name="sorting_order">Парадак сартавання</string>
<string name="unverified">Неправераныя</string>
<string name="root_permission">Ціхая ўстаноўка</string>
<string name="invalid_fingerprint_format">Няправільны фармат адбітка</string>
<string name="saving_details">Захаванне даных…</string>
<string name="whats_new">Што новага</string>
@ -218,14 +202,12 @@
<string name="search">Пошук</string>
<string name="unknown_error_DESC">Невядомая памылка.</string>
<string name="themes">Тэмы</string>
<string name="list_animation">Анімацыя спісаў</string>
<string name="home_screen_swiping_DESC">Дазволіць карыстальніку гартаць старонкі на галоўным экране</string>
<string name="export_repos_title">Экспарт рэпазіторыяў</string>
<string name="show_less">Паказаць менш</string>
<string name="settings">Налады</string>
<string name="upstream_source_code_is_not_free">Зыходны код не з\'яўляецца свабодным</string>
<string name="versions">Версіі</string>
<string name="repository_not_used_DESC">Гэты рэпазіторый яшчэ не выкарыстоўваўся. Вам трэба ўключыць яго для прагляду прымянення ў ім.</string>
<string name="import_repos_DESC">Імпарт усіх рэпазіторыяў з файла</string>
<string name="system">Як у сістэме</string>
<string name="special_credits">Асаблівыя падзякі</string>
@ -233,8 +215,6 @@
<string name="show_older_versions">Паказаць старыя версіі</string>
<string name="shizuku_not_installed">Shizuku не ўстаноўлена</string>
<string name="unstable_updates">Нестабільнае абнаўленне</string>
<string name="latest">Апошнія</string>
<string name="root_permission_description">Падайце root-правы для ўключэння ціхай устаноўкі</string>
<string name="unknown_FORMAT">Невядома: %s</string>
<string name="cannot_open_link">Немагчыма адкрыць спасылку</string>
<string name="has_tethered_network">Прывязка да вызначанай сеткавай службы</string>
@ -246,4 +226,15 @@
<string name="ignore_signature">Ігнараваць подпіс</string>
<string name="installation_failed_DESC">Не ўдалося ўсталяваць %s</string>
<string name="ignore_signature_summary">Ігнараванне праверкі подпісу пры ўстаноўцы apk, для карыстальнікаў LSPosed або прасунутых карыстальнікаў</string>
<string name="error_shizuku_service_unavailable">Shizuku не працуе</string>
<string name="error_shizuku_not_running_DESC">Сэрвіс Shizuku не працуе. Калі ласка, праверце праграму Shizuku</string>
<string name="error_shizuku_not_granted">Shizuku няма дазволу</string>
<string name="error_shizuku_not_granted_DESC">Не дадзены дазвол на сэрвіс Shizuku. Калі ласка, праверце праграму Shizuku</string>
<string name="error_shizuku_not_installed">Shizuku не ўсталявана</string>
<string name="error_shizuku_not_installed_DESC">Здаецца, Shizuku не ўсталявана</string>
<string name="insufficient_storage">Не хапае месца</string>
<string name="insufficient_storage_DESC">На вашай прыладзе не хапае памяці для ўсталявання гэтай праграмы. Паспрабуйце вызваліць крыху месца</string>
<string name="open_shizuku">Адкрыць Shizuku</string>
<string name="switch_to_default_installer">Пераключыцца на стандартны</string>
<string name="label_open_video">Відэа</string>
</resources>

View File

@ -55,7 +55,6 @@
<string name="license_FORMAT">%s лиценз</string>
<string name="light">Светла</string>
<string name="links">Линкове</string>
<string name="list_animation_description">Анимирай списъка на главната страница</string>
<string name="merging_FORMAT">Сливане на %s</string>
<string name="name">Име</string>
<string name="network_error_DESC">Мрежова грешка</string>
@ -66,7 +65,7 @@
<string name="notify_about_updates">Уведомления за актуализации</string>
<string name="number_of_applications">Брой приложения</string>
<string name="ok">Окей</string>
<string name="only_on_wifi">Само на Wi-Fi</string>
<string name="only_on_wifi">Само при Wi-Fi</string>
<string name="open_DESC_FORMAT">Отвори %s\?</string>
<string name="other">Други</string>
<string name="parsing_index_error_DESC">Не може да се прочете индекс файла.</string>
@ -79,14 +78,10 @@
<string name="recently_updated">Наскоро обновени</string>
<string name="repositories">Хранилища</string>
<string name="repository">Хранилище</string>
<string name="repository_not_used_DESC">Това хранилище все още не е използвано.Включете го, за да видите приложенията в него.</string>
<string name="repository_unsigned_DESC">Неподписано. Не може да провери списъка с неподписаните приложения. Внимавайте с тегленето на приложения от неподписани хранилища.</string>
<string name="requires_FORMAT">Изисква %s</string>
<string name="root_permission">Безшумна Инсталация</string>
<string name="root_permission_description">За безшумни инсталации дайте root разрешение</string>
<string name="save">Запази</string>
<string name="saving_details">Запазване на подробности…</string>
<string name="screenshots">Екранни снимки</string>
<string name="select_mirror">Избери източник</string>
<string name="share">Сподели</string>
<string name="show_more">Покажи повече</string>
@ -100,12 +95,10 @@
<string name="suggested">Предложено</string>
<string name="sync_repositories_automatically">Автоматично синхронизиране на хранилищата</string>
<string name="syncing">Синхронизиране</string>
<string name="target">Цел</string>
<string name="theme">Тема</string>
<string name="tracks_or_reports_your_activity">Проследява или отчита вашата дейност</string>
<string name="unknown">Неизвестно</string>
<string name="unknown_FORMAT">Неизвестно: %s</string>
<string name="unverified">Непроверено</string>
<string name="update">Актуализация</string>
<string name="updates">Актуализации</string>
<string name="username">Потребителско име</string>
@ -114,12 +107,7 @@
<string name="waiting_to_start_download">В очакване да започне изтеглянето…</string>
<string name="whats_new">Какво е новото</string>
<string name="show_less">Покажи по-малко</string>
<string name="latest">Последни</string>
<string name="explore">Разглеждане</string>
<string name="update_all">Инсталирай всички</string>
<string name="installed_applications">Инсталирани приложения</string>
<string name="sort_filter">Сортиране &amp; Филтриране</string>
<string name="new_applications">Нови приложения</string>
<string name="anti_features">Антифункции</string>
<string name="author_website">Уебстраница на автора</string>
<string name="cant_edit_sync_DESC">Не може да се редактират синхронизиращи се хранилища.</string>
@ -134,7 +122,6 @@
<string name="incompatible_api_DESC_FORMAT">Вашата %1$s (АПИ версия %2$d) се поддържа. %3$s</string>
<string name="incompatible_older_DESC">Тази версия е по-стара от инсталираната на вашето устройство. Деинсталирайте първо нея.</string>
<string name="invalid_username_format">Невалиден формат на потребителското име</string>
<string name="list_animation">Анимации на списъците</string>
<string name="unstable_updates">Нестабилни актуализации</string>
<string name="incompatible_signature_DESC">Тази версия е подписана със сертификат, различен от този, инсталиран на вашето устройство. Деинсталирайте първо нея.</string>
<string name="incompatible_versions_summary">Показване на версии, несъвместими с устройството</string>
@ -170,7 +157,6 @@
<string name="tap_to_install_DESC">Докосни за инсталиране.</string>
<string name="unknown_error_DESC">Неизвестна грешка.</string>
<string name="unstable_updates_summary">Предложи инсталирането на нестабилни версии</string>
<string name="unsigned">Неподписано</string>
<string name="upstream_source_code_is_not_free">Актуалният програмен код вече не е със свободен лиценз</string>
<string name="username_missing">Потребителско име липсва</string>
<string name="validation_index_error_DESC">Не може да валидира индексът.</string>
@ -188,24 +174,19 @@
</plurals>
<string name="only_on_wifi_with_charging">Само при Wi-Fi и зареждане</string>
<string name="cleanup_title">Интервал за почистване на APK</string>
<string name="cleanup_description">Период за проверка и премахване на изтеглените файлове</string>
<plurals name="hours">
<item quantity="one">Час</item>
<item quantity="other">Часа</item>
</plurals>
<string name="no_internet">Нямате интернет връзка</string>
<string name="allow_collapsing_toolbar">Разрешете горната лента на приложението да се разшири</string>
<string name="io_error_DESC">Невъзможност за извършване на определени действия.</string>
<string name="allow_collapsing_toolbar_DESC">Разрешете горната лента на приложението да се разширява и свива</string>
<string name="material_you_desc">Използвайте material you цветова тема</string>
<string name="material_you">Material You</string>
<string name="auto_update">Автоматично актуализиране на приложения</string>
<string name="installing">Инсталиране</string>
<string name="auto_update_apps">Опитайте се да инсталирате актуализации автоматично</string>
<string name="restart_app">Рестартирайте Droid-ify, за да видите промените</string>
<string name="waiting_to_start_installation">Изчакване за стартиране на инсталацията…</string>
<string name="favourites">Любими</string>
<string name="enable_repo">Активирайте хранилището</string>
<string name="enable_repo">Активирай хранилището</string>
<string name="force_clean_up">Принудително почистване</string>
<string name="force_clean_up_DESC">Почиства излишните файлове</string>
<string name="repository_unreachable">Хранилището е недостъпно</string>
@ -215,7 +196,6 @@
<string name="home_screen_swiping">Плъзгане на началния екран</string>
<string name="contains_nsfw">Съдържа неподходящо за работа съдържание</string>
<string name="shizuku_not_alive">Shizuku не работи</string>
<string name="label_copy">Копирай</string>
<string name="proxy_port_error_not_int">Прокси портът може да бъде само цяло число</string>
<string name="home_screen_swiping_DESC">Позволете на потребителя да плъзга между страниците в началния екран</string>
<string name="repository_not_found">Следното хранилище не бе намерено</string>
@ -225,12 +205,12 @@
<string name="import_settings_title">Внеси Настройки</string>
<string name="import_settings_DESC">Внасяне на настройки и любими от файл</string>
<string name="export_settings_title">Изнеси Настройки</string>
<string name="export_repos_DESC">Изнеси всички хранилища във файл</string>
<string name="export_repos_DESC">Изнасяне на всички хранилища във файл</string>
<string name="import_repos_title">Внеси хранилища</string>
<string name="export_settings_DESC">Изнасяне на настройки и любими във файл</string>
<string name="export_repos_title">Изнеси хранилища</string>
<string name="cannot_open_link">Линкът не може да се отвори</string>
<string name="import_repos_DESC">Внеси всички хранилища от файл</string>
<string name="import_repos_DESC">Внасяне на всички хранилища от файл</string>
<string name="has_tethered_network">Обвързан с определена мрежова услуга</string>
<string name="require_background_access">Изискване на фонов достъп</string>
<string name="require_background_access_DESC">Необходим е фонов достъп, за да стартирате правилно фоновото синхронизиране</string>
@ -242,4 +222,14 @@
<string name="ignore_signature_summary">*Внимание* Игнорирайте проверката на подписа при инсталиране на APK за LSPosed потребители или напреднали потребители</string>
<string name="insufficient_storage">Недостатъчно място</string>
<string name="insufficient_storage_DESC">Няма достатъчно свободно място на устройството за инсталиране на това приложение. Опитайте да освободите малко място</string>
<string name="error_shizuku_not_installed">Shizuku Не е инсталиран</string>
<string name="label_open_video">Видео</string>
<string name="switch_to_default_installer">Превключване към подразбиране</string>
<string name="open_shizuku">Отворете Shizuku</string>
<string name="error_shizuku_not_running_DESC">Услугата Shizuku не работи. Моля, проверете в приложението Shizuku</string>
<string name="error_shizuku_not_granted">Липсва разрешение за Shizuku</string>
<string name="error_shizuku_not_granted_DESC">Разрешението за услуга Shizuku не е дадено. Моля, проверете в приложението Shizuku</string>
<string name="error_shizuku_not_installed_DESC">Shizuku изглежда не е инсталиран</string>
<string name="error_shizuku_service_unavailable">Shizuku Не работи</string>
<string name="label_unknown_sdk">Неизвестен (%d)</string>
</resources>

View File

@ -21,7 +21,6 @@
<string name="already_exists">আগে থেকেই আছে</string>
<string name="always">সর্বদা</string>
<string name="amoled">অন্ধকার</string>
<string name="allow_collapsing_toolbar">অ্যাপের টপ বার সম্প্রসারিত হতে দাও</string>
<string name="could_not_validate_FORMAT">%s সত্যায়িত করা সম্ভব হয়নি</string>
<string name="donate">অনুদান</string>
<string name="description">বর্ণনা</string>
@ -30,7 +29,6 @@
<item quantity="other">ঘণ্টা</item>
</plurals>
<string name="incompatible_api_max_DESC_FORMAT">সর্বোচ্চ এপিআই সংস্করণ %d।</string>
<string name="allow_collapsing_toolbar_DESC">অ্যাপের টপ বার সম্প্রসারিত ও সংকুচিত হতে দাও</string>
<string name="anti_features">অপবৈশিষ্ট্য</string>
<string name="has_advertising">বিজ্ঞাপন আছে</string>
<string name="http_proxy">HTTP প্রক্সি</string>
@ -60,7 +58,6 @@
<string name="delete_repository_DESC">রিপোজিটরি ডিলিট করতে চান\?</string>
<string name="edit_repository">রিপোজিটরি সম্পাদনা করুন</string>
<string name="ignore_this_update">এই সংস্করণটি অগ্রাহ্য করো</string>
<string name="cleanup_description">ডাউনলোড করা নথিগুলো পরীক্ষা এবং অপসারণের সময়কাল</string>
<string name="compiled_for_debugging">ডিবাগিংয়ের জন্য কম্পাইল করা হয়েছে</string>
<string name="bug_tracker">বাগ ট্র্যাকার</string>
<string name="incompatible_signature_DESC">এই সংস্করণটি আপনার ডিভাইসে ইনস্টল করা একটি থেকে একটি ভিন্ন শংসাপত্রের সাথে স্বাক্ষরিত৷ প্রথমে এটি আনইনস্টল করুন।</string>
@ -91,8 +88,6 @@
<string name="license">লাইসেন্স</string>
<string name="license_FORMAT">%s লাইসেন্স</string>
<string name="light">আলো</string>
<string name="list_animation">তালিকা অ্যানিমেশন</string>
<string name="list_animation_description">প্রধান পৃষ্ঠায় তালিকা অ্যানিমেশন দেখান</string>
<string name="merging_FORMAT">%s মার্জ করা হচ্ছে</string>
<string name="material_you">উপাদান আপনি</string>
<string name="session_installer">সেশন ইনস্টলার</string>
@ -108,7 +103,6 @@
<item quantity="other">নতুন সংস্করণ সহ %dটি অ্যাপ্লিকেশন।</item>
</plurals>
<string name="no_applications_installed">কোনো ইনস্টল করা অ্যাপ্লিকেশন নেই</string>
<string name="no_internet">আপনার কোন ইন্টারনেট সংযোগ নেই</string>
<string name="no_matching_applications_found">এই ধরনের কোনো অ্যাপ্লিকেশন খুঁজে পাওয়া যায়নি</string>
<string name="number_of_applications">আবেদনের সংখ্যা</string>
<string name="auto_update">স্বয়ংক্রিয়ভাবে অ্যাপ্লিকেশন হালনাগাদ</string>
@ -129,8 +123,6 @@
<string name="proxy_port">প্রক্সি পর্ট</string>
<string name="recently_updated">সম্প্রতি আপডেট করা হয়েছে</string>
<string name="repository">ভান্ডার</string>
<string name="repository_not_used_DESC">এই সংগ্রহস্থল এখনও ব্যবহার করা হয় নি. এটিতে থাকা অ্যাপ্লিকেশনগুলি দেখতে এটি চালু করুন।</string>
<string name="restart_app">পরিবর্তনগুলি দেখতে Droid-ify পুনরায় চালু করুন</string>
<string name="show_older_versions">পুরানো সংস্করণ দেখান</string>
<string name="plus_more_FORMAT">+%d আরো</string>
<string name="favourites">প্রিয়</string>
@ -149,10 +141,7 @@
<string name="project_website">প্রকল্প ওয়েবসাইট</string>
<string name="repository_unreachable">রিপোজিটরি পৌঁছানো যায় না</string>
<string name="requires_FORMAT">%s প্রয়োজন</string>
<string name="root_permission">নীরব ইনস্টল</string>
<string name="saving_details">বিবরণ সংরক্ষণ করা হচ্ছে…</string>
<string name="screenshots">স্ক্রিনশট</string>
<string name="root_permission_description">নীরব ইনস্টলেশনের জন্য রুট অনুমতির অনুমতি দিন</string>
<string name="save">সংরক্ষণ</string>
<string name="search">অনুসন্ধান করুন</string>
<string name="select_mirror">একটি আয়না নির্বাচন করুন</string>
@ -172,24 +161,18 @@
<string name="syncing">সিঙ্ক হচ্ছে</string>
<string name="system">পদ্ধতি</string>
<string name="tap_to_install_DESC">ইনস্টল করতে আলতো চাপুন।</string>
<string name="target">টার্গেট</string>
<string name="uninstall">আনইনস্টল করুন</string>
<string name="unknown">অজানা</string>
<string name="unknown_error_DESC">অজানা ত্রুটি.</string>
<string name="unknown_FORMAT">অজানা: %s</string>
<string name="unsigned">স্বাক্ষরবিহীন</string>
<string name="unverified">যাচাই করা হয়নি</string>
<string name="username">ব্যবহারকারীর নাম</string>
<string name="version_FORMAT">সংস্করণ %s</string>
<string name="website">ওয়েবসাইট</string>
<string name="prefs_personalization">ব্যক্তিগতকরণ</string>
<string name="show_less">প্রদর্শন কম</string>
<string name="latest">সর্বশেষ</string>
<string name="sort_filter">বাছাই এবং ফিল্টার</string>
<string name="sync_repositories">সিঙ্ক রিপোজিটরি</string>
<string name="tracks_or_reports_your_activity">আপনার কার্যকলাপ ট্র্যাক বা রিপোর্ট</string>
<string name="versions">সংস্করণ</string>
<string name="explore">অন্বেষণ</string>
<string name="syncing_FORMAT">%s সিঙ্ক হচ্ছে…</string>
<string name="whats_new">নতুন কি</string>
<string name="themes">রঙ</string>
@ -204,12 +187,49 @@
<string name="waiting_to_start_installation">ইনস্টলেশন শুরু করার জন্য অপেক্ষা করা হচ্ছে…</string>
<string name="upstream_source_code_is_not_free">আপস্ট্রিম সোর্স কোড বিনামূল্যে নয়</string>
<string name="validation_index_error_DESC">সূচক যাচাই করা যায়নি.</string>
<string name="installed_applications">ইনস্টল করা অ্যাপ্লিকেশন</string>
<string name="update">হালনাগাদ</string>
<string name="waiting_to_start_download">ডাউনলোড শুরু করার জন্য অপেক্ষা করা হচ্ছে…</string>
<string name="prefs_language_title">ভাষা</string>
<string name="new_applications">নতুন অ্যাপ্লিকেশন</string>
<string name="cannot_open_link">লিংকটি ওপেন করা সম্ভব হয়নি</string>
<string name="import_export">ইম্পোর্ট/এক্সপোর্ট</string>
<string name="import_settings_title">সেটিংস ইম্পোর্ট করুন</string>
<string name="uninstalled_application">অপসারিত</string>
<string name="special_credits">বিশেষ কৃতজ্ঞতাস্বীকার</string>
<string name="error_shizuku_not_granted_DESC">শিজুকু সেবার অনুমতি দেওয়া হয়নি। শিজুকু অ্যাপটি দেখো</string>
<string name="ignore_signature_summary">*সতর্কতা* এপিকে ইন্সটলে সময় স্বাক্ষর যাচাইকরণ এড়াও, এলএসপোজড ব্যবহারকারী বা অগ্রগামী ব্যবহারকারীর জন্য</string>
<string name="contains_nsfw">নিষিদ্ধ বিষয়বস্ত বিদ্যমান</string>
<string name="label_open_video">ভিডিও</string>
<string name="export_settings_title">পছন্দসমূহ রপ্তানি করো</string>
<string name="insufficient_storage">অপর্যাপ্ত জায়গা</string>
<string name="shizuku_not_alive">শিজুকু চলছে না</string>
<string name="error_shizuku_not_running_DESC">শিজুকু চলছে না। অনুগ্রহ করে শিজুকু অ্যাপ দেখো</string>
<string name="export_repos_DESC">সব ভাণ্ডার ফাইলে রপ্তানি</string>
<string name="home_screen_swiping_DESC">ব্যবহারকারীকে মূলপাতাসমূহের মধ্যে টান দেওয়া অনুমোদন করো</string>
<string name="connection_error_DESC">সার্ভারে যুক্ত হতে ব্যর্থ</string>
<string name="shizuku_not_installed">শিজুকু ইন্সটল করা নেই</string>
<string name="installation_failed">ইন্সটল ব্যর্থ</string>
<string name="installation_failed_DESC">%s ইন্সটল করতে ব্যর্থ</string>
<string name="ignore_signature">স্বাক্ষর উপেক্ষা করো</string>
<string name="error_shizuku_service_unavailable">শিজুকু চলছে না</string>
<string name="error_shizuku_not_granted">শিজুকু অনুমতি নেই</string>
<string name="error_shizuku_not_installed">শিজুকু ইন্সটল করা হয়নি</string>
<string name="error_shizuku_not_installed_DESC">শিজুকু মনে হচ্ছে না ইন্সটল করা</string>
<string name="import_settings_DESC">পছন্দসমূহ ফাইল থেকে আমদানি করো</string>
<string name="export_settings_DESC">পছন্দসমূহ ফাইলে রপ্তানি করো</string>
<string name="import_repos_title">ভাণ্ডার আমদানি করো</string>
<string name="import_repos_DESC">ফাইল থেকে সব ভাণ্ডার আমদানি করো</string>
<string name="export_repos_title">ভাণ্ডার রপ্তানি</string>
<string name="has_non_free_components">অমুক্ত অংশ আছে</string>
<string name="has_tethered_network">একটি নির্দিষ্ট নেটওয়ার্ক সেবার সাথে আবদ্ধ</string>
<string name="home_screen_swiping">মূলপাতা টান দেওয়া(সোয়াইপিং)</string>
<string name="socket_error_DESC">সার্ভার নতুন প্যাকেট সরবরাহে ব্যর্থ।</string>
<string name="uninstalled_application_DESC">%s অপসারিত হয়েছে</string>
<string name="insufficient_storage_DESC">এই অ্যাপটি ইন্সটল করার জন্য পর্যাপ্ত জায়গা নেই। কিছু জায়গা পরিষ্কারের চেষ্টা করো</string>
<string name="open_shizuku">শিজুকু খুলো</string>
<string name="proxy_port_error_not_int">প্রক্সি পোর্ট শুধু পূর্ণ ধনাত্মক সংখ্যা হতে পারে</string>
<string name="require_background_access">পটভূমিতে চলার অনুমতি প্রয়োজন</string>
<string name="require_background_access_DESC">পটভূমি অনুমোদন প্রয়োজন পটভূমি সিঙ্কের জন্য</string>
<string name="repository_not_found">এই ভাণ্ডারগুলো পাওয়া যায়নি</string>
<string name="switch_to_default_installer">সহজাতে পরিবর্তন করো</string>
<string name="label_unknown_sdk">অজানা (%d)</string>
</resources>

View File

@ -16,7 +16,6 @@
<string name="changes">Canvis</string>
<string name="checking_repository">Comprovant repositori…</string>
<string name="cleanup_title">Interval de neteja de l\'APK</string>
<string name="cleanup_description">Període per comprovar i eliminar els fitxers descarregats</string>
<string name="compiled_for_debugging">Compilat per a depuració</string>
<string name="confirmation">Confirmació</string>
<string name="connecting">Connectant…</string>
@ -87,11 +86,8 @@
<string name="recently_updated">Recentment actualitzat</string>
<string name="repositories">Repositoris</string>
<string name="repository">Repositori</string>
<string name="repository_not_used_DESC">Aquest repositori no ha estat utilitzat tot i així. Gira\'l damunt per veure les aplicacions en ell.</string>
<string name="repository_unsigned_DESC">Sense signar. No podria verificar la llista d\'aplicació. Ser prudent descarregar aplicacions des de repositoris sense signar.</string>
<string name="requires_FORMAT">Requereix %s</string>
<string name="root_permission">Silenciós Instal·lar</string>
<string name="root_permission_description">Permet permís d\'arrel per silenciós instal·la</string>
<string name="save">Salva</string>
<string name="search">Recerca</string>
<string name="select_mirror">Selecciona un mirall</string>
@ -112,10 +108,8 @@
<string name="unknown">Desconegut</string>
<string name="unknown_error_DESC">Error desconegut.</string>
<string name="unknown_FORMAT">Desconegut: %s</string>
<string name="unsigned">Sense signar</string>
<string name="unstable_updates">Actualitzacions inestables</string>
<string name="unstable_updates_summary">Suggereix instal·lant versions inestables</string>
<string name="unverified">Sense verificar</string>
<string name="update">Actualització</string>
<string name="updates">Actualitzacions</string>
<string name="upstream_source_code_is_not_free">El codi font no és lliure</string>
@ -129,12 +123,7 @@
<string name="prefs_language_title">Llengua</string>
<string name="prefs_personalization">Personalització</string>
<string name="show_less">Mostrar menys</string>
<string name="latest">Més recent</string>
<string name="explore">Explora</string>
<string name="update_all">Actualitzar tot</string>
<string name="installed_applications">Aplicacions instal·lades</string>
<string name="sort_filter">Ordenar i filtrar</string>
<string name="new_applications">Aplicacions noves</string>
<string name="fingerprint">Empremta digital</string>
<string name="incompatible_platforms_DESC_FORMAT">El vostre %1$s el programa no és recolzat. Programes recolzats: %2$s.</string>
<string name="all_applications">Totes les aplicacions</string>
@ -165,17 +154,14 @@
<string name="license_FORMAT">%s llicència</string>
<string name="no_proxy">Cap proxy</string>
<string name="link_copied_to_clipboard">El nexe va copiar a portapapers</string>
<string name="target">Objectiu</string>
<string name="license">Llicència</string>
<string name="theme">Tema</string>
<string name="invalid_username_format">Nul username format</string>
<string name="launch">Executa</string>
<string name="light">Llum</string>
<string name="links">Nexes</string>
<string name="list_animation">Animacions de llista</string>
<string name="size">Mida</string>
<string name="no_applications_installed">Aplicacions instal·lades no</string>
<string name="list_animation_description">L\'espectacle llista animació en la pàgina major</string>
<string name="merging_FORMAT">Fusionant %s</string>
<string name="network_error_DESC">Error de xarxa</string>
<string name="new_updates_available">Versions noves de les aplicacions disponibles</string>
@ -188,15 +174,11 @@
<string name="no_description_available_DESC">Cap descripció disponible</string>
<string name="notify_about_updates_summary">Mostra una notificació quan les versions noves són disponibles</string>
<string name="saving_details">Salvant detalls…</string>
<string name="screenshots">Captures de pantalla</string>
<string name="no_matching_applications_found">No podria trobar qualsevol tals aplicacions</string>
<string name="notify_about_updates">Notifica sobre versions noves d\'aplicacions</string>
<string name="show_older_versions">Mostrar versions més velles</string>
<string name="suggested">Suggerit</string>
<string name="syncing">Sincronitzant</string>
<string name="no_internet">No teniu cap connexió a Internet</string>
<string name="allow_collapsing_toolbar_DESC">Permet que la barra d\'aplicacions superior s\'expandeixi i es col·lapsi</string>
<string name="allow_collapsing_toolbar">Permet que la barra d\'aplicacions superior s\'expandeixi</string>
<string name="io_error_DESC">No es pot realitzar determinades accions.</string>
<string name="auto_update">Actualització automàtica d\'aplicacions</string>
<string name="auto_update_apps">Intenta instal·lar les actualitzacions automàticament</string>
@ -208,7 +190,6 @@
<string name="force_clean_up_DESC">Neteja els fitxers redundants</string>
<string name="enable_repo">Habiliteu el repositori</string>
<string name="waiting_to_start_installation">S\'està esperant per iniciar la instal·lació…</string>
<string name="restart_app">Reinicieu Droid-ify per veure els canvis</string>
<string name="installing">Instal·lació</string>
<string name="import_settings_DESC">Importa configuració i preferits des de fitxer</string>
<string name="has_tethered_network">Enllaçat a un determinat servei de xarxa</string>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_failed">کردارەکە شکستیهێنا</string>
<string name="amoled">ڕەش</string>
<string name="dark">تاریک</string>
<string name="address">ناونیشان</string>
<string name="always">هەمووکات</string>
</resources>

View File

@ -78,8 +78,6 @@
<string name="license">Licence</string>
<string name="license_FORMAT">%s licence</string>
<string name="links">Odkazy</string>
<string name="list_animation">List Animací</string>
<string name="list_animation_description">Zobrazit animaci listu na hlavní stránce</string>
<string name="merging_FORMAT">Slučování %s</string>
<string name="name">Název</string>
<string name="network_error_DESC">Chyba sítě</string>
@ -110,14 +108,10 @@
<string name="promotes_non_free_software">Propaguje ne-svobodný software</string>
<string name="provided_by_FORMAT">Poskytuje %s</string>
<string name="proxy">Proxy</string>
<string name="repository_not_used_DESC">Tento zdroj zatím použit. Zapněte jej pro zobrazení aplikací na něm.</string>
<string name="repository_unsigned_DESC">Nepodepsáno. Nezdařilo se ověřit seznam aplikací. Buďte opatrní při stahování aplikací z nepodepsaných zdrojů.</string>
<string name="requires_FORMAT">Vyžaduje %s</string>
<string name="root_permission">Tichá instalace</string>
<string name="root_permission_description">Povolit root oprávnění pro tiché instalace</string>
<string name="save">Uložit</string>
<string name="saving_details">Ukládám detaily…</string>
<string name="screenshots">Snímky obrazovky</string>
<string name="search">Hledat</string>
<string name="share">Sdílet</string>
<string name="show_more">Zobrazit více</string>
@ -137,7 +131,6 @@
<string name="syncing_FORMAT">Synchronizuji %s…</string>
<string name="system">Systém</string>
<string name="tap_to_install_DESC">Klikněte pro instalaci.</string>
<string name="target">Cíl</string>
<string name="theme">Téma</string>
<string name="themes">Témata</string>
<string name="tracks_or_reports_your_activity">Sleduje nebo hlásí vaší aktivitu</string>
@ -145,9 +138,7 @@
<string name="unknown">Neznámé</string>
<string name="unknown_error_DESC">Neznámá chyba.</string>
<string name="unknown_FORMAT">Neznámé: %s</string>
<string name="unsigned">Nepodepsáno</string>
<string name="unstable_updates">Nestabilní aktualizace</string>
<string name="unverified">Neověřeno</string>
<string name="update">Aktualizovat</string>
<string name="updates">Aktualizace</string>
<string name="upstream_source_code_is_not_free">Originální zdrojový kód není svobodný</string>
@ -163,12 +154,7 @@
<string name="prefs_language_title">Jazyk</string>
<string name="prefs_personalization">Personalizace</string>
<string name="show_less">Zobrazit méně</string>
<string name="latest">Nejnovější</string>
<string name="explore">Prozkoumat</string>
<string name="update_all">Aktualizovat vše</string>
<string name="installed_applications">Instalované aplikace</string>
<string name="sort_filter">Třídit &amp; Filtrovat</string>
<string name="new_applications">Nové aplikace</string>
<string name="action_failed">Akce se nezdařila</string>
<string name="all_applications">Všechny aplikace</string>
<string name="amoled">Černá</string>
@ -194,12 +180,8 @@
<item quantity="other">hodin</item>
</plurals>
<string name="cleanup_title">Interval čištění APK</string>
<string name="cleanup_description">Období pro kontrolu a odstranění stažených souborů</string>
<string name="only_on_wifi_with_charging">Pouze na Wi-Fi a při nabíjení</string>
<string name="io_error_DESC">Nepodařilo se vykonat některé akce.</string>
<string name="no_internet">Nejste připojeni k internetu</string>
<string name="allow_collapsing_toolbar">Povolení rozšíření horního panelu aplikací</string>
<string name="allow_collapsing_toolbar_DESC">Povolení rozbalování a sbalování horního panelu aplikace</string>
<string name="material_you_desc">Použít barevný motiv Material You</string>
<string name="material_you">Material You</string>
<string name="favourites">Oblíbené</string>
@ -207,7 +189,6 @@
<string name="force_clean_up">Vynutit vyčištění</string>
<string name="repository_unreachable">Repozitář nedostupný</string>
<string name="enable_repo">Povolit repozitář</string>
<string name="restart_app">Pro zobrazení změn restartujte Droid-ify</string>
<string name="waiting_to_start_installation">Čekání na spuštění instalace…</string>
<string name="installing">Instalace</string>
<string name="auto_update">Automatická aktualizace aplikací</string>
@ -221,7 +202,6 @@
<string name="special_credits">Speciální poděkování</string>
<string name="home_screen_swiping">Posouvání na domovské stránce</string>
<string name="home_screen_swiping_DESC">Umožnit uživateli posouvat mezi stránkami na domovské stránce</string>
<string name="label_copy">Kopírovat</string>
<string name="repository_not_found">Následující repozitář nebyl nalezen</string>
<string name="proxy_port_error_not_int">Port proxy smí být pouze celé číslo</string>
<string name="import_settings_title">Importovat nastavení</string>
@ -245,4 +225,14 @@
<string name="uninstalled_application">Odinstalováno</string>
<string name="insufficient_storage">Nedostatek místa</string>
<string name="insufficient_storage_DESC">Na zařízení není dostatek místa k instalaci této aplikace. Zkuste uvolnit trochu místa</string>
<string name="error_shizuku_service_unavailable">Shizuku není spuštěno</string>
<string name="error_shizuku_not_granted">Chybějící oprávnění Shizuku</string>
<string name="error_shizuku_not_granted_DESC">Nebylo uděleno oprávnění ke službě Shizuku. Zkontrolujte prosím aplikaci Shizuku</string>
<string name="error_shizuku_not_installed_DESC">Aplikace Shizuku nejspíše není nainstalována</string>
<string name="switch_to_default_installer">Přepnout na výchozí</string>
<string name="label_unknown_sdk">Neznámé (%d)</string>
<string name="error_shizuku_not_running_DESC">Služba Shizuku není spuštěna. Zkontrolujte prosím aplikaci Shizuku</string>
<string name="error_shizuku_not_installed">Shizuku není nainstalováno</string>
<string name="label_open_video">Video</string>
<string name="open_shizuku">Otevřít Shizuku</string>
</resources>

View File

@ -14,8 +14,6 @@
<string name="cancel">Annuller</string>
<string name="add_repository">Tilføj repository</string>
<string name="address">Adresse</string>
<string name="allow_collapsing_toolbar">Lad øverste app-linje udvide sig</string>
<string name="allow_collapsing_toolbar_DESC">Lad øverste app-linje udvide og skjule sig</string>
<string name="anti_features">Anti-funktioner</string>
<string name="author_email">Udviklerens e-mail</string>
<string name="author_website">Udviklerens hjemmeside</string>
@ -24,7 +22,6 @@
<string name="changelog">Ændringslog</string>
<string name="changes">Ændringer</string>
<string name="cleanup_title">APK-oprydningsinterval</string>
<string name="cleanup_description">Periodisk kontrol og fjernelse af hentede filer</string>
<string name="confirmation">Bekræftelse</string>
<string name="connecting">Forbinder…</string>
<string name="contains_non_free_media">Indeholder ikke-frie medier</string>
@ -42,10 +39,10 @@
<string name="downloaded_FORMAT">Hentet %s</string>
<string name="downloading">Henter</string>
<string name="downloading_FORMAT">Henter %s…</string>
<string name="import_export">Import/Eksport</string>
<string name="import_settings_title">Importér Indstillinger</string>
<string name="import_export">Import/eksport</string>
<string name="import_settings_title">Importér indstillinger</string>
<string name="import_settings_DESC">Importér indstillinger og favoritter fra fil</string>
<string name="export_settings_title">Eksportér Indstillinger</string>
<string name="export_settings_title">Eksportér indstillinger</string>
<string name="favourites">Favoritter</string>
<string name="file_format_error_DESC">Ugyldigt filformat.</string>
<string name="fingerprint">Fingeraftryk</string>
@ -63,8 +60,8 @@
<string name="connection_error_DESC">Kunne ikke forbinde til server</string>
<string name="ignore_all_updates">Ignorer alle nye versioner</string>
<string name="incompatible_api_DESC_FORMAT">Din %1$s (API-version %2$d) understøttes ikke. %3$s</string>
<string name="incompatible_api_max_DESC_FORMAT">Maksimal API-version er %d.</string>
<string name="incompatible_api_min_DESC_FORMAT">Minimum API-version er %d.</string>
<string name="incompatible_api_max_DESC_FORMAT">Maks. API-version er %d.</string>
<string name="incompatible_api_min_DESC_FORMAT">Min. API-version er %d.</string>
<string name="incompatible_features_DESC">Manglende funktioner.</string>
<string name="incompatible_older_DESC">Denne version er ældre end den, der er installeret på din enhed. Afinstaller den først.</string>
<string name="incompatible_version">Inkompatibel version</string>
@ -74,7 +71,7 @@
<string name="install">Installer</string>
<string name="install_types">Installationstyper</string>
<string name="installer">Installatør</string>
<string name="shizuku_installer">Shizuku Installatør</string>
<string name="shizuku_installer">Shizuku-installatør</string>
<string name="shizuku_not_alive">Shizuku kører ikke</string>
<string name="shizuku_not_installed">Shizuku er ikke installeret</string>
<string name="installing">Installerer</string>
@ -83,19 +80,17 @@
<string name="invalid_fingerprint_format">Ugyldigt fingeraftryksformat</string>
<string name="invalid_username_format">Ugyldigt brugernavnsformat</string>
<string name="io_error_DESC">Kan ikke udføre visse handlinger.</string>
<string name="label_copy">Kopiér</string>
<string name="launch">Start</string>
<string name="license">Licens</string>
<string name="license_FORMAT">%s licens</string>
<string name="light">Lys</string>
<string name="link_copied_to_clipboard">Link kopieret</string>
<string name="links">Links</string>
<string name="list_animation">Listeanimationer</string>
<string name="home_screen_swiping">Strygning på Startskærm</string>
<string name="home_screen_swiping">Strygning på startside</string>
<string name="socket_error_DESC">Server kunne ikke levere ny pakke.</string>
<string name="http_proxy">HTTP-proxy</string>
<string name="incompatible_platforms_DESC_FORMAT">Din %1$s platform understøttes ikke. Understøttede platforme: %2$s.</string>
<string name="root_installer">Root Installatør</string>
<string name="root_installer">Root-installatør</string>
<string name="material_you">Material You</string>
<string name="material_you_desc">Brug Material You-farvetema</string>
<string name="merging_FORMAT">Fletter %s</string>
@ -110,7 +105,6 @@
<string name="no_applications_available">Ingen tilgængelige applikationer</string>
<string name="no_applications_installed">Ingen installerede applikationer</string>
<string name="no_description_available_DESC">Ingen beskrivelse tilgængelig</string>
<string name="no_internet">Du har ingen internetforbindelse</string>
<string name="no_matching_applications_found">Kunne ikke finde sådanne applikationer</string>
<string name="no_proxy">Ingen proxy</string>
<string name="notify_about_updates">Underret om opdateringer</string>
@ -119,10 +113,10 @@
<string name="ok">OK</string>
<string name="only_compatible_with_FORMAT">Kun kompatibel med %s</string>
<string name="only_on_wifi">Kun på Wi-Fi</string>
<string name="only_on_wifi_with_charging">Kun på Wi-Fi &amp; Opladning</string>
<string name="only_on_wifi_with_charging">Kun på Wi-Fi og opladning</string>
<string name="open_DESC_FORMAT">Åbn %s?</string>
<string name="other">Andet</string>
<string name="parsing_index_error_DESC">Kunne ikke analysere indeksfilen.</string>
<string name="parsing_index_error_DESC">Kunne ikke fortolke indeksfilen.</string>
<string name="password">Adgangskode</string>
<string name="password_missing">Manglende adgangskode</string>
<string name="plus_more_FORMAT">+%d mere</string>
@ -136,10 +130,8 @@
<string name="proxy_port_error_not_int">Proxyport kan kun være et heltal</string>
<string name="proxy_type">Proxytype</string>
<string name="requires_FORMAT">Kræver %s</string>
<string name="restart_app">Genstart Droid-ify for at se ændringer</string>
<string name="save">Gem</string>
<string name="saving_details">Gemmer detaljer…</string>
<string name="screenshots">Skærmbilleder</string>
<string name="settings">Indstillinger</string>
<string name="share">Del</string>
<string name="signed_using_unsafe_algorithm">Signeret med en usikker algoritme</string>
@ -153,26 +145,20 @@
<string name="themes">Temaer</string>
<string name="tracks_or_reports_your_activity">Sporer eller rapporterer din aktivitet</string>
<string name="repositories">Repositories</string>
<string name="repository_not_used_DESC">Dette repository er ikke blevet brugt endnu. Slå det til for at se applikationer i den.</string>
<string name="repository_not_found">Følgende repository blev ikke fundet</string>
<string name="repository_unsigned_DESC">Usigneret. Kunne ikke verificere applikationslisten. Vær forsigtig med at hente applikationer fra usignerede repositories.</string>
<string name="repository_unreachable">Repository utilgængeligt</string>
<string name="root_permission">Stille Installation</string>
<string name="root_permission_description">Tillad root for stille installationer</string>
<string name="select_mirror">Vælg et mirror</string>
<string name="socks_proxy">SOCKS-proxy</string>
<string name="sync_repositories">Synkroniser repositories</string>
<string name="sync_repositories_automatically">Synkroniser repositories automatisk</string>
<string name="target">Mål</string>
<string name="repository">Repository</string>
<string name="uninstall">Afinstaller</string>
<string name="unknown">Ukendt</string>
<string name="unknown_error_DESC">Ukendt fejl.</string>
<string name="unknown_FORMAT">Ukendt: %s</string>
<string name="unsigned">Usigneret</string>
<string name="unstable_updates">Ustabile opdateringer</string>
<string name="unstable_updates_summary">Foreslå at installere ustabile versioner</string>
<string name="unverified">Ikke verificeret</string>
<string name="upstream_source_code_is_not_free">Den oprindelige kildekode er ikke gratis</string>
<string name="username">Brugernavn</string>
<string name="username_missing">Manglende brugernavn</string>
@ -187,18 +173,13 @@
<string name="prefs_language_title">Sprog</string>
<string name="prefs_personalization">Personalisering</string>
<string name="show_less">Vis mindre</string>
<string name="latest">Seneste</string>
<string name="explore">Udforsk</string>
<string name="update_all">Opdater alle</string>
<string name="installed_applications">Installerede applikationer</string>
<string name="sort_filter">Sortér &amp; Filtrér</string>
<string name="new_applications">Nye applikationer</string>
<string name="updates">Opdateringer</string>
<string name="donate">Donér</string>
<string name="export_settings_DESC">Eksportér indstillinger og favoritter til fil</string>
<string name="has_non_free_components">Har ikke-frie komponenter</string>
<string name="has_tethered_network">Bundet til en bestemt netværkstjeneste</string>
<string name="home_screen_swiping_DESC">Tillad at stryge mellem sider på startskærm</string>
<string name="home_screen_swiping_DESC">Tillad at stryge mellem sider på startside</string>
<string name="ignore_this_update">Ignorer denne version</string>
<string name="incompatible_signature_DESC">Denne version er signeret med et andet certifikat end den, der er installeret på din enhed. Afinstaller den først.</string>
<string name="installed">Installeret</string>
@ -220,26 +201,35 @@
<string name="compiled_for_debugging">Kompileret til fejlfinding</string>
<string name="delete_repository_DESC">Slet repositoriet?</string>
<string name="edit_repository">Rediger repository</string>
<string name="import_repos_title">Importér Repositories</string>
<string name="import_repos_title">Importér repositories</string>
<string name="import_repos_DESC">Importér alle repositories fra fil</string>
<string name="export_repos_title">Eksportér Repositories</string>
<string name="export_repos_title">Eksportér repositories</string>
<string name="export_repos_DESC">Eksportér alle repositories til fil</string>
<string name="enable_repo">Aktivér repositoriet</string>
<string name="credits">Krediteringer</string>
<string name="update">Opdatering</string>
<string name="list_animation_description">Vis listeanimation på hovedsiden</string>
<string name="require_background_access_DESC">Baggrundsadgang er nødvendig for at køre baggrundssynkronisering korrekt</string>
<string name="require_background_access">Kræver Baggrundsadgang</string>
<string name="legacy_installer">Ældre Installatør</string>
<string name="session_installer">Session Installatør</string>
<string name="require_background_access">Kræver baggrundsadgang</string>
<string name="legacy_installer">Ældre installatør</string>
<string name="session_installer">Sessionsinstallatør</string>
<string name="special_credits">Særlige Krediteringer</string>
<string name="contains_nsfw">Indeholder potentielt stødende indhold</string>
<string name="ignore_signature_summary">*Advarsel* Ignorer signaturverifikation ved APK-installation; for LSPosed- eller avancerede brugere</string>
<string name="installation_failed">Installation Mislykkedes</string>
<string name="ignore_signature_summary">*Advarsel* Ignorer signaturverifikation ved APK-installation. For LSPosed- eller avancerede brugere</string>
<string name="installation_failed">Installation mislykkedes</string>
<string name="installation_failed_DESC">Kunne ikke installere %s</string>
<string name="uninstalled_application">Afinstalleret</string>
<string name="uninstalled_application_DESC">%s blev afinstalleret</string>
<string name="ignore_signature">Ignorer Signatur</string>
<string name="ignore_signature">Ignorer signatur</string>
<string name="insufficient_storage">Utilstrækkelig plads</string>
<string name="insufficient_storage_DESC">Enheden har ikke nok ledig plads til at installere denne applikation. Prøv at frigøre noget plads</string>
<string name="error_shizuku_not_running_DESC">Shizuku-tjenesten kører ikke. Tjek venligst i Shizuku-appen</string>
<string name="error_shizuku_not_granted">Shizuku-tilladelse mangler</string>
<string name="error_shizuku_not_granted_DESC">Shizuku-tilladelse er ikke givet. Tjek venligst i Shizuku-appen</string>
<string name="error_shizuku_not_installed">Shizuku ikke installeret</string>
<string name="error_shizuku_not_installed_DESC">Shizuku ser ikke ud til at være installeret</string>
<string name="open_shizuku">Åbn Shizuku</string>
<string name="label_unknown_sdk">Ukendt (%d)</string>
<string name="switch_to_default_installer">Skift til standard</string>
<string name="label_open_video">Video</string>
<string name="error_shizuku_service_unavailable">Shizuku kører ikke</string>
</resources>

View File

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="add_repository">Repository hinzufügen</string>
<string name="all_applications">Alle Anwendungen</string>
<string name="all_applications_up_to_date">All deine Anwendungen sind aktuell</string>
<string name="add_repository">Paketquelle hinzufügen</string>
<string name="all_applications">Alle Apps</string>
<string name="all_applications_up_to_date">Alle Apps sind aktuell</string>
<string name="already_exists">Bereits vorhanden</string>
<string name="always">Immer</string>
<string name="address">Adresse</string>
<string name="action_failed">Vorgang fehlgeschlagen</string>
<string name="amoled">Schwarz</string>
<string name="application">Anwendung</string>
<string name="application">App</string>
<string name="anti_features">Unerwünschte Merkmale</string>
<string name="available">Entdecken</string>
<string name="cancel">Abbrechen</string>
<string name="application_not_found">Diese Anwendung konnte nicht gefunden werden</string>
<string name="application_not_found">App konnte nicht gefunden werden</string>
<string name="bug_tracker">Fehlerverwaltung</string>
<string name="changelog">Änderungsprotokoll</string>
<string name="cant_edit_sync_DESC">Die Paketquelle kann nicht bearbeitet werden, da sie gerade synchronisiert wird.</string>
<string name="checking_repository">Paketquelle wird abgefragt </string>
<string name="checking_repository">Paketquelle wird abgefragt </string>
<string name="compiled_for_debugging">Kompiliert für die Fehlersuche</string>
<string name="connecting">Verbinde </string>
<string name="connecting">Wird verbunden </string>
<string name="confirmation">Bestätigung</string>
<string name="could_not_validate_FORMAT">Konnte %s nicht validieren</string>
<string name="could_not_validate_FORMAT">%s konnte nicht überprüft werden</string>
<string name="dark">Dunkel</string>
<string name="contains_non_free_media">Enthält nicht-freie Medien</string>
<string name="credits">Mitwirkende</string>
@ -37,146 +37,132 @@
<string name="incompatible_features_DESC">Fehlende Funktionen.</string>
<string name="incompatible_platforms_DESC_FORMAT">Deine %1$s-Plattform wird nicht unterstützt. Unterstützte Plattformen: %2$s.</string>
<string name="installed">Installiert</string>
<string name="incompatible_versions_summary">Mit dem Gerät inkompatible Anwendungsversionen anzeigen</string>
<string name="install_types">Installationstypen</string>
<string name="incompatible_versions_summary">Mit diesem Gerät inkompatible App-Versionen anzeigen</string>
<string name="install_types">Installationsarten</string>
<string name="install">Installieren</string>
<string name="integrity_check_error_DESC">Integrität konnte nicht überprüft werden.</string>
<string name="invalid_metadata_error_DESC">Ungültige Metadaten.</string>
<string name="invalid_signature_error_DESC">Ungültige Signatur.</string>
<string name="launch">Öffnen</string>
<string name="light">Hell</string>
<string name="list_animation_description">Listenanimation auf der Hauptseite anzeigen</string>
<string name="new_updates_available">Neue Anwendungsversionen verfügbar</string>
<string name="new_updates_available">Neue Versionen von Apps verfügbar</string>
<string name="never">Nie</string>
<string name="network_error_DESC">Netzwerkfehler</string>
<string name="notify_about_updates">Über neue Versionen benachrichtigen</string>
<string name="notify_about_updates">Update-Benachrichtigungen</string>
<string name="no_description_available_DESC">Keine Beschreibung vorhanden</string>
<string name="no_matching_applications_found">Keine derartigen Anwendungen konnten gefunden werden</string>
<string name="no_applications_installed">Keine installierten Anwendungen</string>
<string name="only_on_wifi">Nur bei Wi-Fi</string>
<string name="open_DESC_FORMAT">Öffne %s\?</string>
<string name="no_matching_applications_found">Keine derartigen Apps auffindbar</string>
<string name="no_applications_installed">Keine installierten Apps</string>
<string name="only_on_wifi">Nur mit WLAN</string>
<string name="open_DESC_FORMAT">%s öffnen?</string>
<string name="other">Andere</string>
<string name="number_of_applications">Anzahl der Anwendungen</string>
<string name="number_of_applications">Anzahl der Apps</string>
<string name="ok">OK</string>
<string name="permissions">Berechtigungen</string>
<string name="plus_more_FORMAT">+%d mehr</string>
<string name="settings">Einstellungen</string>
<string name="processing_FORMAT">Verarbeitung %1$s …</string>
<string name="promotes_non_free_software">Bewirbt unfreie Software</string>
<string name="processing_FORMAT">%1$s wird verarbeitet …</string>
<string name="promotes_non_free_software">Bewirbt nicht-freie Software</string>
<string name="proxy">Proxy</string>
<string name="repositories">Paketquellen</string>
<string name="requires_FORMAT">Benötigt %s</string>
<string name="root_permission">Stumme Installation</string>
<string name="save">Speichern</string>
<string name="saving_details">Details werden gespeichert </string>
<string name="screenshots">Bildschirmfotos</string>
<string name="skip">Überspringen</string>
<string name="suggested">Empfohlen</string>
<string name="syncing">Synchronisierung</string>
<string name="themes">Themen</string>
<string name="target">Ziel</string>
<string name="themes">Designs</string>
<string name="unknown">Unbekannt</string>
<string name="uninstall">Deinstallation</string>
<string name="unsigned">Unsigniert</string>
<string name="uninstall">Deinstallieren</string>
<string name="update">Aktualisierung</string>
<string name="version">Version</string>
<string name="versions">Versionen</string>
<string name="whats_new">Was gibt es Neues</string>
<string name="waiting_to_start_download">Warten auf den Downloadbeginn …</string>
<string name="validation_index_error_DESC">Der Index konnte nicht validiert werden.</string>
<string name="website">Webseite</string>
<string name="whats_new">Neu hinzugefügt</string>
<string name="waiting_to_start_download">Warten auf den Start des Downloads …</string>
<string name="validation_index_error_DESC">Der Index konnte nicht überprüft werden.</string>
<string name="website">Website</string>
<string name="changes">Änderungen</string>
<string name="author_email">Autor-E-Mail-Adresse</string>
<string name="could_not_download_FORMAT">Konnte %s nicht herunterladen</string>
<string name="author_website">Autor-Webseite</string>
<string name="author_email">E-Mail-Adresse</string>
<string name="could_not_download_FORMAT">%s konnte nicht heruntergeladen werden</string>
<string name="author_website">Website</string>
<string name="delete">Löschen</string>
<string name="recently_updated">Zuletzt aktualisiert</string>
<string name="recently_updated">Kürzlich aktualisiert</string>
<string name="license">Lizenz</string>
<string name="could_not_sync_FORMAT">Konnte %s nicht synchronisieren</string>
<string name="could_not_sync_FORMAT">%s konnte nicht synchronisiert werden</string>
<string name="only_compatible_with_FORMAT">Nur kompatibel mit %s</string>
<string name="license_FORMAT">%s-Lizenz</string>
<string name="link_copied_to_clipboard">Link kopiert</string>
<string name="project_website">Projekt-Website</string>
<string name="proxy_type">Proxy Typ</string>
<string name="project_website">Website des Projekts</string>
<string name="proxy_type">Proxy-Art</string>
<string name="repository">Paketquelle</string>
<string name="parsing_index_error_DESC">Die Indexdatei konnte nicht geparst werden.</string>
<string name="password">Passwort</string>
<string name="notify_about_updates_summary">Eine Benachrichtigung anzeigen, wenn neue Versionen verfügbar sind</string>
<string name="notify_about_updates_summary">Benachrichtigung anzeigen, wenn neue Versionen verfügbar sind</string>
<string name="provided_by_FORMAT">Bereitgestellt von %s</string>
<string name="source_code_no_longer_available">Quellcode nicht mehr verfügbar</string>
<string name="invalid_username_format">Ungültiges Benutzernamen-Format</string>
<string name="invalid_username_format">Ungültiges Format des Benutzernamens</string>
<string name="no_proxy">Kein Proxy</string>
<string name="password_missing">Passwort fehlt</string>
<string name="source_code">Quellcode</string>
<string name="sync_repositories">Paketquellen synchronisieren</string>
<string name="sync_repositories_automatically">Paketquellen automatisch synchronisieren</string>
<string name="repository_not_used_DESC">Diese Paketquelle wurde noch nicht verwendet. Aktivieren Sie es, um die darin enthaltenen Anwendungen anzuzeigen.</string>
<string name="signature_FORMAT">Signatur %s</string>
<string name="has_non_free_dependencies">Enthält nicht-freie Abhängigkeiten</string>
<string name="incompatible_version">Inkompatible Version</string>
<string name="system">System</string>
<string name="theme">Thema</string>
<string name="theme">Design</string>
<string name="ignore_all_updates">Alle neuen Versionen ignorieren</string>
<string name="ignore_this_update">Diese Version ignorieren</string>
<string name="size">Größe</string>
<string name="updates">Aktualisierungen</string>
<string name="updates">Updates</string>
<string name="username">Benutzername</string>
<string name="version_FORMAT">Version %s</string>
<string name="downloading">Herunterladen</string>
<string name="downloading">Wird heruntergeladen </string>
<string name="share">Teilen</string>
<string name="show_more">Zeige mehr</string>
<string name="show_older_versions">Ältere Versionen zeigen</string>
<string name="unverified">Ungeprüft</string>
<string name="show_more">Mehr anzeigen</string>
<string name="show_older_versions">Ältere Versionen anzeigen</string>
<string name="username_missing">Benutzername fehlt</string>
<string name="edit_repository">Paketquelle bearbeiten</string>
<string name="file_format_error_DESC">Ungültiges Dateiformat.</string>
<string name="downloading_FORMAT">%s wird heruntergeladen </string>
<string name="downloading_FORMAT">%s wird heruntergeladen </string>
<string name="incompatible_with_FORMAT">Inkompatibel mit %s</string>
<string name="incompatible_versions">Inkompatible Versionen</string>
<string name="invalid_address">Ungültige Adresse</string>
<string name="invalid_fingerprint_format">Ungültiges Fingerabdruckformat</string>
<string name="invalid_permissions_error_DESC">Ungültige Berechtigungen.</string>
<string name="promotes_non_free_network_services">Bewirbt unfreie Netzwerkdienste</string>
<string name="promotes_non_free_network_services">Bewirbt nicht-freie Netzwerkdienste</string>
<string name="unknown_FORMAT">Unbekannt: %s</string>
<string name="unknown_error_DESC">Unbekannter Fehler.</string>
<string name="syncing_FORMAT">Synchronisierung %s …</string>
<string name="incompatible_signature_DESC">Diese Version ist mit einem anderen Zertifikat signiert, als die auf Deinem Gerät installierte. Deinstalliere diese zuerst.</string>
<string name="delete_repository_DESC">Die Paketquelle löschen\?</string>
<string name="incompatible_older_DESC">Diese Version ist älter als die auf deinem Gerät installierte. Deinstalliere diese zuerst.</string>
<string name="syncing_FORMAT">%s wird synchronisiert …</string>
<string name="incompatible_signature_DESC">Diese Version ist mit einem anderen Zertifikat signiert als die auf deinem Gerät installierte Version. Deinstalliere diese zuerst.</string>
<string name="delete_repository_DESC">Paketquelle löschen?</string>
<string name="incompatible_older_DESC">Diese Version ist älter als diejenige, die auf deinem Gerät installiert ist. Deinstalliere diese zuerst.</string>
<string name="incompatible_api_DESC_FORMAT">Deine %1$s (API-Version %2$d) wird nicht unterstützt. %3$s</string>
<string name="incompatible_api_min_DESC_FORMAT">Die minimale API-Version ist %d.</string>
<string name="repository_unsigned_DESC">Nicht signiert. Die Anwendungsliste konnte nicht verifiziert werden. Sei vorsichtig beim Herunterladen von Anwendungen aus nicht signierten Paketquellen.</string>
<string name="repository_unsigned_DESC">Nicht signiert. Die App-Liste konnte nicht verifiziert werden. Sei beim Herunterladen von Apps aus nicht signierten Paketquellen vorsichtig.</string>
<string name="unstable_updates_summary">Installation von instabilen Versionen vorschlagen</string>
<string name="unstable_updates">Instabile Aktualisierungen</string>
<string name="root_permission_description">Root-Rechte für stille Installationen zulassen</string>
<string name="proxy_host">Proxy Host</string>
<string name="tap_to_install_DESC">Tippe um zu installieren.</string>
<string name="tracks_or_reports_your_activity">Verfolgt oder erfasst deine Aktivitäten</string>
<string name="proxy_port">Proxy Port</string>
<string name="search">Suche</string>
<string name="unstable_updates">Instabile Updates</string>
<string name="proxy_host">Proxy-Host</string>
<string name="tap_to_install_DESC">Zum Installieren tippen.</string>
<string name="tracks_or_reports_your_activity">Verfolgt oder versendet deine Aktivitäten</string>
<string name="proxy_port">Proxy-Port</string>
<string name="search">Suchen</string>
<string name="sorting_order">Sortierreihenfolge</string>
<string name="socks_proxy">SOCKS Proxy</string>
<string name="no_applications_available">Keine Anwendungen verfügbar</string>
<string name="socks_proxy">SOCKS-Proxy</string>
<string name="no_applications_available">Keine Apps verfügbar</string>
<plurals name="new_updates_DESC_FORMAT">
<item quantity="one">%d Anwendung hat eine neue Version.</item>
<item quantity="other">%d Anwendungen haben eine neue Version.</item>
<item quantity="one">%d App hat eine neue Version.</item>
<item quantity="other">%d Apps haben eine neue Version.</item>
</plurals>
<string name="signed_using_unsafe_algorithm">Mit einem unsicheren Algorithmus signiert</string>
<string name="select_mirror">Wähle einen Spiegel</string>
<string name="list_animation">Animationen anzeigen</string>
<string name="links">Links</string>
<string name="merging_FORMAT">Führe %s zusammen</string>
<string name="merging_FORMAT">%s wird zusammengeführt </string>
<string name="name">Name</string>
<string name="upstream_source_code_is_not_free">Der Upstream-Quellcode ist nicht frei</string>
<string name="prefs_language_title">Sprache</string>
<string name="prefs_personalization">Personalisierung</string>
<string name="prefs_personalization">Personalisieren</string>
<string name="show_less">Weniger anzeigen</string>
<string name="latest">Neueste</string>
<string name="explore">Entdecken</string>
<string name="update_all">Alle aktualisieren</string>
<string name="installed_applications">Installierte Anwendungen</string>
<string name="sort_filter">Sortieren und filtern</string>
<string name="new_applications">Neue Anwendungen</string>
<plurals name="days">
<item quantity="one">Tag</item>
<item quantity="other">Tage</item>
@ -185,61 +171,65 @@
<item quantity="one">Stunde</item>
<item quantity="other">Stunden</item>
</plurals>
<string name="only_on_wifi_with_charging">Nur während des Ladevorgangs und aktiviertem WLAN</string>
<string name="only_on_wifi_with_charging">Nur mit WLAN während des Aufladens</string>
<string name="installer">Installationsmethode</string>
<string name="cleanup_description">Zeitraum zum Prüfen und Entfernen heruntergeladener Dateien</string>
<string name="cleanup_title">APK-Bereinigungsintervall</string>
<string name="root_installer">Root-Installation</string>
<string name="legacy_installer">Alte Installationsmethode</string>
<string name="session_installer">Sitzungs-Installation</string>
<string name="session_installer">Sitzungsinstallation</string>
<string name="shizuku_installer">Shizuku-Installation</string>
<string name="io_error_DESC">Bestimmte Aktionen können nicht durchgeführt werden.</string>
<string name="no_internet">Sie haben keine Internetverbindung</string>
<string name="allow_collapsing_toolbar">Erweiterung der oberen Anwendungsleiste zulassen</string>
<string name="allow_collapsing_toolbar_DESC">Das Erweitern und Reduzieren der oberen Anwendungsleiste erlauben</string>
<string name="favourites">Favoriten</string>
<string name="material_you">Material You</string>
<string name="material_you_desc">Material You-Farbschema verwenden</string>
<string name="repository_unreachable">Repository unerreichbar</string>
<string name="force_clean_up">Aufräumen erzwingen</string>
<string name="enable_repo">Repository aktivieren</string>
<string name="repository_unreachable">Paketquelle unerreichbar</string>
<string name="force_clean_up">Bereinigung erzwingen</string>
<string name="enable_repo">Paketquelle aktivieren</string>
<string name="force_clean_up_DESC">Entfernt doppelte Dateien</string>
<string name="installing">Installation</string>
<string name="restart_app">Starten Sie Droid-ify neu, um die Änderungen zu sehen</string>
<string name="waiting_to_start_installation">Warten auf den Beginn der Installation </string>
<string name="installing">Wird installiert </string>
<string name="waiting_to_start_installation">Warten auf den Start der Installation </string>
<string name="auto_update">Apps automatisch aktualisieren</string>
<string name="auto_update_apps">Versuche, Updates automatisch zu installieren</string>
<string name="has_non_free_components">Hat nicht-freie Komponenten</string>
<string name="auto_update_apps">Updates möglichst automatisch installieren</string>
<string name="has_non_free_components">Enthält nicht-freie Komponenten</string>
<string name="socket_error_DESC">Server konnte kein neues Datenpaket liefern.</string>
<string name="shizuku_not_alive">Shizuku läuft nicht</string>
<string name="contains_nsfw">Enthält für den Arbeitsplatz unangemessene Inhalte</string>
<string name="connection_error_DESC">Verbindung zum Server nicht möglich</string>
<string name="shizuku_not_installed">Shizuku ist nicht installiert</string>
<string name="home_screen_swiping">Wischgesten</string>
<string name="home_screen_swiping_DESC">Dem Benutzer erlauben, auf dem Startbildschirm zwischen den Seiten zu wischen</string>
<string name="home_screen_swiping_DESC">Durch Wischen nach links/rechts zwischen Seiten navigieren</string>
<string name="special_credits">Besonderer Dank</string>
<string name="label_copy">Kopieren</string>
<string name="proxy_port_error_not_int">Proxy-Port muss eine natürliche Zahl sein</string>
<string name="repository_not_found">Folgende Repos konnten nicht gefunden werden</string>
<string name="repository_not_found">Folgende Paketquelle konnten nicht gefunden werden</string>
<string name="import_settings_title">Einstellungen importieren</string>
<string name="import_export">Importieren/Exportieren</string>
<string name="import_settings_DESC">Importiere Einstellung und Favoriten von Datei</string>
<string name="import_settings_DESC">Einstellungen und Favoriten aus Datei importieren</string>
<string name="export_settings_title">Einstellungen exportieren</string>
<string name="export_repos_DESC">Alle Repositories in eine Datei exportieren</string>
<string name="import_repos_title">Importiere eine Sammlung</string>
<string name="export_repos_DESC">Paketquellen in eine Datei exportieren</string>
<string name="import_repos_title">Paketquellen importieren</string>
<string name="export_settings_DESC">Einstellungen und Favoriten in eine Datei exportieren</string>
<string name="export_repos_title">Repositories exportieren</string>
<string name="import_repos_DESC">Alle Repositories aus einer Datei importieren</string>
<string name="export_repos_title">Paketquellen exportieren</string>
<string name="import_repos_DESC">Paketquellen aus einer Datei importieren</string>
<string name="cannot_open_link">Link kann nicht geöffnet werden</string>
<string name="has_tethered_network">An einen bestimmten Netzwerkdienst gebunden</string>
<string name="ignore_signature">Signatur ignorieren</string>
<string name="ignore_signature_summary">*Achtung* Signaturüberprüfung bei der Installation der APK ignorieren. Für LSPosed-Benutzer oder Experten.</string>
<string name="ignore_signature_summary">*Achtung* Signaturüberprüfung bei der Installation der APK ignorieren. Für LSPosed-Benutzer oder Experten</string>
<string name="installation_failed">Installation fehlgeschlagen</string>
<string name="uninstalled_application_DESC">%s wurde deinstalliert</string>
<string name="installation_failed_DESC">%s konnte nicht installiert werden</string>
<string name="uninstalled_application">Deinstalliert</string>
<string name="require_background_access">Hintergrundzugriff anfordern</string>
<string name="require_background_access_DESC">Hintergrundzugriff ist erforderlich, um die Hintergrundsynchronisation ordnungsgemäß durchzuführen</string>
<string name="insufficient_storage">Nicht genug Speicherplatz</string>
<string name="insufficient_storage_DESC">Es gibt nicht genug Speicherplatz, um diese Anwendung zu installieren. Versuche etwas Platz zu schafffen.</string>
<string name="insufficient_storage">Nicht genügend Speicherplatz</string>
<string name="insufficient_storage_DESC">Nicht genügend Speicherplatz, um diese App zu installieren. Versuche, etwas Platz zu schaffen</string>
<string name="error_shizuku_not_granted">Shizuku-Erlaubnis fehlt</string>
<string name="error_shizuku_not_granted_DESC">Erlaubnis für den Shizuku-Dienst ist nicht gewährt. Bitte in der Shizuku-App prüfen</string>
<string name="error_shizuku_not_installed">Shizuku nicht installiert</string>
<string name="error_shizuku_not_installed_DESC">Shizuku scheint nicht installiert zu sein</string>
<string name="switch_to_default_installer">Auf Standard wechseln</string>
<string name="open_shizuku">Shizuku öffnen</string>
<string name="error_shizuku_service_unavailable">Shizuku läuft nicht</string>
<string name="error_shizuku_not_running_DESC">Der Shizuku-Dienst läuft nicht. Bitte in der Shizuku-App prüfen</string>
<string name="label_open_video">Video</string>
<string name="label_unknown_sdk">Unbekannt (%d)</string>
</resources>

View File

@ -64,8 +64,6 @@
<string name="light">Φωτεινό</string>
<string name="link_copied_to_clipboard">Ο σύνδεσμος αντιγράφηκε</string>
<string name="links">Σύνδεσμοι</string>
<string name="list_animation">Κινήσεις Λίστας</string>
<string name="list_animation_description">Εμφάνιση κινήσεων λίστας στην αρχική σελίδα</string>
<string name="merging_FORMAT">Συγχώνευση %s</string>
<string name="name">Όνομα</string>
<string name="network_error_DESC">Σφάλμα δικτύου</string>
@ -107,10 +105,8 @@
<string name="repository">Αποθετήριο</string>
<string name="repository_unsigned_DESC">Μη υπογεγραμμένο. Αδυναμία επαλήθευσης της λίστας εφαρμογών. Προσέχετε όταν κατεβάζετε εφαρμογές από μη υπογεγραμμένα αποθετήρια.</string>
<string name="requires_FORMAT">Απαιτεί %s</string>
<string name="root_permission">Σιωπηλή Εγκατάσταση</string>
<string name="save">Αποθήκευση</string>
<string name="saving_details">Αποθήκευση λεπτομερειών…</string>
<string name="screenshots">Στιγμιότυπα οθόνης</string>
<string name="search">Αναζήτηση</string>
<string name="select_mirror">Επιλέξτε ένα mirror</string>
<string name="share">Κοινοποίηση</string>
@ -139,11 +135,8 @@
<string name="unstable_updates_summary">Πρόταση για εγκατάσταση ασταθών εκδόσεων</string>
<string name="update">Ενημέρωση</string>
<string name="updates">Ενημερώσεις</string>
<string name="unsigned">Μη υπογεγραμμένο</string>
<string name="unverified">Μη επιβεβαιωμένο</string>
<string name="delete">Διαγραφή</string>
<string name="recently_updated">Πρόσφατα ενημερωμένα</string>
<string name="target">Στόχος</string>
<string name="whats_new">Τι νέο υπάρχει</string>
<string name="upstream_source_code_is_not_free">Upstream source code is not free</string>
<string name="username">Όνομα χρήστη</string>
@ -161,28 +154,20 @@
<string name="incompatible_signature_DESC">Αυτή η έκδοση είναι υπογεγραμμένη με ένα διαφορετικό πιστοποιητικό από αυτή που είναι εγκατεστημένη στη συσκευή σας. Απεγκαταστήστε εκείνη πρώτα.</string>
<string name="integrity_check_error_DESC">Αδυναμία ελέγχου ακεραιότητας.</string>
<string name="invalid_signature_error_DESC">Μη έγκυρη υπογραφή.</string>
<string name="repository_not_used_DESC">Αυτό το αποθετήριο δεν έχει χρησιμοποιηθεί ακόμα. Χρειάζεται να το ενεργοποιήσετε για να δείτε τις εφαρμογές που παρέχει.</string>
<string name="signed_using_unsafe_algorithm">Έχει υπογραφεί χρησιμοποιώντας έναν μη ασφαλή αλγόριθμο</string>
<string name="socks_proxy">SOCKS διακομιστής μεσολάβησης</string>
<string name="tracks_or_reports_your_activity">Καταγράφει ή αναφέρει τη δραστηριότητά σας</string>
<string name="validation_index_error_DESC">Αδυναμία επαλήθευσης δείκτη.</string>
<string name="root_permission_description">Επιτρέψτε την άδεια root για σιωπηλή εγκατάσταση</string>
<string name="amoled">Amoled</string>
<string name="prefs_personalization">Εξατομίκευση</string>
<string name="prefs_language_title">Γλώσσα</string>
<string name="latest">Πρόσφατα</string>
<string name="sort_filter">Ταξινόμηση &amp; Φιλτράρισμα</string>
<string name="installed_applications">Εγκατεστημένες εφαρμογές</string>
<string name="installer">Πρόγραμμα Εγκατάστασης</string>
<string name="legacy_installer">Παλιό πρόγραμμα Εγκατάστασης</string>
<string name="session_installer">Πρόγραμμα Εκατάστασης Συνεδρίας</string>
<string name="root_installer">Πρόγραμμα Εγκατάστασης Root</string>
<string name="shizuku_installer">Πρόγραμμα Εγκατάστασης Shizuku</string>
<string name="show_less">Εμφάνιση Λιγότερων</string>
<string name="explore">Εξερεύνηση</string>
<string name="update_all">Ενημέρωση όλων</string>
<string name="new_applications">Νέες εφαρμογές</string>
<string name="cleanup_description">Period to check and remove downloaded files</string>
<string name="cleanup_title">APK cleanup interval</string>
<plurals name="days">
<item quantity="one">Ημέρα</item>
@ -194,9 +179,6 @@
</plurals>
<string name="only_on_wifi_with_charging">Μόνο σε Wi-Fi και Φόρτιση</string>
<string name="io_error_DESC">Δεν είναι δυνατή η εκτέλεση ορισμένων ενεργειών.</string>
<string name="no_internet">Δεν έχετε σύνδεση στο διαδίκτυο</string>
<string name="allow_collapsing_toolbar">Να επιτρέπεται η Επέκταση της Γραμμής Κορυφαίων Εφαρμογών</string>
<string name="allow_collapsing_toolbar_DESC">Να επιτρέπεται η επέκταση και σύμπτυξη της επάνω γραμμής εφαρμογών</string>
<string name="material_you_desc">Χρησιμοποιήστε material you με θέμα το χρώμα σας</string>
<string name="material_you">Material You</string>
<string name="favourites">Αγαπημένα</string>
@ -204,7 +186,6 @@
<string name="force_clean_up">Αναγκαστική εκκαθάριση</string>
<string name="force_clean_up_DESC">Καθαρίζει τα περιττά αρχεία</string>
<string name="enable_repo">Ενεργοποιήστε το αποθετήριο</string>
<string name="restart_app">Επανεκκινήστε το Droid-ify για να δείτε αλλαγές</string>
<string name="installing">Εγκατάσταση</string>
<string name="waiting_to_start_installation">Αναμονή για έναρξη εγκατάστασης…</string>
<string name="auto_update">Αυτόματη ενημέρωση εφαρμογών</string>
@ -218,7 +199,6 @@
<string name="shizuku_not_installed">Το Shizuku δεν είναι εγκατεστημένο</string>
<string name="home_screen_swiping">Σύρσιμο Αρχικής Οθόνης</string>
<string name="home_screen_swiping_DESC">Επιτρέψτε στον χρήστη να συρθεί μεταξύ σελίδων στην αρχική οθόνη</string>
<string name="label_copy">Αντιγραφή</string>
<string name="repository_not_found">Το παρακάτω αποθετήριο δεν βρέθηκε</string>
<string name="proxy_port_error_not_int">Η θύρα Proxy μπορεί να είναι μόνο Ακέραιος</string>
<string name="import_settings_title">Εισαγωγή Ρυθμίσεων</string>
@ -240,4 +220,16 @@
<string name="uninstalled_application_DESC">Το %s απεγκαταστάθηκε</string>
<string name="ignore_signature">Αγνόησή Υπογραφής</string>
<string name="ignore_signature_summary">Αγνοήστε την επαλήθευση υπογραφής κατά την εγκατάσταση apk, για χρήστες με LSP ή προχωρημένους χρήστες</string>
<string name="error_shizuku_not_granted_DESC">Η άδεια λειτουργίας της υπηρεσίας Shizuku δεν έχει παραχωρηθεί. Ελέγξτε την εφαρμογή Shizuku</string>
<string name="error_shizuku_not_installed">Το Shizuku δεν έχει εγκατασταθεί</string>
<string name="open_shizuku">Άνοιγμα Shizuku</string>
<string name="label_unknown_sdk">Άγνωστο (%d)</string>
<string name="label_open_video">Βίντεο</string>
<string name="switch_to_default_installer">Μετάβαση στην Προεπιλογή</string>
<string name="insufficient_storage">Ανεπαρκής Χώρος</string>
<string name="error_shizuku_not_installed_DESC">Το Shizuku δεν φαίνεται να είναι εγκατεστημένο</string>
<string name="error_shizuku_service_unavailable">Δεν είναι σε λειτουργία το Shizuku</string>
<string name="error_shizuku_not_running_DESC">Η υπηρεσία Shizuku δεν λειτουργεί. Ελέγξτε την εφαρμογή Shizuku</string>
<string name="error_shizuku_not_granted">Λείπει η άδεια Shizuku</string>
<string name="insufficient_storage_DESC">Δεν υπάρχει αρκετός ελεύθερος χώρος στη συσκευή για την εγκατάσταση αυτής της εφαρμογής. Προσπαθήστε να ελευθερώσετε και να καθαρίσετε λίγο χώρο</string>
</resources>

View File

@ -29,14 +29,12 @@
<string name="incompatible_api_DESC_FORMAT">Via %1$s (API-versio %2$d) ne estas subtenata. %3$s</string>
<string name="only_on_wifi">Nur sur Wi-Fi</string>
<string name="password">Pasvorto</string>
<string name="unsigned">Nesubskribita</string>
<string name="cleanup_title">Intervalo de purigado de APK</string>
<string name="repository_unsigned_DESC">Nesubskribita. Ne eblis kontroli la aplikliston. Atentu elŝutante aplikaĵojn el nesubskribitaj deponejoj.</string>
<string name="contains_nsfw">Enhavas enhavon ne taŭgan por laboro</string>
<string name="select_mirror">Elekti spegulon</string>
<string name="has_non_free_components">Havas neliberajn komponantojn</string>
<string name="promotes_non_free_network_services">Promocias ne-liberajn retservojn</string>
<string name="list_animation_description">Montri listanimacion sur la ĉefpaĝo</string>
<string name="syncing_FORMAT">Sinkroniganta %s…</string>
<string name="favourites">Favoratoj</string>
<string name="tracks_or_reports_your_activity">Spuras aŭ raportas vian agadon</string>
@ -49,11 +47,9 @@
<string name="waiting_to_start_download">Atendante komenci elŝuton…</string>
<string name="auto_update_apps">Provi instali ĝisdatigojn aŭtomate</string>
<string name="downloading">Elŝutanta</string>
<string name="explore">Esplori</string>
<string name="notify_about_updates_summary">Montri sciigon kiam novaj versioj estas disponebla</string>
<string name="suggested">Sugestita</string>
<string name="permissions">Permesoj</string>
<string name="sort_filter">Ordigi ⳤ Filtri</string>
<string name="open_DESC_FORMAT">Ĉu malfermi %s\?</string>
<string name="validation_index_error_DESC">Indekso ne povis esti validigita.</string>
<string name="new_updates_available">Novaj versioj de aplikaĵoj disponeblaj</string>
@ -70,7 +66,6 @@
<string name="installing">Instalante</string>
<string name="incompatible_platforms_DESC_FORMAT">Via %1$s platformo ne estas subtenata. Subtenataj platformoj: %2$s.</string>
<string name="repository_unreachable">Deponejo neatingebla</string>
<string name="restart_app">Rekomenci Droid-ify por vidi ŝanĝojn</string>
<string name="username_missing">Uzantnomo mankas</string>
<string name="connecting">Konektanta…</string>
<string name="update">Ĝisdatigi</string>
@ -83,7 +78,6 @@
<string name="password_missing">Pasvorto mankas</string>
<string name="description">Priskribo</string>
<string name="material_you">Material You</string>
<string name="target">Celo</string>
<string name="waiting_to_start_installation">Atendante komenci instaladon…</string>
<string name="installed">Instalita</string>
<string name="name">Nomo</string>
@ -94,7 +88,6 @@
<string name="version_FORMAT">Versio %s</string>
<string name="light">Hela</string>
<string name="all_applications">Ĉiuj aplikaĵoj</string>
<string name="installed_applications">Instalitaj aplikaĵoj</string>
<string name="proxy_port">Prokura haveno</string>
<string name="requires_FORMAT">Postulas %s</string>
<string name="delete">Forigi</string>
@ -112,10 +105,7 @@
<string name="link_copied_to_clipboard">Ligilo kopiita</string>
<string name="processing_FORMAT">Prilaboranta %1$s…</string>
<string name="shizuku_installer">Shizuku Instalilo</string>
<string name="new_applications">Novaj aplikaĵoj</string>
<string name="label_copy">Kopii</string>
<string name="uninstall">Malinstali</string>
<string name="no_internet">Vi ne havas interretan konekton</string>
<string name="skip">Preterpasi</string>
<string name="downloaded_FORMAT">Elŝutis %s</string>
<string name="cant_edit_sync_DESC">Ne povas redakti deponejon ĉar ĝi nun sinkronigas.</string>
@ -125,7 +115,6 @@
<string name="network_error_DESC">Reteraro</string>
<string name="license">Licenco</string>
<string name="auto_update">Aŭtomate ĝisdatigi aplikaĵoj</string>
<string name="screenshots">Ekrankopioj</string>
<string name="recently_updated">Lastatempe ĝisdatigita</string>
<string name="already_exists">Jam ekzistas</string>
<string name="author_website">Aŭtora retejo</string>
@ -133,12 +122,10 @@
<string name="edit_repository">Redakti deponejon</string>
<string name="sorting_order">Ordo de ordigo</string>
<string name="ignore_this_update">Ignori ĉi tiun version</string>
<string name="unverified">Nekontrolita</string>
<string name="http_proxy">HTTP prokurilo</string>
<string name="application">Aplikaĵo</string>
<string name="always">Ĉiam</string>
<string name="address">Adreso</string>
<string name="root_permission">Silenta Instalo</string>
<string name="no_description_available_DESC">Neniu priskribo disponebla</string>
<string name="invalid_fingerprint_format">Nevalida fingrospura formato</string>
<string name="saving_details">Konservanta detalojn…</string>
@ -163,7 +150,6 @@
<string name="search">Serĉi</string>
<string name="unknown_error_DESC">Nekonata eraro.</string>
<string name="themes">Haŭtoj</string>
<string name="list_animation">Enlistigi Animacioj</string>
<string name="file_format_error_DESC">Nevalida dosierformato.</string>
<plurals name="new_updates_DESC_FORMAT">
<item quantity="one">%d aplikaĵo havas novan version.</item>
@ -176,15 +162,12 @@
<string name="project_website">Projekto retejo</string>
<string name="donate">Donaci</string>
<string name="add_repository">Aldoni deponejo</string>
<string name="allow_collapsing_toolbar">Permesi al Supra Aplika Breto Etendi</string>
<string name="could_not_validate_FORMAT">Ne eblis validigi %s</string>
<string name="available">Esplori</string>
<string name="show_less">Montri Malpli</string>
<string name="never">Neniam</string>
<string name="no_matching_applications_found">Ne povis trovi tiajn aplikaĵojn</string>
<string name="incompatible_api_min_DESC_FORMAT">La minimuma API-versio estas %d.</string>
<string name="allow_collapsing_toolbar_DESC">Permesi al supra aplika breto etendi kaj maletendi</string>
<string name="cleanup_description">Periodo por kontroli kaj forigi elŝutitajn dosierojn</string>
<string name="settings">Agordoj</string>
<string name="invalid_address">Nevalida adreso</string>
<string name="upstream_source_code_is_not_free">La kontraŭflua fontkodo ne estas libera</string>
@ -193,7 +176,6 @@
<string name="checking_repository">Kontrolanta deponejon…</string>
<string name="versions">Versioj</string>
<string name="incompatible_api_max_DESC_FORMAT">La maksimuma API-versio estas %d.</string>
<string name="repository_not_used_DESC">Ĉi tiu deponejo ankoraŭ ne estis uzata. Ŝaltu ĝin por vidi la aplikaĵojn en ĝi.</string>
<string name="parsing_index_error_DESC">Ne eblis analizi la indeksan dosieron.</string>
<string name="install">Instali</string>
<string name="system">Sistemo</string>
@ -215,10 +197,8 @@
<string name="integrity_check_error_DESC">Ne eblis kontroli integrecon.</string>
<string name="application_not_found">Ne eblis trovi tiun aplikaĵon</string>
<string name="proxy">Prokurilo</string>
<string name="latest">Plej lasta</string>
<string name="credits">Kreditoj</string>
<string name="repositories">Deponejoj</string>
<string name="root_permission_description">Permesi radikan permeson por silenta instalo</string>
<string name="compiled_for_debugging">Kompilita por sencimigado</string>
<string name="unknown_FORMAT">Nekonata: %s</string>
<string name="import_settings_title">Importi Agordojn</string>

View File

@ -74,8 +74,6 @@
<string name="light">Claro</string>
<string name="link_copied_to_clipboard">Enlace copiado</string>
<string name="links">Enlaces</string>
<string name="list_animation">Animaciones de la lista</string>
<string name="list_animation_description">Mostrar la animación de la lista en la página principal</string>
<string name="merging_FORMAT">Uniendo %s</string>
<string name="name">Nombre</string>
<string name="network_error_DESC">Error de red</string>
@ -112,14 +110,10 @@
<string name="recently_updated">Actualizado recientemente</string>
<string name="repositories">Repositorios</string>
<string name="repository">Repositorio</string>
<string name="repository_not_used_DESC">Este repositorio no se ha utilizado todavía. Actívalo para ver las aplicaciones que contiene.</string>
<string name="repository_unsigned_DESC">Sin firmar. No se ha podido verificar la lista de aplicaciones. Ten cuidado al descargar aplicaciones de repositorios no firmados.</string>
<string name="requires_FORMAT">Requiere %s</string>
<string name="root_permission">Instalación silenciosa</string>
<string name="root_permission_description">Conceder permiso root para las instalaciones silenciosas</string>
<string name="save">Guardar</string>
<string name="saving_details">Guardando detalles…</string>
<string name="screenshots">Capturas de pantalla</string>
<string name="search">Buscar</string>
<string name="select_mirror">Selecciona un espejo</string>
<string name="share">Compartir</string>
@ -140,7 +134,6 @@
<string name="syncing_FORMAT">Sincronizando %s…</string>
<string name="system">Sistema</string>
<string name="tap_to_install_DESC">Pulse para instalar.</string>
<string name="target">Objetivo</string>
<string name="theme">Tema</string>
<string name="themes">Temas</string>
<string name="tracks_or_reports_your_activity">Rastrea o informa de tu actividad</string>
@ -148,10 +141,8 @@
<string name="unknown">Desconocido</string>
<string name="unknown_error_DESC">Error desconocido.</string>
<string name="unknown_FORMAT">Desconocido: %s</string>
<string name="unsigned">No firmado</string>
<string name="unstable_updates">Actualizaciones inestables</string>
<string name="unstable_updates_summary">Sugerir la instalación de versiones inestables</string>
<string name="unverified">No verificado</string>
<string name="update">Actualizar</string>
<string name="updates">Actualizaciones</string>
<string name="upstream_source_code_is_not_free">El código fuente no es libre</string>
@ -172,19 +163,13 @@
<string name="prefs_language_title">Idioma</string>
<string name="prefs_personalization">Personalización</string>
<string name="show_less">Mostrar menos</string>
<string name="latest">Lo más reciente</string>
<string name="explore">Explorar</string>
<string name="installed_applications">Aplicaciones instaladas</string>
<string name="sort_filter">Ordenar y filtrar</string>
<string name="update_all">Actualizar todo</string>
<string name="new_applications">Nuevas aplicaciones</string>
<string name="root_installer">Instalador root</string>
<string name="installer">Instalador</string>
<string name="session_installer">Instalador de sesión</string>
<string name="legacy_installer">Instalador heredado</string>
<string name="shizuku_installer">Instalador de Shizuku</string>
<string name="cleanup_title">Intervalo de limpieza de APKs</string>
<string name="cleanup_description">Periodo para comprobar y eliminar los archivos descargados</string>
<plurals name="days">
<item quantity="one">Día</item>
<item quantity="many">Días</item>
@ -197,9 +182,6 @@
</plurals>
<string name="only_on_wifi_with_charging">Solo en Wi-Fi y cargando</string>
<string name="io_error_DESC">No se pueden realizar ciertas acciones.</string>
<string name="no_internet">No hay conexión a internet</string>
<string name="allow_collapsing_toolbar">Permitir que la barra superior de la aplicación se expanda</string>
<string name="allow_collapsing_toolbar_DESC">Permitir que la barra superior de la aplicación se expanda y se contraiga</string>
<string name="material_you_desc">Utiliza el tema de color Material You</string>
<string name="material_you">Material You</string>
<string name="favourites">Favoritas</string>
@ -208,7 +190,6 @@
<string name="force_clean_up_DESC">Limpiar archivos redundantes</string>
<string name="enable_repo">Habilitar el repositorio</string>
<string name="installing">Instalando</string>
<string name="restart_app">Reinicia Droid-ify para ver los cambios</string>
<string name="waiting_to_start_installation">Esperando para iniciar la instalación…</string>
<string name="auto_update">Actualizar automáticamente las aplicaciones</string>
<string name="auto_update_apps">Intentar instalar actualizaciones automáticamente</string>
@ -221,7 +202,6 @@
<string name="special_credits">Gracias a</string>
<string name="home_screen_swiping">Deslizamiento por la pantalla de inicio</string>
<string name="home_screen_swiping_DESC">Permitir al usuario pasar de una página a otra en la pantalla de inicio</string>
<string name="label_copy">Copiar</string>
<string name="repository_not_found">No pudimos encontrar el siguiente repositorio</string>
<string name="proxy_port_error_not_int">El puerto del proxy sólo puede ser un número entero</string>
<string name="import_settings_title">Importar configuración</string>
@ -245,4 +225,14 @@
<string name="uninstalled_application">Desinstalada</string>
<string name="insufficient_storage">Espacio insuficiente</string>
<string name="insufficient_storage_DESC">No hay suficiente espacio libre en el dispositivo para instalar esta aplicación. Intente liberar algo de espacio</string>
<string name="error_shizuku_not_granted_DESC">No se permitió el permiso del servicio de Shizuku. Por favor revisa la app de Shizuku</string>
<string name="error_shizuku_service_unavailable">Shizuku no está ejecutando</string>
<string name="error_shizuku_not_running_DESC">El servicio de Shizuku no está corriendo. Por favor revisa la app de Shizuku</string>
<string name="error_shizuku_not_granted">Falta permiso de Shizuku</string>
<string name="error_shizuku_not_installed">Shizuku no está instalado</string>
<string name="error_shizuku_not_installed_DESC">Al parecer Shizuku no está instalado</string>
<string name="open_shizuku">Abrir Shizuku</string>
<string name="switch_to_default_installer">Cambiar a Predeterminado</string>
<string name="label_open_video">Video</string>
<string name="label_unknown_sdk">Desconocido (%d)</string>
</resources>

View File

@ -0,0 +1,235 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="cannot_open_link">Linki ei saa avada</string>
<string name="auto_update_apps">Ürita värskendusi automaatselt paigaldada</string>
<string name="add_repository">Lisa hoidla</string>
<string name="address">Aadress</string>
<string name="all_applications">Kõik rakendused</string>
<string name="already_exists">Juba olemas</string>
<string name="always">Alati</string>
<string name="amoled">Must</string>
<string name="anti_features">Anti-omadused</string>
<string name="application">Rakendus</string>
<string name="author_email">Autori e-post</string>
<string name="author_website">Autori veebileht</string>
<string name="auto_update">Uuenda rakendusi automaatselt</string>
<string name="available">Avasta</string>
<string name="bug_tracker">Veajälgija</string>
<string name="cancel">Tühista</string>
<string name="cant_edit_sync_DESC">Hoidlat ei saa redigeerida, kuna seda sünkroonitakse praegu.</string>
<string name="changelog">Muudatuste logi</string>
<string name="changes">Muudatused</string>
<string name="checking_repository">Hoidla kontrollimine…</string>
<string name="cleanup_title">APK puhastamise intervall</string>
<string name="confirmation">Kinnitus</string>
<string name="contains_non_free_media">Sisaldab mittevaba meediat</string>
<string name="could_not_download_FORMAT">%s allalaadimine nurjus</string>
<string name="could_not_sync_FORMAT">%s sünkroonimine nurjus</string>
<string name="credits">Tänud</string>
<string name="dark">Tume</string>
<string name="delete">Kustuta</string>
<string name="description">Kirjeldus</string>
<string name="details">Üksikasjad</string>
<string name="donate">Anneta</string>
<string name="downloading">Allalaadimine</string>
<string name="downloaded_FORMAT">%s laeti alla</string>
<string name="edit_repository">Redigeeri hoidlat</string>
<string name="error_shizuku_service_unavailable">Shizuku ei tööta</string>
<string name="error_shizuku_not_running_DESC">Shizuku teenus ei tööta. Palun kontrolli Shizuku rakendust</string>
<string name="error_shizuku_not_granted">Shizuku luba puudub</string>
<string name="error_shizuku_not_installed">Shizuku pole paigaldatud</string>
<string name="import_export">Impordi/ekspordi</string>
<string name="import_settings_title">Impordi seaded</string>
<string name="import_settings_DESC">Impordi seaded ja lemmikud failist</string>
<string name="export_settings_title">Ekspordi seaded</string>
<string name="import_repos_DESC">Impordi kõik hoidlad failist</string>
<string name="export_repos_title">Ekspordi hoidlad</string>
<string name="export_repos_DESC">Ekspordi kõik hoidlad faili</string>
<string name="enable_repo">Luba hoidla</string>
<string name="favourites">Lemmikud</string>
<string name="fingerprint">Sõrmejälg</string>
<string name="force_clean_up">Sundpuhastus</string>
<string name="force_clean_up_DESC">Puhastab üleliigsed failid</string>
<string name="has_advertising">Sisaldab reklaami</string>
<string name="has_non_free_dependencies">Omab mittevabu sõltuvusi</string>
<string name="has_non_free_components">Omab mittevabu komponente</string>
<string name="has_security_vulnerabilities">Omab turvaauke</string>
<string name="has_tethered_network">Seotud teatud võrguteenusega</string>
<string name="home_screen_swiping">Avakuval nipsamine</string>
<string name="home_screen_swiping_DESC">Luba kasutajal avakuval nipsates lehti vahetada</string>
<plurals name="hours">
<item quantity="one">tund</item>
<item quantity="other">tundi</item>
</plurals>
<string name="connection_error_DESC">Serveriga ei õnnestunud ühendust luua</string>
<string name="http_proxy">HTTP puhverserver</string>
<string name="incompatible_api_max_DESC_FORMAT">Suurim API versioon on %d.</string>
<string name="incompatible_features_DESC">Puuduvad funktsioonid.</string>
<string name="incompatible_older_DESC">See versioon on vanem kui seadmesse paigaldatud versioon. Esmalt eemalda see.</string>
<string name="incompatible_version">Ühildumatu versioon</string>
<string name="action_failed">Toiming nurjus</string>
<string name="all_applications_up_to_date">Kõik rakendused on ajakohased</string>
<plurals name="days">
<item quantity="one">päev</item>
<item quantity="other">päeva</item>
</plurals>
<string name="application_not_found">Seda rakendust ei leitud</string>
<string name="error_shizuku_not_granted_DESC">Shizuku teenusele pole luba antud. Palun kontrolli Shizuku rakendust</string>
<string name="compiled_for_debugging">Kompileeritud silumiseks</string>
<string name="could_not_validate_FORMAT">%s valideerimine nurjus</string>
<string name="connecting">Ühendamine…</string>
<string name="socket_error_DESC">Serveril ei õnnestunud uut paketti pakkuda.</string>
<string name="incompatible_versions">Ühildumatud versioonid</string>
<string name="delete_repository_DESC">Kas kustutada hoidla?</string>
<string name="import_repos_title">Impordi hoidlad</string>
<string name="downloading_FORMAT">%s allalaadimine…</string>
<string name="incompatible_api_DESC_FORMAT">Sinu %1$s (API versioon %2$d) ei ole toetatud. %3$s</string>
<string name="error_shizuku_not_installed_DESC">Paistab, et Shizuku pole paigaldatud</string>
<string name="export_settings_DESC">Ekspordi seaded ja lemmikud faili</string>
<string name="file_format_error_DESC">Kehtetu failivorming.</string>
<string name="incompatible_api_min_DESC_FORMAT">Väikseim API versioon on %d.</string>
<string name="incompatible_platforms_DESC_FORMAT">Sinu %1$s platvormi ei toetata. Toetatud platvormid: %2$s.</string>
<string name="ignore_all_updates">Eira kõiki uusi versioone</string>
<string name="ignore_this_update">Eira seda versiooni</string>
<string name="http_error_DESC">Vigane serveri vastus.</string>
<string name="incompatible_signature_DESC">See versioon on allkirjastatud erineva sertifikaadiga kui seadmesse paigaldatu. Esmalt eemalda see.</string>
<string name="uninstalled_application">Eemaldatud</string>
<string name="integrity_check_error_DESC">Terviklikkust ei saanud kontrollida.</string>
<string name="invalid_address">Vigane aadress</string>
<string name="invalid_metadata_error_DESC">Vigased metaandmed.</string>
<string name="invalid_permissions_error_DESC">Vigased load.</string>
<string name="invalid_signature_error_DESC">Vigane allkiri.</string>
<string name="io_error_DESC">Teatud toiminguid ei saa teha.</string>
<string name="launch">Käivita</string>
<string name="license">Litsents</string>
<string name="license_FORMAT">%s litsents</string>
<string name="light">Hele</string>
<string name="material_you_desc">Kasuta Material You värviskeemi</string>
<string name="merging_FORMAT">Ühendatakse %s</string>
<string name="name">Nimi</string>
<string name="new_updates_available">Saadaval on rakenduste uued versioonid</string>
<plurals name="new_updates_DESC_FORMAT">
<item quantity="one">%d uuendus saadaval.</item>
<item quantity="other">%d uuendust saadaval.</item>
</plurals>
<string name="no_applications_available">Saadaolevaid rakendusi pole</string>
<string name="no_proxy">Puhverserverita</string>
<string name="notify_about_updates">Teavita uuendustest</string>
<string name="notify_about_updates_summary">Kuva märguanne, kui uued versioonid on saadaval</string>
<string name="number_of_applications">Rakenduste arv</string>
<string name="ok">OK</string>
<string name="only_compatible_with_FORMAT">Ühildub ainult %s</string>
<string name="only_on_wifi">Ainult WiFi võrgus</string>
<string name="open_DESC_FORMAT">Kas avada %s?</string>
<string name="other">Muud</string>
<string name="password">Parool</string>
<string name="permissions">Load</string>
<string name="project_website">Projekti veebileht</string>
<string name="promotes_non_free_network_services">Reklaamib mittevabu võrguteenuseid</string>
<string name="provided_by_FORMAT">Pakub %s</string>
<string name="proxy_host">Puhverserveri masin</string>
<string name="proxy_type">Puhverserveri tüüp</string>
<string name="repository">Hoidla</string>
<string name="repository_not_found">Järgnevat hoidlat ei leitud</string>
<string name="repository_unreachable">Hoidla on kättesaamatu</string>
<string name="requires_FORMAT">Nõuab %s</string>
<string name="require_background_access">Küsi taustal töötamise luba</string>
<string name="require_background_access_DESC">Taustal sünkroonimise õnnestumiseks tuleb lubada taustal töötamine</string>
<string name="save">Salvesta</string>
<string name="saving_details">Üksikasjade salvestamine…</string>
<string name="settings">Seaded</string>
<string name="share">Jaga</string>
<string name="show_more">Näita rohkem</string>
<string name="signature_FORMAT">Allkiri %s</string>
<string name="signed_using_unsafe_algorithm">Allkirjastatud kasutades ebaturvalist algoritmi</string>
<string name="skip">Jäta vahele</string>
<string name="special_credits">Eriline tänu</string>
<string name="suggested">Soovitatud</string>
<string name="sync_repositories">Sünkrooni hoidlad</string>
<string name="syncing_FORMAT">%s sünkroonimine…</string>
<string name="system">Süsteem</string>
<string name="tap_to_install_DESC">Puuduta paigaldamiseks.</string>
<string name="tracks_or_reports_your_activity">Jälgib või teatab sinu tegevusest</string>
<string name="ignore_signature">Eira allkirja</string>
<string name="no_applications_installed">Paigaldatud rakendusi pole</string>
<string name="network_error_DESC">Võrgu viga</string>
<string name="links">Lingid</string>
<string name="never">Mitte kunagi</string>
<string name="installation_failed_DESC">%s paigaldamine nurjus</string>
<string name="uninstalled_application_DESC">%s eemaldati</string>
<string name="invalid_fingerprint_format">Vigane sõrmejäljevorming</string>
<string name="invalid_username_format">Vigane kasutajanime vorming</string>
<string name="link_copied_to_clipboard">Link kopeeriti</string>
<string name="material_you">Material You</string>
<string name="open_shizuku">Ava Shizuku</string>
<string name="no_matching_applications_found">Selliseid rakendusi ei leitud</string>
<string name="recently_updated">Hiljuti värskendatud</string>
<string name="no_description_available_DESC">Kirjeldus puudub</string>
<string name="only_on_wifi_with_charging">Ainult WiFi võrgus laadimise ajal</string>
<string name="plus_more_FORMAT">+%d veel</string>
<string name="promotes_non_free_software">Reklaamib mittevaba tarkvara</string>
<string name="proxy_port_error_not_int">Puhverserveri port saab olla ainult täisarv</string>
<string name="show_older_versions">Kuva vanemad versioonid</string>
<string name="themes">Teemad</string>
<string name="parsing_index_error_DESC">Indeksfaili ei saanud sõeluda.</string>
<string name="proxy_port">Puhverserveri port</string>
<string name="repositories">Hoidlad</string>
<string name="password_missing">Parool puudub</string>
<string name="proxy">Puhverserver</string>
<string name="sorting_order">Sorteerimisjärjekord</string>
<string name="repository_unsigned_DESC">Allkirjata. Rakenduste loendit ei saanud kinnitada. Ole ettevaatlik rakenduste allalaadimisel allkirjastamata hoidlatest.</string>
<string name="size">Suurus</string>
<string name="source_code_no_longer_available">Lähtekood pole enam saadaval</string>
<string name="switch_to_default_installer">Lülitu vaikeseadele</string>
<string name="syncing">Sünkroonimine</string>
<string name="uninstall">Eemalda</string>
<string name="unknown_error_DESC">Tundmatu viga.</string>
<string name="unstable_updates_summary">Paku ebastabiilsete versioonide paigaldamist</string>
<string name="unknown">Tundmatu</string>
<string name="unknown_FORMAT">Tundmatu: %s</string>
<string name="unstable_updates">Ebastabiilsed uuendused</string>
<string name="processing_FORMAT">%1$s töötlemine…</string>
<string name="search">Otsi</string>
<string name="select_mirror">Vali peegel</string>
<string name="socks_proxy">SOCKS puhverserver</string>
<string name="source_code">Lähtekood</string>
<string name="sync_repositories_automatically">Sünkrooni hoidlad automaatselt</string>
<string name="theme">Teema</string>
<string name="incompatible_versions_summary">Kuva seadmega mitteühilduvaid rakenduste versioone</string>
<string name="incompatible_with_FORMAT">Ei ühildu rakendusega %s</string>
<string name="install">Paigalda</string>
<string name="install_types">Paigalduse tüübid</string>
<string name="installer">Paigaldaja</string>
<string name="insufficient_storage">Ruumi napib</string>
<string name="legacy_installer">Vana paigaldaja</string>
<string name="session_installer">Seansi paigaldaja</string>
<string name="shizuku_installer">Shizuku paigaldaja</string>
<string name="root_installer">Root-paigaldaja</string>
<string name="shizuku_not_alive">Shizuku ei tööta</string>
<string name="shizuku_not_installed">Shizuku pole paigaldatud</string>
<string name="installed">Paigaldatud</string>
<string name="installing">Paigaldamine</string>
<string name="installation_failed">Paigaldamine nurjus</string>
<string name="ignore_signature_summary">*Hoiatus* LSPosed-kasutajate või edasijõudnud kasutajate puhul eira APK paigaldamisel allkirja kontrollimist</string>
<string name="update">Uuendus</string>
<string name="upstream_source_code_is_not_free">Algne lähtekood ei ole vaba</string>
<string name="username">Kasutajanimi</string>
<string name="username_missing">Kasutajanimi puudub</string>
<string name="validation_index_error_DESC">Indeksit ei saadud kinnitada.</string>
<string name="version">Versioon</string>
<string name="version_FORMAT">Versioon %s</string>
<string name="versions">Versioonid</string>
<string name="waiting_to_start_installation">Paigaldamise alustamise ootel…</string>
<string name="whats_new">Mis on uut</string>
<string name="website">Veebileht</string>
<string name="update_all">Uuenda kõik</string>
<string name="label_open_video">Video</string>
<string name="label_unknown_sdk">Tundmatu (%d)</string>
<string name="show_less">Näita vähem</string>
<string name="prefs_personalization">Kohandamine</string>
<string name="contains_nsfw">Sisaldab NSFW sisu</string>
<string name="insufficient_storage_DESC">Seadmes pole selle rakenduse paigaldamiseks piisavalt vaba ruumi. Proovi ruumi vabastada</string>
<string name="waiting_to_start_download">Allalaadimise alustamise ootel…</string>
<string name="prefs_language_title">Keel</string>
<string name="updates">Uuendused</string>
</resources>

View File

@ -50,7 +50,6 @@
<string name="invalid_metadata_error_DESC">فراداده نادرست.</string>
<string name="invalid_permissions_error_DESC">دسترسی‌های نادرست.</string>
<string name="links">پیوندها</string>
<string name="list_animation_description">نمایش لیست انیمیشن در صفحه‌اصلی</string>
<string name="merging_FORMAT">ادغام %s</string>
<string name="name">نام</string>
<string name="network_error_DESC">خطای شبکه</string>
@ -63,19 +62,16 @@
<string name="other">دیگر</string>
<string name="parsing_index_error_DESC">فایل ایندکس قابل تحلیل نیست.</string>
<string name="open_DESC_FORMAT">باز کردن %s ؟</string>
<string name="repository_not_used_DESC">این مخزن هنوز استفاده نشده. فعالش کنید تا کاره‌های داخلش را ببینید.</string>
<string name="unknown">ناشناس</string>
<string name="uninstall">پاک کردن</string>
<string name="waiting_to_start_download">منتظر شروع بارگیری…</string>
<string name="versions">نسخه‌ها</string>
<string name="cleanup_title">وقفه پاکسازی APK</string>
<string name="cleanup_description">دوره بررسی و پاک سازی داده های دریافت شده</string>
<string name="has_security_vulnerabilities">دارای حفره‌های امنیتی</string>
<string name="http_error_DESC">پاسخ نادرست از مرکز.</string>
<string name="incompatible_versions">نسخه‌های ناسازگار</string>
<string name="incompatible_with_FORMAT">ناسازگاری با %s</string>
<string name="invalid_signature_error_DESC">امضای نادرست.</string>
<string name="list_animation">لیست انیمیشن‌ها</string>
<string name="no_applications_available">هیچ کاره‌ای فراهم نیست</string>
<string name="no_applications_installed">هیچ کاره‌ای نصب نیست</string>
<string name="number_of_applications">تعداد کاره‌ها</string>
@ -85,7 +81,6 @@
<string name="plus_more_FORMAT">%d+ بیشتر</string>
<string name="settings">تنظیمات</string>
<string name="processing_FORMAT">درحال‌پردازش %1$s …</string>
<string name="root_permission_description">اجازه دسترسی روت برای نصب بدون‌پرسش</string>
<string name="signature_FORMAT">امضاء %s</string>
<string name="signed_using_unsafe_algorithm">با الگوریتم ناامن امضاء شده</string>
<string name="size">حجم</string>
@ -103,10 +98,7 @@
<string name="prefs_language_title">زبان</string>
<string name="prefs_personalization">شخصی‌سازی</string>
<string name="show_less">نمایش کمتر</string>
<string name="latest">آخرین</string>
<string name="explore">اکتشاف</string>
<string name="update_all">به‌روزرسانی همه</string>
<string name="sort_filter">ترتیب و فیلتر</string>
<string name="root_installer">نصاب روت</string>
<string name="installer">نصاب</string>
<string name="legacy_installer">نصاب قدیمی</string>
@ -126,8 +118,6 @@
<string name="repositories">مخازن</string>
<string name="repository">مخزن</string>
<string name="requires_FORMAT">نیازمند %s</string>
<string name="root_permission">نصب بدون‌پرسش</string>
<string name="screenshots">اسکرین‌شات‌ها</string>
<string name="search">جستجو</string>
<string name="select_mirror">از کجا دریافت کنم</string>
<string name="share">هم‌رسانی</string>
@ -135,15 +125,12 @@
<string name="saving_details">درحال‌ذخیره جزییات…</string>
<string name="show_more">نمایش بیشتر</string>
<string name="show_older_versions">نمایش نسخه‌های قدیمی</string>
<string name="target">هدف</string>
<string name="theme">پوسته</string>
<string name="themes">پوسته‌ها</string>
<string name="tracks_or_reports_your_activity">رهگیری یا گزارش فعالیت شما</string>
<string name="unknown_error_DESC">خطای ناشناخته.</string>
<string name="unknown_FORMAT">ناشناخته: %s</string>
<string name="unsigned">امضاءنشده</string>
<string name="unstable_updates">به‌روزرسانی های ناپایدار</string>
<string name="unverified">تایید نشده</string>
<string name="username">نام‌کاربری</string>
<string name="validation_index_error_DESC">ایندکس قابل تایید شدن نیست.</string>
<string name="whats_new">چه خبر</string>
@ -188,20 +175,14 @@
<string name="unstable_updates_summary">پیشنهاد نصب نسخه‌های آزمایشی</string>
<string name="source_code_no_longer_available">کد منبع دیگر موجود نیست</string>
<string name="syncing_FORMAT">در حال به‌روزرسانی %s …</string>
<string name="installed_applications">کاره‌های نصب شده</string>
<string name="updates">به‌روزرسانی ها</string>
<string name="new_applications">کاره‌های جدید</string>
<string name="version">نسخه</string>
<string name="version_FORMAT">نسخه %s</string>
<string name="no_internet">شما هیچ اتصال اینترنتی ندارید</string>
<string name="io_error_DESC">قادر به انجام برخی اقدامات خاص نیست.</string>
<string name="allow_collapsing_toolbar">به نوار بالای کاره اجازه دهید تا گسترش یابد</string>
<string name="allow_collapsing_toolbar_DESC">به نوار بالای کاره اجازه دهید تا گسترده و فشرده شود</string>
<string name="material_you">Material You</string>
<string name="material_you_desc">از پوسته رنگی material you استفاده کنید</string>
<string name="enable_repo">مخزن را فعال کنید</string>
<string name="force_clean_up">پاکسازی اجباری</string>
<string name="restart_app">برای مشاهده تغییرات، Droid-ify را مجددا راه اندازی کنید</string>
<string name="favourites">موارد دلخواه</string>
<string name="repository_unreachable">مخزن قابل دسترسی نیست</string>
<string name="auto_update">به‌روز رسانی خودکار کاره‌ها</string>
@ -230,7 +211,6 @@
<string name="socket_error_DESC">سرور در فراهم کردن بسته اطلاعات جدید ناموفق بود</string>
<string name="connection_error_DESC">اتصال به سرور ناموفق بود</string>
<string name="home_screen_swiping">حرکات صفحه اصلی</string>
<string name="label_copy">کپی</string>
<string name="special_credits">تشکر ویژه</string>
<string name="require_background_access">نیازمند دسترسی پس‌زمینه</string>
<string name="require_background_access_DESC">برای همگام‌سازی در پس‌زمینه، دسترسی پس‌زمینه لازم است</string>
@ -241,4 +221,16 @@
<string name="ignore_signature_summary">هنگام نصب APK ، برای کاربران Lsposed یا کاربران پیشرفته ، تأیید امضا نادیده گرفته شود</string>
<string name="installation_failed_DESC">نصب %s ناموفق بود</string>
<string name="insufficient_storage_DESC">فضای کافی برای نصب این برنامه روی دستگاه وجود ندارد. سعی کنید مقداری فضا خالی کنید.</string>
<string name="label_targets_sdk">هدف: اندروید %s</string>
<string name="error_shizuku_not_granted">دسترسی شیزوکو داده نشده</string>
<string name="error_shizuku_not_running_DESC">خدمت شیزوکو اجرا نمیشود. لطفا برنامه شیزوکو را بررسی کنید</string>
<string name="error_shizuku_service_unavailable">شیزوکو اجرا نمیشود</string>
<string name="error_shizuku_not_granted_DESC">مجوز خدمت شیزوکو داده نشده. لطفا برنامه شیزوکو را بررسی کنید</string>
<string name="error_shizuku_not_installed">شیزوکو نصب نشده</string>
<string name="error_shizuku_not_installed_DESC">به نظر میرسد شیزوکو نصب نشده</string>
<string name="insufficient_storage">فضای ناکافی</string>
<string name="open_shizuku">بازکردن شیزوکو</string>
<string name="switch_to_default_installer">جابه‌جایی به عادی</string>
<string name="label_open_video">ویدیو</string>
<string name="label_unknown_sdk">ناشناخته (%d)</string>
</resources>

View File

@ -72,8 +72,6 @@
<string name="light">Vaalea</string>
<string name="link_copied_to_clipboard">Linkki kopioitu</string>
<string name="links">Linkit</string>
<string name="list_animation">Luettelon animaatiot</string>
<string name="list_animation_description">Näytä luettelon animaatio pääsivulla</string>
<string name="merging_FORMAT">Yhdistetään %s</string>
<string name="name">Nimi</string>
<string name="network_error_DESC">Verkkovirhe</string>
@ -95,11 +93,8 @@
<string name="repositories">Ohjelmavarastot</string>
<string name="repository">Ohjelmavarasto</string>
<string name="requires_FORMAT">Vaatii %s</string>
<string name="root_permission">Hiljainen asennus</string>
<string name="root_permission_description">Salli pääkäyttäjän oikeudet hiljaisiin asennuksiin</string>
<string name="save">Tallenna</string>
<string name="saving_details">Tallennetaan tietoja…</string>
<string name="screenshots">Kuvakaappaukset</string>
<string name="only_on_wifi">Vain Wi-Fi verkossa</string>
<string name="open_DESC_FORMAT">Avaa %s\?</string>
<string name="other">Muut</string>
@ -127,9 +122,7 @@
<string name="unknown">Tuntematon</string>
<string name="unknown_error_DESC">Tuntematon virhe.</string>
<string name="unknown_FORMAT">Tuntematon: %s</string>
<string name="unsigned">Allekirjoittamaton</string>
<string name="unstable_updates">Epävakaat päivitykset</string>
<string name="unverified">Vahvistamaton</string>
<string name="upstream_source_code_is_not_free">Alkuperäinen lähdekoodi ei ole vapaa</string>
<string name="website">Verkkosivusto</string>
<string name="whats_new">Mitä uutta</string>
@ -151,13 +144,11 @@
<string name="syncing_FORMAT">Synkronoidaan %s…</string>
<string name="themes">Teemat</string>
<string name="theme">Teema</string>
<string name="target">Kohde</string>
<string name="tap_to_install_DESC">Napauta asentaaksesi.</string>
<string name="system">Järjestelmä</string>
<string name="update">Päivitys</string>
<string name="updates">Päivitykset</string>
<string name="promotes_non_free_software">Edistää ei-vapaita ohjelmia</string>
<string name="repository_not_used_DESC">Tätä ohjelmavarastoa ei ole vielä käytetty. Ota se käyttöön nähdäksesi siinä olevat sovellukset.</string>
<string name="proxy">Välityspalvelin</string>
<string name="repository_unsigned_DESC">Allekirjoittamaton. Sovellusluetteloa ei voitu tarkistaa. Ole varovainen ladatessasi sovelluksia allekirjoittamattomista arkistoista.</string>
<string name="version_FORMAT">Versio %s</string>
@ -171,19 +162,13 @@
<string name="prefs_personalization">Mukauttaminen</string>
<string name="prefs_language_title">Kieli</string>
<string name="show_less">Näytä vähemmän</string>
<string name="latest">Uusimmat</string>
<string name="explore">Tutustu</string>
<string name="update_all">Päivitä kaikki</string>
<string name="installed_applications">Asennetut sovellukset</string>
<string name="new_applications">Uudet sovellukset</string>
<string name="sort_filter">Lajittele ja suodata</string>
<string name="installer">Asentaja</string>
<string name="legacy_installer">Vanha asentaja</string>
<string name="session_installer">Sessioasentaja</string>
<string name="root_installer">Root-asentaja</string>
<string name="shizuku_installer">Shizuku-asentaja</string>
<string name="cleanup_title">APK:n puhdistusväli</string>
<string name="cleanup_description">Aika ladattujen tiedostojen tarkistamiseen ja poistamiseen</string>
<plurals name="days">
<item quantity="one">Päivä</item>
<item quantity="other">Päivää</item>
@ -194,9 +179,6 @@
</plurals>
<string name="only_on_wifi_with_charging">Vain Wi-Fi verkossa latauksessa ollessa</string>
<string name="io_error_DESC">Tiettyjä toimia ei voida suorittaa.</string>
<string name="allow_collapsing_toolbar">Salli yläosan sovelluspalkin laajeneminen</string>
<string name="allow_collapsing_toolbar_DESC">Salli yläosan sovelluspalkin laajentua ja tiivistyä</string>
<string name="no_internet">Ei internet yhteyttä</string>
<string name="favourites">Suosikit</string>
<string name="material_you">Material You</string>
<string name="material_you_desc">Käytä Material You -väriteemaa</string>
@ -205,7 +187,6 @@
<string name="force_clean_up_DESC">Puhdistaa tarpeettomat tiedostot</string>
<string name="repository_unreachable">Ohjelmavarasto ei ole tavoitettavissa</string>
<string name="installing">Asentaa</string>
<string name="restart_app">Käynnistä Droid-ify uudelleen nähdäksesi muutokset</string>
<string name="waiting_to_start_installation">Odotetaan asennuksen aloittamista…</string>
<string name="auto_update">Päivitä sovelluksia automaattisesti</string>
<string name="auto_update_apps">Yritä asentaa päivitykset automaattisesti</string>
@ -215,7 +196,6 @@
<string name="shizuku_not_alive">Shizuku ei ole käynnissä</string>
<string name="connection_error_DESC">Palvelimeen ei saatu yhteyttä</string>
<string name="socket_error_DESC">Palvelin ei pystynyt toimittamaan uutta pakettia.</string>
<string name="label_copy">Kopioi</string>
<string name="proxy_port_error_not_int">Välityspalvelimen portti voi olla vain kokonaisluku</string>
<string name="home_screen_swiping_DESC">Salli käyttäjän pyyhkäistä sivujen välillä aloitusnäytössä</string>
<string name="repository_not_found">Seuraavaa ohjelmavarastoa ei löytynyt</string>

View File

@ -61,7 +61,6 @@
<string name="source_code">Code source</string>
<string name="size">Taille</string>
<string name="skip">Ignorer</string>
<string name="screenshots">Captures décran</string>
<string name="search">Chercher</string>
<string name="select_mirror">Sélectionnez un miroir</string>
<string name="share">Partager</string>
@ -81,7 +80,6 @@
<string name="website">Site internet</string>
<string name="update">Mettre à jour</string>
<string name="updates">Mises à jour</string>
<string name="unverified">Non vérifié</string>
<string name="unknown_FORMAT">Inconnu : %s</string>
<string name="unknown_error_DESC">Erreur inconnue.</string>
<string name="add_repository">Ajouter un dépôt</string>
@ -98,7 +96,6 @@
<string name="uninstall">Désinstaller</string>
<string name="could_not_validate_FORMAT">Impossible de valider %s</string>
<string name="system">Système</string>
<string name="target">Cible</string>
<string name="password_missing">Mot de passe manquant</string>
<string name="credits">Crédits</string>
<string name="themes">Thèmes</string>
@ -116,7 +113,6 @@
<string name="incompatible_features_DESC">Fonctionnalités manquantes.</string>
<string name="incompatible_older_DESC">Cette version est plus ancienne que celle qui est installée sur votre appareil. Désinstallez-la dabord.</string>
<string name="notify_about_updates_summary">Afficher une notification quand de nouvelles versions sont disponibles</string>
<string name="repository_not_used_DESC">Ce dépôt na pas encore été utilisé. Activez-le pour voir les applications quil contient.</string>
<string name="no_applications_installed">Aucune application installée</string>
<string name="repository_unsigned_DESC">Non signé. Impossible de vérifier la liste dapplis. Soyez prudents lorsque vous téléchargez des applis de dépôts non signés.</string>
<string name="has_non_free_dependencies">Dépend dapplications qui ne sont pas libres</string>
@ -143,8 +139,6 @@
<string name="proxy_host">Adresse du proxy</string>
<string name="proxy_port">Port du proxy</string>
<string name="proxy_type">Type de proxy</string>
<string name="root_permission">Installation silencieuse</string>
<string name="root_permission_description">Autoriser la permission de lutilisateur root pour les installations silencieuses</string>
<string name="show_older_versions">Afficher les versions plus anciennes</string>
<string name="signature_FORMAT">Signature %s</string>
<string name="signed_using_unsafe_algorithm">Signé avec un algorithme qui nest pas sécurisé</string>
@ -152,7 +146,6 @@
<string name="sync_repositories_automatically">Synchroniser les dépôts automatiquement</string>
<string name="syncing">Synchronisation en cours</string>
<string name="syncing_FORMAT">Synchronisation de %s en cours…</string>
<string name="unsigned">Non-signé</string>
<string name="unstable_updates">Mises à jour instables</string>
<string name="upstream_source_code_is_not_free">Le code source nest pas entièrement libre</string>
<string name="username">Nom dutilisateur</string>
@ -161,30 +154,22 @@
<string name="incompatible_api_DESC_FORMAT">Votre %1$s (API version %2$d) nest pas pris en charge. %3$s</string>
<string name="integrity_check_error_DESC">Impossible de vérifier lintégrité.</string>
<string name="invalid_fingerprint_format">Format dempreinte digitale non valide</string>
<string name="list_animation">Animations de listes</string>
<string name="processing_FORMAT">Traitement de %1$s…</string>
<string name="parsing_index_error_DESC">Impossible danalyser le fichier dindex.</string>
<string name="merging_FORMAT">Fusionner %s</string>
<string name="list_animation_description">Afficher lanimation de la liste sur la page principale</string>
<string name="sorting_order">Ordre de tri</string>
<string name="requires_FORMAT">Requiert %s</string>
<string name="unstable_updates_summary">Suggérer linstallation de versions instables</string>
<string name="prefs_language_title">Langue</string>
<string name="prefs_personalization">Personnalisation</string>
<string name="show_less">Afficher moins</string>
<string name="latest">Le plus récent</string>
<string name="sort_filter">Trier et filtrer</string>
<string name="new_applications">Nouvelles applications</string>
<string name="explore">Explorer</string>
<string name="update_all">Tout mettre à jour</string>
<string name="installed_applications">Applications installées</string>
<string name="session_installer">Installateur de session</string>
<string name="legacy_installer">Installateur hérité</string>
<string name="installer">Installateur</string>
<string name="root_installer">Installateur racine</string>
<string name="shizuku_installer">Installateur Shizuku</string>
<string name="cleanup_title">Intervalle de nettoyage des APK</string>
<string name="cleanup_description">Période de vérification et de suppression des fichiers téléchargés</string>
<plurals name="days">
<item quantity="one">Jour</item>
<item quantity="many">Jours</item>
@ -197,9 +182,6 @@
</plurals>
<string name="only_on_wifi_with_charging">Uniquement sur Wi-Fi et charge</string>
<string name="io_error_DESC">Impossible deffectuer certaines actions.</string>
<string name="no_internet">Vous navez pas de connexion internet</string>
<string name="allow_collapsing_toolbar_DESC">Autoriser l\'expansion et la réduction de la barre supérieure de l\'appli</string>
<string name="allow_collapsing_toolbar">Autoriser l\'expansion de la barre supérieure de l\'appli</string>
<string name="material_you_desc">Utiliser le thème couleur Material You</string>
<string name="material_you">Material You</string>
<string name="favourites">Favoris</string>
@ -208,7 +190,6 @@
<string name="force_clean_up_DESC">Nettoyer les fichiers redondants</string>
<string name="enable_repo">Activer le dépôt</string>
<string name="installing">Installation</string>
<string name="restart_app">Redémarrez Droid-ify pour voir les changements</string>
<string name="waiting_to_start_installation">En attente du démarrage de l\'installation…</string>
<string name="auto_update">Mise à jour automatique des applis</string>
<string name="auto_update_apps">Essayez d\'installer les mises à jour automatiquement</string>
@ -221,7 +202,6 @@
<string name="special_credits">Crédits spéciaux</string>
<string name="home_screen_swiping">Balayage de l\'écran d\'accueil</string>
<string name="home_screen_swiping_DESC">Permettre à l\'utilisateur de passer d\'une page à l\'autre dans l\'écran d\'accueil</string>
<string name="label_copy">Copier</string>
<string name="repository_not_found">Le dépôt suivant n\'a pas été trouvé</string>
<string name="proxy_port_error_not_int">Le port proxy ne peut être qu\'un entier</string>
<string name="import_settings_title">Importer les paramètres</string>
@ -245,4 +225,14 @@
<string name="ignore_signature_summary">*Attention* Ignorer la vérification de la signature lors de l\'installation de l\'APK, pour les utilisateurs de LSPosed ou les utilisateurs avancés</string>
<string name="insufficient_storage">Espace insuffisant</string>
<string name="insufficient_storage_DESC">Il n\'y a pas assez d\'espace libre sur l\'appareil pour installer cette application. Essayez de libérer de l\'espace</string>
<string name="error_shizuku_not_running_DESC">Le service Shizuku n\'est pas démarré. Merci de le vérifier dans l\'appli Shizuku</string>
<string name="error_shizuku_service_unavailable">Shizuku Non Démarré</string>
<string name="error_shizuku_not_granted">Permission Shizuku Manquante</string>
<string name="error_shizuku_not_granted_DESC">La permission du service Shizuku n\'est pas accordée. Merci de la vérifier via l\'appli Shizuku</string>
<string name="error_shizuku_not_installed">Shizuku Non Installé</string>
<string name="error_shizuku_not_installed_DESC">Shizuku ne semble pas être installé</string>
<string name="open_shizuku">Ouvrir Shizuku</string>
<string name="label_open_video">Vidéo</string>
<string name="label_unknown_sdk">Inconnu (%d)</string>
<string name="switch_to_default_installer">Rétablir par défaut</string>
</resources>

View File

@ -13,7 +13,6 @@
<string name="changes">Cambios</string>
<string name="checking_repository">Comprobando o repositorio…</string>
<string name="cleanup_title">APK cleanup interval</string>
<string name="cleanup_description">Period to check and remove downloaded files</string>
<string name="compiled_for_debugging">Compilado para a depuración</string>
<string name="confirmation">Confirmación</string>
<string name="connecting">Conectando…</string>
@ -38,8 +37,6 @@
<string name="light">Claro</string>
<string name="link_copied_to_clipboard">A ligazón copiouse no portapapeis</string>
<string name="links">Ligazóns</string>
<string name="list_animation">Lista de animacións</string>
<string name="list_animation_description">Mostra a animación da lista na páxina principal</string>
<string name="merging_FORMAT">Fusionando %s</string>
<string name="name">Nome</string>
<string name="network_error_DESC">Erro na rede</string>
@ -74,9 +71,7 @@
<string name="recently_updated">Actualizado recentemente</string>
<string name="repositories">Repositorios</string>
<string name="repository">Repositorio</string>
<string name="repository_not_used_DESC">Este repositorio aínda non se utilizou. Accéndeo para ver as aplicacións nel.</string>
<string name="requires_FORMAT">Require %s</string>
<string name="screenshots">Capturas de pantalla</string>
<string name="search">Procurar</string>
<string name="select_mirror">Seleccione un espello</string>
<string name="share">Compartir</string>
@ -91,7 +86,6 @@
<string name="sync_repositories_automatically">Sincronizar repositorios automaticamente</string>
<string name="syncing">Sincronización</string>
<string name="system">Sistema</string>
<string name="target">Obxectivo</string>
<string name="theme">Tema</string>
<string name="themes">Temas</string>
<string name="tracks_or_reports_your_activity">Rastrexa ou informa da túa actividade</string>
@ -100,7 +94,6 @@
<string name="unknown_error_DESC">Erro descoñecido.</string>
<string name="unknown_FORMAT">Descoñecido: %s</string>
<string name="unstable_updates">Actualizacións inestables</string>
<string name="unverified">Sen verificar</string>
<string name="update">Actualizar</string>
<string name="updates">Actualizacións</string>
<string name="upstream_source_code_is_not_free">O código fonte non é ceibe</string>
@ -114,12 +107,7 @@
<string name="prefs_language_title">Lingua</string>
<string name="prefs_personalization">Personalización</string>
<string name="show_less">Mostrar menos</string>
<string name="latest">Derradeiro</string>
<string name="explore">Explora</string>
<string name="update_all">Actualiza todo</string>
<string name="installed_applications">Aplicacións instaladas</string>
<string name="sort_filter">Ordear e filtrar</string>
<string name="new_applications">Novas aplicacións</string>
<string name="all_applications">Todas as aplicacións</string>
<string name="amoled">Negro</string>
<string name="already_exists">Xa existe</string>
@ -178,8 +166,6 @@
<string name="only_on_wifi">Só con wifi</string>
<string name="only_on_wifi_with_charging">Só con wifi e carga</string>
<string name="proxy">Proxy</string>
<string name="root_permission">Instalación silenciosa</string>
<string name="root_permission_description">Permitir permiso de root para instalacións silenciosas</string>
<string name="repository_unsigned_DESC">Sen asinar. Non se puido verificar a lista de solicitudes. Teña coidado ao descargar aplicacións desde repositorios sen asinar.</string>
<string name="save">Gardar</string>
<string name="sorting_order">Orde de clasificación</string>
@ -189,14 +175,10 @@
<string name="show_older_versions">Mostrar versións antigas</string>
<string name="source_code">Código fonte</string>
<string name="tap_to_install_DESC">Preme para instalar.</string>
<string name="unsigned">Sen asinar</string>
<string name="unstable_updates_summary">Suxire instalar versións inestables</string>
<string name="validation_index_error_DESC">Non se puido validar o índice.</string>
<string name="version">Versión</string>
<string name="io_error_DESC">Non se poden realizar determinadas accións.</string>
<string name="no_internet">Non tes conexión a internet</string>
<string name="allow_collapsing_toolbar">Permitir ca barra das aplicacións superior se amplíe</string>
<string name="allow_collapsing_toolbar_DESC">Permitir cas barras das aplicacións superior se amplíe e contraiga</string>
<string name="material_you">Material You</string>
<string name="material_you_desc">Usalo material que coloree o tema</string>
<string name="favourites">Favoritas</string>
@ -205,7 +187,6 @@
<string name="force_clean_up">Forzala limpeza</string>
<string name="enable_repo">Activalo repositorio</string>
<string name="installing">Instalando</string>
<string name="restart_app">Reinicia Droid-ify para velos cambios</string>
<string name="waiting_to_start_installation">Agardando para iniciala instalación…</string>
<string name="auto_update">Actualizacións automática das aplicacións</string>
<string name="auto_update_apps">Tenta instalar actualizacións automaticamente</string>

View File

@ -62,10 +62,7 @@
<string name="incompatible_versions">अनुचित संस्करण</string>
<string name="only_on_wifi">केवल वाई-फ़ाई पर</string>
<string name="open_DESC_FORMAT">%s को खोले\?</string>
<string name="repository_not_used_DESC">इस रिपॉजिटरी का अभी तक उपयोग नहीं किया गया है। इसमें एप्लिकेशन देखने के लिए इसे चालू करें।</string>
<string name="save">सहेजें</string>
<string name="root_permission_description">साइलेंट इंस्टाल के लिए रूट अनुमति दें</string>
<string name="screenshots">स्क्रीनशॉट</string>
<string name="saving_details">ब्यौरा सहेजा जा रहा है…</string>
<string name="skip">छोड़ें</string>
<string name="syncing">सिंक्रनाइज़ किए जा रहे</string>
@ -80,7 +77,6 @@
<string name="prefs_personalization">पर्सनलाइजेशन</string>
<string name="prefs_language_title">भाषा</string>
<string name="show_less">कम दिखाएं</string>
<string name="new_applications">नए एप्लीकेशंस</string>
<string name="installer">इंस्टालर</string>
<string name="legacy_installer">लीगेसी इंस्टालर</string>
<string name="session_installer">सत्र इंस्टॉलर</string>
@ -97,8 +93,6 @@
<string name="license_FORMAT">%s लाइसेंस</string>
<string name="light">हल्की</string>
<string name="link_copied_to_clipboard">लिंक कॉपी किया गया</string>
<string name="list_animation">एनिमेशन की सूची दिखाए</string>
<string name="list_animation_description">मुख्य पृष्ठ पर एनीमेशन की सूची दिखाएं</string>
<string name="merging_FORMAT">%s . को मर्ज कर रहा है</string>
<string name="name">नाम</string>
<string name="network_error_DESC">नेटवर्क त्रुटि</string>
@ -150,13 +144,11 @@
<string name="suggested">सुझाव</string>
<string name="sync_repositories">सिंक रिपॉजिटरी</string>
<string name="sync_repositories_automatically">रिपोजिटरी स्वचालित रूप से सिंक्रनाइज़ करें</string>
<string name="target">लक्ष्य</string>
<string name="theme">थीम</string>
<string name="uninstall">स्थापना रद्द करें</string>
<string name="unknown">अनजान</string>
<string name="unknown_error_DESC">अज्ञात त्रुटि।</string>
<string name="unknown_FORMAT">अज्ञात: %s</string>
<string name="unsigned">अहस्ताक्षरित</string>
<string name="unstable_updates_summary">अस्थिर संस्करण स्थापित करने का सुझाव दें</string>
<string name="update">अपडेट</string>
<string name="username">उपयोगकर्ता नाम</string>
@ -165,10 +157,7 @@
<string name="waiting_to_start_download">डाउनलोड शुरू होने की प्रतीक्षा की जा रही है…</string>
<string name="whats_new">नया क्या है</string>
<string name="website">वेबसाइट</string>
<string name="latest">नवीनतम</string>
<string name="installed_applications">इंस्टॉल किए गए एप्लिकेशन</string>
<string name="update_all">सभी अपडेट करें</string>
<string name="explore">एक्सप्लोर करें</string>
<string name="no_applications_installed">कोई इंस्टॉल किए गए एप्लिकेशंनस नहीं</string>
<string name="invalid_address">गलत पता</string>
<string name="invalid_fingerprint_format">अमान्य फ़िंगरप्रिंट प्रारूप</string>
@ -177,13 +166,9 @@
<string name="other">अन्य</string>
<string name="repository_unsigned_DESC">अहस्ताक्षरित। आवेदन सूची का सत्यापन नहीं किया जा सका। अहस्ताक्षरित रिपॉजिटरी से एप्लिकेशन डाउनलोड करने में सावधानी बरतें।</string>
<string name="provided_by_FORMAT">%s . द्वारा प्रदान किया गया</string>
<string name="root_permission">साइलेंट इंस्टाल</string>
<string name="tracks_or_reports_your_activity">आपकी गतिविधि को ट्रैक या रिपोर्ट करता है</string>
<string name="validation_index_error_DESC">अनुक्रमणिका सत्यापित नहीं की जा सकी।</string>
<string name="unverified">असत्यापित</string>
<string name="sort_filter">छाँटें और फ़िल्टर करें</string>
<string name="cleanup_title">एपीके सफाई अंतराल</string>
<string name="cleanup_description">डाउनलोड की गई फ़ाइलों को जांचने और निकालने की अवधि</string>
<string name="only_on_wifi_with_charging">केवल वाई-फ़ाई व च्राजिंग पर</string>
<plurals name="days">
<item quantity="one">दिन</item>
@ -194,9 +179,6 @@
<item quantity="other">घंटे</item>
</plurals>
<string name="io_error_DESC">कुछ क्रियाएं करने में असमर्थ।</string>
<string name="no_internet">आपका इंटरनैट कनेक्शन चालू नहीं है</string>
<string name="allow_collapsing_toolbar">टॉप ऐप बार को विस्तृत होने दें</string>
<string name="allow_collapsing_toolbar_DESC">टॉप ऐप बार को विस्तृत और संक्षिप्त होने दें</string>
<string name="favourites">पसंदीदा</string>
<string name="force_clean_up">जबरी साफ करो</string>
<string name="force_clean_up_DESC">अनावश्यक फाइलों को साफ करता है</string>
@ -207,7 +189,6 @@
<string name="auto_update">ऐप्स को ऑटो अपडेट करें</string>
<string name="auto_update_apps">अपडेटस को स्वचालित रूप से इंस्टॉल करने का प्रयास करें</string>
<string name="enable_repo">रिपॉजिटरी को सक्षम करें</string>
<string name="restart_app">बदलाव देखने के लिए Droid-ify को रीस्टार्ट करें</string>
<string name="installing">इंस्टॉल कर रहा है</string>
<string name="has_non_free_components">गैर-मुक्त घटक हैं</string>
<string name="socket_error_DESC">सर्वर नया पैकेट प्रदान करने में विफल रहा।</string>
@ -218,7 +199,6 @@
<string name="special_credits">विशेष श्रेय</string>
<string name="home_screen_swiping">होम स्क्रीन स्वाइपिंग</string>
<string name="home_screen_swiping_DESC">उपयोगकर्ता को होम स्क्रीन में पृष्ठों के बीच स्वाइप करने की अनुमति दें</string>
<string name="label_copy">कापी करें</string>
<string name="proxy_port_error_not_int">प्रॉक्सी पोर्ट केवल पूर्णांक हो सकता है</string>
<string name="repository_not_found">निम्नलिखित रिपॉजिटरी नहीं मिली</string>
<string name="import_settings_title">आयात सेटिंग्स</string>
@ -240,4 +220,9 @@
<string name="ignore_signature_summary">LSPosed उपयोगकर्ताओं या उन्नत उपयोगकर्ताओं के लिए एपीके इंस्टॉल करते समय हस्ताक्षर सत्यापन को अनदेखा करें</string>
<string name="uninstalled_application">अनइंस्टॉल किया गया</string>
<string name="installation_failed_DESC">%s इंस्टॉल करने में विफल</string>
<string name="label_unknown_sdk">अज्ञात (%d)</string>
<string name="insufficient_storage">अपर्याप्त जगह</string>
<string name="insufficient_storage_DESC">इस एप्लिकेशन को इंस्टॉल करने के लिए डिवाइस पर पर्याप्त खाली जगह नहीं है। कुछ जगह खाली करने का प्रयास करें</string>
<string name="switch_to_default_installer">डिफ़ॉल्ट पर स्विच करें</string>
<string name="label_open_video">वीडियो</string>
</resources>

View File

@ -27,16 +27,13 @@
<string name="password">Zaporka</string>
<string name="settings">Postavke</string>
<string name="promotes_non_free_network_services">Promovira mrežne usluge koje nisu besplatne</string>
<string name="repository_not_used_DESC">Još niste koristili ovaj repozitorij. Omogućite ga kako biste vidjeli aplikacije koje sadrži.</string>
<string name="show_older_versions">Prikaži starije inačice</string>
<string name="repository_unsigned_DESC">Nije potpisano. Nemoguće potvrditi liste aplikacija. Budite oprezni prilikom preuzimanja aplikacija s nepotpisanih repozitorija.</string>
<string name="socks_proxy">SOCKS proxy</string>
<string name="root_permission_description">Omogućite root dozvole kako bi neometana instalacija funkcionirala</string>
<string name="tracks_or_reports_your_activity">Prati i prijavljuje Vašu aktivnost</string>
<string name="unknown_FORMAT">Nepoznato: %s</string>
<string name="show_more">Prikaži više</string>
<string name="signature_FORMAT">Potpis %s</string>
<string name="unsigned">Nepotpisano</string>
<string name="signed_using_unsafe_algorithm">Potpisano nesigurnosnim algoritmom</string>
<string name="size">Veličina</string>
<string name="skip">Preskoči</string>
@ -68,7 +65,6 @@
<string name="changes">Promjene</string>
<string name="checking_repository">Provjeravam repozitorij…</string>
<string name="cleanup_title">APK interval čišćenja</string>
<string name="cleanup_description">Razdoblje za provjeru i uklanjanje preuzetih datoteka</string>
<string name="compiled_for_debugging">Stvoreno za ispravljanje pogrešaka</string>
<string name="connecting">Povezujem…</string>
<string name="contains_non_free_media">Sadrži medije koji nisu besplatni</string>
@ -108,8 +104,6 @@
<string name="invalid_signature_error_DESC">Neispravan potpis.</string>
<string name="invalid_username_format">Neispravan oblik korisničkog imena</string>
<string name="launch">Pokreni</string>
<string name="list_animation">Animacije popisa</string>
<string name="list_animation_description">Prikaži animacije popisa na početnoj stranici</string>
<string name="merging_FORMAT">Stapanje %s</string>
<string name="name">Naziv</string>
<string name="network_error_DESC">Mrežna greška</string>
@ -140,10 +134,8 @@
<string name="repositories">Repozitoriji</string>
<string name="repository">Repozitorij</string>
<string name="requires_FORMAT">Zahtijeva %s</string>
<string name="root_permission">Neometana instalacija</string>
<string name="save">Spremi</string>
<string name="saving_details">Spremam detalje…</string>
<string name="screenshots">Snimke zaslona</string>
<string name="search">Traži</string>
<string name="select_mirror">Odaberite poslužitelj</string>
<string name="share">Podijeli</string>
@ -155,13 +147,11 @@
<string name="syncing">Sinkroniziram</string>
<string name="system">Sistem</string>
<string name="tap_to_install_DESC">Kliknite kako biste instalirali.</string>
<string name="target">Cilj</string>
<string name="theme">Tema</string>
<string name="uninstall">Deinstaliraj</string>
<string name="unknown">Nepoznato</string>
<string name="unknown_error_DESC">Nepoznata greška.</string>
<string name="unstable_updates">Nestabilna ažuriranja</string>
<string name="unverified">Nepotvrđeno</string>
<string name="update">Ažuriraj</string>
<string name="updates">Ažuriranja</string>
<string name="upstream_source_code_is_not_free">Glavni izvorni kod nije besplatan</string>
@ -170,13 +160,8 @@
<string name="whats_new">Što je Novo</string>
<string name="prefs_language_title">Jezik</string>
<string name="show_less">Prikaži manje</string>
<string name="latest">Najnovije</string>
<string name="explore">Otkrij</string>
<string name="update_all">Ažuriraj sve</string>
<string name="installed_applications">Instalirane aplikacije</string>
<string name="sort_filter">Sortiraj i filtriraj</string>
<string name="all_applications_up_to_date">Sve Vaše aplikacije su ažurne</string>
<string name="new_applications">Nove aplikacije</string>
<string name="action_failed">Radnja neuspjela</string>
<string name="all_applications">Sve aplikacije</string>
<string name="available">Otkrij</string>
@ -197,9 +182,6 @@
<string name="http_proxy">HTTP proxy</string>
<string name="never">Nikad</string>
<string name="io_error_DESC">Nije moguće izvršiti određene radnje.</string>
<string name="no_internet">Nemate internetsku vezu</string>
<string name="allow_collapsing_toolbar_DESC">Dozvoli rasklapanje i sklapanje gornje trake aplikacije</string>
<string name="allow_collapsing_toolbar">Dozvoli rasklapanje gornje trake aplikacije</string>
<string name="installing">Instaliranje</string>
<string name="material_you">Material You</string>
<string name="material_you_desc">Upotrijebite Material You temu boja</string>
@ -210,7 +192,6 @@
<string name="repository_unreachable">Repozitorij je nedostupan</string>
<string name="auto_update">Automatsko ažuriranje aplikacija</string>
<string name="auto_update_apps">Pokušajte automatski instalirati ažuriranja</string>
<string name="restart_app">Ponovo pokrenite droid-ify da biste vidjeli promjene</string>
<string name="waiting_to_start_installation">Čekajući da započnete instalaciju …</string>
<string name="has_non_free_components">Sadrži komponente koje nisu besplatne</string>
<string name="connection_error_DESC">Nije bilo moguće spojiti se s poslužiteljem</string>
@ -218,7 +199,6 @@
<string name="home_screen_swiping">Prevlačenje početnim zaslonom</string>
<string name="contains_nsfw">Sadrži sadržaj koji nije siguran za rad</string>
<string name="shizuku_not_alive">Shizuku nije pokrenut</string>
<string name="label_copy">Kopiraj</string>
<string name="proxy_port_error_not_int">Proxy port može biti samo cijeli broj</string>
<string name="home_screen_swiping_DESC">Dopušta korisniku da prelazi između stranica na početnom zaslonu</string>
<string name="repository_not_found">Sljedeći repozitorij nije pronađen</string>

Some files were not shown because too many files have changed in this diff Show More