mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-06-06 09:59:27 +02:00
feat(Compose): hide developer settings (#2551)
This commit is contained in:
parent
a1f5dd3c26
commit
5153e5e0cb
@ -3,6 +3,7 @@ package app.revanced.manager.domain.manager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.revanced.manager.domain.manager.base.BasePreferencesManager
|
import app.revanced.manager.domain.manager.base.BasePreferencesManager
|
||||||
import app.revanced.manager.ui.theme.Theme
|
import app.revanced.manager.ui.theme.Theme
|
||||||
|
import app.revanced.manager.util.isDebuggable
|
||||||
|
|
||||||
class PreferencesManager(
|
class PreferencesManager(
|
||||||
context: Context
|
context: Context
|
||||||
@ -28,4 +29,6 @@ class PreferencesManager(
|
|||||||
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
|
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
|
||||||
|
|
||||||
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
|
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
|
||||||
|
|
||||||
|
val showDeveloperSettings = booleanPreference("show_developer_settings", context.isDebuggable)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.revanced.manager.ui.screen
|
package app.revanced.manager.ui.screen
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -7,55 +8,79 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.outlined.*
|
import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||||
import app.revanced.manager.ui.model.navigation.Settings
|
import app.revanced.manager.ui.model.navigation.Settings
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
private val settingsSections = listOf(
|
private data class Section(
|
||||||
Triple(
|
@StringRes val name: Int,
|
||||||
R.string.general,
|
@StringRes val description: Int,
|
||||||
R.string.general_description,
|
val image: ImageVector,
|
||||||
Icons.Outlined.Settings
|
val destination: Settings.Destination,
|
||||||
) to Settings.General,
|
|
||||||
Triple(
|
|
||||||
R.string.updates,
|
|
||||||
R.string.updates_description,
|
|
||||||
Icons.Outlined.Update
|
|
||||||
) to Settings.Updates,
|
|
||||||
Triple(
|
|
||||||
R.string.downloads,
|
|
||||||
R.string.downloads_description,
|
|
||||||
Icons.Outlined.Download
|
|
||||||
) to Settings.Downloads,
|
|
||||||
Triple(
|
|
||||||
R.string.import_export,
|
|
||||||
R.string.import_export_description,
|
|
||||||
Icons.Outlined.SwapVert
|
|
||||||
) to Settings.ImportExport,
|
|
||||||
Triple(
|
|
||||||
R.string.advanced,
|
|
||||||
R.string.advanced_description,
|
|
||||||
Icons.Outlined.Tune
|
|
||||||
) to Settings.Advanced,
|
|
||||||
Triple(
|
|
||||||
R.string.developer_options,
|
|
||||||
R.string.developer_options_description,
|
|
||||||
Icons.Outlined.Code
|
|
||||||
) to Settings.Developer,
|
|
||||||
Triple(
|
|
||||||
R.string.about,
|
|
||||||
R.string.app_name,
|
|
||||||
Icons.Outlined.Info
|
|
||||||
) to Settings.About,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) -> Unit) {
|
fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) -> Unit) {
|
||||||
|
val prefs: PreferencesManager = koinInject()
|
||||||
|
val showDeveloperSettings by prefs.showDeveloperSettings.getAsState()
|
||||||
|
|
||||||
|
val settingsSections = remember(showDeveloperSettings) {
|
||||||
|
listOfNotNull(
|
||||||
|
Section(
|
||||||
|
R.string.general,
|
||||||
|
R.string.general_description,
|
||||||
|
Icons.Outlined.Settings,
|
||||||
|
Settings.General
|
||||||
|
),
|
||||||
|
Section(
|
||||||
|
R.string.updates,
|
||||||
|
R.string.updates_description,
|
||||||
|
Icons.Outlined.Update,
|
||||||
|
Settings.Updates
|
||||||
|
),
|
||||||
|
Section(
|
||||||
|
R.string.downloads,
|
||||||
|
R.string.downloads_description,
|
||||||
|
Icons.Outlined.Download,
|
||||||
|
Settings.Downloads
|
||||||
|
),
|
||||||
|
Section(
|
||||||
|
R.string.import_export,
|
||||||
|
R.string.import_export_description,
|
||||||
|
Icons.Outlined.SwapVert,
|
||||||
|
Settings.ImportExport
|
||||||
|
),
|
||||||
|
Section(
|
||||||
|
R.string.advanced,
|
||||||
|
R.string.advanced_description,
|
||||||
|
Icons.Outlined.Tune,
|
||||||
|
Settings.Advanced
|
||||||
|
),
|
||||||
|
Section(
|
||||||
|
R.string.about,
|
||||||
|
R.string.app_name,
|
||||||
|
Icons.Outlined.Info,
|
||||||
|
Settings.About
|
||||||
|
),
|
||||||
|
Section(
|
||||||
|
R.string.developer_options,
|
||||||
|
R.string.developer_options_description,
|
||||||
|
Icons.Outlined.Code,
|
||||||
|
Settings.Developer
|
||||||
|
).takeIf { showDeveloperSettings }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopBar(
|
AppTopBar(
|
||||||
@ -69,12 +94,12 @@ fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) ->
|
|||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
settingsSections.forEach { (titleDescIcon, destination) ->
|
settingsSections.forEach { (name, description, icon, destination) ->
|
||||||
SettingsListItem(
|
SettingsListItem(
|
||||||
modifier = Modifier.clickable { navigate(destination) },
|
modifier = Modifier.clickable { navigate(destination) },
|
||||||
headlineContent = stringResource(titleDescIcon.first),
|
headlineContent = stringResource(name),
|
||||||
supportingContent = stringResource(titleDescIcon.second),
|
supportingContent = stringResource(description),
|
||||||
leadingContent = { Icon(titleDescIcon.third, null) }
|
leadingContent = { Icon(icon, null) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,16 +23,26 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedCard
|
import androidx.compose.material3.OutlinedCard
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.hideFromAccessibility
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.revanced.manager.BuildConfig
|
import app.revanced.manager.BuildConfig
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
@ -42,6 +52,7 @@ import app.revanced.manager.ui.component.ColumnWithScrollbar
|
|||||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||||
import app.revanced.manager.ui.model.navigation.Settings
|
import app.revanced.manager.ui.model.navigation.Settings
|
||||||
import app.revanced.manager.ui.viewmodel.AboutViewModel
|
import app.revanced.manager.ui.viewmodel.AboutViewModel
|
||||||
|
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.DEVELOPER_OPTIONS_TAPS
|
||||||
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
|
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
|
||||||
import app.revanced.manager.util.openUrl
|
import app.revanced.manager.util.openUrl
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
@ -109,7 +120,8 @@ fun AboutSettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val listItems = listOfNotNull(
|
val listItems = listOfNotNull(
|
||||||
Triple(stringResource(R.string.submit_feedback),
|
Triple(
|
||||||
|
stringResource(R.string.submit_feedback),
|
||||||
stringResource(R.string.submit_feedback_description),
|
stringResource(R.string.submit_feedback_description),
|
||||||
third = {
|
third = {
|
||||||
context.openUrl("https://github.com/ReVanced/revanced-manager/issues/new/choose")
|
context.openUrl("https://github.com/ReVanced/revanced-manager/issues/new/choose")
|
||||||
@ -134,6 +146,35 @@ fun AboutSettingsScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
val showDeveloperSettings by viewModel.showDeveloperSettings.getAsState()
|
||||||
|
var developerTaps by rememberSaveable { mutableIntStateOf(0) }
|
||||||
|
LaunchedEffect(developerTaps) {
|
||||||
|
if (developerTaps == 0) return@LaunchedEffect
|
||||||
|
if (showDeveloperSettings) {
|
||||||
|
snackbarHostState.showSnackbar(context.getString(R.string.developer_options_already_enabled))
|
||||||
|
developerTaps = 0
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
val remaining = DEVELOPER_OPTIONS_TAPS - developerTaps
|
||||||
|
if (remaining > 0) {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
context.getString(
|
||||||
|
R.string.developer_options_taps,
|
||||||
|
remaining
|
||||||
|
),
|
||||||
|
duration = SnackbarDuration.Long
|
||||||
|
)
|
||||||
|
} else if (remaining == 0) {
|
||||||
|
viewModel.showDeveloperSettings.update(true)
|
||||||
|
snackbarHostState.showSnackbar(context.getString(R.string.developer_options_enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the counter
|
||||||
|
developerTaps = 0
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -143,6 +184,9 @@ fun AboutSettingsScreen(
|
|||||||
onBackClick = onBackClick
|
onBackClick = onBackClick
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(hostState = snackbarHostState)
|
||||||
|
},
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
ColumnWithScrollbar(
|
ColumnWithScrollbar(
|
||||||
@ -153,9 +197,11 @@ fun AboutSettingsScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
.clickable { developerTaps += 1 },
|
||||||
painter = icon,
|
painter = icon,
|
||||||
contentDescription = null
|
contentDescription = stringResource(R.string.app_name)
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
@ -163,7 +209,11 @@ fun AboutSettingsScreen(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.app_name),
|
stringResource(R.string.app_name),
|
||||||
style = MaterialTheme.typography.headlineSmall
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
// Icon already has this information for the purpose of being clickable.
|
||||||
|
hideFromAccessibility()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")",
|
text = stringResource(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")",
|
||||||
|
@ -12,11 +12,14 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.GroupHeader
|
import app.revanced.manager.ui.component.GroupHeader
|
||||||
|
import app.revanced.manager.ui.component.settings.BooleanItem
|
||||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||||
import app.revanced.manager.ui.viewmodel.DeveloperOptionsViewModel
|
import app.revanced.manager.ui.viewmodel.DeveloperOptionsViewModel
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -25,6 +28,7 @@ fun DeveloperSettingsScreen(
|
|||||||
vm: DeveloperOptionsViewModel = koinViewModel()
|
vm: DeveloperOptionsViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
val prefs: PreferencesManager = koinInject()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -37,6 +41,13 @@ fun DeveloperSettingsScreen(
|
|||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(modifier = Modifier.padding(paddingValues)) {
|
Column(modifier = Modifier.padding(paddingValues)) {
|
||||||
|
GroupHeader(stringResource(R.string.manager))
|
||||||
|
BooleanItem(
|
||||||
|
preference = prefs.showDeveloperSettings,
|
||||||
|
headline = R.string.developer_options,
|
||||||
|
description = R.string.developer_options_description,
|
||||||
|
)
|
||||||
|
|
||||||
GroupHeader(stringResource(R.string.patch_bundles_section))
|
GroupHeader(stringResource(R.string.patch_bundles_section))
|
||||||
SettingsListItem(
|
SettingsListItem(
|
||||||
headlineContent = stringResource(R.string.patch_bundles_force_download),
|
headlineContent = stringResource(R.string.patch_bundles_force_download),
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.data.platform.NetworkInfo
|
import app.revanced.manager.data.platform.NetworkInfo
|
||||||
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.network.api.ReVancedAPI
|
import app.revanced.manager.network.api.ReVancedAPI
|
||||||
import app.revanced.manager.network.dto.ReVancedDonationLink
|
import app.revanced.manager.network.dto.ReVancedDonationLink
|
||||||
import app.revanced.manager.network.dto.ReVancedSocial
|
import app.revanced.manager.network.dto.ReVancedSocial
|
||||||
@ -27,6 +28,7 @@ import kotlinx.coroutines.withContext
|
|||||||
class AboutViewModel(
|
class AboutViewModel(
|
||||||
private val reVancedAPI: ReVancedAPI,
|
private val reVancedAPI: ReVancedAPI,
|
||||||
private val network: NetworkInfo,
|
private val network: NetworkInfo,
|
||||||
|
prefs: PreferencesManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
var socials by mutableStateOf(emptyList<ReVancedSocial>())
|
var socials by mutableStateOf(emptyList<ReVancedSocial>())
|
||||||
private set
|
private set
|
||||||
@ -37,6 +39,8 @@ class AboutViewModel(
|
|||||||
val isConnected: Boolean
|
val isConnected: Boolean
|
||||||
get() = network.isConnected()
|
get() = network.isConnected()
|
||||||
|
|
||||||
|
val showDeveloperSettings = prefs.showDeveloperSettings
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
@ -53,6 +57,8 @@ class AboutViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val DEVELOPER_OPTIONS_TAPS = 5
|
||||||
|
|
||||||
private val socialIcons = mapOf(
|
private val socialIcons = mapOf(
|
||||||
"Discord" to FontAwesomeIcons.Brands.Discord,
|
"Discord" to FontAwesomeIcons.Brands.Discord,
|
||||||
"GitHub" to FontAwesomeIcons.Brands.Github,
|
"GitHub" to FontAwesomeIcons.Brands.Github,
|
||||||
|
@ -348,6 +348,9 @@
|
|||||||
|
|
||||||
<string name="about_revanced_manager">About ReVanced Manager</string>
|
<string name="about_revanced_manager">About ReVanced Manager</string>
|
||||||
<string name="revanced_manager_description">ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance.</string>
|
<string name="revanced_manager_description">ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance.</string>
|
||||||
|
<string name="developer_options_taps">%d taps remaining</string>
|
||||||
|
<string name="developer_options_enabled">Developer options enabled</string>
|
||||||
|
<string name="developer_options_already_enabled">Developer options are already enabled</string>
|
||||||
<string name="update_available">An update is available</string>
|
<string name="update_available">An update is available</string>
|
||||||
<string name="current_version">Current version: %s</string>
|
<string name="current_version">Current version: %s</string>
|
||||||
<string name="new_version">New version: %s</string>
|
<string name="new_version">New version: %s</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user