mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-09 10:54:31 +02:00
feat: feature section search bar
This commit is contained in:
parent
f0df0045d6
commit
b0dfcd5470
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.material.icons.Icons
|
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.FolderOpen
|
||||||
import androidx.compose.material.icons.filled.OpenInNew
|
import androidx.compose.material.icons.filled.OpenInNew
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.rounded.Save
|
import androidx.compose.material.icons.rounded.Save
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@ -35,13 +39,18 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
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.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavOptions
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.navigation
|
import androidx.navigation.navigation
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.core.config.ConfigContainer
|
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||||
import me.rhunk.snapenhance.core.config.DataProcessors
|
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.PropertyPair
|
||||||
|
import me.rhunk.snapenhance.core.config.PropertyValue
|
||||||
import me.rhunk.snapenhance.ui.manager.Section
|
import me.rhunk.snapenhance.ui.manager.Section
|
||||||
import me.rhunk.snapenhance.ui.util.ChooseFolderHelper
|
import me.rhunk.snapenhance.ui.util.ChooseFolderHelper
|
||||||
|
|
||||||
@ -64,7 +78,8 @@ class FeaturesSection : Section() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAIN_ROUTE = "feature_root"
|
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
|
private lateinit var openFolderCallback: (uri: String) -> Unit
|
||||||
@ -86,6 +101,17 @@ class FeaturesSection : Section() {
|
|||||||
containers
|
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() {
|
override fun init() {
|
||||||
openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity!! as ComponentActivity) {
|
openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity!! as ComponentActivity) {
|
||||||
openFolderCallback(it)
|
openFolderCallback(it)
|
||||||
@ -122,13 +148,25 @@ class FeaturesSection : Section() {
|
|||||||
Container(context.config.root)
|
Container(context.config.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(FEATURE_CONTAINER_ROOT) { backStackEntry ->
|
composable(FEATURE_CONTAINER_ROUTE) { backStackEntry ->
|
||||||
backStackEntry.arguments?.getString("name")?.let { containerName ->
|
backStackEntry.arguments?.getString("name")?.let { containerName ->
|
||||||
allContainers[containerName]?.let {
|
allContainers[containerName]?.let {
|
||||||
Container(it.value.get() as ConfigContainer)
|
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
|
val container = propertyValue.get() as ConfigContainer
|
||||||
|
|
||||||
registerClickCallback {
|
registerClickCallback {
|
||||||
navController.navigate(FEATURE_CONTAINER_ROOT.replace("{name}", property.name))
|
navController.navigate(FEATURE_CONTAINER_ROUTE.replace("{name}", property.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.globalState == null) return
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun Container(
|
private fun PropertiesView(
|
||||||
configContainer: ConfigContainer
|
properties: List<PropertyPair<*>>
|
||||||
) {
|
) {
|
||||||
val properties = remember {
|
|
||||||
configContainer.properties.map { PropertyPair(it.key, it.value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||||
Scaffold(
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
@ -332,7 +332,7 @@
|
|||||||
"progress": "Progress",
|
"progress": "Progress",
|
||||||
"failure": "Failure"
|
"failure": "Failure"
|
||||||
},
|
},
|
||||||
"auto_save_messages": {
|
"auto_save_messages_in_conversations": {
|
||||||
"NOTE": "Audio Note",
|
"NOTE": "Audio Note",
|
||||||
"CHAT": "Chat",
|
"CHAT": "Chat",
|
||||||
"EXTERNAL_MEDIA": "External Media",
|
"EXTERNAL_MEDIA": "External Media",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user