Compare commits
26 commits
nightly-20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eda5bf6c9d | ||
|
|
823fb11546 | ||
|
|
74c0e35c76 | ||
|
|
0cc3da6949 | ||
|
|
1ee5d87aeb | ||
|
|
f4320992c8 | ||
|
|
f4ee755783 | ||
|
|
f09226ef3a | ||
|
|
a6509993a3 | ||
|
|
22e85ecb8d | ||
|
|
273f572968 | ||
|
|
e64a1d4581 | ||
|
|
7793e50941 | ||
|
|
8a67a6a0dd | ||
|
|
ae608e8370 | ||
|
|
2dc1f788e7 | ||
|
|
ba815e82b0 | ||
|
|
24b2c8cf9b | ||
|
|
45ef4ec35b | ||
|
|
61b6b80192 | ||
|
|
490adada3d | ||
|
|
07ef45a004 | ||
|
|
9066918227 | ||
|
|
9e96a96a2c | ||
|
|
87a763cca8 | ||
|
|
848b859920 |
|
|
@ -54,8 +54,8 @@ jobs:
|
|||
if: steps.get_release.outputs.prerelease == 'true'
|
||||
run: |
|
||||
./gradlew assembleNightly --stacktrace \
|
||||
-PversionName=${{ steps.get_release.outputs.tag_name }} \
|
||||
-PversionCode=$(date +%s)
|
||||
# -PversionName=${{ steps.get_release.outputs.tag_name }}
|
||||
|
||||
- name: Build unsigned release APK
|
||||
if: steps.get_release.outputs.prerelease != 'true'
|
||||
|
|
|
|||
1
Maestro/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.maestro
|
||||
37
Maestro/flow.yaml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
appId: dev.mi6e4ka.openstore
|
||||
---
|
||||
- launchApp:
|
||||
clearState: true
|
||||
|
||||
- assertVisible: "OpenStore"
|
||||
- takeScreenshot: "01_main_screen"
|
||||
|
||||
- tapOn: "Search"
|
||||
- inputText: "Mir Pay"
|
||||
- pressKey: Enter
|
||||
- assertVisible: "Mir Pay"
|
||||
- takeScreenshot: "02_search_screen"
|
||||
|
||||
- tapOn: "Mir Pay"
|
||||
- assertVisible: "Mir Pay"
|
||||
- takeScreenshot: "03_details_screen"
|
||||
|
||||
- scrollUntilVisible:
|
||||
element: "Rating and reviews"
|
||||
direction: DOWN
|
||||
- tapOn: "Rating and reviews"
|
||||
- assertVisible: "Reviews"
|
||||
- takeScreenshot: "04_review_screen"
|
||||
|
||||
- back
|
||||
- back
|
||||
- back
|
||||
- assertVisible: "OpenStore"
|
||||
- tapOn: "updates|обновления"
|
||||
- assertVisible: "Updates"
|
||||
- takeScreenshot: "05_updates_screen"
|
||||
|
||||
- back
|
||||
- tapOn: "settings|настройки"
|
||||
- assertVisible: "Settings|Настройки"
|
||||
- takeScreenshot: "06_settings_screen"
|
||||
16
README.md
|
|
@ -27,21 +27,21 @@ Verification info:
|
|||
- [x] Search apps
|
||||
- [x] View apps descriptions, rating and reviews
|
||||
- [x] Native app installing
|
||||
- [x] Updating apps (for now only manually)
|
||||
- [x] Android 5.0+ support!
|
||||
- [x] Search and view TV apps
|
||||
- [x] Check all apps for updates
|
||||
|
||||
## Roadmap
|
||||
- [ ] TV navigation support
|
||||
- [ ] Automatically check for updates
|
||||
- [ ] Better TV navigation support
|
||||
- [ ] ...maybe more?
|
||||
|
||||
## Screenshots
|
||||
[<img src="https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" width=250>](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png)
|
||||
[<img src="https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" width=250>](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png)
|
||||
[<img src="https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width=250>](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png)
|
||||
[<img src="https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width=250>](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png)
|
||||
[<img src="https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" width=250>](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png)
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/01_main_screen.png" width=250>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/02_search_screen.png" width=250>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/03_details_screen.png" width=250>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/04_review_screen.png" width=250>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/05_updates_screen.png" width=250>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/06_settings_screen.png" width=250>
|
||||
|
||||
## Building the app
|
||||
See [here](https://codeberg.org/mi6e4ka/openstore/src/branch/main/BUILDING.md)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:localeConfig="@xml/locale_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:targetApi="36"
|
||||
android:memtagMode="async">
|
||||
<activity
|
||||
|
|
@ -35,11 +34,12 @@
|
|||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http" />
|
||||
<data android:host="rustore.ru" />
|
||||
<data android:host="www.rustore.ru" />
|
||||
<data android:host="apps.rustore.ru" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="https" android:host="www.rustore.ru" android:pathPrefix="/catalog/app/" />
|
||||
<data android:scheme="https" android:host="apps.rustore.ru" android:pathPrefix="/app/" />
|
||||
<data android:scheme="https" android:host="www.rustore.ru" android:pathPrefix="/instruction" />
|
||||
<data android:scheme="http" android:host="www.rustore.ru" android:pathPrefix="/catalog/app/" />
|
||||
<data android:scheme="http" android:host="apps.rustore.ru" android:pathPrefix="/app/" />
|
||||
<data android:scheme="http" android:host="www.rustore.ru" android:pathPrefix="/instruction" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
@ -76,4 +76,4 @@
|
|||
android:name=".internal.installer.PackageInstallerStatusReceiver"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ package dev.mi6e4ka.openstore
|
|||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -34,6 +36,7 @@ import dev.mi6e4ka.openstore.ui.theme.AppTheme
|
|||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
val cacheDir = externalCacheDir ?: cacheDir
|
||||
cacheDir.listFiles()?.forEach { file ->
|
||||
|
|
@ -66,20 +69,34 @@ class MainActivity : ComponentActivity() {
|
|||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val duration = 300
|
||||
val easing = FastOutSlowInEasing
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "search",
|
||||
enterTransition = {
|
||||
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(300))
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { (it * 0.1f).toInt() },
|
||||
animationSpec = tween(duration, easing = easing)
|
||||
) + fadeIn(animationSpec = tween(duration))
|
||||
},
|
||||
exitTransition = {
|
||||
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(300))
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { -(it * 0.1f).toInt() },
|
||||
animationSpec = tween(duration, easing = easing)
|
||||
) + fadeOut(animationSpec = tween(duration))
|
||||
},
|
||||
popEnterTransition = {
|
||||
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(300))
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { -(it * 0.1f).toInt() },
|
||||
animationSpec = tween(duration, easing = easing)
|
||||
) + fadeIn(animationSpec = tween(duration))
|
||||
},
|
||||
popExitTransition = {
|
||||
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(300))
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { (it * 0.1f).toInt() },
|
||||
animationSpec = tween(duration, easing = easing)
|
||||
) + fadeOut(animationSpec = tween(duration))
|
||||
}
|
||||
) {
|
||||
composable("search") {
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ import java.io.File
|
|||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
|
@ -36,6 +39,7 @@ import java.util.zip.ZipOutputStream
|
|||
import kotlin.concurrent.atomics.AtomicLong
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
class ApkInstaller(private val context: Context) {
|
||||
|
||||
|
|
@ -60,6 +64,8 @@ class ApkInstaller(private val context: Context) {
|
|||
val totalBytesToDownload = files.sumOf { it.size }
|
||||
val totalBytesDownloaded = AtomicLong(0)
|
||||
val downloadedFiles = Collections.synchronizedList(mutableListOf<File>())
|
||||
var lastUpdateMillis = 0L
|
||||
|
||||
try {
|
||||
val resultingFiles = coroutineScope {
|
||||
files.map { file ->
|
||||
|
|
@ -72,6 +78,7 @@ class ApkInstaller(private val context: Context) {
|
|||
|
||||
if (apkFile.exists() && apkFile.length() == file.size) {
|
||||
if (verifyFileHash(apkFile, file.hash)) {
|
||||
totalBytesDownloaded.addAndFetch(file.size)
|
||||
return@async apkFile
|
||||
}
|
||||
apkFile.delete()
|
||||
|
|
@ -79,46 +86,52 @@ class ApkInstaller(private val context: Context) {
|
|||
|
||||
val request = Request.Builder().url(file.url).build()
|
||||
val call = okHttpClient.newCall(request)
|
||||
|
||||
// Связываем отмену корутины с OkHttp
|
||||
val job = coroutineContext[Job]!!
|
||||
val cancelHandler = job.invokeOnCompletion { call.cancel() }
|
||||
|
||||
call.execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Ошибка загрузки: ${response.code}")
|
||||
try {
|
||||
call.execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Ошибка загрузки: ${response.code}")
|
||||
|
||||
val responseBody =
|
||||
response.body ?: throw IOException("Пустой ответ от сервера")
|
||||
|
||||
responseBody.byteStream().use { inputStream ->
|
||||
FileOutputStream(apkFile).use { outputStream ->
|
||||
val buffer = ByteArray(8192)
|
||||
var bytesRead: Int
|
||||
val responseBody = response.body ?: throw IOException("Пустой ответ")
|
||||
|
||||
responseBody.byteStream().use { inputStream ->
|
||||
FileOutputStream(apkFile).use { outputStream ->
|
||||
val buffer = ByteArray(16384) // Увеличил буфер для скорости
|
||||
var bytesRead: Int
|
||||
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
if (call.isCanceled()) {
|
||||
throw IOException("Canceled")
|
||||
}
|
||||
|
||||
outputStream.write(buffer, 0, bytesRead)
|
||||
|
||||
val newTotalDownloaded =
|
||||
totalBytesDownloaded.addAndFetch(bytesRead.toLong())
|
||||
val percent = if (totalBytesToDownload > 0) {
|
||||
(newTotalDownloaded * 100 / totalBytesToDownload).toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
onProgress(percent, newTotalDownloaded, totalBytesToDownload)
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
job.ensureActive()
|
||||
outputStream.write(buffer, 0, bytesRead)
|
||||
|
||||
val currentTotal = totalBytesDownloaded.addAndFetch(bytesRead.toLong())
|
||||
|
||||
// Троттлинг обновлений UI (не чаще 10 раз в секунду)
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastUpdateMillis > 100) {
|
||||
lastUpdateMillis = now
|
||||
val percent = if (totalBytesToDownload > 0) {
|
||||
(currentTotal * 100 / totalBytesToDownload).toInt()
|
||||
} else 0
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
onProgress(percent, currentTotal, totalBytesToDownload)
|
||||
}
|
||||
}
|
||||
}
|
||||
outputStream.flush()
|
||||
}
|
||||
outputStream.flush()
|
||||
}
|
||||
}
|
||||
|
||||
if (!verifyFileHash(apkFile, file.hash)) {
|
||||
throw HashMismatchException(apkFile, file.hash)
|
||||
if (!verifyFileHash(apkFile, file.hash)) {
|
||||
throw HashMismatchException(apkFile, file.hash)
|
||||
}
|
||||
apkFile
|
||||
}
|
||||
|
||||
apkFile
|
||||
} finally {
|
||||
cancelHandler.dispose()
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
|
|
@ -128,35 +141,36 @@ class ApkInstaller(private val context: Context) {
|
|||
onSuccess(resultingFiles)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is HashMismatchException) {
|
||||
val expectedHash = formatHashForDisplay(e.expectedHash)
|
||||
val actualHash = computeExistingHash(e.file)
|
||||
val filesCopy = downloadedFiles.toList()
|
||||
withContext(Dispatchers.Main) {
|
||||
onInvalidHash(filesCopy, expectedHash, actualHash)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
downloadedFiles.forEach { file ->
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (e is CancellationException) {
|
||||
onCancel()
|
||||
withContext(NonCancellable) {
|
||||
if (e is HashMismatchException) {
|
||||
val expectedHash = formatHashForDisplay(e.expectedHash)
|
||||
val actualHash = computeExistingHash(e.file)
|
||||
val filesCopy = downloadedFiles.toList()
|
||||
withContext(Dispatchers.Main) {
|
||||
onInvalidHash(filesCopy, expectedHash, actualHash)
|
||||
}
|
||||
} else {
|
||||
onError(e)
|
||||
// Удаляем недокачанные файлы при любой другой ошибке или отмене
|
||||
downloadedFiles.forEach { if (it.exists()) it.delete() }
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (e is CancellationException || (e is IOException && callIsCanceled(e))) {
|
||||
onCancel()
|
||||
} else {
|
||||
onError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
downloadRunner.cancel()
|
||||
}
|
||||
return { downloadRunner.cancel() }
|
||||
}
|
||||
|
||||
private fun callIsCanceled(e: IOException): Boolean {
|
||||
return e.message?.contains("Canceled", ignoreCase = true) == true ||
|
||||
e.message?.contains("Socket closed", ignoreCase = true) == true
|
||||
}
|
||||
|
||||
private class HashMismatchException(val file: File, val expectedHash: String) : IOException("Hash mismatch")
|
||||
|
|
@ -180,8 +194,6 @@ class ApkInstaller(private val context: Context) {
|
|||
private fun verifyFileHash(file: File, expectedHash: String): Boolean {
|
||||
val normalizedHash = expectedHash.trim().ifEmpty { return true }
|
||||
val expectedBytes = decodeHashValue(normalizedHash) ?: return true
|
||||
|
||||
// Only XXH64 is supported now (8 bytes)
|
||||
if (expectedBytes.size != 8) return true
|
||||
|
||||
val actualBytes = digestFileXXH64(file) ?: return false
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.semantics.hideFromAccessibility
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
|
|
@ -62,7 +65,8 @@ fun GalleryWithPager(images: List<Files>) {
|
|||
modifier = Modifier.height(250.dp)
|
||||
) {
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.clearAndSetSemantics{}
|
||||
) {
|
||||
itemsIndexed(images) { i, file ->
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ import kotlinx.datetime.toLocalDateTime
|
|||
import android.Manifest
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.Instant
|
||||
|
||||
|
|
@ -141,7 +144,7 @@ fun DetailsScreen(
|
|||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_arrow_back_24px),
|
||||
contentDescription = "Back"
|
||||
contentDescription = stringResource(R.string.back_button_desc)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -153,7 +156,10 @@ fun DetailsScreen(
|
|||
}
|
||||
}
|
||||
){
|
||||
Icon(painterResource(R.drawable.ic_link_24px), null)
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_link_24px),
|
||||
stringResource(R.string.copy_link_desc)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
{
|
||||
|
|
@ -175,7 +181,10 @@ fun DetailsScreen(
|
|||
}
|
||||
}
|
||||
){
|
||||
Icon(painterResource(R.drawable.apk_install_24px), null)
|
||||
Icon(
|
||||
painterResource(R.drawable.apk_install_24px),
|
||||
stringResource(R.string.download_apk_desc)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, "https://www.rustore.ru/catalog/app/${state.item?.packageName ?: ""}".toUri()).apply {
|
||||
|
|
@ -184,7 +193,10 @@ fun DetailsScreen(
|
|||
}
|
||||
context.startActivity(intent)
|
||||
}, enabled = state.item != null) {
|
||||
Icon(painterResource(R.drawable.ic_captive_portal_24px), null)
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_captive_portal_24px),
|
||||
stringResource(R.string.open_in_browser_desc)
|
||||
)
|
||||
}
|
||||
}
|
||||
) }
|
||||
|
|
@ -282,9 +294,9 @@ fun DetailsScreen(
|
|||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
if (item.aggregatorInfo != null) {
|
||||
Text(stringResource(R.string.provided_by, item.aggregatorInfo.source))
|
||||
Text(stringResource(R.string.provided_by_third_party, item.aggregatorInfo.source))
|
||||
} else {
|
||||
Text(stringResource(R.string.provided_by, "RuStore"))
|
||||
Text(stringResource(R.string.provided_by_developer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -332,7 +344,7 @@ fun DetailsScreen(
|
|||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(item.appName, fontSize = 24.sp, fontWeight = FontWeight.Bold, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
Text(item.aggregatorInfo?.companyName ?: item.companyName, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
Text(item.aggregatorInfo?.companyName ?: item.companyName, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.clearAndSetSemantics(){contentDescription = "Разработчик: ${item.aggregatorInfo?.companyName ?: item.companyName}"})
|
||||
}
|
||||
}
|
||||
if (!state.isSupported) {
|
||||
|
|
@ -348,7 +360,7 @@ fun DetailsScreen(
|
|||
state.installerPackageName != context.packageName &&
|
||||
state.installerPackageName != "ru.vk.store") {
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -356,11 +368,11 @@ fun DetailsScreen(
|
|||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(painterResource(R.drawable.ic_warning_24px), null, tint = MaterialTheme.colorScheme.error)
|
||||
Icon(painterResource(R.drawable.ic_warning_24px), null, tint = MaterialTheme.colorScheme.onPrimaryContainer)
|
||||
Text(
|
||||
stringResource(R.string.signature_mismatch_warning),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer
|
||||
fontSize = 16.sp
|
||||
//color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -433,18 +445,30 @@ fun DetailsScreen(
|
|||
(state.appFiles?.sumOf { it.size } ?: 0L) / 1024.0 / 1024.0)
|
||||
)
|
||||
},
|
||||
leadingIcon = {Icon(painterResource(R.drawable.ic_upload_file_24px), contentDescription = null)},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_upload_file_24px),
|
||||
stringResource(R.string.app_size_desc)
|
||||
)},
|
||||
onClick = {},
|
||||
)
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = {Text(item.versionName)},
|
||||
leadingIcon = {Icon(painterResource(R.drawable.ic_arrow_circle_up_24px), contentDescription = null)}
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_arrow_circle_up_24px),
|
||||
stringResource(R.string.latest_version_desc)
|
||||
)}
|
||||
)
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = {FormattedDateText(item.appVerUpdatedAt)},
|
||||
leadingIcon = {Icon(painterResource(R.drawable.ic_update_24px), contentDescription = null)}
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_update_24px),
|
||||
stringResource(R.string.last_update_desc)
|
||||
)}
|
||||
)
|
||||
var detailedApiVersion by remember { mutableStateOf(false) }
|
||||
AssistChip(
|
||||
|
|
@ -458,22 +482,38 @@ fun DetailsScreen(
|
|||
Text("API ${item.minSdkVersion} +")
|
||||
}
|
||||
},
|
||||
leadingIcon = {Icon(painterResource(R.drawable.ic_android_24px), contentDescription = null)}
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_android_24px),
|
||||
stringResource(R.string.min_api_desc)
|
||||
)}
|
||||
)
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = {Text(if ((state.appFiles?.size ?: 1) > 1) "${state.appFiles?.size} Split APKs" else "APK")},
|
||||
leadingIcon = {Icon(painterResource(R.drawable.ic_adb_24px), contentDescription = null)}
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_adb_24px),
|
||||
stringResource(R.string.installer_type_desc)
|
||||
)}
|
||||
)
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = {Text(item.roundedDownloadsText)},
|
||||
leadingIcon = {Icon(painterResource(R.drawable.ic_download_24px), contentDescription = null)}
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_download_24px),
|
||||
stringResource(R.string.downloads_desc)
|
||||
)}
|
||||
)
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = {Text(item.ageLegal)},
|
||||
leadingIcon = {Icon(painterResource(R.drawable.ic_supervised_user_circle_24px), contentDescription = null)}
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_supervised_user_circle_24px),
|
||||
stringResource(R.string.age_legal_desc)
|
||||
)}
|
||||
)
|
||||
}
|
||||
GalleryWithPager(item.fileUrls)
|
||||
|
|
@ -539,6 +579,13 @@ fun DetailsScreen(
|
|||
restoreState = true
|
||||
}
|
||||
})
|
||||
.clearAndSetSemantics {
|
||||
contentDescription = context.getString(
|
||||
R.string.reviews_desc,
|
||||
state.rating?.averageUserRating ?: 0,
|
||||
state.rating?.totalRatings ?: 0
|
||||
)
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
Modifier
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.hideFromAccessibility
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -68,22 +70,25 @@ fun ResultsScreen(
|
|||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_arrow_back_24px),
|
||||
contentDescription = "Back"
|
||||
contentDescription = stringResource(R.string.back_button_desc)
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
val platforms = remember {
|
||||
listOf(
|
||||
Platform("Mobile", "mobile", R.drawable.ic_mobile_24px),
|
||||
Platform("TV", "tv", R.drawable.ic_tv_gen_24px)
|
||||
Platform(R.string.mobile_label, "mobile", R.drawable.ic_mobile_24px),
|
||||
Platform(R.string.tv_label, "tv", R.drawable.ic_tv_gen_24px)
|
||||
)
|
||||
}
|
||||
|
||||
TextButton({platformSelectorExpanded = true}) {
|
||||
Icon(painterResource(state.selectedPlatform.icon), null)
|
||||
Icon(
|
||||
painterResource(state.selectedPlatform.icon),
|
||||
contentDescription = stringResource(R.string.tts_current_platform)
|
||||
)
|
||||
Spacer(Modifier.size(8.dp))
|
||||
Text(state.selectedPlatform.label)
|
||||
Text(stringResource(state.selectedPlatform.label))
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = platformSelectorExpanded,
|
||||
|
|
@ -92,7 +97,7 @@ fun ResultsScreen(
|
|||
platforms.forEach {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {Icon(painterResource(it.icon), null)},
|
||||
text = { Text(it.label) },
|
||||
text = { Text(stringResource(it.label)) },
|
||||
onClick = { viewModel.selectPlatform(it); platformSelectorExpanded=false }
|
||||
)
|
||||
}
|
||||
|
|
@ -125,7 +130,16 @@ fun ResultsScreen(
|
|||
modifier = Modifier.weight(1f).padding(start = 16.dp,end = 4.dp)
|
||||
) {
|
||||
Text(text = app.appName, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = app.packageName, fontSize = 14.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, color = MaterialTheme.colorScheme.secondary)
|
||||
Text(
|
||||
text = app.packageName,
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier.semantics{
|
||||
hideFromAccessibility()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ import kotlinx.coroutines.flow.flatMapLatest
|
|||
import kotlinx.coroutines.flow.update
|
||||
|
||||
data class Platform(
|
||||
val label: String,
|
||||
val label: Int,
|
||||
val value: String,
|
||||
val icon: Int
|
||||
)
|
||||
data class ResultsState(
|
||||
val selectedPlatform: Platform = Platform("Mobile", "mobile", R.drawable.ic_mobile_24px),
|
||||
val selectedPlatform: Platform = Platform(R.string.mobile_label, "mobile", R.drawable.ic_mobile_24px),
|
||||
)
|
||||
|
||||
class ResultsViewModel(private val query: String) : ViewModel() {
|
||||
|
|
|
|||
|
|
@ -35,8 +35,12 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
|
|
@ -95,7 +99,7 @@ fun ReviewsScreen(packageName: String, navController: NavController = rememberNa
|
|||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_arrow_back_24px),
|
||||
contentDescription = "Back"
|
||||
contentDescription = stringResource(R.string.back_button_desc)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -135,11 +139,12 @@ fun ReviewsScreen(packageName: String, navController: NavController = rememberNa
|
|||
}
|
||||
items(reviews ?: listOf()) { review ->
|
||||
Card(
|
||||
Modifier.fillMaxWidth()
|
||||
Modifier.fillMaxWidth().semantics(mergeDescendants = true){}
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(16.dp)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
|
|
@ -155,7 +160,10 @@ fun ReviewsScreen(packageName: String, navController: NavController = rememberNa
|
|||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clearAndSetSemantics{
|
||||
contentDescription = context.getString(R.string.review_rating_desc, review.appRating)
|
||||
}
|
||||
) {
|
||||
repeat(review.appRating) {
|
||||
Icon(painterResource(R.drawable.ic_star_filled_24px), null, Modifier.size(16.dp), tint = MaterialTheme.colorScheme.primary)
|
||||
|
|
@ -171,12 +179,12 @@ fun ReviewsScreen(packageName: String, navController: NavController = rememberNa
|
|||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(painterResource(R.drawable.ic_thumb_up_24px), contentDescription = null, Modifier.size(20.dp), tint = MaterialTheme.colorScheme.secondary)
|
||||
Icon(painterResource(R.drawable.ic_thumb_up_24px), contentDescription = stringResource(R.string.likes_desc), Modifier.size(20.dp), tint = MaterialTheme.colorScheme.secondary)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("${review.likeCounter}")
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(painterResource(R.drawable.ic_thumb_down_24px), contentDescription = null, Modifier.size(20.dp), tint = MaterialTheme.colorScheme.secondary)
|
||||
Icon(painterResource(R.drawable.ic_thumb_down_24px), contentDescription = stringResource(R.string.dislikes_desc), Modifier.size(20.dp), tint = MaterialTheme.colorScheme.secondary)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text("${review.dislikeCounter}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,8 +45,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -95,7 +98,10 @@ fun SearchScreen(navController: NavController = rememberNavController()) {
|
|||
)
|
||||
}
|
||||
IconButton({navController.navigate("settings")}) {
|
||||
Icon(painterResource(R.drawable.ic_settings_24px), "settings")
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_settings_24px),
|
||||
contentDescription = stringResource(R.string.settings_title)
|
||||
)
|
||||
}
|
||||
}
|
||||
) },
|
||||
|
|
@ -147,7 +153,9 @@ fun SearchScreen(navController: NavController = rememberNavController()) {
|
|||
value = search,
|
||||
onValueChange = {s -> search=s},
|
||||
leadingIcon = { Icon(painterResource(R.drawable.ic_search_24px), contentDescription = null) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().semantics{
|
||||
contentDescription = context.getString(R.string.search_title)
|
||||
},
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Search
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package dev.mi6e4ka.openstore.ui.screen.settings
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.AlertDialog
|
||||
|
|
@ -53,6 +56,7 @@ data class SettingsSection(
|
|||
val select: List<SettingsSectionSelect>? = null
|
||||
)
|
||||
|
||||
@SuppressLint("ModifierParameter")
|
||||
data class SettingsSectionSelect(
|
||||
val title: String,
|
||||
val value: String,
|
||||
|
|
@ -72,6 +76,12 @@ enum class ParamType {
|
|||
fun SettingsScreen(
|
||||
navController: NavController
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val dynamicSources = remember {
|
||||
getDynamicInstallerSources(context)
|
||||
}
|
||||
|
||||
val sections = listOf(
|
||||
SettingsSection(
|
||||
id = "installer_type",
|
||||
|
|
@ -95,68 +105,16 @@ fun SettingsScreen(
|
|||
id = "updates_sources",
|
||||
title = R.string.settings_updates_sources_title,
|
||||
description = R.string.settings_updates_sources_description,
|
||||
icon = R.drawable.ic_update_24px,
|
||||
icon = R.drawable.ic_checklist_24px,
|
||||
type = ParamType.MULTI_SELECT,
|
||||
defaultVal = "dev.mi6e4ka.openstore;dev.mi6e4ka.openstore.nightly;dev.mi6e4ka.openstore.debug;ru.vk.store",
|
||||
select = listOf(
|
||||
SettingsSectionSelect(
|
||||
title = "OpenStore",
|
||||
value = "group_openstore",
|
||||
isGroup = true,
|
||||
groupPackages = listOf(
|
||||
"dev.mi6e4ka.openstore",
|
||||
"dev.mi6e4ka.openstore.nightly",
|
||||
"dev.mi6e4ka.openstore.debug"
|
||||
)
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = "RuStore",
|
||||
value = "ru.vk.store"
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = stringResource(R.string.settings_source_aurora_store),
|
||||
value = "com.aurora.store"
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = "Google Play",
|
||||
value = "com.android.vending"
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = stringResource(R.string.settings_group_file_managers),
|
||||
value = "group_filemanagers",
|
||||
isGroup = true,
|
||||
groupPackages = listOf(
|
||||
"com.hihonor.filemanager",
|
||||
"com.google.android.documentsui",
|
||||
"com.android.documentsui",
|
||||
"com.google.android.apps.nbu.files",
|
||||
"com.mi.android.globalFileexplorer"
|
||||
)
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = stringResource(R.string.settings_group_browsers),
|
||||
value = "group_browsers",
|
||||
isGroup = true,
|
||||
groupPackages = listOf(
|
||||
"com.android.chrome",
|
||||
"com.yandex.browser"
|
||||
)
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = stringResource(R.string.settings_source_manual),
|
||||
value = "com.android.packageinstaller"
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = stringResource(R.string.settings_source_unknown),
|
||||
value = "null"
|
||||
)
|
||||
)
|
||||
select = dynamicSources
|
||||
),
|
||||
SettingsSection(
|
||||
id = "updates_check_all",
|
||||
title = R.string.settings_updates_check_all_title,
|
||||
description = R.string.settings_updates_check_all_description,
|
||||
icon = R.drawable.ic_arrow_circle_up_24px,
|
||||
icon = R.drawable.ic_arrow_shape_up_stack_2_24px,
|
||||
type = ParamType.SWITCH,
|
||||
defaultVal = ""
|
||||
),
|
||||
|
|
@ -169,21 +127,22 @@ fun SettingsScreen(
|
|||
defaultVal = "auto",
|
||||
select = listOf(
|
||||
SettingsSectionSelect(
|
||||
title = "Auto",
|
||||
title = context.getString(R.string.theme_auto_label),
|
||||
value = "auto"
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = "Dark",
|
||||
title = context.getString(R.string.theme_dark_label),
|
||||
value = "dark"
|
||||
),
|
||||
SettingsSectionSelect(
|
||||
title = "Light",
|
||||
title = context.getString(R.string.theme_light_label),
|
||||
value = "light"
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
val context = LocalContext.current as? Activity
|
||||
|
||||
val activity = context as? Activity
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
|
@ -192,13 +151,15 @@ fun SettingsScreen(
|
|||
IconButton(
|
||||
{
|
||||
val popped = navController.popBackStack()
|
||||
|
||||
if (!popped) {
|
||||
context?.finish()
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(painterResource(R.drawable.ic_arrow_back_24px), "back")
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_arrow_back_24px),
|
||||
stringResource(R.string.back_button_desc)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -217,6 +178,72 @@ fun SettingsScreen(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getDynamicInstallerSources(context: Context): List<SettingsSectionSelect> {
|
||||
val pm = context.packageManager
|
||||
val installedPackages = pm.getInstalledPackages(0)
|
||||
|
||||
val foundInstallers = installedPackages.map { pkg ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
try {
|
||||
pm.getInstallSourceInfo(pkg.packageName).installingPackageName
|
||||
} catch (e: Exception) { null }
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.getInstallerPackageName(pkg.packageName)
|
||||
}
|
||||
}.toSet()
|
||||
|
||||
val result = mutableListOf<SettingsSectionSelect>()
|
||||
|
||||
result.add(SettingsSectionSelect(
|
||||
title = "RuStore/OpenStore",
|
||||
value = "group_openstore",
|
||||
isGroup = true,
|
||||
groupPackages = listOf(
|
||||
"dev.mi6e4ka.openstore",
|
||||
"dev.mi6e4ka.openstore.nightly",
|
||||
"dev.mi6e4ka.openstore.debug",
|
||||
"ru.vk.store"
|
||||
)
|
||||
))
|
||||
|
||||
result.add(SettingsSectionSelect(
|
||||
title = context.getString(R.string.settings_source_manual),
|
||||
value = "group_installers",
|
||||
isGroup = true,
|
||||
groupPackages = listOf(
|
||||
"com.google.android.packageinstaller",
|
||||
"com.android.packageinstaller"
|
||||
)
|
||||
))
|
||||
|
||||
result.add(SettingsSectionSelect(
|
||||
title = context.getString(R.string.settings_source_unknown),
|
||||
value = "null"
|
||||
))
|
||||
|
||||
val handledPackages = mutableSetOf<String?>()
|
||||
handledPackages.add(null)
|
||||
handledPackages.add("null")
|
||||
handledPackages.add("com.google.android.packageinstaller")
|
||||
handledPackages.add("com.android.packageinstaller")
|
||||
handledPackages.add("ru.vk.store")
|
||||
handledPackages.add(context.packageName)
|
||||
handledPackages.addAll(listOf("dev.mi6e4ka.openstore", "dev.mi6e4ka.openstore.nightly", "dev.mi6e4ka.openstore.debug"))
|
||||
|
||||
foundInstallers.filter { it !in handledPackages && it != null }.forEach { pkg ->
|
||||
val label = try {
|
||||
val info = pm.getApplicationInfo(pkg!!, 0)
|
||||
pm.getApplicationLabel(info).toString()
|
||||
} catch (e: Exception) {
|
||||
pkg
|
||||
}
|
||||
result.add(SettingsSectionSelect(title = label ?: pkg!!, value = pkg!!))
|
||||
}
|
||||
Log.i("OS", "Found installers: $result")
|
||||
return result
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsCard(
|
||||
item: SettingsSection
|
||||
|
|
@ -305,7 +332,7 @@ fun SettingsCard(
|
|||
onDismissRequest = { showDialog = false },
|
||||
title = { Text(stringResource(item.title)) },
|
||||
text = {
|
||||
LazyColumn {
|
||||
LazyColumn(modifier = Modifier.heightIn(max = 380.dp)) {
|
||||
items(item.select ?: listOf()) { entry ->
|
||||
val isChecked = if (entry.isGroup) {
|
||||
entry.groupPackages.all { it in selectedPackages }
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@ fun UpdatesScreen(navController: NavController) {
|
|||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.initPreferences(context)
|
||||
viewModel.loadAndCheckUpdates(context)
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +114,7 @@ fun UpdatesScreen(navController: NavController) {
|
|||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_arrow_back_24px),
|
||||
contentDescription = "Back"
|
||||
contentDescription = stringResource(R.string.back_button_desc)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,8 +44,7 @@ data class UpdatesState(
|
|||
val invalidDownloadedApkFiles: List<File>? = null,
|
||||
val invalidDownloadedAppPackage: String? = null,
|
||||
val invalidExpectedHash: String? = null,
|
||||
val invalidActualHash: String? = null,
|
||||
val excludeGooglePlay: Boolean = true
|
||||
val invalidActualHash: String? = null
|
||||
)
|
||||
|
||||
class UpdatesViewModel(private val apkInstaller: ApkInstaller) : ViewModel() {
|
||||
|
|
@ -55,27 +54,7 @@ class UpdatesViewModel(private val apkInstaller: ApkInstaller) : ViewModel() {
|
|||
private val cancelDownloads = mutableMapOf<String, () -> Unit>()
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "updates_prefs"
|
||||
private const val KEY_EXCLUDE_GOOGLE_PLAY = "exclude_google_play"
|
||||
}
|
||||
|
||||
fun initPreferences(context: Context) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
val excludeGooglePlay = prefs.getBoolean(KEY_EXCLUDE_GOOGLE_PLAY, true)
|
||||
_state.update { it.copy(excludeGooglePlay = excludeGooglePlay) }
|
||||
}
|
||||
|
||||
fun toggleExcludeGooglePlay(context: Context) {
|
||||
val newValue = !_state.value.excludeGooglePlay
|
||||
_state.update { it.copy(excludeGooglePlay = newValue) }
|
||||
|
||||
// Сохраняем в SharedPreferences
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(KEY_EXCLUDE_GOOGLE_PLAY, newValue)
|
||||
.apply()
|
||||
|
||||
loadAndCheckUpdates(context)
|
||||
private const val DEFAULT_SOURCES = "dev.mi6e4ka.openstore;dev.mi6e4ka.openstore.nightly;dev.mi6e4ka.openstore.debug;ru.vk.store"
|
||||
}
|
||||
|
||||
fun loadAndCheckUpdates(context: Context) {
|
||||
|
|
@ -300,7 +279,7 @@ class UpdatesViewModel(private val apkInstaller: ApkInstaller) : ViewModel() {
|
|||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
val allowedSources = prefs.getString("updates_sources", "")?.split(";") ?: listOf()
|
||||
val allowedSources = prefs.getString("updates_sources", DEFAULT_SOURCES)?.split(";") ?: listOf()
|
||||
val allowAllSources = prefs.getBoolean("updates_check_all", false)
|
||||
|
||||
return packages
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="199"
|
||||
android:viewportHeight="199">
|
||||
android:viewportWidth="130"
|
||||
android:viewportHeight="130">
|
||||
<path
|
||||
android:pathData="M0,0h199v199h-199z">
|
||||
android:pathData="M0,0h130v130h-130z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="40"
|
||||
android:startY="15.5"
|
||||
android:endX="156.5"
|
||||
android:endY="180"
|
||||
<gradient
|
||||
android:startX="26.13"
|
||||
android:startY="10.13"
|
||||
android:endX="102.24"
|
||||
android:endY="117.59"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF009DFF"/>
|
||||
<item android:offset="1" android:color="#FF004DB1"/>
|
||||
<item android:offset="0" android:color="#FF05A0FF"/>
|
||||
<item android:offset="1" android:color="#FF014DB1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
|
|
|
|||
10
app/src/main/res/drawable/ic_arrow_shape_up_stack_2_24px.xml
Normal 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="M360,920L360,800L160,800L480,440L800,800L600,800L600,920L360,920ZM440,840L520,840L520,720L622,720L480,560L338,720L440,720L440,840ZM160,600L480,240L800,600L693,600L480,360L267,600L160,600ZM160,400L480,40L800,400L693,400L480,160L267,400L160,400ZM480,720L480,720L480,720L480,720L480,720L480,720L480,720Z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_checklist_24px.xml
Normal 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="M222,760L80,618L136,562L221,647L391,477L447,534L222,760ZM222,440L80,298L136,242L221,327L391,157L447,214L222,440ZM520,680L520,600L880,600L880,680L520,680ZM520,360L520,280L880,280L880,360L520,360Z"/>
|
||||
</vector>
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="127"
|
||||
android:viewportHeight="133">
|
||||
<group android:scaleX="0.40847954"
|
||||
android:scaleY="0.42777777"
|
||||
android:translateX="37.56155"
|
||||
android:translateY="38.052776">
|
||||
android:viewportWidth="137"
|
||||
android:viewportHeight="130">
|
||||
<group android:scaleX="0.7078261"
|
||||
android:scaleY="0.67165977"
|
||||
android:translateX="20.462858"
|
||||
android:translateY="21.342113">
|
||||
<path
|
||||
android:pathData="M126.59,82.78C126.64,77.58 122.45,73.06 116.98,73.06H105.29L111.37,48.64C111.56,47.88 111.66,47.12 111.67,46.37C111.72,41.17 107.53,36.65 102.05,36.65H90.41L96.45,12.45C97.96,6.37 93.37,0.47 87.13,0.47L22.23,0.47C16.71,0.47 11.92,4.22 10.58,9.58L0.95,48.25C-0.57,54.33 4.02,60.24 10.27,60.24L75.17,60.24C79.12,60.24 82.7,58.3 84.89,55.24C85.05,55.19 85.23,55.2 85.37,55.3C85.62,55.47 85.74,55.78 85.65,56.06C84.11,61.15 78.91,65.99 72.35,66.48L22.83,68.16C21.17,68.21 19.75,69.31 19.23,70.88L15.86,84.44C14.35,90.53 18.94,96.43 25.18,96.43C46.6,96.59 68.37,96.54 89.86,96.55C92.77,96.55 95.01,95.56 96.64,94.46C96.91,94.28 97.18,94.09 97.44,93.89C97.74,93.64 98.04,93.38 98.32,93.11C98.75,92.7 99.15,92.27 99.51,91.8L99.52,91.8C99.71,91.6 100.01,91.55 100.24,91.71C100.49,91.88 100.6,92.18 100.52,92.47C98.98,97.56 93.78,102.42 87.22,102.89L37.7,104.57C36.05,104.61 34.64,105.71 34.15,107.26L30.76,120.85C29.24,126.93 33.83,132.84 40.07,132.84H104.97C110.49,132.84 115.29,129.08 116.62,123.72L126.25,85.05C126.44,84.29 126.54,83.54 126.54,82.79L126.59,82.78Z"
|
||||
android:pathData="M107.11,60.88V95.33C107.11,97.72 106.26,99.76 104.56,101.45C102.86,103.15 100.82,104 98.44,104H37.78C35.39,104 33.35,103.15 31.65,101.45C29.96,99.76 29.11,97.72 29.11,95.33V60.88C27.45,59.37 26.17,57.42 25.26,55.03C24.36,52.65 24.34,50.05 25.21,47.23L29.76,32.5C30.34,30.62 31.37,29.07 32.85,27.84C34.33,26.61 36.04,26 37.99,26H98.22C100.18,26 101.87,26.6 103.32,27.79C104.76,28.98 105.81,30.55 106.46,32.5L111.01,47.23C111.88,50.05 111.86,52.61 110.95,54.92C110.05,57.24 108.77,59.22 107.11,60.88ZM77.64,56.33C79.59,56.33 81.07,55.67 82.08,54.33C83.09,52.99 83.49,51.49 83.28,49.83L80.89,34.67H72.44V50.7C72.44,52.22 72.95,53.53 73.96,54.65C74.97,55.77 76.2,56.33 77.64,56.33ZM58.14,56.33C59.8,56.33 61.16,55.77 62.2,54.65C63.25,53.53 63.78,52.22 63.78,50.7V34.67H55.33L52.94,49.83C52.65,51.57 53.03,53.08 54.08,54.38C55.13,55.68 56.48,56.33 58.14,56.33ZM38.86,56.33C40.16,56.33 41.3,55.86 42.27,54.92C43.25,53.99 43.84,52.79 44.06,51.35L46.44,34.67H37.99L33.66,49.18C33.22,50.63 33.46,52.18 34.36,53.84C35.27,55.5 36.76,56.33 38.86,56.33ZM97.36,56.33C99.45,56.33 100.97,55.5 101.91,53.84C102.85,52.18 103.06,50.63 102.56,49.18L98.01,34.67H89.78L92.16,51.35C92.38,52.79 92.97,53.99 93.95,54.92C94.92,55.86 96.06,56.33 97.36,56.33ZM37.78,95.33H98.44V64.78C98.08,64.93 97.85,65 97.74,65H97.36C95.41,65 93.69,64.68 92.21,64.03C90.73,63.38 89.27,62.33 87.82,60.88C86.53,62.18 85.04,63.19 83.38,63.92C81.72,64.64 79.95,65 78.07,65C76.13,65 74.3,64.64 72.6,63.92C70.91,63.19 69.41,62.18 68.11,60.88C66.88,62.18 65.45,63.19 63.83,63.92C62.2,64.64 60.45,65 58.58,65C56.48,65 54.58,64.64 52.89,63.92C51.19,63.19 49.69,62.18 48.39,60.88C46.88,62.4 45.38,63.47 43.9,64.08C42.42,64.69 40.74,65 38.86,65H38.37C38.19,65 37.99,64.93 37.78,64.78V95.33Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
|
|||
14
app/src/main/res/drawable/ic_launcher_monochrome.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="137"
|
||||
android:viewportHeight="130">
|
||||
<group android:scaleX="0.72"
|
||||
android:scaleY="0.6832117"
|
||||
android:translateX="19.18"
|
||||
android:translateY="20.59124">
|
||||
<path
|
||||
android:pathData="M107.11,60.88V95.33C107.11,97.72 106.26,99.76 104.56,101.45C102.86,103.15 100.82,104 98.44,104H37.78C35.39,104 33.35,103.15 31.65,101.45C29.96,99.76 29.11,97.72 29.11,95.33V60.88C27.45,59.37 26.17,57.42 25.26,55.03C24.36,52.65 24.34,50.05 25.21,47.23L29.76,32.5C30.34,30.62 31.37,29.07 32.85,27.84C34.33,26.61 36.04,26 37.99,26H98.22C100.18,26 101.87,26.6 103.32,27.79C104.76,28.98 105.81,30.55 106.46,32.5L111.01,47.23C111.88,50.05 111.86,52.61 110.95,54.92C110.05,57.24 108.77,59.22 107.11,60.88ZM77.64,56.33C79.59,56.33 81.07,55.67 82.08,54.33C83.09,52.99 83.49,51.49 83.28,49.83L80.89,34.67H72.44V50.7C72.44,52.22 72.95,53.53 73.96,54.65C74.97,55.77 76.2,56.33 77.64,56.33ZM58.14,56.33C59.8,56.33 61.16,55.77 62.2,54.65C63.25,53.53 63.78,52.22 63.78,50.7V34.67H55.33L52.94,49.83C52.65,51.57 53.03,53.08 54.08,54.38C55.13,55.68 56.48,56.33 58.14,56.33ZM38.86,56.33C40.16,56.33 41.3,55.86 42.27,54.92C43.25,53.99 43.84,52.79 44.06,51.35L46.44,34.67H37.99L33.66,49.18C33.22,50.63 33.46,52.18 34.36,53.84C35.27,55.5 36.76,56.33 38.86,56.33ZM97.36,56.33C99.45,56.33 100.97,55.5 101.91,53.84C102.85,52.18 103.06,50.63 102.56,49.18L98.01,34.67H89.78L92.16,51.35C92.38,52.79 92.97,53.99 93.95,54.92C94.92,55.86 96.06,56.33 97.36,56.33ZM37.78,95.33H98.44V64.78C98.08,64.93 97.85,65 97.74,65H97.36C95.41,65 93.69,64.68 92.21,64.03C90.73,63.38 89.27,62.33 87.82,60.88C86.53,62.18 85.04,63.19 83.38,63.92C81.72,64.64 79.95,65 78.07,65C76.13,65 74.3,64.64 72.6,63.92C70.91,63.19 69.41,62.18 68.11,60.88C66.88,62.18 65.45,63.19 63.83,63.92C62.2,64.64 60.45,65 58.58,65C56.48,65 54.58,64.64 52.89,63.92C51.19,63.19 49.69,62.18 48.39,60.88C46.88,62.4 45.38,63.47 43.9,64.08C42.42,64.69 40.74,65 38.86,65H38.37C38.19,65 37.99,64.93 37.78,64.78V95.33Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -2,5 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -2,5 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
|
|
@ -2,7 +2,7 @@
|
|||
<string name="app_name">OpenStore</string>
|
||||
<string name="safety_card_text">About app safety</string>
|
||||
<string name="error_message">Error: %1$s</string>
|
||||
<string name="provided_by">Provided by %1$s</string>
|
||||
<string name="provided_by_third_party">App from %1$s</string>
|
||||
<string name="device_unsupported">Not compatible with your device</string>
|
||||
<string name="installing">Installing…</string>
|
||||
<string name="downloading">Downloading…%1$s</string>
|
||||
|
|
@ -44,10 +44,10 @@
|
|||
<string name="settings_title">Settings</string>
|
||||
<string name="settings_installer_title">Installer</string>
|
||||
<string name="settings_installer_description">If there are problems installing applications</string>
|
||||
<string name="settings_updates_sources_title">Source updates whitelist</string>
|
||||
<string name="settings_updates_sources_description">Sources allowed to check for updates</string>
|
||||
<string name="settings_updates_check_all_title">Check all installed applications</string>
|
||||
<string name="settings_updates_check_all_description">Do not limit update checks to the whitelist. RuStore will get access to the entire list of installed applications.</string>
|
||||
<string name="settings_updates_sources_title">Tracked sources</string>
|
||||
<string name="settings_updates_sources_description">Updates will be checked for applications from selected sources</string>
|
||||
<string name="settings_updates_check_all_title">Check all applications</string>
|
||||
<string name="settings_updates_check_all_description">Do not limit the update check to selected sources. RuStore will get access to the entire list of applications on device</string>
|
||||
<string name="settings_save_button">Save</string>
|
||||
<string name="updates_title">Updates</string>
|
||||
<string name="check_updates">Check for updates</string>
|
||||
|
|
@ -64,7 +64,15 @@
|
|||
<string name="settings_source_aurora_store">Aurora Store</string>
|
||||
<string name="settings_group_file_managers">File Managers</string>
|
||||
<string name="settings_group_browsers">Browsers</string>
|
||||
<string name="signature_mismatch_warning">Possible installation error due to different signatures</string>
|
||||
<string name="signature_mismatch_warning">Installed from another source</string>
|
||||
<string name="settings_source_manual">Manual Installed</string>
|
||||
<string name="settings_source_unknown">Unknown (ADB/Other)</string>
|
||||
<string name="provided_by_developer">Provided by developer</string>
|
||||
<string name="mobile_label">Mobile</string>
|
||||
<string name="tv_label">TV</string>
|
||||
<string name="theme_auto_label">Auto</string>
|
||||
<string name="theme_dark_label">Dark</string>
|
||||
<string name="theme_light_label">Light</string>
|
||||
<string name="back_button_desc">Back</string>
|
||||
<string name="tts_current_platform">Apps for platfom</string>
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
<resources>
|
||||
<style name="AppTheme" parent="android:Theme.Material.NoActionBar">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="AppTheme" parent="android:Theme.Material.NoActionBar" />
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
<string name="app_name">OpenStore</string>
|
||||
<string name="safety_card_text">О безопасности приложений</string>
|
||||
<string name="error_message">Ошибка: %1$s</string>
|
||||
<string name="provided_by">Предоставлено %1$s</string>
|
||||
<string name="provided_by_developer">Предоставлено разработчиком</string>
|
||||
<string name="provided_by_third_party">Приложение из %1$s</string>
|
||||
<string name="device_unsupported">Не поддерживается на этом устройстве</string>
|
||||
<string name="installing">Установка…</string>
|
||||
<string name="downloading">Загрузка…%1$s</string>
|
||||
|
|
@ -44,10 +45,9 @@
|
|||
<string name="settings_title">Настройки</string>
|
||||
<string name="settings_installer_title">Установщик</string>
|
||||
<string name="settings_installer_description">Если есть проблемы при установке приложений</string>
|
||||
<string name="settings_updates_sources_title">Белый список источников обновлений</string>
|
||||
<string name="settings_updates_sources_description">Источники, для которых разрешена проверка обновлений</string>
|
||||
<string name="settings_updates_check_all_title">Проверять все установленные приложения</string>
|
||||
<string name="settings_updates_check_all_description">Не ограничивать проверку обновлений белым списком. RuStore получит доступ ко всему списку установленных приложений.</string>
|
||||
<string name="settings_updates_sources_title">Отслеживаемые источники</string>
|
||||
<string name="settings_updates_check_all_title">Проверять все приложения</string>
|
||||
<string name="settings_updates_check_all_description">Не ограничивать проверку обновлений выбранными источниками. RuStore получит доступ ко всему списку приложений на устройстве</string>
|
||||
<string name="settings_save_button">Сохранить</string>
|
||||
<string name="updates_title">Обновления</string>
|
||||
<string name="check_updates">Проверить обновления</string>
|
||||
|
|
@ -64,7 +64,29 @@
|
|||
<string name="settings_source_aurora_store">Aurora Store</string>
|
||||
<string name="settings_group_file_managers">Файловые менеджеры</string>
|
||||
<string name="settings_group_browsers">Браузеры</string>
|
||||
<string name="signature_mismatch_warning">Возможна ошибка установки из-за отличия в подписях</string>
|
||||
<string name="settings_source_manual">Установлено вручную</string>
|
||||
<string name="settings_source_unknown">Неизвестно (ADB/Другое)</string>
|
||||
<string name="signature_mismatch_warning">Установлено из другого источника</string>
|
||||
<string name="mobile_label">Телефон</string>
|
||||
<string name="tv_label">ТВ</string>
|
||||
<string name="theme_auto_label">Системная</string>
|
||||
<string name="theme_dark_label">Тёмная</string>
|
||||
<string name="theme_light_label">Светлая</string>
|
||||
<string name="settings_updates_sources_description">Обновления будут проверяться для приложений из выбранных источников</string>
|
||||
<string name="back_button_desc">Назад</string>
|
||||
<string name="tts_current_platform">Приложения для платформы</string>
|
||||
<string name="copy_link_desc">Скопировать ссылку на apk</string>
|
||||
<string name="download_apk_desc">Скачать apk без установки</string>
|
||||
<string name="open_in_browser_desc">Открыть страницу приложения в браузере</string>
|
||||
<string name="app_size_desc">Вес приложения</string>
|
||||
<string name="latest_version_desc">Текущая версия</string>
|
||||
<string name="last_update_desc">Последнее обновление</string>
|
||||
<string name="min_api_desc">Минимальная версия android</string>
|
||||
<string name="installer_type_desc">Тип установочного файла</string>
|
||||
<string name="downloads_desc">Количество установок</string>
|
||||
<string name="age_legal_desc">Возрастная категория</string>
|
||||
<string name="reviews_desc">Рейтинг и отзывы\nСредняя оценка: %1$.2f\nВсего оценок: %2$d</string>
|
||||
<string name="review_rating_desc">оценка: %1$d</string>
|
||||
<string name="likes_desc">Лайки</string>
|
||||
<string name="dislikes_desc">Дизлайки</string>
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
<resources>
|
||||
<style name="AppTheme" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:statusBarColor">#33000000</item>
|
||||
</style>
|
||||
<style name="AppTheme" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">rustore.ru</domain>
|
||||
<pin-set expiration="2028-11-21">
|
||||
<pin digest="SHA-256">hETpgVvaLC0bvcGG3t0cuqiHvr4XyP2MTwCiqhgRWwU=</pin>
|
||||
</pin-set>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 426 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 52 KiB |