diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/dao/ToolDao.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/dao/ToolDao.kt index 3c6f54d..34bf0ff 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/dao/ToolDao.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/dao/ToolDao.kt @@ -20,12 +20,19 @@ interface ToolDao { @Delete suspend fun deleteTool(tool: ToolEntity) - @Query("SELECT * FROM tools WHERE id = :id") + @Query("SELECT * FROM tools " + + "WHERE id = :id") fun selectToolById(id: Long): Flow - @Query("SELECT * FROM tools ORDER BY updateTime DESC") - fun selectAllTools(): Flow> + @Query("SELECT * FROM tools " + + "WHERE :searchValue = '' " + + "OR name LIKE '%' || :searchValue || '%' COLLATE NOCASE " + + "OR keywords LIKE '%\"%' || :searchValue || '%\"%' COLLATE NOCASE " + + "ORDER BY updateTime DESC") + fun selectAllTools(searchValue: String): Flow> - @Query("SELECT * FROM tools WHERE authorUsername = :username and toolId = :toolId LIMIT 1") + @Query("SELECT * FROM tools " + + "WHERE authorUsername = :username " + + "and toolId = :toolId LIMIT 1") fun selectToolByUsernameAndToolId(username: String, toolId: String): Flow } \ No newline at end of file diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt index b990688..720ebdd 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt @@ -11,7 +11,9 @@ fun OxygenNavHost( appState: OxygenAppState, onShowSnackbar: suspend (message: String, action: String?) -> Boolean, startDestination: String, - isVertical: Boolean + isVertical: Boolean, + searchValue: String, + searchCount: Int ) { val navController = appState.navController NavHost( @@ -19,9 +21,6 @@ fun OxygenNavHost( navController = navController, startDestination = startDestination ) { - searchScreen( - onBackClick = navController::popBackStack - ) aboutScreen( onBackClick = navController::popBackStack, onNavigateToLibraries = navController::navigateToLibraries @@ -31,10 +30,13 @@ fun OxygenNavHost( ) toolStoreScreen( isVertical = isVertical, + searchValue = searchValue, + searchCount = searchCount, onNavigateToToolView = navController::navigateToToolView ) toolsScreen( isVertical = isVertical, + searchValue = searchValue, onShowSnackbar = onShowSnackbar, onNavigateToToolView = navController::navigateToToolView, onNavigateToToolStore = { appState.navigateToTopLevelDestination(TopLevelDestination.ToolStore) } diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/SearchNavigation.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/SearchNavigation.kt deleted file mode 100644 index a2c7714..0000000 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/SearchNavigation.kt +++ /dev/null @@ -1,22 +0,0 @@ -package top.fatweb.oxygen.toolbox.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import top.fatweb.oxygen.toolbox.ui.search.SearchRoute - -const val SEARCH_ROUTE = "search_route" - -fun NavController.navigateToSearch(navOptions: NavOptions? = null) = - navigate(SEARCH_ROUTE, navOptions) - -fun NavGraphBuilder.searchScreen( - onBackClick: () -> Unit -) { - composable( - route = SEARCH_ROUTE - ) { - SearchRoute(onBackClick = onBackClick) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolStoreNavigation.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolStoreNavigation.kt index 9b15c26..58cfd1c 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolStoreNavigation.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolStoreNavigation.kt @@ -17,6 +17,8 @@ fun NavController.navigateToToolStore(navOptions: NavOptions? = null) = fun NavGraphBuilder.toolStoreScreen( isVertical: Boolean, + searchValue: String, + searchCount: Int, onNavigateToToolView: (username: String, toolId: String, preview: Boolean) -> Unit ) { composable( @@ -41,6 +43,8 @@ fun NavGraphBuilder.toolStoreScreen( } ) { ToolStoreRoute( + searchValue = searchValue, + searchCount = searchCount, onNavigateToToolView = onNavigateToToolView ) } diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolsNavigation.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolsNavigation.kt index 0b60335..1e83de6 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolsNavigation.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/ToolsNavigation.kt @@ -16,6 +16,7 @@ fun NavController.navigateToTools(navOptions: NavOptions) = navigate(TOOLS_ROUTE fun NavGraphBuilder.toolsScreen( isVertical: Boolean, + searchValue: String, onShowSnackbar: suspend (message: String, action: String?) -> Boolean, onNavigateToToolView: (username: String, toolId: String, preview: Boolean) -> Unit, onNavigateToToolStore: () -> Unit @@ -50,6 +51,7 @@ fun NavGraphBuilder.toolsScreen( } ) { ToolsRoute( + searchValue = searchValue, onShowSnackbar = onShowSnackbar, onNavigateToToolView = onNavigateToToolView, onNavigateToToolStore = onNavigateToToolStore diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt index 58ad233..c5543da 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt @@ -6,7 +6,7 @@ import top.fatweb.oxygen.toolbox.model.tool.ToolEntity interface ToolRepository { val toolViewTemplate: Flow - fun getAllToolsStream(): Flow> + fun getAllToolsStream(searchValue: String): Flow> fun getToolById(id: Long): Flow diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/impl/OfflineToolRepository.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/impl/OfflineToolRepository.kt index 80e8ce5..abd2858 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/impl/OfflineToolRepository.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/impl/OfflineToolRepository.kt @@ -14,8 +14,8 @@ class OfflineToolRepository @Inject constructor( override val toolViewTemplate: Flow get() = toolDataSource.toolViewTemplate - override fun getAllToolsStream(): Flow> = - toolDao.selectAllTools() + override fun getAllToolsStream(searchValue: String): Flow> = + toolDao.selectAllTools(searchValue) override fun getToolById(id: Long): Flow = toolDao.selectToolById(id) diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt index 30142be..c429d97 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt @@ -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 ) } } diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenAppState.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenAppState.kt index ce92dc4..dcf841b 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenAppState.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenAppState.kt @@ -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() diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/OxygenTopAppBar.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/OxygenTopAppBar.kt index e937a25..72ae6cc 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/OxygenTopAppBar.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/OxygenTopAppBar.kt @@ -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 diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/search/SearchScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/search/SearchScreen.kt deleted file mode 100644 index 6a2dd24..0000000 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/search/SearchScreen.kt +++ /dev/null @@ -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") - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/search/SearchViewModel.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/search/SearchViewModel.kt deleted file mode 100644 index aba0fc2..0000000 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/search/SearchViewModel.kt +++ /dev/null @@ -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() \ No newline at end of file diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreScreen.kt index af92da7..62e79bf 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreScreen.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreScreen.kt @@ -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, diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreViewModel.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreViewModel.kt index b381a95..9d49669 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreViewModel.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolStoreViewModel.kt @@ -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 diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt index bf11140..7530731 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt @@ -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, diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt index dfbe071..869fd27 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt @@ -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 = - 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 {