Feat(ToolStore and Tools): Support query by tool name or keywords

This commit is contained in:
2024-08-19 17:48:42 +08:00
parent 47647217f1
commit 167df010a9
16 changed files with 111 additions and 87 deletions

View File

@@ -27,6 +27,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
@@ -52,6 +53,7 @@ import top.fatweb.oxygen.toolbox.ui.component.OxygenNavigationBarItem
import top.fatweb.oxygen.toolbox.ui.component.OxygenNavigationRail
import top.fatweb.oxygen.toolbox.ui.component.OxygenNavigationRailItem
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
import top.fatweb.oxygen.toolbox.ui.component.SearchButtonPosition
import top.fatweb.oxygen.toolbox.ui.settings.SettingsDialog
import top.fatweb.oxygen.toolbox.ui.theme.GradientColors
import top.fatweb.oxygen.toolbox.ui.theme.LocalGradientColors
@@ -79,6 +81,20 @@ fun OxygenApp(appState: OxygenAppState) {
val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
var activeSearch by remember { mutableStateOf(false) }
var searchValue by remember { mutableStateOf("") }
var searchCount by remember { mutableIntStateOf(0) }
LaunchedEffect(destination) {
activeSearch = false
searchValue = ""
if (searchCount == 0) {
searchCount++
} else {
searchCount = 0
}
}
LaunchedEffect(isOffline) {
if (isOffline) {
snackbarHostState.showSnackbar(
@@ -152,12 +168,26 @@ fun OxygenApp(appState: OxygenAppState) {
navigationIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_navigation_icon_description),
actionIcon = OxygenIcons.MoreVert,
actionIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_action_icon_description),
activeSearch = activeSearch,
searchButtonPosition = SearchButtonPosition.Navigation,
query = searchValue,
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
),
onNavigationClick = { appState.navigateToSearch() },
onActionClick = { showSettingsDialog = true }
onNavigationClick = { activeSearch = true },
onActionClick = { showSettingsDialog = true },
onQueryChange = {
searchValue = it
},
onSearch = {
searchCount++
},
onCancelSearch = {
searchValue = ""
activeSearch = false
searchCount = 0
}
)
}
@@ -174,7 +204,9 @@ fun OxygenApp(appState: OxygenAppState) {
LaunchPageConfig.Tools -> TOOLS_ROUTE
LaunchPageConfig.Star -> STAR_ROUTE
},
isVertical = appState.shouldShowBottomBar
isVertical = appState.shouldShowBottomBar,
searchValue = searchValue,
searchCount = searchCount
)
}
}

View File

@@ -26,7 +26,6 @@ import top.fatweb.oxygen.toolbox.navigation.TOOL_STORE_ROUTE
import top.fatweb.oxygen.toolbox.navigation.TopLevelDestination
import top.fatweb.oxygen.toolbox.navigation.navigateToAbout
import top.fatweb.oxygen.toolbox.navigation.navigateToLibraries
import top.fatweb.oxygen.toolbox.navigation.navigateToSearch
import top.fatweb.oxygen.toolbox.navigation.navigateToStar
import top.fatweb.oxygen.toolbox.navigation.navigateToToolStore
import top.fatweb.oxygen.toolbox.navigation.navigateToTools
@@ -119,8 +118,6 @@ class OxygenAppState(
}
}
fun navigateToSearch() = navController.navigateToSearch()
fun navigateToLibraries() = navController.navigateToLibraries()
fun navigateToAbout() = navController.navigateToAbout()

View File

@@ -51,6 +51,7 @@ fun OxygenTopAppBar(
actionIcon: ImageVector? = null,
actionIconContentDescription: String? = null,
activeSearch: Boolean = false,
searchButtonPosition: SearchButtonPosition = SearchButtonPosition.Action,
query: String = "",
colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(),
onNavigationClick: () -> Unit = {},
@@ -122,7 +123,15 @@ fun OxygenTopAppBar(
else title()
},
navigationIcon = {
navigationIcon?.let {
if (activeSearch && searchButtonPosition == SearchButtonPosition.Navigation) IconButton(
onClick = onCancelSearch
) {
Icon(
imageVector = OxygenIcons.Close,
contentDescription = stringResource(R.string.core_close)
)
}
else navigationIcon?.let {
IconButton(onClick = onNavigationClick) {
Icon(
imageVector = navigationIcon,
@@ -133,7 +142,9 @@ fun OxygenTopAppBar(
}
},
actions = {
if (activeSearch) IconButton(onClick = onCancelSearch) {
if (activeSearch && searchButtonPosition == SearchButtonPosition.Action) IconButton(
onClick = onCancelSearch
) {
Icon(
imageVector = OxygenIcons.Close,
contentDescription = stringResource(R.string.core_close)
@@ -154,6 +165,10 @@ fun OxygenTopAppBar(
)
}
enum class SearchButtonPosition {
Navigation, Action
}
@OptIn(ExperimentalMaterial3Api::class)
@OxygenPreviews
@Composable

View File

@@ -1,29 +0,0 @@
package top.fatweb.oxygen.toolbox.ui.search
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
@Composable
internal fun SearchRoute(
modifier: Modifier = Modifier,
onBackClick: () -> Unit,
// searchViewmodel: SearchViewModel = hiltViewModel()
) {
Row(
modifier = modifier
.fillMaxSize()
.safeDrawingPadding()
) {
IconButton(onClick = onBackClick) {
Icon(imageVector = OxygenIcons.Back, contentDescription = null)
}
Text("Search")
}
}

View File

@@ -1,9 +0,0 @@
package top.fatweb.oxygen.toolbox.ui.search
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class SearchViewModel @Inject constructor(
) : ViewModel()

View File

@@ -37,6 +37,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -64,11 +65,17 @@ import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState
internal fun ToolStoreRoute(
modifier: Modifier = Modifier,
viewModel: ToolStoreViewModel = hiltViewModel(),
searchValue: String,
searchCount: Int,
onNavigateToToolView: (username: String, toolId: String, preview: Boolean) -> Unit,
) {
val toolStorePagingItems = viewModel.storeData.collectAsLazyPagingItems()
val installInfo by viewModel.installInfo.collectAsState()
LaunchedEffect(searchCount) {
viewModel.onSearchValueChange(searchValue)
}
ToolStoreScreen(
modifier = modifier,
onNavigateToToolView = onNavigateToToolView,

View File

@@ -38,6 +38,10 @@ class ToolStoreViewModel @Inject constructor(
)
}
fun onSearchValueChange(value: String) {
savedStateHandle[SEARCH_VALUE] = value
}
fun changeInstallInfo(
status: ToolStoreUiState.InstallInfo.Status = installInfo.value.status,
type: ToolStoreUiState.InstallInfo.Type = installInfo.value.type

View File

@@ -36,6 +36,7 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -67,12 +68,17 @@ import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
internal fun ToolsRoute(
modifier: Modifier = Modifier,
viewModel: ToolsScreenViewModel = hiltViewModel(),
searchValue: String,
onShowSnackbar: suspend (message: String, action: String?) -> Boolean,
onNavigateToToolView: (username: String, toolId: String, preview: Boolean) -> Unit,
onNavigateToToolStore: () -> Unit
) {
val toolsScreenUiStateState by viewModel.toolsScreenUiState.collectAsStateWithLifecycle()
LaunchedEffect(searchValue) {
viewModel.onSearchValueChange(searchValue)
}
ToolsScreen(
modifier = modifier,
onShowSnackbar = onShowSnackbar,

View File

@@ -4,8 +4,10 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -19,24 +21,30 @@ import kotlin.time.Duration.Companion.seconds
class ToolsScreenViewModel @Inject constructor(
private val storeRepository: StoreRepository,
private val toolRepository: ToolRepository,
savedStateHandle: SavedStateHandle
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "")
@OptIn(ExperimentalCoroutinesApi::class)
val toolsScreenUiState: StateFlow<ToolsScreenUiState> =
toolRepository.getAllToolsStream()
.map {
searchValue.flatMapLatest { searchValue ->
toolRepository.getAllToolsStream(searchValue).map {
if (it.isEmpty()) {
ToolsScreenUiState.Nothing
} else {
ToolsScreenUiState.Success(it)
}
}
.stateIn(
scope = viewModelScope,
initialValue = ToolsScreenUiState.Loading,
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
)
}.stateIn(
scope = viewModelScope,
initialValue = ToolsScreenUiState.Loading,
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
)
fun onSearchValueChange(value: String) {
savedStateHandle[SEARCH_VALUE] = value
}
fun uninstall(tool: ToolEntity) {
viewModelScope.launch {