Compare commits
17 commits
nightly-20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eda5bf6c9d | ||
|
|
823fb11546 | ||
|
|
74c0e35c76 | ||
|
|
0cc3da6949 | ||
|
|
1ee5d87aeb | ||
|
|
f4320992c8 | ||
|
|
f4ee755783 | ||
|
|
f09226ef3a | ||
|
|
a6509993a3 | ||
|
|
22e85ecb8d | ||
|
|
273f572968 | ||
|
|
e64a1d4581 | ||
|
|
7793e50941 | ||
|
|
8a67a6a0dd | ||
|
|
ae608e8370 | ||
|
|
2dc1f788e7 | ||
|
|
ba815e82b0 |
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"
|
||||
11
README.md
|
|
@ -36,11 +36,12 @@ Verification info:
|
|||
- [ ] ...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") {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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
|
||||
|
|
@ -63,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 ->
|
||||
|
|
@ -70,11 +73,12 @@ class ApkInstaller(private val context: Context) {
|
|||
val fileName = file.url.split("/").last()
|
||||
val apkFile = getApkFile(fileName)
|
||||
if (!downloadedFiles.contains(apkFile)) {
|
||||
downloadedPackages.add(apkFile)
|
||||
downloadedFiles.add(apkFile)
|
||||
}
|
||||
|
||||
if (apkFile.exists() && apkFile.length() == file.size) {
|
||||
if (verifyFileHash(apkFile, file.hash)) {
|
||||
totalBytesDownloaded.addAndFetch(file.size)
|
||||
return@async apkFile
|
||||
}
|
||||
apkFile.delete()
|
||||
|
|
@ -83,42 +87,38 @@ class ApkInstaller(private val context: Context) {
|
|||
val request = Request.Builder().url(file.url).build()
|
||||
val call = okHttpClient.newCall(request)
|
||||
|
||||
// Связываем отмену корутины с отменой сетевого вызова
|
||||
val job = coroutineContext[Job]
|
||||
val cancellationDisposable = job?.invokeOnCompletion {
|
||||
if (job.isCancelled) call.cancel()
|
||||
}
|
||||
// Связываем отмену корутины с OkHttp
|
||||
val job = coroutineContext[Job]!!
|
||||
val cancelHandler = job.invokeOnCompletion { call.cancel() }
|
||||
|
||||
try {
|
||||
call.execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Ошибка загрузки: ${response.code}")
|
||||
|
||||
val responseBody =
|
||||
response.body ?: throw IOException("Пустой ответ от сервера")
|
||||
val responseBody = response.body ?: throw IOException("Пустой ответ")
|
||||
|
||||
responseBody.byteStream().use { inputStream ->
|
||||
FileOutputStream(apkFile).use { outputStream ->
|
||||
val buffer = ByteArray(8192)
|
||||
val buffer = ByteArray(16384) // Увеличил буфер для скорости
|
||||
var bytesRead: Int
|
||||
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
// Проверяем статус отмены на каждой итерации
|
||||
if (call.isCanceled()) {
|
||||
throw IOException("Canceled")
|
||||
}
|
||||
|
||||
job.ensureActive()
|
||||
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)
|
||||
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()
|
||||
|
|
@ -128,11 +128,10 @@ class ApkInstaller(private val context: Context) {
|
|||
if (!verifyFileHash(apkFile, file.hash)) {
|
||||
throw HashMismatchException(apkFile, file.hash)
|
||||
}
|
||||
|
||||
apkFile
|
||||
}
|
||||
} finally {
|
||||
cancellationDisposable?.dispose()
|
||||
cancelHandler.dispose()
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
|
|
@ -142,7 +141,6 @@ class ApkInstaller(private val context: Context) {
|
|||
onSuccess(resultingFiles)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Выполняем очистку и уведомление в NonCancellable, чтобы код не пролетел мимо
|
||||
withContext(NonCancellable) {
|
||||
if (e is HashMismatchException) {
|
||||
val expectedHash = formatHashForDisplay(e.expectedHash)
|
||||
|
|
@ -152,14 +150,11 @@ class ApkInstaller(private val context: Context) {
|
|||
onInvalidHash(filesCopy, expectedHash, actualHash)
|
||||
}
|
||||
} else {
|
||||
downloadedFiles.forEach { file ->
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
// Удаляем недокачанные файлы при любой другой ошибке или отмене
|
||||
downloadedFiles.forEach { if (it.exists()) it.delete() }
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (e is CancellationException || (e is IOException && e.message == "Canceled")) {
|
||||
if (e is CancellationException || (e is IOException && callIsCanceled(e))) {
|
||||
onCancel()
|
||||
} else {
|
||||
onError(e)
|
||||
|
|
@ -170,13 +165,13 @@ class ApkInstaller(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
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 val downloadedPackages = Collections.synchronizedList(mutableListOf<File>())
|
||||
|
||||
private class HashMismatchException(val file: File, val expectedHash: String) : IOException("Hash mismatch")
|
||||
|
||||
|
|
@ -199,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)
|
||||
)
|
||||
}
|
||||
}
|
||||
) }
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ 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 = dynamicSources
|
||||
|
|
@ -114,7 +114,7 @@ fun SettingsScreen(
|
|||
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 = ""
|
||||
),
|
||||
|
|
@ -127,15 +127,15 @@ 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"
|
||||
),
|
||||
)
|
||||
|
|
@ -156,7 +156,10 @@ fun SettingsScreen(
|
|||
}
|
||||
}
|
||||
) {
|
||||
Icon(painterResource(R.drawable.ic_arrow_back_24px), "back")
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_arrow_back_24px),
|
||||
stringResource(R.string.back_button_desc)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -206,7 +209,12 @@ private fun getDynamicInstallerSources(context: Context): List<SettingsSectionSe
|
|||
|
||||
result.add(SettingsSectionSelect(
|
||||
title = context.getString(R.string.settings_source_manual),
|
||||
value = "com.google.android.packageinstaller"
|
||||
value = "group_installers",
|
||||
isGroup = true,
|
||||
groupPackages = listOf(
|
||||
"com.google.android.packageinstaller",
|
||||
"com.android.packageinstaller"
|
||||
)
|
||||
))
|
||||
|
||||
result.add(SettingsSectionSelect(
|
||||
|
|
@ -218,6 +226,7 @@ private fun getDynamicInstallerSources(context: Context): List<SettingsSectionSe
|
|||
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"))
|
||||
|
|
|
|||
|
|
@ -114,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)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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>
|
||||
|
|
@ -68,4 +68,11 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -45,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>
|
||||
|
|
@ -68,4 +67,26 @@
|
|||
<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 |