commit b0dfcd5470ff981e35c38e2ca5b95dfd78794ed9
parent f0df0045d6195b0f850439b646f9d9009dc1c125
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Fri, 18 Aug 2023 11:03:14 +0200

feat: feature section search bar

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/features/FeaturesSection.kt | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mcore/src/main/assets/lang/en_US.json | 2+-
2 files changed, 127 insertions(+), 10 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/features/FeaturesSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/features/FeaturesSection.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -20,9 +21,12 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.OpenInNew +import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api @@ -35,13 +39,18 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight @@ -50,12 +59,17 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.navigation +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.rhunk.snapenhance.core.config.ConfigContainer import me.rhunk.snapenhance.core.config.DataProcessors +import me.rhunk.snapenhance.core.config.PropertyKey import me.rhunk.snapenhance.core.config.PropertyPair +import me.rhunk.snapenhance.core.config.PropertyValue import me.rhunk.snapenhance.ui.manager.Section import me.rhunk.snapenhance.ui.util.ChooseFolderHelper @@ -64,7 +78,8 @@ class FeaturesSection : Section() { companion object { const val MAIN_ROUTE = "feature_root" - const val FEATURE_CONTAINER_ROOT = "feature_container/{name}" + const val FEATURE_CONTAINER_ROUTE = "feature_container/{name}" + const val SEARCH_FEATURE_ROUTE = "search_feature/{keyword}" } private lateinit var openFolderCallback: (uri: String) -> Unit @@ -86,6 +101,17 @@ class FeaturesSection : Section() { containers } + private val allProperties by lazy { + val properties = mutableMapOf<PropertyKey<*>, PropertyValue<*>>() + allContainers.values.forEach { + val container = it.value.get() as ConfigContainer + container.properties.forEach { property -> + properties[property.key] = property.value + } + } + properties + } + override fun init() { openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity!! as ComponentActivity) { openFolderCallback(it) @@ -122,13 +148,25 @@ class FeaturesSection : Section() { Container(context.config.root) } - composable(FEATURE_CONTAINER_ROOT) { backStackEntry -> + composable(FEATURE_CONTAINER_ROUTE) { backStackEntry -> backStackEntry.arguments?.getString("name")?.let { containerName -> allContainers[containerName]?.let { Container(it.value.get() as ConfigContainer) } } } + + composable(SEARCH_FEATURE_ROUTE) { backStackEntry -> + backStackEntry.arguments?.getString("keyword")?.let { keyword -> + val properties = allProperties.filter { + it.key.name.contains(keyword, ignoreCase = true) || + context.translation["${it.key.propertyTranslationPath()}.name"].contains(keyword, ignoreCase = true) || + context.translation["${it.key.propertyTranslationPath()}.description"].contains(keyword, ignoreCase = true) + }.map { PropertyPair(it.key, it.value) } + + PropertiesView(properties) + } + } } } @@ -228,7 +266,7 @@ class FeaturesSection : Section() { val container = propertyValue.get() as ConfigContainer registerClickCallback { - navController.navigate(FEATURE_CONTAINER_ROOT.replace("{name}", property.name)) + navController.navigate(FEATURE_CONTAINER_ROUTE.replace("{name}", property.name)) } if (container.globalState == null) return @@ -341,16 +379,84 @@ class FeaturesSection : Section() { } } + @Composable + private fun FeatureSearchBar(rowScope: RowScope, focusRequester: FocusRequester) { + val searchValue = remember { mutableStateOf("") } + val scope = rememberCoroutineScope() + val currentSearchJob = remember { mutableStateOf<Job?>(null) } + + rowScope.apply { + TextField( + value = searchValue.value, + onValueChange = { keyword -> + searchValue.value = keyword + if (keyword.isEmpty()) { + navController.navigate(MAIN_ROUTE) + return@TextField + } + currentSearchJob.value?.cancel() + scope.launch { + delay(300) + navController.navigate(SEARCH_FEATURE_ROUTE.replace("{keyword}", keyword), NavOptions.Builder() + .setLaunchSingleTop(true) + .setPopUpTo(MAIN_ROUTE, false) + .build() + ) + }.also { currentSearchJob.value = it } + }, + + keyboardActions = KeyboardActions(onDone = { + focusRequester.freeFocus() + }), + modifier = Modifier + .focusRequester(focusRequester) + .weight(1f, fill = true) + .padding(end = 10.dp) + .height(70.dp), + singleLine = true, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + focusedContainerColor = MaterialTheme.colorScheme.surface, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + cursorColor = MaterialTheme.colorScheme.primary + ) + ) + } + } - @OptIn(ExperimentalMaterial3Api::class) @Composable - private fun Container( - configContainer: ConfigContainer - ) { - val properties = remember { - configContainer.properties.map { PropertyPair(it.key, it.value) } + override fun TopBarActions(rowScope: RowScope) { + val showSearchBar = remember { mutableStateOf(false) } + val focusRequester = remember { FocusRequester() } + + if (showSearchBar.value) { + FeatureSearchBar(rowScope, focusRequester) + LaunchedEffect(true) { + focusRequester.requestFocus() + } + } + + IconButton(onClick = { + showSearchBar.value = showSearchBar.value.not() + if (!showSearchBar.value && navController.currentBackStackEntry?.destination?.route == SEARCH_FEATURE_ROUTE) { + navController.navigate(MAIN_ROUTE) + } + }) { + Icon( + imageVector = if (showSearchBar.value) Icons.Filled.Close + else Icons.Filled.Search, + contentDescription = null + ) } + } + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun PropertiesView( + properties: List<PropertyPair<*>> + ) { val scope = rememberCoroutineScope() val scaffoldState = rememberBottomSheetScaffoldState() Scaffold( @@ -392,4 +498,15 @@ class FeaturesSection : Section() { ) } + + @Composable + private fun Container( + configContainer: ConfigContainer + ) { + val properties = remember { + configContainer.properties.map { PropertyPair(it.key, it.value) } + } + + PropertiesView(properties) + } } \ No newline at end of file diff --git a/core/src/main/assets/lang/en_US.json b/core/src/main/assets/lang/en_US.json @@ -332,7 +332,7 @@ "progress": "Progress", "failure": "Failure" }, - "auto_save_messages": { + "auto_save_messages_in_conversations": { "NOTE": "Audio Note", "CHAT": "Chat", "EXTERNAL_MEDIA": "External Media",