diff --git a/app/src/main/java/com/planify/mobile/ui/search/SearchScreen.kt b/app/src/main/java/com/planify/mobile/ui/search/SearchScreen.kt new file mode 100644 index 0000000..6814714 --- /dev/null +++ b/app/src/main/java/com/planify/mobile/ui/search/SearchScreen.kt @@ -0,0 +1,85 @@ +package com.planify.mobile.ui.search + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.planify.mobile.domain.model.Task +import com.planify.mobile.ui.components.TaskRow + +@Composable +fun SearchScreen( + onTaskClick: (Task) -> Unit, + viewModel: SearchViewModel = hiltViewModel(), +) { + val query by viewModel.query.collectAsState() + val results by viewModel.results.collectAsState() + + Column(modifier = Modifier.fillMaxSize()) { + OutlinedTextField( + value = query, + onValueChange = viewModel::setQuery, + placeholder = { Text("Rechercher des tâches…") }, + leadingIcon = { Icon(Icons.Outlined.Search, contentDescription = null) }, + trailingIcon = { + if (query.isNotEmpty()) { + IconButton(onClick = { viewModel.setQuery("") }) { + Icon(Icons.Outlined.Close, contentDescription = "Effacer") + } + } + }, + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + + when { + query.length < 2 -> Text( + text = "Saisissez au moins 2 caractères", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp), + ) + results.isEmpty() -> Text( + text = "Aucun résultat pour « $query »", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp), + ) + else -> LazyColumn { + item { + Text( + text = "${results.size} résultat(s)", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + ) + } + items(results, key = { it.id }) { task -> + TaskRow( + task = task, + onClick = { onTaskClick(task) }, + onCheckedChange = { viewModel.toggleTask(task) }, + ) + } + } + } + } +} diff --git a/app/src/main/java/com/planify/mobile/ui/search/SearchViewModel.kt b/app/src/main/java/com/planify/mobile/ui/search/SearchViewModel.kt new file mode 100644 index 0000000..369d6dd --- /dev/null +++ b/app/src/main/java/com/planify/mobile/ui/search/SearchViewModel.kt @@ -0,0 +1,40 @@ +package com.planify.mobile.ui.search + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.planify.mobile.domain.model.Task +import com.planify.mobile.domain.repository.TaskRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val taskRepository: TaskRepository, +) : ViewModel() { + + val query = MutableStateFlow("") + + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) + val results = query + .debounce(300) + .flatMapLatest { q -> + if (q.length < 2) flowOf(emptyList()) + else taskRepository.searchTasks(q) + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + fun setQuery(q: String) { query.value = q } + + fun toggleTask(task: Task) { + viewModelScope.launch { taskRepository.checkTask(task.id, !task.checked) } + } +}