Compare commits

...

17 commits

Author SHA1 Message Date
mi6e4ka
eda5bf6c9d Merge pull request 'feat: enable edge-to-edge display' (#27) from oltine/openstore:edge-to-edge into main
Reviewed-on: https://codeberg.org/mi6e4ka/openstore/pulls/27
2026-06-25 16:13:51 +02:00
oltnd
823fb11546 feat: enable edge-to-edge display 2026-06-18 21:41:50 +03:00
Michael
74c0e35c76
chore: revert ssl pinning 2026-06-18 21:31:45 +03:00
Michael
0cc3da6949
chore: revert ssl pinning 2026-06-18 21:27:39 +03:00
mi6e4ka
1ee5d87aeb Merge pull request 'Help fix' (#23) from oltine/openstore:main into main
Reviewed-on: https://codeberg.org/mi6e4ka/openstore/pulls/23
2026-06-06 23:32:05 +02:00
oltine
f4320992c8 Merge pull request 'Применить правку' (#1) from rustore-help-fix into main
Reviewed-on: https://codeberg.org/oltine/openstore/pulls/1
2026-06-04 21:09:57 +02:00
oltine
f4ee755783 Применить правку 2026-06-04 21:09:36 +02:00
Michael
f09226ef3a
feat: improved accessibility (fixed #22, fixed #21) 2026-05-19 14:00:54 +03:00
Michael
a6509993a3
chore: add new GS root CA for cert pinning 2026-05-18 20:24:11 +03:00
Michael
22e85ecb8d
chore: change icons 2026-05-04 16:21:41 +03:00
Michael
273f572968
chore: update translations 2026-05-04 16:18:08 +03:00
Michael
e64a1d4581
feat: add another AOSP installer in group 2026-05-04 15:33:27 +03:00
Michael
7793e50941
fix: another trying to fix apk downloading 2026-05-03 19:30:39 +03:00
Michael
8a67a6a0dd
feat: change animations 2026-05-03 19:18:44 +03:00
Michael
ae608e8370
chore: update screenshots 2026-05-03 19:08:26 +03:00
Michael
2dc1f788e7
chore: update release icon 2026-05-03 18:39:04 +03:00
Michael
ba815e82b0
tests: add UI tests for automatization screenshots 2026-05-03 18:27:49 +03:00
47 changed files with 324 additions and 138 deletions

1
Maestro/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.maestro

37
Maestro/flow.yaml Normal file
View 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"

View file

@ -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)

View file

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

View file

@ -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") {

View file

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

View file

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

View file

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

View file

@ -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()
}
)
}
}
}

View file

@ -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() {

View file

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

View file

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

View file

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

View file

@ -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)
)
}
},

View file

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

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="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>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="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>

View file

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

View 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>

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB