Feat(ToolScreen): Support uninstall tool
This commit is contained in:
@@ -8,8 +8,8 @@ import androidx.compose.material.icons.filled.Build
|
|||||||
import androidx.compose.material.icons.filled.Cancel
|
import androidx.compose.material.icons.filled.Cancel
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Code
|
import androidx.compose.material.icons.filled.Code
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.Download
|
import androidx.compose.material.icons.filled.Download
|
||||||
import androidx.compose.material.icons.filled.Error
|
|
||||||
import androidx.compose.material.icons.filled.Inbox
|
import androidx.compose.material.icons.filled.Inbox
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Reorder
|
import androidx.compose.material.icons.filled.Reorder
|
||||||
@@ -39,6 +39,7 @@ object OxygenIcons {
|
|||||||
val Box = Icons.Default.Inbox
|
val Box = Icons.Default.Inbox
|
||||||
val Close = Icons.Default.Close
|
val Close = Icons.Default.Close
|
||||||
val Code = Icons.Default.Code
|
val Code = Icons.Default.Code
|
||||||
|
val Delete = Icons.Default.Delete
|
||||||
val Download = Icons.Default.Download
|
val Download = Icons.Default.Download
|
||||||
val Error = Icons.Default.Cancel
|
val Error = Icons.Default.Cancel
|
||||||
val Home = Icons.Rounded.Home
|
val Home = Icons.Rounded.Home
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import top.fatweb.oxygen.toolbox.ui.OxygenAppState
|
|||||||
fun OxygenNavHost(
|
fun OxygenNavHost(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
appState: OxygenAppState,
|
appState: OxygenAppState,
|
||||||
onShowSnackbar: suspend (String, String?) -> Boolean,
|
onShowSnackbar: suspend (message: String, action: String?) -> Boolean,
|
||||||
startDestination: String
|
startDestination: String
|
||||||
) {
|
) {
|
||||||
val navController = appState.navController
|
val navController = appState.navController
|
||||||
@@ -32,6 +32,7 @@ fun OxygenNavHost(
|
|||||||
onNavigateToToolView = navController::navigateToToolView
|
onNavigateToToolView = navController::navigateToToolView
|
||||||
)
|
)
|
||||||
toolsScreen(
|
toolsScreen(
|
||||||
|
onShowSnackbar = onShowSnackbar,
|
||||||
onNavigateToToolView = navController::navigateToToolView,
|
onNavigateToToolView = navController::navigateToToolView,
|
||||||
onNavigateToToolStore = { appState.navigateToTopLevelDestination(TopLevelDestination.TOOL_STORE) }
|
onNavigateToToolStore = { appState.navigateToTopLevelDestination(TopLevelDestination.TOOL_STORE) }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const val TOOLS_ROUTE = "tools_route"
|
|||||||
fun NavController.navigateToTools(navOptions: NavOptions) = navigate(TOOLS_ROUTE, navOptions)
|
fun NavController.navigateToTools(navOptions: NavOptions) = navigate(TOOLS_ROUTE, navOptions)
|
||||||
|
|
||||||
fun NavGraphBuilder.toolsScreen(
|
fun NavGraphBuilder.toolsScreen(
|
||||||
|
onShowSnackbar: suspend (message: String, action: String?) -> Boolean,
|
||||||
onNavigateToToolView: (username: String, toolId: String) -> Unit,
|
onNavigateToToolView: (username: String, toolId: String) -> Unit,
|
||||||
onNavigateToToolStore: () -> Unit
|
onNavigateToToolStore: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -18,6 +19,7 @@ fun NavGraphBuilder.toolsScreen(
|
|||||||
route = TOOLS_ROUTE
|
route = TOOLS_ROUTE
|
||||||
) {
|
) {
|
||||||
ToolsRoute(
|
ToolsRoute(
|
||||||
|
onShowSnackbar = onShowSnackbar,
|
||||||
onNavigateToToolView = onNavigateToToolView,
|
onNavigateToToolView = onNavigateToToolView,
|
||||||
onNavigateToToolStore = onNavigateToToolStore
|
onNavigateToToolStore = onNavigateToToolStore
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,31 +29,45 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
|||||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
|
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
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.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import top.fatweb.oxygen.toolbox.R
|
import top.fatweb.oxygen.toolbox.R
|
||||||
import top.fatweb.oxygen.toolbox.icon.Loading
|
import top.fatweb.oxygen.toolbox.icon.Loading
|
||||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||||
|
import top.fatweb.oxygen.toolbox.ui.component.DialogClickerRow
|
||||||
|
import top.fatweb.oxygen.toolbox.ui.component.DialogSectionGroup
|
||||||
|
import top.fatweb.oxygen.toolbox.ui.component.DialogTitle
|
||||||
import top.fatweb.oxygen.toolbox.ui.component.ToolCard
|
import top.fatweb.oxygen.toolbox.ui.component.ToolCard
|
||||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
|
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
|
||||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller
|
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller
|
||||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState
|
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState
|
||||||
|
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ToolsRoute(
|
internal fun ToolsRoute(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: ToolsScreenViewModel = hiltViewModel(),
|
viewModel: ToolsScreenViewModel = hiltViewModel(),
|
||||||
|
onShowSnackbar: suspend (message: String, action: String?) -> Boolean,
|
||||||
onNavigateToToolView: (username: String, toolId: String) -> Unit,
|
onNavigateToToolView: (username: String, toolId: String) -> Unit,
|
||||||
onNavigateToToolStore: () -> Unit
|
onNavigateToToolStore: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -61,19 +75,29 @@ internal fun ToolsRoute(
|
|||||||
|
|
||||||
ToolsScreen(
|
ToolsScreen(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
onShowSnackbar = onShowSnackbar,
|
||||||
onNavigateToToolView = onNavigateToToolView,
|
onNavigateToToolView = onNavigateToToolView,
|
||||||
onNavigateToToolStore = onNavigateToToolStore,
|
onNavigateToToolStore = onNavigateToToolStore,
|
||||||
toolsScreenUiState = toolsScreenUiStateState
|
toolsScreenUiState = toolsScreenUiStateState,
|
||||||
|
onUninstall = viewModel::uninstall,
|
||||||
|
onUndo = viewModel::undo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ToolsScreen(
|
internal fun ToolsScreen(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onShowSnackbar: suspend (message: String, action: String?) -> Boolean,
|
||||||
onNavigateToToolView: (username: String, toolId: String) -> Unit,
|
onNavigateToToolView: (username: String, toolId: String) -> Unit,
|
||||||
onNavigateToToolStore: () -> Unit,
|
onNavigateToToolStore: () -> Unit,
|
||||||
toolsScreenUiState: ToolsScreenUiState
|
toolsScreenUiState: ToolsScreenUiState,
|
||||||
|
onUninstall: (ToolEntity) -> Unit,
|
||||||
|
onUndo: (ToolEntity) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val localContext = LocalContext.current
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
ReportDrawnWhen { toolsScreenUiState !is ToolsScreenUiState.Loading }
|
ReportDrawnWhen { toolsScreenUiState !is ToolsScreenUiState.Loading }
|
||||||
|
|
||||||
val itemsAvailable = howManyTools(toolsScreenUiState)
|
val itemsAvailable = howManyTools(toolsScreenUiState)
|
||||||
@@ -83,6 +107,9 @@ internal fun ToolsScreen(
|
|||||||
|
|
||||||
val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition")
|
val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition")
|
||||||
|
|
||||||
|
var selectedTool by remember { mutableStateOf<ToolEntity?>(null) }
|
||||||
|
var isShowMenu by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier.fillMaxSize()
|
modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
@@ -95,9 +122,7 @@ internal fun ToolsScreen(
|
|||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
val angle by infiniteTransition.animateFloat(
|
val angle by infiniteTransition.animateFloat(
|
||||||
initialValue = 0F,
|
initialValue = 0F, targetValue = 360F, animationSpec = infiniteRepeatable(
|
||||||
targetValue = 360F,
|
|
||||||
animationSpec = infiniteRepeatable(
|
|
||||||
animation = tween(800, easing = Ease),
|
animation = tween(800, easing = Ease),
|
||||||
), label = "angle"
|
), label = "angle"
|
||||||
)
|
)
|
||||||
@@ -110,6 +135,7 @@ internal fun ToolsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolsScreenUiState.Nothing -> {
|
ToolsScreenUiState.Nothing -> {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@@ -122,6 +148,7 @@ internal fun ToolsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ToolsScreenUiState.Success -> {
|
is ToolsScreenUiState.Success -> {
|
||||||
LazyVerticalStaggeredGrid(
|
LazyVerticalStaggeredGrid(
|
||||||
columns = StaggeredGridCells.Adaptive(160.dp),
|
columns = StaggeredGridCells.Adaptive(160.dp),
|
||||||
@@ -131,10 +158,12 @@ internal fun ToolsScreen(
|
|||||||
state = state
|
state = state
|
||||||
) {
|
) {
|
||||||
|
|
||||||
toolsPanel(
|
toolsPanel(toolItems = toolsScreenUiState.tools,
|
||||||
toolItems = toolsScreenUiState.tools,
|
onClick = onNavigateToToolView,
|
||||||
onClickToolCard = onNavigateToToolView
|
onLongClick = {
|
||||||
)
|
selectedTool = it
|
||||||
|
isShowMenu = true
|
||||||
|
})
|
||||||
|
|
||||||
item(span = StaggeredGridItemSpan.FullLine) {
|
item(span = StaggeredGridItemSpan.FullLine) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -150,27 +179,74 @@ internal fun ToolsScreen(
|
|||||||
.windowInsetsPadding(WindowInsets.systemBars)
|
.windowInsetsPadding(WindowInsets.systemBars)
|
||||||
.padding(horizontal = 2.dp)
|
.padding(horizontal = 2.dp)
|
||||||
.align(Alignment.CenterEnd),
|
.align(Alignment.CenterEnd),
|
||||||
state = scrollbarState, orientation = Orientation.Vertical,
|
state = scrollbarState,
|
||||||
|
orientation = Orientation.Vertical,
|
||||||
onThumbMoved = state.rememberDraggableScroller(itemsAvailable = itemsAvailable)
|
onThumbMoved = state.rememberDraggableScroller(itemsAvailable = itemsAvailable)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isShowMenu && selectedTool != null) {
|
||||||
|
ToolMenu(
|
||||||
|
onDismiss = { isShowMenu = false },
|
||||||
|
selectedTool = selectedTool!!,
|
||||||
|
onUninstall = {
|
||||||
|
isShowMenu = false
|
||||||
|
onUninstall(selectedTool!!)
|
||||||
|
scope.launch {
|
||||||
|
if (onShowSnackbar(
|
||||||
|
ResourcesUtils.getString(localContext, R.string.core_uninstall_success),
|
||||||
|
ResourcesUtils.getString(localContext, R.string.core_undo)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
onUndo(selectedTool!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LazyStaggeredGridScope.toolsPanel(
|
private fun LazyStaggeredGridScope.toolsPanel(
|
||||||
toolItems: List<ToolEntity>,
|
toolItems: List<ToolEntity>,
|
||||||
onClickToolCard: (username: String, toolId: String) -> Unit
|
onClick: (username: String, toolId: String) -> Unit,
|
||||||
|
onLongClick: (ToolEntity) -> Unit
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = toolItems,
|
items = toolItems,
|
||||||
key = { it.id },
|
key = { it.id },
|
||||||
) {
|
) {
|
||||||
ToolCard(
|
ToolCard(tool = it,
|
||||||
tool = it,
|
onClick = { onClick(it.authorUsername, it.toolId) },
|
||||||
onClick = {onClickToolCard(it.authorUsername, it.toolId)},
|
onLongClick = { onLongClick(it) })
|
||||||
onLongClick = {onClickToolCard(it.authorUsername, it.toolId)}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun ToolMenu(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
selectedTool: ToolEntity,
|
||||||
|
onUninstall: () -> Unit
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(onDismissRequest = onDismiss, dragHandle = {}) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
DialogTitle(text = selectedTool.name)
|
||||||
|
HorizontalDivider()
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
DialogSectionGroup {
|
||||||
|
DialogClickerRow(
|
||||||
|
icon = OxygenIcons.Delete,
|
||||||
|
text = stringResource(R.string.core_uninstall),
|
||||||
|
onClick = onUninstall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun howManyTools(toolsScreenUiState: ToolsScreenUiState) =
|
private fun howManyTools(toolsScreenUiState: ToolsScreenUiState) =
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||||
import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository
|
import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository
|
||||||
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
||||||
@@ -17,7 +18,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ToolsScreenViewModel @Inject constructor(
|
class ToolsScreenViewModel @Inject constructor(
|
||||||
private val storeRepository: StoreRepository,
|
private val storeRepository: StoreRepository,
|
||||||
toolRepository: ToolRepository,
|
private val toolRepository: ToolRepository,
|
||||||
savedStateHandle: SavedStateHandle
|
savedStateHandle: SavedStateHandle
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "")
|
private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "")
|
||||||
@@ -37,6 +38,17 @@ class ToolsScreenViewModel @Inject constructor(
|
|||||||
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
|
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun uninstall(tool: ToolEntity) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toolRepository.removeTool(tool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun undo(tool: ToolEntity) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toolRepository.saveTool(tool)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface ToolsScreenUiState {
|
sealed interface ToolsScreenUiState {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.os.ConfigurationCompat
|
import androidx.core.os.ConfigurationCompat
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -33,8 +34,14 @@ object ResourcesUtils {
|
|||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||||
context.packageManager.getPackageInfo(context.packageName, 0)?.longVersionCode ?: -1
|
context.packageManager.getPackageInfo(context.packageName, 0)?.longVersionCode ?: -1
|
||||||
else context.packageManager.getPackageInfo(context.packageName, 0)?.versionCode?.toLong() ?: -1
|
else context.packageManager.getPackageInfo(
|
||||||
|
context.packageName,
|
||||||
|
0
|
||||||
|
)?.versionCode?.toLong() ?: -1
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getString(context: Context, @StringRes resId: Int): String =
|
||||||
|
context.resources.getString(resId)
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,10 @@
|
|||||||
<string name="core_no_connect">⚠️ 无法连接至互联网</string>
|
<string name="core_no_connect">⚠️ 无法连接至互联网</string>
|
||||||
<string name="core_install">安装</string>
|
<string name="core_install">安装</string>
|
||||||
<string name="core_installing">安装中……</string>
|
<string name="core_installing">安装中……</string>
|
||||||
|
<string name="core_uninstall">卸载</string>
|
||||||
|
<string name="core_uninstall_success">卸载成功</string>
|
||||||
<string name="core_cancel">取消</string>
|
<string name="core_cancel">取消</string>
|
||||||
|
<string name="core_undo">撤消</string>
|
||||||
|
|
||||||
<string name="feature_store_title">商店</string>
|
<string name="feature_store_title">商店</string>
|
||||||
<string name="feature_store_install_tool">安装工具</string>
|
<string name="feature_store_install_tool">安装工具</string>
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
<string name="core_no_connect">⚠️ Unable to connect to the internet</string>
|
<string name="core_no_connect">⚠️ Unable to connect to the internet</string>
|
||||||
<string name="core_install">Install</string>
|
<string name="core_install">Install</string>
|
||||||
<string name="core_installing">Installing…</string>
|
<string name="core_installing">Installing…</string>
|
||||||
|
<string name="core_uninstall">Uninstall</string>
|
||||||
|
<string name="core_uninstall_success">Uninstalled successfully</string>
|
||||||
<string name="core_cancel">Cancel</string>
|
<string name="core_cancel">Cancel</string>
|
||||||
|
<string name="core_undo">Undo</string>
|
||||||
|
|
||||||
<string name="feature_store_title">Store</string>
|
<string name="feature_store_title">Store</string>
|
||||||
<string name="feature_store_install_tool">Install Tool</string>
|
<string name="feature_store_install_tool">Install Tool</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user