diff --git a/app/src/main/java/top/laoxin/modmanager/MainActivity.kt b/app/src/main/java/top/laoxin/modmanager/MainActivity.kt index 1351acb..5b2bc7d 100644 --- a/app/src/main/java/top/laoxin/modmanager/MainActivity.kt +++ b/app/src/main/java/top/laoxin/modmanager/MainActivity.kt @@ -53,7 +53,7 @@ class MainActivity : ComponentActivity() { darkIcons = true ) systemUiController.setNavigationBarColor( - color = colors.background, + color = colors.surfaceContainer, darkIcons = true ) } diff --git a/app/src/main/java/top/laoxin/modmanager/database/UserPreferencesRepository.kt b/app/src/main/java/top/laoxin/modmanager/database/UserPreferencesRepository.kt index 8f6fde4..5992c01 100644 --- a/app/src/main/java/top/laoxin/modmanager/database/UserPreferencesRepository.kt +++ b/app/src/main/java/top/laoxin/modmanager/database/UserPreferencesRepository.kt @@ -41,6 +41,8 @@ class UserPreferencesRepository( "SCAN_DIRECTORY_MODS" to booleanPreferencesKey("SCAN_DIRECTORY_MODS"), // 删除解压目录 "DELETE_UNZIP_DIRECTORY" to booleanPreferencesKey("DELETE_UNZIP_DIRECTORY"), + // 展示分类视图 + "SHOW_CATEGORY_VIEW" to booleanPreferencesKey("SHOW_CATEGORY_VIEW"), ) const val TAG = "UserPreferencesRepo" } diff --git a/app/src/main/java/top/laoxin/modmanager/ui/state/ConsoleUiState.kt b/app/src/main/java/top/laoxin/modmanager/ui/state/ConsoleUiState.kt index a9c584f..ac31331 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/state/ConsoleUiState.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/state/ConsoleUiState.kt @@ -30,4 +30,6 @@ data class ConsoleUiState( // 显示删除解压目录弹窗 val showDeleteUnzipDialog: Boolean = false, val delUnzipDictionary: Boolean = false, + // 展示分类视图 + val showCategoryView: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/top/laoxin/modmanager/ui/state/ModUiState.kt b/app/src/main/java/top/laoxin/modmanager/ui/state/ModUiState.kt index c5c3137..cb28418 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/state/ModUiState.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/state/ModUiState.kt @@ -39,8 +39,10 @@ data class ModUiState( val searchContent: String = "", // 当前的游戏mod目录 val currentGameModPath: String = "", + // 当前页面的文件 val currentFiles: List = emptyList(), - val isShowBack: Boolean = true + // 当前页面的mods + val currentMods: List = emptyList() ) { } diff --git a/app/src/main/java/top/laoxin/modmanager/ui/state/UserPreferencesState.kt b/app/src/main/java/top/laoxin/modmanager/ui/state/UserPreferencesState.kt index c1936df..e10ba82 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/state/UserPreferencesState.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/state/UserPreferencesState.kt @@ -9,4 +9,6 @@ data class UserPreferencesState( val selectedGameIndex: Int = 0, val scanDirectoryMods: Boolean = false, val delUnzipDictionary: Boolean = false, + // 展示分类视图 + val showCategoryView: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/top/laoxin/modmanager/ui/view/Console.kt b/app/src/main/java/top/laoxin/modmanager/ui/view/Console.kt index c78677e..75a93a6 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/view/Console.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/view/Console.kt @@ -1,6 +1,7 @@ package top.laoxin.modmanager.ui.view import android.annotation.SuppressLint +import android.content.res.Configuration import android.net.Uri import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult @@ -139,11 +140,11 @@ fun ConsoleContent(viewModel: ConsoleViewModel) { viewModel.setOpenPermissionRequestDialog(false) } } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(8.dp)) // 权限信息 // PermissionInformationCard() - Spacer(modifier = Modifier.height(16.dp)) + // Spacer(modifier = Modifier.height(16.dp)) // 游戏信息 GameInformationCard( viewModel, @@ -418,6 +419,19 @@ fun ConfigurationCard(viewModel: ConsoleViewModel, uiState: ConsoleUiState) { viewModel.openDelUnzipDialog(it) }) } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = stringResource(id = R.string.console_configuration_show_browswer), + style = typography.titleMedium + ) + Switch(checked = uiState.showCategoryView, onCheckedChange = { + viewModel.setShowCategoryView(it) + }) + } Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, @@ -467,19 +481,28 @@ fun ConsolePage(viewModel: ConsoleViewModel) { @Composable @OptIn(ExperimentalMaterial3Api::class) -fun ConsoleTopBar(viewModel: ConsoleViewModel) { +fun ConsoleTopBar( + viewModel: ConsoleViewModel, + modifier: Modifier = Modifier, + + configuration: Int +) { TopAppBar( colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - titleContentColor = MaterialTheme.colorScheme.onSecondaryContainer, - navigationIconContentColor = MaterialTheme.colorScheme.onSecondaryContainer, + containerColor = if (configuration == Configuration.ORIENTATION_LANDSCAPE) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceContainer, + //titleContentColor = MaterialTheme.colorScheme.onSecondaryContainer, + //navigationIconContentColor = MaterialTheme.colorScheme.onSecondaryContainer, ), + modifier = modifier, + title = { - Text( - stringResource(id = R.string.console), - style = typography.titleLarge + if ( configuration != Configuration.ORIENTATION_LANDSCAPE) { + Text( + stringResource(id = R.string.console), + style = typography.titleLarge - ) + ) + } }, actions = { Text(text = "启动游戏") diff --git a/app/src/main/java/top/laoxin/modmanager/ui/view/ModManagerApp.kt b/app/src/main/java/top/laoxin/modmanager/ui/view/ModManagerApp.kt index a09ff4d..e62b107 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/view/ModManagerApp.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/view/ModManagerApp.kt @@ -10,9 +10,15 @@ import android.graphics.drawable.BitmapDrawable import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.SizeTransform import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -24,6 +30,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Dashboard import androidx.compose.material.icons.filled.ImagesearchRoller import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem @@ -33,6 +40,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.vector.ImageVector @@ -69,6 +77,7 @@ enum class NavigationIndex( } +@OptIn(ExperimentalAnimationApi::class) @Composable fun ModManagerApp() { val modViewModel: ModViewModel = viewModel(factory = ModViewModel.Factory) @@ -93,9 +102,9 @@ fun ModManagerApp() { topBar = { // 根据当前页面显示不同的顶部工具栏 when (pagerState.currentPage) { - NavigationIndex.CONSOLE.ordinal -> ConsoleTopBar(consoleViewModel) - NavigationIndex.MOD.ordinal -> ModTopBar(modViewModel) - NavigationIndex.SETTINGS.ordinal -> SettingTopBar() + NavigationIndex.CONSOLE.ordinal -> ConsoleTopBar(consoleViewModel,configuration = configuration.orientation) + NavigationIndex.MOD.ordinal -> ModTopBar(modViewModel,configuration = configuration.orientation) + NavigationIndex.SETTINGS.ordinal -> SettingTopBar(configuration = configuration.orientation) } val uiState by modViewModel.uiState.collectAsState() Tips( @@ -117,7 +126,7 @@ fun ModManagerApp() { ) { innerPadding -> val context = LocalContext.current // 在这里获取 Context val exitToast: Toast = - remember { Toast.makeText(context, "再按一次退出应用", Toast.LENGTH_SHORT) } + remember { Toast.makeText(context, context.getText(R.string.toast_quit_qpp), Toast.LENGTH_SHORT) } val activity = context as? Activity // 获取当前 Activity BackHandler(enabled = pagerState.currentPage == NavigationIndex.CONSOLE.ordinal) { @@ -140,16 +149,31 @@ fun ModManagerApp() { } } - HorizontalPager( - state = pagerState, - count = NavigationIndex.entries.size, - modifier = Modifier.padding(innerPadding) - ) { page -> - // 根据当前页显示不同的内容 - when (page) { - NavigationIndex.CONSOLE.ordinal -> ConsolePage(consoleViewModel) - NavigationIndex.MOD.ordinal -> ModPage(modViewModel) - NavigationIndex.SETTINGS.ordinal -> SettingPage() + // 使用 AnimatedContent 实现页面切换动画 + AnimatedContent( + targetState = pagerState.currentPage, + transitionSpec = { + if (targetState > initialState) { + (slideInHorizontally(initialOffsetX = { fullWidth -> fullWidth }) + fadeIn()).togetherWith( + slideOutHorizontally(targetOffsetX = { fullWidth -> -fullWidth }) + fadeOut() + ) + } else { + (slideInHorizontally(initialOffsetX = { fullWidth -> -fullWidth }) + fadeIn()).togetherWith( + slideOutHorizontally(targetOffsetX = { fullWidth -> fullWidth }) + fadeOut() + ) + }.using(SizeTransform(clip = false)) + } + ) { _ -> + HorizontalPager( + state = pagerState, + count = NavigationIndex.entries.size, + modifier = Modifier.padding(innerPadding) + ) { page -> + when (page) { + NavigationIndex.CONSOLE.ordinal -> ConsolePage(consoleViewModel) + NavigationIndex.MOD.ordinal -> ModPage(modViewModel) + NavigationIndex.SETTINGS.ordinal -> SettingPage() + } } } } @@ -173,25 +197,33 @@ fun NavigationRail( NavigationRail( modifier = Modifier .fillMaxHeight() - .padding(0.dp) + .padding(0.dp), + // containerColor = MaterialTheme.colorScheme.surfaceContainer, ) { - // 顶部的按钮 + // 顶部的当前页面名称 + val currentPageName = stringResource(id = NavigationIndex.entries[pagerState.currentPage].title) + Text( + text = currentPageName, + modifier = Modifier.padding(16.dp) + ) Column { + NavigationIndex.entries.forEachIndexed { index, navigationItem -> val isSelected = pagerState.currentPage == index + + NavigationRailItem( selected = isSelected, onClick = { val currentTime = System.currentTimeMillis() - if ((currentTime - lastClickTime) < 300 && isSelected) { // 检测双击 - // 刷新当前页面的逻辑 + if ((currentTime - lastClickTime) < 300 && isSelected) { refreshCurrentPage(pagerState.currentPage, modViewModel) } else { modViewModel.exitSelect() if (!isSelected) { coroutineScope.launch { - pagerState.scrollToPage(index) + pagerState.animateScrollToPage(index) } } } @@ -209,26 +241,24 @@ fun NavigationRail( Text(text = stringResource(id = navigationItem.title)) } }, - alwaysShowLabel = false // 确保标签只在 isSelected 为 true 时显示 + alwaysShowLabel = false ) - // 两个选项间添加间隔 Spacer(modifier = Modifier.padding(10.dp)) } } - Spacer(modifier = Modifier.weight(1f)) // 将游戏图标推到最底部 + Spacer(modifier = Modifier.weight(1f)) - // 底部的游戏图标 Column( modifier = Modifier - .padding(bottom = 16.dp) // 调整底部间距 + .padding(bottom = 16.dp) ) { gameIcon?.let { Image( bitmap = it, contentDescription = null, modifier = Modifier - .size(64.dp) // 调整图标大小 + .size(64.dp) .padding(8.dp) ) } @@ -245,7 +275,7 @@ fun NavigationBar( val coroutineScope = rememberCoroutineScope() var lastClickTime by remember { mutableLongStateOf(0L) } - NavigationBar { + NavigationBar() { NavigationIndex.entries.forEachIndexed { index, navigationItem -> val isSelected = pagerState.currentPage == index @@ -254,13 +284,12 @@ fun NavigationBar( onClick = { val currentTime = System.currentTimeMillis() if ((currentTime - lastClickTime) < 300 && isSelected) { // 检测双击 - // 刷新当前页面的逻辑 refreshCurrentPage(pagerState.currentPage, modViewModel) } else { modViewModel.exitSelect() if (!isSelected) { coroutineScope.launch { - pagerState.scrollToPage(index) + pagerState.animateScrollToPage(index) } } } @@ -278,7 +307,7 @@ fun NavigationBar( Text(text = stringResource(id = navigationItem.title)) } }, - alwaysShowLabel = false // 确保标签只在 isSelected 为 true 时显示 + alwaysShowLabel = false ) } } @@ -340,8 +369,8 @@ fun getGameIcon(packageName: String): ImageBitmap? { Bitmap.Config.ARGB_8888 ).also { bitmap -> val canvas = Canvas(bitmap) - drawable.setBounds(0, 0, canvas.width, canvas.height) - drawable.draw(canvas) + (drawable as AdaptiveIconDrawable).setBounds(0, 0, canvas.width, canvas.height) + (drawable as AdaptiveIconDrawable).draw(canvas) } } diff --git a/app/src/main/java/top/laoxin/modmanager/ui/view/Setting.kt b/app/src/main/java/top/laoxin/modmanager/ui/view/Setting.kt index bfb5691..c3ebaf3 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/view/Setting.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/view/Setting.kt @@ -1,6 +1,7 @@ package top.laoxin.modmanager.ui.view import android.content.Context +import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -442,17 +443,23 @@ fun ThinksDialogCommon( @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingTopBar() { - TopAppBar(colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - titleContentColor = MaterialTheme.colorScheme.onSecondaryContainer, - navigationIconContentColor = MaterialTheme.colorScheme.onSecondaryContainer, - ), - title = { - Text( - stringResource(id = R.string.settings), style = MaterialTheme.typography.titleLarge - ) - }) +fun SettingTopBar(modifier: Modifier = Modifier, configuration: Int) { + if( configuration != Configuration.ORIENTATION_LANDSCAPE) { + TopAppBar( + modifier = modifier, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + + ), + title = { + + Text( + stringResource(id = R.string.settings), style = MaterialTheme.typography.titleLarge + ) + + }) + } + } @Preview diff --git a/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModTopBar.kt b/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModTopBar.kt index 4c63e61..e5fb11c 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModTopBar.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModTopBar.kt @@ -1,13 +1,13 @@ package top.laoxin.modmanager.ui.view.modview +import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -68,7 +68,7 @@ fun Tips( text } val tipsEnd = if (uiState.multitaskingProgress.isNotEmpty()) { - "总进度 : ${uiState.multitaskingProgress}" + stringResource(R.string.mod_top_bar_tips, uiState.multitaskingProgress) } else { "" } @@ -95,7 +95,7 @@ fun Tips( @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ModTopBar(viewModel: ModViewModel) { +fun ModTopBar(viewModel: ModViewModel, modifier: Modifier = Modifier, configuration: Int) { val uiState by viewModel.uiState.collectAsState() if (uiState.isMultiSelect) { DialogCommon( @@ -105,28 +105,33 @@ fun ModTopBar(viewModel: ModViewModel) { onCancel = { viewModel.setShowDelSelectModsDialog(false) }, showDialog = uiState.showDelSelectModsDialog ) - MultiSelectTopBar(viewModel, uiState) + MultiSelectTopBar(viewModel, uiState, modifier = modifier, configuration = configuration) } else { - GeneralTopBar(viewModel, uiState) + GeneralTopBar(viewModel, uiState, modifier = modifier,configuration = configuration) } } @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MultiSelectTopBar(viewModel: ModViewModel, uiState: ModUiState) { +fun MultiSelectTopBar( + viewModel: ModViewModel, + uiState: ModUiState, + modifier: Modifier, + configuration: Int +) { val modList = when (uiState.modsView) { NavigationIndex.ALL_MODS -> uiState.modList NavigationIndex.ENABLE_MODS -> uiState.enableModList NavigationIndex.DISABLE_MODS -> uiState.disableModList NavigationIndex.SEARCH_MODS -> uiState.searchModList - NavigationIndex.MODS_BROWSER -> uiState.modList + NavigationIndex.MODS_BROWSER -> uiState.currentMods } Column { - TopAppBar(colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - titleContentColor = MaterialTheme.colorScheme.onSecondaryContainer, - navigationIconContentColor = MaterialTheme.colorScheme.onSecondaryContainer, + TopAppBar( + modifier = modifier, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = if (configuration == Configuration.ORIENTATION_LANDSCAPE) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceContainer, ), title = { Box(contentAlignment = Alignment.CenterStart) { Text( @@ -151,7 +156,7 @@ fun MultiSelectTopBar(viewModel: ModViewModel, uiState: ModUiState) { if (uiState.modsSelected.isNotEmpty()) "${uiState.modsSelected.size}/${modList.size}" else "${modList.size}" Text( - text = "统计:$total", + text = stringResource(R.string.mod_top_bar_count, total), style = MaterialTheme.typography.labelMedium, // color = MaterialTheme.colorScheme.onPrimary, textAlign = TextAlign.Start, @@ -230,7 +235,11 @@ fun MultiSelectTopBar(viewModel: ModViewModel, uiState: ModUiState) { hint = stringResource(R.string.mod_page_search_hit), onClose = { viewModel.setSearchBoxVisible(false) - viewModel.setModsView(NavigationIndex.ALL_MODS) + + when (uiState.modsView) { + NavigationIndex.MODS_BROWSER -> viewModel.setModsView(NavigationIndex.MODS_BROWSER) + else -> viewModel.setModsView(NavigationIndex.ALL_MODS) + } // 清空搜索框 viewModel.setSearchText("") } @@ -242,16 +251,27 @@ fun MultiSelectTopBar(viewModel: ModViewModel, uiState: ModUiState) { @OptIn(ExperimentalMaterial3Api::class) @Composable -fun GeneralTopBar(viewModel: ModViewModel, uiState: ModUiState) { - +fun GeneralTopBar( + viewModel: ModViewModel, + uiState: ModUiState, + modifier: Modifier, + configuration: Int +) { + val modList = when (uiState.modsView) { + NavigationIndex.ALL_MODS -> uiState.modList + NavigationIndex.ENABLE_MODS -> uiState.enableModList + NavigationIndex.DISABLE_MODS -> uiState.disableModList + NavigationIndex.SEARCH_MODS -> uiState.searchModList + NavigationIndex.MODS_BROWSER -> uiState.currentMods + } var showMenu by remember { mutableStateOf(false) } Column { - TopAppBar(colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - titleContentColor = MaterialTheme.colorScheme.onSecondaryContainer, - navigationIconContentColor = MaterialTheme.colorScheme.onSecondaryContainer, - ), title = { + TopAppBar( + modifier = modifier, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = if (configuration == Configuration.ORIENTATION_LANDSCAPE) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceContainer, + ), title = { Box(contentAlignment = Alignment.CenterStart) { Text( stringResource(id = uiState.modsView.title), @@ -261,18 +281,12 @@ fun GeneralTopBar(viewModel: ModViewModel, uiState: ModUiState) { Row( modifier = Modifier.padding(top = 40.dp), ) { - val modList = when (uiState.modsView) { - NavigationIndex.ALL_MODS -> uiState.modList - NavigationIndex.ENABLE_MODS -> uiState.enableModList - NavigationIndex.DISABLE_MODS -> uiState.disableModList - NavigationIndex.SEARCH_MODS -> uiState.searchModList - NavigationIndex.MODS_BROWSER -> uiState.modList - } + val total = if (uiState.modsSelected.isNotEmpty()) "${uiState.modsSelected.size}/${modList.size}" else "${modList.size}" Text( - text = "统计:$total", + text = stringResource(R.string.mod_top_bar_count, total), style = MaterialTheme.typography.labelMedium, //color = MaterialTheme.colorScheme.onPrimary, textAlign = TextAlign.Start, @@ -296,7 +310,11 @@ fun GeneralTopBar(viewModel: ModViewModel, uiState: ModUiState) { } IconButton(onClick = { viewModel.setSearchBoxVisible(true) - viewModel.setModsView(NavigationIndex.SEARCH_MODS) + when (uiState.modsView) { + NavigationIndex.MODS_BROWSER -> viewModel.setModsView(NavigationIndex.MODS_BROWSER) + else -> viewModel.setModsView(NavigationIndex.SEARCH_MODS) + } + // 请求焦点 }, modifier = Modifier) { Icon( @@ -319,26 +337,31 @@ fun GeneralTopBar(viewModel: ModViewModel, uiState: ModUiState) { // tint = MaterialTheme.colorScheme.primaryContainer ) } - DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { - DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_show_enable_mods)) }, - onClick = { - viewModel.setModsView(NavigationIndex.ENABLE_MODS) - - }) - DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_show_disable_mods)) }, - onClick = { - viewModel.setModsView(NavigationIndex.DISABLE_MODS) - }) - DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_show_all_mods)) }, - onClick = { - viewModel.setModsView(NavigationIndex.ALL_MODS) - }) - DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_mods_browser)) }, - onClick = { - viewModel.setModsView(NavigationIndex.MODS_BROWSER) - }) - - // 添加更多的菜单项 + AnimatedVisibility(visible = showMenu,modifier = Modifier.offset(y = 20.dp) ) { + DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { + DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_show_enable_mods)) }, + onClick = { + viewModel.setModsView(NavigationIndex.ENABLE_MODS) + showMenu = false + }) + DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_show_disable_mods)) }, + onClick = { + viewModel.setModsView(NavigationIndex.DISABLE_MODS) + showMenu = false + }) + DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_show_all_mods)) }, + onClick = { + viewModel.setModsView(NavigationIndex.ALL_MODS) + showMenu = false + }) + DropdownMenuItem(text = { Text(stringResource(R.string.mod_page_dropdownMenu_mods_browser)) }, + onClick = { + viewModel.setModsView(NavigationIndex.MODS_BROWSER) + showMenu = false + }) + + // 添加更多的菜单项 + } } }) @@ -352,7 +375,10 @@ fun GeneralTopBar(viewModel: ModViewModel, uiState: ModUiState) { hint = stringResource(R.string.mod_page_search_hit), onClose = { viewModel.setSearchBoxVisible(false) - viewModel.setModsView(NavigationIndex.ALL_MODS) + when (uiState.modsView) { + NavigationIndex.MODS_BROWSER -> viewModel.setModsView(NavigationIndex.MODS_BROWSER) + else -> viewModel.setModsView(NavigationIndex.ALL_MODS) + } // 清空搜索框 viewModel.setSearchText("") } diff --git a/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModsBrowser.kt b/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModsBrowser.kt index 198eadb..0c9b62c 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModsBrowser.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/view/modview/ModsBrowser.kt @@ -1,6 +1,7 @@ import android.os.Environment import androidx.activity.compose.BackHandler import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image @@ -41,6 +42,8 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith +import androidx.compose.ui.res.stringResource +import top.laoxin.modmanager.bean.ModBean import top.laoxin.modmanager.ui.view.modview.ModListItem @OptIn(ExperimentalAnimationApi::class) @@ -62,30 +65,7 @@ fun ModsBrowser(viewModel: ModViewModel, uiState: ModUiState) { Column(modifier = Modifier.fillMaxSize()) { - // 返回上级目录按钮 - if (currentPath != uiState.currentGameModPath){ - FileListItem( - modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp), - name = "返回上级目录", - isSelected = false, - onLongClick = { - // 长按事�� - }, - onClick = { - if (currentPath != uiState.currentGameModPath) { - previousPath = currentPath - currentPath = File(currentPath).parent ?: currentPath - } - }, - onMultiSelectClick = { - // 多选状态下的点击事件 - }, - isMultiSelect = uiState.isMultiSelect, - description = currentPath.replace(uiState.currentGameModPath, ""), - iconId = R.drawable.back_icon - ) - } // 监听返回按键事件 if (currentPath != uiState.currentGameModPath) { @@ -109,14 +89,48 @@ fun ModsBrowser(viewModel: ModViewModel, uiState: ModUiState) { } }, label = "" ) { path -> + val mods : MutableList = mutableListOf() + for (file in files) { + val modsByPath = viewModel.getModsByPathStrict(file.path) + val modsByVirtualPaths = viewModel.getModsByVirtualPathsStrict(file.path) + mods.addAll(modsByPath) + mods.addAll(modsByVirtualPaths) + } + viewModel.setCurrentMods(mods) + // 返回上级目录按钮 + LazyColumn { - items(files) { file -> + items(files) { file:File -> + if (currentPath != uiState.currentGameModPath && files.indexOf(file) == 0){ + FileListItem( + modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp), + name = stringResource(R.string.mod_browser_file_list_back), + isSelected = false, + onLongClick = { + // 长按事�� + }, + onClick = { + if (currentPath != uiState.currentGameModPath) { + previousPath = currentPath + currentPath = File(currentPath).parent ?: currentPath + } + }, + onMultiSelectClick = { + // 多选状态下的点击事件 + }, + isMultiSelect = uiState.isMultiSelect, + description = currentPath.replace(uiState.currentGameModPath, ""), + iconId = R.drawable.back_icon + ) + } // 通过filepath获取mods val modsByPath = viewModel.getModsByPathStrict(file.path) // 通过虚拟路径获取mods val modsByVirtualPaths = viewModel.getModsByVirtualPathsStrict(file.path) - // mod数量 + + // 设置当前页面的mods + val modCount = if (viewModel.getModsByPath(file.path).isNotEmpty()) viewModel.getModsByPath(file.path).size else viewModel.getModsByVirtualPaths(file.path).size if (modsByPath.isEmpty() && modsByVirtualPaths.isEmpty() && (file.isDirectory || !file.exists())) { @@ -135,7 +149,7 @@ fun ModsBrowser(viewModel: ModViewModel, uiState: ModUiState) { }, onMultiSelectClick = {}, isMultiSelect = uiState.isMultiSelect, - description = "MOD数量: $modCount", + description = stringResource(R.string.mod_browser_file_description, modCount), iconId = if (modCount > 0) R.drawable.folder_mod_icon else R.drawable.folder_icon ) } @@ -147,7 +161,7 @@ fun ModsBrowser(viewModel: ModViewModel, uiState: ModUiState) { onLongClick = {viewModel.modLongClick(modsByPath.firstOrNull() ?: modsByVirtualPaths.firstOrNull()!!)}, onMultiSelectClick = {viewModel.modMultiSelectClick(modsByPath.firstOrNull() ?: modsByVirtualPaths.firstOrNull()!!)}, isMultiSelect = uiState.isMultiSelect, - modSwitchEnable = true, + modSwitchEnable = uiState.modSwitchEnable, openModDetail = { mod, _ -> viewModel.openModDetail(mod, true) }, @@ -168,7 +182,7 @@ fun ModsBrowser(viewModel: ModViewModel, uiState: ModUiState) { }, onMultiSelectClick = {}, isMultiSelect = uiState.isMultiSelect, - description = "MOD数量: ${viewModel.getModsByPath(file.path).size}", + description = stringResource(R.string.mod_browser_file_description, viewModel.getModsByPath(file.path).size), iconId = R.drawable.zip_mod_icon ) } @@ -199,18 +213,20 @@ fun FileListItem( elevation = if (isSelected) CardDefaults.cardElevation(2.dp) else CardDefaults.cardElevation( 0.dp ), - modifier = modifier.combinedClickable( - onClick = { - if (isMultiSelect) { - onMultiSelectClick() - } else { - onClick() + modifier = modifier + .combinedClickable( + onClick = { + if (isMultiSelect) { + onMultiSelectClick() + } else { + onClick() + } + }, + onLongClick = { + onLongClick() } - }, - onLongClick = { - onLongClick() - } - ).animateContentSize(), + ) + .animateContentSize(), colors = CardDefaults.cardColors( containerColor = if (!isSelected) CardDefaults.cardColors().containerColor else MaterialTheme.colorScheme.secondaryContainer, ) diff --git a/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ConsoleViewModel.kt b/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ConsoleViewModel.kt index 4e2ad18..5a75fbb 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ConsoleViewModel.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ConsoleViewModel.kt @@ -117,6 +117,8 @@ class ConsoleViewModel( userPreferencesRepository.getPreferenceFlow("SCAN_DIRECTORY_MODS", false) private val delUnzipDictionaryFlow = userPreferencesRepository.getPreferenceFlow("DELETE_UNZIP_DIRECTORY", false) + private val showCategoryViewFlow = + userPreferencesRepository.getPreferenceFlow("SHOW_CATEGORY_VIEW", false) private val userPreferencesState = combine( selectedGameFlow, selectedDirectoryFlow @@ -139,15 +141,17 @@ class ConsoleViewModel( openPermissionRequestDialogFlow, scanDirectoryModsFlow, delUnzipDictionaryFlow, + showCategoryViewFlow, _uiState ) { values -> - (values[6] as ConsoleUiState).copy( + (values[7] as ConsoleUiState).copy( scanQQDirectory = values[0] as Boolean, selectedDirectory = values[1] as String, scanDownload = values[2] as Boolean, openPermissionRequestDialog = values[3] as Boolean, scanDirectoryMods = values[4] as Boolean, delUnzipDictionary = values[5] as Boolean, + showCategoryView = values[6] as Boolean ) }.stateIn( @@ -415,8 +419,8 @@ class ConsoleViewModel( Bitmap.Config.ARGB_8888 ).also { bitmap -> val canvas = Canvas(bitmap) - drawable.setBounds(0, 0, canvas.width, canvas.height) - drawable.draw(canvas) + (drawable as AdaptiveIconDrawable).setBounds(0, 0, canvas.width, canvas.height) + (drawable as AdaptiveIconDrawable).draw(canvas) } } @@ -609,6 +613,14 @@ class ConsoleViewModel( } } } + + fun setShowCategoryView(it: Boolean) { + // 展示分类视图 + viewModelScope.launch { + userPreferencesRepository.savePreference("SHOW_CATEGORY_VIEW", it) + } + + } } diff --git a/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ModViewModel.kt b/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ModViewModel.kt index 90c85ef..ea0fd1a 100644 --- a/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ModViewModel.kt +++ b/app/src/main/java/top/laoxin/modmanager/ui/viewmodel/ModViewModel.kt @@ -134,6 +134,9 @@ class ModViewModel( private val delUnzipDictionaryFlow = userPreferencesRepository.getPreferenceFlow("DELETE_UNZIP_DIRECTORY", false) + // 展示分类视图 + private val showCategoryView = + userPreferencesRepository.getPreferenceFlow("SHOW_CATEGORY_VIEW", false) // 生成用户配置对象 private val userPreferences: StateFlow = combine( @@ -142,7 +145,8 @@ class ModViewModel( scanDownloadFlow, selectedGame, scanDirectoryMods, - delUnzipDictionaryFlow + delUnzipDictionaryFlow, + showCategoryView ) { values -> UserPreferencesState( scanQQDirectory = values[0] as Boolean, @@ -150,7 +154,8 @@ class ModViewModel( scanDownload = values[2] as Boolean, selectedGameIndex = values[3] as Int, scanDirectoryMods = values[4] as Boolean, - delUnzipDictionary = values[5] as Boolean + delUnzipDictionary = values[5] as Boolean, + showCategoryView = values[6] as Boolean ) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), UserPreferencesState() @@ -179,6 +184,9 @@ class ModViewModel( startFileObserver(it) // 设置当前游戏mod目录 updateCurrentGameModPath(it) + // 设置当前的视图 + upDateCurrentModsView(it) + } @@ -187,6 +195,18 @@ class ModViewModel( } + private fun upDateCurrentModsView(it: UserPreferencesState) { + if (it.showCategoryView) { + _uiState.update { + it.copy(modsView = NavigationIndex.MODS_BROWSER) + } + } else { + _uiState.update { + it.copy(modsView = NavigationIndex.ALL_MODS) + } + } + } + // 更新当前游戏mod目录 private fun updateCurrentGameModPath(userPreferences: UserPreferencesState) { _uiState.update { @@ -756,13 +776,26 @@ class ModViewModel( // 取消上一个搜索任务 searchJob?.cancel() searchJob = viewModelScope.launch(Dispatchers.IO) { - if (searchText.isEmpty()) { - setSearchMods(emptyList()) - } else { - modRepository.search(searchText, _gameInfo.packageName).collect { - setSearchMods(it) + when (_uiState.value.modsView) { + NavigationIndex.MODS_BROWSER -> { + if (searchText.isEmpty()) { + updateFiles(_currentPath) + } else { + val searchFiles = _uiState.value.currentFiles.filter { it.name.contains(searchText) } + _uiState.update { it.copy(currentFiles = searchFiles)} + } + } + else -> { + if (searchText.isEmpty()) { + setSearchMods(emptyList()) + } else { + modRepository.search(searchText, _gameInfo.packageName).collect { + setSearchMods(it) + } + } } } + } } @@ -1068,6 +1101,12 @@ class ModViewModel( return _uiState.value.modList.filter { it.virtualPaths?.contains(path) == true } } + fun setCurrentMods(mods: List) { + _uiState.update { + it.copy(currentMods = mods) + } + } + } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0dd586..a94ec25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -181,5 +181,11 @@ 将会在开启MOD后始终删除解压文件,可以节省空间,但下次开启同样的MOD会再次解压导致二次开启速度变慢! 分类视图 分类 + 默认展示分类视图 + 返回上级分类 + MOD数量: %1$s + 统计:%1$s + 总进度 : %1$s + 再按一次退出应用 \ No newline at end of file