diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml
index 3ff6b97..033ebe2 100644
--- a/.forgejo/workflows/build.yaml
+++ b/.forgejo/workflows/build.yaml
@@ -54,8 +54,8 @@ jobs:
if: steps.get_release.outputs.prerelease == 'true'
run: |
./gradlew assembleNightly --stacktrace \
- -PversionName=${{ steps.get_release.outputs.tag_name }} \
-PversionCode=$(date +%s)
+ # -PversionName=${{ steps.get_release.outputs.tag_name }}
- name: Build unsigned release APK
if: steps.get_release.outputs.prerelease != 'true'
diff --git a/Maestro/.gitignore b/Maestro/.gitignore
new file mode 100644
index 0000000..ca7dc48
--- /dev/null
+++ b/Maestro/.gitignore
@@ -0,0 +1 @@
+.maestro
\ No newline at end of file
diff --git a/Maestro/flow.yaml b/Maestro/flow.yaml
new file mode 100644
index 0000000..a96d651
--- /dev/null
+++ b/Maestro/flow.yaml
@@ -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"
\ No newline at end of file
diff --git a/README.md b/README.md
index c0ccfb8..04104d7 100644
--- a/README.md
+++ b/README.md
@@ -27,21 +27,21 @@ Verification info:
- [x] Search apps
- [x] View apps descriptions, rating and reviews
- [x] Native app installing
-- [x] Updating apps (for now only manually)
- [x] Android 5.0+ support!
- [x] Search and view TV apps
+- [x] Check all apps for updates
## Roadmap
-- [ ] TV navigation support
-- [ ] Automatically check for updates
+- [ ] Better TV navigation support
- [ ] ...maybe more?
## Screenshots
-[
](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png)
-[
](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png)
-[
](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png)
-[
](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png)
-[
](https://codeberg.org/mi6e4ka/openstore/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png)
+
+
+
+
+
+
## Building the app
See [here](https://codeberg.org/mi6e4ka/openstore/src/branch/main/BUILDING.md)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 90144cc..7deb6c6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -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">
-
-
-
-
-
+
+
+
+
+
+
@@ -76,4 +76,4 @@
android:name=".internal.installer.PackageInstallerStatusReceiver"
android:exported="false" />
-
\ No newline at end of file
+
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt b/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt
index 84a8635..5df0999 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt
@@ -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") {
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/internal/installer/ApkInstaller.kt b/app/src/main/java/dev/mi6e4ka/openstore/internal/installer/ApkInstaller.kt
index 0f958ed..bedb935 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/internal/installer/ApkInstaller.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/internal/installer/ApkInstaller.kt
@@ -19,9 +19,12 @@ import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
@@ -36,6 +39,7 @@ import java.util.zip.ZipOutputStream
import kotlin.concurrent.atomics.AtomicLong
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.coroutines.cancellation.CancellationException
+import kotlin.coroutines.coroutineContext
class ApkInstaller(private val context: Context) {
@@ -60,6 +64,8 @@ class ApkInstaller(private val context: Context) {
val totalBytesToDownload = files.sumOf { it.size }
val totalBytesDownloaded = AtomicLong(0)
val downloadedFiles = Collections.synchronizedList(mutableListOf())
+ var lastUpdateMillis = 0L
+
try {
val resultingFiles = coroutineScope {
files.map { file ->
@@ -72,6 +78,7 @@ class ApkInstaller(private val context: Context) {
if (apkFile.exists() && apkFile.length() == file.size) {
if (verifyFileHash(apkFile, file.hash)) {
+ totalBytesDownloaded.addAndFetch(file.size)
return@async apkFile
}
apkFile.delete()
@@ -79,46 +86,52 @@ class ApkInstaller(private val context: Context) {
val request = Request.Builder().url(file.url).build()
val call = okHttpClient.newCall(request)
+
+ // Связываем отмену корутины с OkHttp
+ val job = coroutineContext[Job]!!
+ val cancelHandler = job.invokeOnCompletion { call.cancel() }
- call.execute().use { response ->
- if (!response.isSuccessful) throw IOException("Ошибка загрузки: ${response.code}")
+ try {
+ call.execute().use { response ->
+ if (!response.isSuccessful) throw IOException("Ошибка загрузки: ${response.code}")
- val responseBody =
- response.body ?: throw IOException("Пустой ответ от сервера")
-
- responseBody.byteStream().use { inputStream ->
- FileOutputStream(apkFile).use { outputStream ->
- val buffer = ByteArray(8192)
- var bytesRead: Int
+ val responseBody = response.body ?: throw IOException("Пустой ответ")
+
+ responseBody.byteStream().use { inputStream ->
+ FileOutputStream(apkFile).use { outputStream ->
+ val buffer = ByteArray(16384) // Увеличил буфер для скорости
+ var bytesRead: Int
- while (inputStream.read(buffer).also { bytesRead = it } != -1) {
- if (call.isCanceled()) {
- throw IOException("Canceled")
- }
-
- outputStream.write(buffer, 0, bytesRead)
-
- val newTotalDownloaded =
- totalBytesDownloaded.addAndFetch(bytesRead.toLong())
- val percent = if (totalBytesToDownload > 0) {
- (newTotalDownloaded * 100 / totalBytesToDownload).toInt()
- } else {
- 0
- }
-
- withContext(Dispatchers.Main) {
- onProgress(percent, newTotalDownloaded, totalBytesToDownload)
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
+ job.ensureActive()
+ outputStream.write(buffer, 0, bytesRead)
+
+ val currentTotal = totalBytesDownloaded.addAndFetch(bytesRead.toLong())
+
+ // Троттлинг обновлений UI (не чаще 10 раз в секунду)
+ val now = System.currentTimeMillis()
+ if (now - lastUpdateMillis > 100) {
+ lastUpdateMillis = now
+ val percent = if (totalBytesToDownload > 0) {
+ (currentTotal * 100 / totalBytesToDownload).toInt()
+ } else 0
+
+ withContext(Dispatchers.Main) {
+ onProgress(percent, currentTotal, totalBytesToDownload)
+ }
+ }
}
+ outputStream.flush()
}
- outputStream.flush()
}
- }
- if (!verifyFileHash(apkFile, file.hash)) {
- throw HashMismatchException(apkFile, file.hash)
+ if (!verifyFileHash(apkFile, file.hash)) {
+ throw HashMismatchException(apkFile, file.hash)
+ }
+ apkFile
}
-
- apkFile
+ } finally {
+ cancelHandler.dispose()
}
}
}.awaitAll()
@@ -128,35 +141,36 @@ class ApkInstaller(private val context: Context) {
onSuccess(resultingFiles)
}
} catch (e: Exception) {
- if (e is HashMismatchException) {
- val expectedHash = formatHashForDisplay(e.expectedHash)
- val actualHash = computeExistingHash(e.file)
- val filesCopy = downloadedFiles.toList()
- withContext(Dispatchers.Main) {
- onInvalidHash(filesCopy, expectedHash, actualHash)
- }
- return@launch
- }
-
- downloadedFiles.forEach { file ->
- if (file.exists()) {
- file.delete()
- }
- }
-
- withContext(Dispatchers.Main) {
- if (e is CancellationException) {
- onCancel()
+ withContext(NonCancellable) {
+ if (e is HashMismatchException) {
+ val expectedHash = formatHashForDisplay(e.expectedHash)
+ val actualHash = computeExistingHash(e.file)
+ val filesCopy = downloadedFiles.toList()
+ withContext(Dispatchers.Main) {
+ onInvalidHash(filesCopy, expectedHash, actualHash)
+ }
} else {
- onError(e)
+ // Удаляем недокачанные файлы при любой другой ошибке или отмене
+ downloadedFiles.forEach { if (it.exists()) it.delete() }
+
+ withContext(Dispatchers.Main) {
+ if (e is CancellationException || (e is IOException && callIsCanceled(e))) {
+ onCancel()
+ } else {
+ onError(e)
+ }
+ }
}
}
}
}
- return {
- downloadRunner.cancel()
- }
+ return { downloadRunner.cancel() }
+ }
+
+ private fun callIsCanceled(e: IOException): Boolean {
+ return e.message?.contains("Canceled", ignoreCase = true) == true ||
+ e.message?.contains("Socket closed", ignoreCase = true) == true
}
private class HashMismatchException(val file: File, val expectedHash: String) : IOException("Hash mismatch")
@@ -180,8 +194,6 @@ class ApkInstaller(private val context: Context) {
private fun verifyFileHash(file: File, expectedHash: String): Boolean {
val normalizedHash = expectedHash.trim().ifEmpty { return true }
val expectedBytes = decodeHashValue(normalizedHash) ?: return true
-
- // Only XXH64 is supported now (8 bytes)
if (expectedBytes.size != 8) return true
val actualBytes = digestFileXXH64(file) ?: return false
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/components/Gallery.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/components/Gallery.kt
index 0865a21..4fc242d 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/components/Gallery.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/components/Gallery.kt
@@ -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) {
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) }
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/details/DetailsScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/details/DetailsScreen.kt
index fbf68ec..1601359 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/details/DetailsScreen.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/details/DetailsScreen.kt
@@ -86,6 +86,9 @@ import kotlinx.datetime.toLocalDateTime
import android.Manifest
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
@@ -141,7 +144,7 @@ fun DetailsScreen(
IconButton(onClick = { navController.popBackStack() }) {
Icon(
painter = painterResource(R.drawable.ic_arrow_back_24px),
- contentDescription = "Back"
+ contentDescription = stringResource(R.string.back_button_desc)
)
}
},
@@ -153,7 +156,10 @@ fun DetailsScreen(
}
}
){
- Icon(painterResource(R.drawable.ic_link_24px), null)
+ Icon(
+ painterResource(R.drawable.ic_link_24px),
+ stringResource(R.string.copy_link_desc)
+ )
}
IconButton(
{
@@ -175,7 +181,10 @@ fun DetailsScreen(
}
}
){
- Icon(painterResource(R.drawable.apk_install_24px), null)
+ Icon(
+ painterResource(R.drawable.apk_install_24px),
+ stringResource(R.string.download_apk_desc)
+ )
}
IconButton(onClick = {
val intent = Intent(Intent.ACTION_VIEW, "https://www.rustore.ru/catalog/app/${state.item?.packageName ?: ""}".toUri()).apply {
@@ -184,7 +193,10 @@ fun DetailsScreen(
}
context.startActivity(intent)
}, enabled = state.item != null) {
- Icon(painterResource(R.drawable.ic_captive_portal_24px), null)
+ Icon(
+ painterResource(R.drawable.ic_captive_portal_24px),
+ stringResource(R.string.open_in_browser_desc)
+ )
}
}
) }
@@ -282,9 +294,9 @@ fun DetailsScreen(
modifier = Modifier.size(28.dp)
)
if (item.aggregatorInfo != null) {
- Text(stringResource(R.string.provided_by, item.aggregatorInfo.source))
+ Text(stringResource(R.string.provided_by_third_party, item.aggregatorInfo.source))
} else {
- Text(stringResource(R.string.provided_by, "RuStore"))
+ Text(stringResource(R.string.provided_by_developer))
}
}
}
@@ -332,7 +344,7 @@ fun DetailsScreen(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(item.appName, fontSize = 24.sp, fontWeight = FontWeight.Bold, maxLines = 2, overflow = TextOverflow.Ellipsis)
- Text(item.aggregatorInfo?.companyName ?: item.companyName, maxLines = 2, overflow = TextOverflow.Ellipsis)
+ Text(item.aggregatorInfo?.companyName ?: item.companyName, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.clearAndSetSemantics(){contentDescription = "Разработчик: ${item.aggregatorInfo?.companyName ?: item.companyName}"})
}
}
if (!state.isSupported) {
@@ -348,7 +360,7 @@ fun DetailsScreen(
state.installerPackageName != context.packageName &&
state.installerPackageName != "ru.vk.store") {
Card(
- colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer),
modifier = Modifier.fillMaxWidth()
) {
Row(
@@ -356,11 +368,11 @@ fun DetailsScreen(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
- Icon(painterResource(R.drawable.ic_warning_24px), null, tint = MaterialTheme.colorScheme.error)
+ Icon(painterResource(R.drawable.ic_warning_24px), null, tint = MaterialTheme.colorScheme.onPrimaryContainer)
Text(
stringResource(R.string.signature_mismatch_warning),
- style = MaterialTheme.typography.bodySmall,
- color = MaterialTheme.colorScheme.onErrorContainer
+ fontSize = 16.sp
+ //color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
@@ -433,18 +445,30 @@ fun DetailsScreen(
(state.appFiles?.sumOf { it.size } ?: 0L) / 1024.0 / 1024.0)
)
},
- leadingIcon = {Icon(painterResource(R.drawable.ic_upload_file_24px), contentDescription = null)},
+ leadingIcon = {
+ Icon(
+ painterResource(R.drawable.ic_upload_file_24px),
+ stringResource(R.string.app_size_desc)
+ )},
onClick = {},
)
AssistChip(
onClick = {},
label = {Text(item.versionName)},
- leadingIcon = {Icon(painterResource(R.drawable.ic_arrow_circle_up_24px), contentDescription = null)}
+ leadingIcon = {
+ Icon(
+ painterResource(R.drawable.ic_arrow_circle_up_24px),
+ stringResource(R.string.latest_version_desc)
+ )}
)
AssistChip(
onClick = {},
label = {FormattedDateText(item.appVerUpdatedAt)},
- leadingIcon = {Icon(painterResource(R.drawable.ic_update_24px), contentDescription = null)}
+ leadingIcon = {
+ Icon(
+ painterResource(R.drawable.ic_update_24px),
+ stringResource(R.string.last_update_desc)
+ )}
)
var detailedApiVersion by remember { mutableStateOf(false) }
AssistChip(
@@ -458,22 +482,38 @@ fun DetailsScreen(
Text("API ${item.minSdkVersion} +")
}
},
- leadingIcon = {Icon(painterResource(R.drawable.ic_android_24px), contentDescription = null)}
+ leadingIcon = {
+ Icon(
+ painterResource(R.drawable.ic_android_24px),
+ stringResource(R.string.min_api_desc)
+ )}
)
AssistChip(
onClick = {},
label = {Text(if ((state.appFiles?.size ?: 1) > 1) "${state.appFiles?.size} Split APKs" else "APK")},
- leadingIcon = {Icon(painterResource(R.drawable.ic_adb_24px), contentDescription = null)}
+ leadingIcon = {
+ Icon(
+ painterResource(R.drawable.ic_adb_24px),
+ stringResource(R.string.installer_type_desc)
+ )}
)
AssistChip(
onClick = {},
label = {Text(item.roundedDownloadsText)},
- leadingIcon = {Icon(painterResource(R.drawable.ic_download_24px), contentDescription = null)}
+ leadingIcon = {
+ Icon(
+ painterResource(R.drawable.ic_download_24px),
+ stringResource(R.string.downloads_desc)
+ )}
)
AssistChip(
onClick = {},
label = {Text(item.ageLegal)},
- leadingIcon = {Icon(painterResource(R.drawable.ic_supervised_user_circle_24px), contentDescription = null)}
+ leadingIcon = {
+ Icon(
+ painterResource(R.drawable.ic_supervised_user_circle_24px),
+ stringResource(R.string.age_legal_desc)
+ )}
)
}
GalleryWithPager(item.fileUrls)
@@ -539,6 +579,13 @@ fun DetailsScreen(
restoreState = true
}
})
+ .clearAndSetSemantics {
+ contentDescription = context.getString(
+ R.string.reviews_desc,
+ state.rating?.averageUserRating ?: 0,
+ state.rating?.totalRatings ?: 0
+ )
+ }
) {
Row(
Modifier
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsScreen.kt
index a84fd15..811dd57 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsScreen.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsScreen.kt
@@ -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()
+ }
+ )
}
}
}
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsViewModel.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsViewModel.kt
index 95866d2..5bfbf0b 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsViewModel.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/results/ResultsViewModel.kt
@@ -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() {
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/reviews/ReviewsScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/reviews/ReviewsScreen.kt
index 2ac553e..56eea6e 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/reviews/ReviewsScreen.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/reviews/ReviewsScreen.kt
@@ -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}")
}
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt
index 4c35931..e5c319b 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt
@@ -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
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt
index 5eda0ea..a5cbd2f 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt
@@ -3,6 +3,8 @@ package dev.mi6e4ka.openstore.ui.screen.settings
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
+import android.os.Build
+import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.AlertDialog
@@ -53,6 +56,7 @@ data class SettingsSection(
val select: List? = null
)
+@SuppressLint("ModifierParameter")
data class SettingsSectionSelect(
val title: String,
val value: String,
@@ -72,6 +76,12 @@ enum class ParamType {
fun SettingsScreen(
navController: NavController
) {
+ val context = LocalContext.current
+
+ val dynamicSources = remember {
+ getDynamicInstallerSources(context)
+ }
+
val sections = listOf(
SettingsSection(
id = "installer_type",
@@ -95,68 +105,16 @@ fun SettingsScreen(
id = "updates_sources",
title = R.string.settings_updates_sources_title,
description = R.string.settings_updates_sources_description,
- icon = R.drawable.ic_update_24px,
+ icon = R.drawable.ic_checklist_24px,
type = ParamType.MULTI_SELECT,
defaultVal = "dev.mi6e4ka.openstore;dev.mi6e4ka.openstore.nightly;dev.mi6e4ka.openstore.debug;ru.vk.store",
- select = listOf(
- SettingsSectionSelect(
- title = "OpenStore",
- value = "group_openstore",
- isGroup = true,
- groupPackages = listOf(
- "dev.mi6e4ka.openstore",
- "dev.mi6e4ka.openstore.nightly",
- "dev.mi6e4ka.openstore.debug"
- )
- ),
- SettingsSectionSelect(
- title = "RuStore",
- value = "ru.vk.store"
- ),
- SettingsSectionSelect(
- title = stringResource(R.string.settings_source_aurora_store),
- value = "com.aurora.store"
- ),
- SettingsSectionSelect(
- title = "Google Play",
- value = "com.android.vending"
- ),
- SettingsSectionSelect(
- title = stringResource(R.string.settings_group_file_managers),
- value = "group_filemanagers",
- isGroup = true,
- groupPackages = listOf(
- "com.hihonor.filemanager",
- "com.google.android.documentsui",
- "com.android.documentsui",
- "com.google.android.apps.nbu.files",
- "com.mi.android.globalFileexplorer"
- )
- ),
- SettingsSectionSelect(
- title = stringResource(R.string.settings_group_browsers),
- value = "group_browsers",
- isGroup = true,
- groupPackages = listOf(
- "com.android.chrome",
- "com.yandex.browser"
- )
- ),
- SettingsSectionSelect(
- title = stringResource(R.string.settings_source_manual),
- value = "com.android.packageinstaller"
- ),
- SettingsSectionSelect(
- title = stringResource(R.string.settings_source_unknown),
- value = "null"
- )
- )
+ select = dynamicSources
),
SettingsSection(
id = "updates_check_all",
title = R.string.settings_updates_check_all_title,
description = R.string.settings_updates_check_all_description,
- icon = R.drawable.ic_arrow_circle_up_24px,
+ icon = R.drawable.ic_arrow_shape_up_stack_2_24px,
type = ParamType.SWITCH,
defaultVal = ""
),
@@ -169,21 +127,22 @@ fun SettingsScreen(
defaultVal = "auto",
select = listOf(
SettingsSectionSelect(
- title = "Auto",
+ title = context.getString(R.string.theme_auto_label),
value = "auto"
),
SettingsSectionSelect(
- title = "Dark",
+ title = context.getString(R.string.theme_dark_label),
value = "dark"
),
SettingsSectionSelect(
- title = "Light",
+ title = context.getString(R.string.theme_light_label),
value = "light"
),
)
)
)
- val context = LocalContext.current as? Activity
+
+ val activity = context as? Activity
Scaffold(
topBar = {
TopAppBar(
@@ -192,13 +151,15 @@ fun SettingsScreen(
IconButton(
{
val popped = navController.popBackStack()
-
if (!popped) {
- context?.finish()
+ activity?.finish()
}
}
) {
- Icon(painterResource(R.drawable.ic_arrow_back_24px), "back")
+ Icon(
+ painterResource(R.drawable.ic_arrow_back_24px),
+ stringResource(R.string.back_button_desc)
+ )
}
}
)
@@ -217,6 +178,72 @@ fun SettingsScreen(
}
}
+private fun getDynamicInstallerSources(context: Context): List {
+ val pm = context.packageManager
+ val installedPackages = pm.getInstalledPackages(0)
+
+ val foundInstallers = installedPackages.map { pkg ->
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ try {
+ pm.getInstallSourceInfo(pkg.packageName).installingPackageName
+ } catch (e: Exception) { null }
+ } else {
+ @Suppress("DEPRECATION")
+ pm.getInstallerPackageName(pkg.packageName)
+ }
+ }.toSet()
+
+ val result = mutableListOf()
+
+ result.add(SettingsSectionSelect(
+ title = "RuStore/OpenStore",
+ value = "group_openstore",
+ isGroup = true,
+ groupPackages = listOf(
+ "dev.mi6e4ka.openstore",
+ "dev.mi6e4ka.openstore.nightly",
+ "dev.mi6e4ka.openstore.debug",
+ "ru.vk.store"
+ )
+ ))
+
+ result.add(SettingsSectionSelect(
+ title = context.getString(R.string.settings_source_manual),
+ value = "group_installers",
+ isGroup = true,
+ groupPackages = listOf(
+ "com.google.android.packageinstaller",
+ "com.android.packageinstaller"
+ )
+ ))
+
+ result.add(SettingsSectionSelect(
+ title = context.getString(R.string.settings_source_unknown),
+ value = "null"
+ ))
+
+ val handledPackages = mutableSetOf()
+ handledPackages.add(null)
+ handledPackages.add("null")
+ handledPackages.add("com.google.android.packageinstaller")
+ handledPackages.add("com.android.packageinstaller")
+ handledPackages.add("ru.vk.store")
+ handledPackages.add(context.packageName)
+ handledPackages.addAll(listOf("dev.mi6e4ka.openstore", "dev.mi6e4ka.openstore.nightly", "dev.mi6e4ka.openstore.debug"))
+
+ foundInstallers.filter { it !in handledPackages && it != null }.forEach { pkg ->
+ val label = try {
+ val info = pm.getApplicationInfo(pkg!!, 0)
+ pm.getApplicationLabel(info).toString()
+ } catch (e: Exception) {
+ pkg
+ }
+ result.add(SettingsSectionSelect(title = label ?: pkg!!, value = pkg!!))
+ }
+ Log.i("OS", "Found installers: $result")
+ return result
+}
+
@Composable
fun SettingsCard(
item: SettingsSection
@@ -305,7 +332,7 @@ fun SettingsCard(
onDismissRequest = { showDialog = false },
title = { Text(stringResource(item.title)) },
text = {
- LazyColumn {
+ LazyColumn(modifier = Modifier.heightIn(max = 380.dp)) {
items(item.select ?: listOf()) { entry ->
val isChecked = if (entry.isGroup) {
entry.groupPackages.all { it in selectedPackages }
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesScreen.kt
index 050715c..ac422c0 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesScreen.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesScreen.kt
@@ -103,7 +103,6 @@ fun UpdatesScreen(navController: NavController) {
}
LaunchedEffect(Unit) {
- viewModel.initPreferences(context)
viewModel.loadAndCheckUpdates(context)
}
@@ -115,7 +114,7 @@ fun UpdatesScreen(navController: NavController) {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
painter = painterResource(R.drawable.ic_arrow_back_24px),
- contentDescription = "Back"
+ contentDescription = stringResource(R.string.back_button_desc)
)
}
},
diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesViewModel.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesViewModel.kt
index 084edb0..a357330 100644
--- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesViewModel.kt
+++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/updates/UpdatesViewModel.kt
@@ -44,8 +44,7 @@ data class UpdatesState(
val invalidDownloadedApkFiles: List? = null,
val invalidDownloadedAppPackage: String? = null,
val invalidExpectedHash: String? = null,
- val invalidActualHash: String? = null,
- val excludeGooglePlay: Boolean = true
+ val invalidActualHash: String? = null
)
class UpdatesViewModel(private val apkInstaller: ApkInstaller) : ViewModel() {
@@ -55,27 +54,7 @@ class UpdatesViewModel(private val apkInstaller: ApkInstaller) : ViewModel() {
private val cancelDownloads = mutableMapOf Unit>()
companion object {
- private const val PREFS_NAME = "updates_prefs"
- private const val KEY_EXCLUDE_GOOGLE_PLAY = "exclude_google_play"
- }
-
- fun initPreferences(context: Context) {
- val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
- val excludeGooglePlay = prefs.getBoolean(KEY_EXCLUDE_GOOGLE_PLAY, true)
- _state.update { it.copy(excludeGooglePlay = excludeGooglePlay) }
- }
-
- fun toggleExcludeGooglePlay(context: Context) {
- val newValue = !_state.value.excludeGooglePlay
- _state.update { it.copy(excludeGooglePlay = newValue) }
-
- // Сохраняем в SharedPreferences
- context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
- .edit()
- .putBoolean(KEY_EXCLUDE_GOOGLE_PLAY, newValue)
- .apply()
-
- loadAndCheckUpdates(context)
+ private const val DEFAULT_SOURCES = "dev.mi6e4ka.openstore;dev.mi6e4ka.openstore.nightly;dev.mi6e4ka.openstore.debug;ru.vk.store"
}
fun loadAndCheckUpdates(context: Context) {
@@ -300,7 +279,7 @@ class UpdatesViewModel(private val apkInstaller: ApkInstaller) : ViewModel() {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
- val allowedSources = prefs.getString("updates_sources", "")?.split(";") ?: listOf()
+ val allowedSources = prefs.getString("updates_sources", DEFAULT_SOURCES)?.split(";") ?: listOf()
val allowAllSources = prefs.getBoolean("updates_check_all", false)
return packages
diff --git a/app/src/main/res/drawable-v24/ic_launcher_background.xml b/app/src/main/res/drawable-v24/ic_launcher_background.xml
index e5e9a2e..721ed9f 100644
--- a/app/src/main/res/drawable-v24/ic_launcher_background.xml
+++ b/app/src/main/res/drawable-v24/ic_launcher_background.xml
@@ -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">
+ android:pathData="M0,0h130v130h-130z">
-
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_arrow_shape_up_stack_2_24px.xml b/app/src/main/res/drawable/ic_arrow_shape_up_stack_2_24px.xml
new file mode 100644
index 0000000..d973457
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_shape_up_stack_2_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_checklist_24px.xml b/app/src/main/res/drawable/ic_checklist_24px.xml
new file mode 100644
index 0000000..28c2be7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_checklist_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
index 068a3c6..239cfed 100644
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -1,14 +1,14 @@
-
+ android:viewportWidth="137"
+ android:viewportHeight="130">
+
diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.xml b/app/src/main/res/drawable/ic_launcher_monochrome.xml
new file mode 100644
index 0000000..15794c1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_monochrome.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 50ec886..04091bd 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,5 +2,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 50ec886..04091bd 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -2,5 +2,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
index 2af4a43..1424eb6 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
index e883ea3..73244a6 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
index 706e6a4..9e24140 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
index a053d4c..7bbd886 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
index b8f9066..495ec0c 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
index c4d13f3..46033b0 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
index fa14656..f300bf0 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
index 8a53082..1e5938b 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
index 929a3a1..fc81277 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
index 54aa9de..9af7294 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index e241be1..43e6f88 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -2,7 +2,7 @@
OpenStore
About app safety
Error: %1$s
- Provided by %1$s
+ App from %1$s
Not compatible with your device
Installing…
Downloading…%1$s
@@ -44,10 +44,10 @@
Settings
Installer
If there are problems installing applications
- Source updates whitelist
- Sources allowed to check for updates
- Check all installed applications
- Do not limit update checks to the whitelist. RuStore will get access to the entire list of installed applications.
+ Tracked sources
+ Updates will be checked for applications from selected sources
+ Check all applications
+ Do not limit the update check to selected sources. RuStore will get access to the entire list of applications on device
Save
Updates
Check for updates
@@ -64,7 +64,15 @@
Aurora Store
File Managers
Browsers
- Possible installation error due to different signatures
+ Installed from another source
Manual Installed
Unknown (ADB/Other)
+ Provided by developer
+ Mobile
+ TV
+ Auto
+ Dark
+ Light
+ Back
+ Apps for platfom
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 8bede2e..45af79c 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,6 +1,3 @@
-
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9167c36..cc0eee6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,7 +2,8 @@
OpenStore
О безопасности приложений
Ошибка: %1$s
- Предоставлено %1$s
+ Предоставлено разработчиком
+ Приложение из %1$s
Не поддерживается на этом устройстве
Установка…
Загрузка…%1$s
@@ -44,10 +45,9 @@
Настройки
Установщик
Если есть проблемы при установке приложений
- Белый список источников обновлений
- Источники, для которых разрешена проверка обновлений
- Проверять все установленные приложения
- Не ограничивать проверку обновлений белым списком. RuStore получит доступ ко всему списку установленных приложений.
+ Отслеживаемые источники
+ Проверять все приложения
+ Не ограничивать проверку обновлений выбранными источниками. RuStore получит доступ ко всему списку приложений на устройстве
Сохранить
Обновления
Проверить обновления
@@ -64,7 +64,29 @@
Aurora Store
Файловые менеджеры
Браузеры
- Возможна ошибка установки из-за отличия в подписях
Установлено вручную
Неизвестно (ADB/Другое)
+ Установлено из другого источника
+ Телефон
+ ТВ
+ Системная
+ Тёмная
+ Светлая
+ Обновления будут проверяться для приложений из выбранных источников
+ Назад
+ Приложения для платформы
+ Скопировать ссылку на apk
+ Скачать apk без установки
+ Открыть страницу приложения в браузере
+ Вес приложения
+ Текущая версия
+ Последнее обновление
+ Минимальная версия android
+ Тип установочного файла
+ Количество установок
+ Возрастная категория
+ Рейтинг и отзывы\nСредняя оценка: %1$.2f\nВсего оценок: %2$d
+ оценка: %1$d
+ Лайки
+ Дизлайки
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 4ada25c..6872e24 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,6 +1,3 @@
-
+
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
deleted file mode 100644
index ad43458..0000000
--- a/app/src/main/res/xml/network_security_config.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- rustore.ru
-
- hETpgVvaLC0bvcGG3t0cuqiHvr4XyP2MTwCiqhgRWwU=
-
-
-
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/01_main_screen.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/01_main_screen.png
new file mode 100644
index 0000000..f1cf5a4
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/01_main_screen.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/02_search_screen.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/02_search_screen.png
new file mode 100644
index 0000000..12b5e79
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/02_search_screen.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/03_details_screen.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/03_details_screen.png
new file mode 100644
index 0000000..86b3c15
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/03_details_screen.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/04_review_screen.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/04_review_screen.png
new file mode 100644
index 0000000..dc7c277
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/04_review_screen.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05_updates_screen.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05_updates_screen.png
new file mode 100644
index 0000000..4b8b54d
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/05_updates_screen.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/06_settings_screen.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/06_settings_screen.png
new file mode 100644
index 0000000..7848c55
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/06_settings_screen.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
deleted file mode 100644
index ff2fd90..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
deleted file mode 100644
index cd83baa..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
deleted file mode 100644
index 4d500ce..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
deleted file mode 100644
index 76bd7cf..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
deleted file mode 100644
index 1bc7664..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png and /dev/null differ