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