Add embedded and load-optimized KPM module installation and uninstallation prompt messages
This commit is contained in:
@@ -29,7 +29,6 @@ import shirkneko.zako.sukisu.R
|
|||||||
import shirkneko.zako.sukisu.ui.component.ConfirmResult
|
import shirkneko.zako.sukisu.ui.component.ConfirmResult
|
||||||
import shirkneko.zako.sukisu.ui.component.SearchAppBar
|
import shirkneko.zako.sukisu.ui.component.SearchAppBar
|
||||||
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
||||||
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
|
|
||||||
import shirkneko.zako.sukisu.ui.theme.getCardColors
|
import shirkneko.zako.sukisu.ui.theme.getCardColors
|
||||||
import shirkneko.zako.sukisu.ui.theme.getCardElevation
|
import shirkneko.zako.sukisu.ui.theme.getCardElevation
|
||||||
import shirkneko.zako.sukisu.ui.viewmodel.KpmViewModel
|
import shirkneko.zako.sukisu.ui.viewmodel.KpmViewModel
|
||||||
@@ -38,6 +37,8 @@ import shirkneko.zako.sukisu.ui.util.unloadKpmModule
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
|
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
|
||||||
|
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
|
||||||
|
import shirkneko.zako.sukisu.ui.component.ConfirmDialogHandle
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KPM 管理界面
|
* KPM 管理界面
|
||||||
@@ -55,7 +56,6 @@ fun KpmScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val snackBarHost = remember { SnackbarHostState() }
|
val snackBarHost = remember { SnackbarHostState() }
|
||||||
val confirmDialog = rememberConfirmDialog()
|
val confirmDialog = rememberConfirmDialog()
|
||||||
val loadingDialog = rememberLoadingDialog()
|
|
||||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
||||||
ThemeConfig.currentTheme.ButtonContrast
|
ThemeConfig.currentTheme.ButtonContrast
|
||||||
} else {
|
} else {
|
||||||
@@ -64,17 +64,88 @@ fun KpmScreen(
|
|||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
|
||||||
val kpmInstall = stringResource(R.string.kpm_install)
|
|
||||||
val kpmInstallConfirm = stringResource(R.string.kpm_install_confirm)
|
|
||||||
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
|
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
|
||||||
val kpmInstallFailed = stringResource(R.string.kpm_install_failed)
|
val kpmInstallFailed = stringResource(R.string.kpm_install_failed)
|
||||||
val install = stringResource(R.string.install)
|
|
||||||
val cancel = stringResource(R.string.cancel)
|
val cancel = stringResource(R.string.cancel)
|
||||||
val kpmUninstall = stringResource(R.string.kpm_uninstall)
|
|
||||||
val kpmUninstallConfirmTemplate = stringResource(R.string.kpm_uninstall_confirm)
|
|
||||||
val uninstall = stringResource(R.string.uninstall)
|
val uninstall = stringResource(R.string.uninstall)
|
||||||
|
val FailedtoCheckModuleFile = stringResource(R.string.snackbar_failed_to_check_module_file)
|
||||||
val kpmUninstallSuccess = stringResource(R.string.kpm_uninstall_success)
|
val kpmUninstallSuccess = stringResource(R.string.kpm_uninstall_success)
|
||||||
val kpmUninstallFailed = stringResource(R.string.kpm_uninstall_failed)
|
val kpmUninstallFailed = stringResource(R.string.kpm_uninstall_failed)
|
||||||
|
val kpmInstallMode = stringResource(R.string.kpm_install_mode)
|
||||||
|
val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load)
|
||||||
|
val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed)
|
||||||
|
val kpmInstallModeDescription = stringResource(R.string.kpm_install_mode_description)
|
||||||
|
|
||||||
|
var tempFileForInstall by remember { mutableStateOf<File?>(null) }
|
||||||
|
val installModeDialog = rememberCustomDialog { dismiss ->
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
dismiss()
|
||||||
|
tempFileForInstall?.delete()
|
||||||
|
tempFileForInstall = null
|
||||||
|
},
|
||||||
|
title = { Text(kpmInstallMode) },
|
||||||
|
text = { Text(kpmInstallModeDescription) },
|
||||||
|
confirmButton = {
|
||||||
|
Column {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
dismiss()
|
||||||
|
tempFileForInstall?.let { tempFile ->
|
||||||
|
handleModuleInstall(
|
||||||
|
tempFile = tempFile,
|
||||||
|
isEmbed = false,
|
||||||
|
viewModel = viewModel,
|
||||||
|
snackBarHost = snackBarHost,
|
||||||
|
kpmInstallSuccess = kpmInstallSuccess,
|
||||||
|
kpmInstallFailed = kpmInstallFailed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tempFileForInstall = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(kpmInstallModeLoad)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
dismiss()
|
||||||
|
tempFileForInstall?.let { tempFile ->
|
||||||
|
handleModuleInstall(
|
||||||
|
tempFile = tempFile,
|
||||||
|
isEmbed = true,
|
||||||
|
viewModel = viewModel,
|
||||||
|
snackBarHost = snackBarHost,
|
||||||
|
kpmInstallSuccess = kpmInstallSuccess,
|
||||||
|
kpmInstallFailed = kpmInstallFailed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tempFileForInstall = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(kpmInstallModeEmbed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
dismiss()
|
||||||
|
tempFileForInstall?.delete()
|
||||||
|
tempFileForInstall = null
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val selectPatchLauncher = rememberLauncherForActivityResult(
|
val selectPatchLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
@@ -84,8 +155,9 @@ fun KpmScreen(
|
|||||||
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
|
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
// 复制文件到临时目录
|
val fileName = uri.lastPathSegment ?: "unknown.kpm"
|
||||||
val tempFile = File(context.cacheDir, "temp_patch.kpm")
|
val tempFile = File(context.cacheDir, fileName)
|
||||||
|
|
||||||
context.contentResolver.openInputStream(uri)?.use { input ->
|
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||||
tempFile.outputStream().use { output ->
|
tempFile.outputStream().use { output ->
|
||||||
input.copyTo(output)
|
input.copyTo(output)
|
||||||
@@ -101,43 +173,8 @@ fun KpmScreen(
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val confirmResult = confirmDialog.awaitConfirm(
|
tempFileForInstall = tempFile
|
||||||
title = kpmInstall,
|
installModeDialog.show()
|
||||||
content = kpmInstallConfirm,
|
|
||||||
confirm = install,
|
|
||||||
dismiss = cancel
|
|
||||||
)
|
|
||||||
|
|
||||||
if (confirmResult == ConfirmResult.Confirmed) {
|
|
||||||
val success = loadingDialog.withLoading {
|
|
||||||
try {
|
|
||||||
val loadResult = loadKpmModule(tempFile.absolutePath)
|
|
||||||
if (true && loadResult.startsWith("Error")) {
|
|
||||||
Log.e("KsuCli", "Failed to load KPM module: $loadResult")
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("KsuCli", "Failed to load KPM module: ${e.message}", e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
viewModel.fetchModuleList()
|
|
||||||
snackBarHost.showSnackbar(
|
|
||||||
message = kpmInstallSuccess,
|
|
||||||
duration = SnackbarDuration.Short
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
snackBarHost.showSnackbar(
|
|
||||||
message = kpmInstallFailed,
|
|
||||||
duration = SnackbarDuration.Short
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tempFile.delete()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,46 +271,21 @@ fun KpmScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
items(viewModel.moduleList) { module ->
|
items(viewModel.moduleList) { module ->
|
||||||
val kpmUninstallConfirm = String.format(kpmUninstallConfirmTemplate, module.name)
|
|
||||||
KpmModuleItem(
|
KpmModuleItem(
|
||||||
module = module,
|
module = module,
|
||||||
onUninstall = {
|
onUninstall = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val confirmResult = confirmDialog.awaitConfirm(
|
handleModuleUninstall(
|
||||||
title = kpmUninstall,
|
module = module,
|
||||||
content = kpmUninstallConfirm,
|
viewModel = viewModel,
|
||||||
confirm = uninstall,
|
snackBarHost = snackBarHost,
|
||||||
dismiss = cancel
|
kpmUninstallSuccess = kpmUninstallSuccess,
|
||||||
|
kpmUninstallFailed = kpmUninstallFailed,
|
||||||
|
confirmDialog = confirmDialog,
|
||||||
|
FailedtoCheckModuleFile = FailedtoCheckModuleFile,
|
||||||
|
uninstall = uninstall,
|
||||||
|
cancel = cancel
|
||||||
)
|
)
|
||||||
if (confirmResult == ConfirmResult.Confirmed) {
|
|
||||||
val success = loadingDialog.withLoading {
|
|
||||||
try {
|
|
||||||
val unloadResult = unloadKpmModule(module.id)
|
|
||||||
if (true && unloadResult.startsWith("Error")) {
|
|
||||||
Log.e("KsuCli", "Failed to unload KPM module: $unloadResult")
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("KsuCli", "Failed to unload KPM module: ${e.message}", e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
viewModel.fetchModuleList()
|
|
||||||
snackBarHost.showSnackbar(
|
|
||||||
message = kpmUninstallSuccess,
|
|
||||||
duration = SnackbarDuration.Short
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
snackBarHost.showSnackbar(
|
|
||||||
message = kpmUninstallFailed,
|
|
||||||
duration = SnackbarDuration.Short
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onControl = {
|
onControl = {
|
||||||
@@ -287,6 +299,111 @@ fun KpmScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleModuleInstall(
|
||||||
|
tempFile: File,
|
||||||
|
isEmbed: Boolean,
|
||||||
|
viewModel: KpmViewModel,
|
||||||
|
snackBarHost: SnackbarHostState,
|
||||||
|
kpmInstallSuccess: String,
|
||||||
|
kpmInstallFailed: String
|
||||||
|
) {
|
||||||
|
val moduleName = tempFile.nameWithoutExtension
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isEmbed) {
|
||||||
|
val targetPath = "/data/adb/kpm/$moduleName.kpm"
|
||||||
|
Runtime.getRuntime().exec(arrayOf("su", "-c", "mkdir -p /data/adb/kpm")).waitFor()
|
||||||
|
Runtime.getRuntime().exec(arrayOf("su", "-c", "cp ${tempFile.absolutePath} $targetPath")).waitFor()
|
||||||
|
}
|
||||||
|
|
||||||
|
val loadResult = loadKpmModule(tempFile.absolutePath)
|
||||||
|
if (loadResult.startsWith("Error")) {
|
||||||
|
Log.e("KsuCli", "Failed to load KPM module: $loadResult")
|
||||||
|
snackBarHost.showSnackbar(
|
||||||
|
message = kpmInstallFailed,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
viewModel.fetchModuleList()
|
||||||
|
snackBarHost.showSnackbar(
|
||||||
|
message = kpmInstallSuccess,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("KsuCli", "Failed to load KPM module: ${e.message}", e)
|
||||||
|
snackBarHost.showSnackbar(
|
||||||
|
message = kpmInstallFailed,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tempFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleModuleUninstall(
|
||||||
|
module: KpmViewModel.ModuleInfo,
|
||||||
|
viewModel: KpmViewModel,
|
||||||
|
snackBarHost: SnackbarHostState,
|
||||||
|
kpmUninstallSuccess: String,
|
||||||
|
kpmUninstallFailed: String,
|
||||||
|
FailedtoCheckModuleFile: String,
|
||||||
|
uninstall: String,
|
||||||
|
cancel: String,
|
||||||
|
confirmDialog: ConfirmDialogHandle
|
||||||
|
) {
|
||||||
|
val moduleFileName = "primary:${module.id}.kpm"
|
||||||
|
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
|
||||||
|
|
||||||
|
val fileExists = try {
|
||||||
|
val result = Runtime.getRuntime().exec(arrayOf("su", "-c", "ls /data/adb/kpm/$moduleFileName")).waitFor() == 0
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("KsuCli", "Failed to check module file existence: ${e.message}", e)
|
||||||
|
snackBarHost.showSnackbar(
|
||||||
|
message = FailedtoCheckModuleFile,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
val confirmResult = confirmDialog.awaitConfirm(
|
||||||
|
title = "将卸载以下kpm模块:\n$moduleFileName",
|
||||||
|
content = "The following kpm modules will be uninstalled:\n$moduleFileName",
|
||||||
|
confirm = uninstall,
|
||||||
|
dismiss = cancel
|
||||||
|
)
|
||||||
|
|
||||||
|
if (confirmResult == ConfirmResult.Confirmed) {
|
||||||
|
try {
|
||||||
|
val unloadResult = unloadKpmModule(module.id)
|
||||||
|
if (unloadResult.startsWith("Error")) {
|
||||||
|
Log.e("KsuCli", "Failed to unload KPM module: $unloadResult")
|
||||||
|
snackBarHost.showSnackbar(
|
||||||
|
message = kpmUninstallFailed,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileExists) {
|
||||||
|
Runtime.getRuntime().exec(arrayOf("su", "-c", "rm $moduleFilePath")).waitFor()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.fetchModuleList()
|
||||||
|
snackBarHost.showSnackbar(
|
||||||
|
message = kpmUninstallSuccess,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("KsuCli", "Failed to unload KPM module: ${e.message}", e)
|
||||||
|
snackBarHost.showSnackbar(
|
||||||
|
message = kpmUninstallFailed,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun KpmModuleItem(
|
private fun KpmModuleItem(
|
||||||
module: KpmViewModel.ModuleInfo,
|
module: KpmViewModel.ModuleInfo,
|
||||||
|
|||||||
@@ -230,11 +230,9 @@
|
|||||||
<string name="kpm_version">版本</string>
|
<string name="kpm_version">版本</string>
|
||||||
<string name="kpm_author">作者</string>
|
<string name="kpm_author">作者</string>
|
||||||
<string name="kpm_uninstall">卸载</string>
|
<string name="kpm_uninstall">卸载</string>
|
||||||
<string name="kpm_uninstall_confirm">确定要卸载内核模块 %1$s 吗?</string>
|
|
||||||
<string name="kpm_uninstall_success">卸载成功</string>
|
<string name="kpm_uninstall_success">卸载成功</string>
|
||||||
<string name="kpm_uninstall_failed">卸载失败</string>
|
<string name="kpm_uninstall_failed">卸载失败</string>
|
||||||
<string name="kpm_install">加载 kpm 模块</string>
|
<string name="kpm_install">选择安装</string>
|
||||||
<string name="kpm_install_confirm">确认加载吗?</string>
|
|
||||||
<string name="kpm_install_success">加载 kpm 模块成功</string>
|
<string name="kpm_install_success">加载 kpm 模块成功</string>
|
||||||
<string name="kpm_install_failed">加载 kpm 模块失败</string>
|
<string name="kpm_install_failed">加载 kpm 模块失败</string>
|
||||||
<string name="home_kpm_version">KPM 版本</string>
|
<string name="home_kpm_version">KPM 版本</string>
|
||||||
@@ -252,4 +250,12 @@
|
|||||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra 展望</string>
|
<string name="home_ContributionCard_kernelsu">SukiSU Ultra 展望</string>
|
||||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra 未来将会成为一个相对独立的 KSU 分支,但是依然感谢官方 KernelSU 和 MKSU 等做出的贡献</string>
|
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra 未来将会成为一个相对独立的 KSU 分支,但是依然感谢官方 KernelSU 和 MKSU 等做出的贡献</string>
|
||||||
<string name="custom_settings">个性化设置</string>
|
<string name="custom_settings">个性化设置</string>
|
||||||
|
<string name="kpm_install_mode">安装</string>
|
||||||
|
<string name="kpm_install_mode_load">加载</string>
|
||||||
|
<string name="kpm_install_mode_embed">嵌入</string>
|
||||||
|
<string name="kpm_install_mode_description">请选择模块安装模式:\n\n加载:临时加载模块\n嵌入:永久安装到系统</string>
|
||||||
|
<string name="snackbar_failed_to_check_module_file">无法检查模块文件是否存在</string>
|
||||||
|
<string name="confirm_uninstall_title">确认卸载</string>
|
||||||
|
<string name="confirm_uninstall_confirm">删除</string>
|
||||||
|
<string name="confirm_uninstall_dismiss">取消</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -232,11 +232,9 @@
|
|||||||
<string name="kpm_version">releases</string>
|
<string name="kpm_version">releases</string>
|
||||||
<string name="kpm_author">author</string>
|
<string name="kpm_author">author</string>
|
||||||
<string name="kpm_uninstall">uninstallation</string>
|
<string name="kpm_uninstall">uninstallation</string>
|
||||||
<string name="kpm_uninstall_confirm">Determine the kernel module to uninstall: %1$s ?</string>
|
|
||||||
<string name="kpm_uninstall_success">Uninstalled successfully</string>
|
<string name="kpm_uninstall_success">Uninstalled successfully</string>
|
||||||
<string name="kpm_uninstall_failed">Failed to uninstall</string>
|
<string name="kpm_uninstall_failed">Failed to uninstall</string>
|
||||||
<string name="kpm_install">Load the kpm module</string>
|
<string name="kpm_install">Select Installation</string>
|
||||||
<string name="kpm_install_confirm">Confirm Load?</string>
|
|
||||||
<string name="kpm_install_success">Load of kpm module successful</string>
|
<string name="kpm_install_success">Load of kpm module successful</string>
|
||||||
<string name="kpm_install_failed">Load of kpm module failed</string>
|
<string name="kpm_install_failed">Load of kpm module failed</string>
|
||||||
<string name="kpm_args">kpm parameters</string>
|
<string name="kpm_args">kpm parameters</string>
|
||||||
@@ -255,4 +253,13 @@
|
|||||||
<string name="kernel_patched">Kernel not patched</string>
|
<string name="kernel_patched">Kernel not patched</string>
|
||||||
<string name="kernel_not_enabled">Kernel not configured</string>
|
<string name="kernel_not_enabled">Kernel not configured</string>
|
||||||
<string name="custom_settings">Custom settings</string>
|
<string name="custom_settings">Custom settings</string>
|
||||||
|
<string name="kpm_install_mode">install</string>
|
||||||
|
<string name="kpm_install_mode_load">Load</string>
|
||||||
|
<string name="kpm_install_mode_embed">embed</string>
|
||||||
|
<string name="kpm_install_mode_description">Please select the module installation mode: \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system</string>
|
||||||
|
<string name="log_failed_to_check_module_file">Failed to check module file existence</string>
|
||||||
|
<string name="snackbar_failed_to_check_module_file">Unable to check if module file exists</string>
|
||||||
|
<string name="confirm_uninstall_title">Confirm uninstallation</string>
|
||||||
|
<string name="confirm_uninstall_confirm">removing</string>
|
||||||
|
<string name="confirm_uninstall_dismiss">abolish</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user