mirror of
https://github.com/openflocon/Flocon.git
synced 2026-05-19 06:27:19 +00:00
feat: [DATABASE] queryview (#325)
Co-authored-by: Florent Champigny <florent@bere.al>
This commit is contained in:
parent
f727a974e8
commit
0b60acfde1
3 changed files with 188 additions and 20 deletions
|
|
@ -5,19 +5,26 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.github.openflocon.domain.common.DispatcherProvider
|
||||
import io.github.openflocon.domain.common.combines
|
||||
import io.github.openflocon.domain.database.usecase.ExecuteDatabaseQueryUseCase
|
||||
import io.github.openflocon.domain.feedback.FeedbackDisplayer
|
||||
import io.github.openflocon.flocondesktop.features.database.mapper.toUi
|
||||
import io.github.openflocon.flocondesktop.features.database.model.DatabaseScreenState
|
||||
import io.github.openflocon.flocondesktop.features.database.model.QueryResultUiModel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class DatabaseTabViewModel(
|
||||
private val params: Params,
|
||||
|
|
@ -34,6 +41,32 @@ class DatabaseTabViewModel(
|
|||
|
||||
var query = mutableStateOf("")
|
||||
|
||||
private val autoUpdateJob = AtomicReference<Job?>(null)
|
||||
data class AutoUpdate(
|
||||
val query: String? = null,
|
||||
val isEnabled: Boolean = false,
|
||||
)
|
||||
|
||||
private val isVisible = MutableStateFlow(false)
|
||||
|
||||
fun onVisible() {
|
||||
isVisible.update { true }
|
||||
}
|
||||
|
||||
fun onNotVisible() {
|
||||
isVisible.update { false }
|
||||
}
|
||||
|
||||
private val _autoUpdate = MutableStateFlow(AutoUpdate())
|
||||
val isAutoUpdateEnabled = _autoUpdate
|
||||
.map { it.isEnabled }
|
||||
.flowOn(dispatcherProvider.viewModel)
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Companion.WhileSubscribed(5000),
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
private val queryResult = MutableStateFlow<QueryResultUiModel?>(null)
|
||||
|
||||
val state: StateFlow<DatabaseScreenState> = queryResult.map { queryResult ->
|
||||
|
|
@ -48,12 +81,15 @@ class DatabaseTabViewModel(
|
|||
|
||||
init {
|
||||
params.tableName?.let {
|
||||
updateQuery(buildString {
|
||||
val query = buildString {
|
||||
appendLine("SELECT * ")
|
||||
appendLine("FROM $it")
|
||||
append("LIMIT 50 OFFSET 0")
|
||||
})
|
||||
executeQuery()
|
||||
}
|
||||
updateQuery(query)
|
||||
viewModelScope.launch(dispatcherProvider.viewModel) {
|
||||
executeQuery(query = query, editAutoUpdate = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,25 +98,79 @@ class DatabaseTabViewModel(
|
|||
}
|
||||
|
||||
fun executeQuery() {
|
||||
executeQuery(query = query.value)
|
||||
viewModelScope.launch(dispatcherProvider.viewModel) {
|
||||
executeQuery(query = query.value, editAutoUpdate = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeQuery(query: String) {
|
||||
init {
|
||||
viewModelScope.launch(dispatcherProvider.viewModel) {
|
||||
executeDatabaseQueryUseCase(
|
||||
query = query,
|
||||
databaseId = params.databaseId,
|
||||
).fold(doOnSuccess = {
|
||||
queryResult.value = it.toUi()
|
||||
}, doOnFailure = {
|
||||
feedbackDisplayer.displayMessage("database failure : $it")
|
||||
})
|
||||
combines(isVisible, _autoUpdate)
|
||||
.distinctUntilChanged()
|
||||
.collect { (isVisible, autoUpdate) ->
|
||||
refreshAutoUpdate(
|
||||
isVisible = isVisible,
|
||||
autoUpdate = autoUpdate,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun executeQuery(query: String, editAutoUpdate: Boolean) {
|
||||
println("executeQuery: $query")
|
||||
executeDatabaseQueryUseCase(
|
||||
query = query,
|
||||
databaseId = params.databaseId,
|
||||
).fold(doOnSuccess = {
|
||||
queryResult.value = it.toUi()
|
||||
if (editAutoUpdate) {
|
||||
_autoUpdate.update {
|
||||
it.copy(
|
||||
query = query,
|
||||
)
|
||||
}
|
||||
}
|
||||
}, doOnFailure = {
|
||||
feedbackDisplayer.displayMessage("database failure : $it")
|
||||
})
|
||||
}
|
||||
|
||||
fun clearQuery() {
|
||||
viewModelScope.launch(dispatcherProvider.viewModel) {
|
||||
updateQuery("")
|
||||
queryResult.update { null }
|
||||
_autoUpdate.update {
|
||||
it.copy(
|
||||
query = null,
|
||||
isEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAutoUpdate(value: Boolean) {
|
||||
_autoUpdate.update {
|
||||
it.copy(
|
||||
isEnabled = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshAutoUpdate(isVisible: Boolean, autoUpdate: AutoUpdate) {
|
||||
val job = autoUpdateJob.get()
|
||||
if (!autoUpdate.isEnabled || !isVisible) {
|
||||
job?.cancel()
|
||||
return
|
||||
} else {
|
||||
job?.cancel()
|
||||
val autoUpdateJob = viewModelScope.launch(dispatcherProvider.viewModel) {
|
||||
while (isActive) {
|
||||
delay(3.seconds)
|
||||
val query = _autoUpdate.value.takeIf { it.isEnabled }?.query ?: return@launch
|
||||
executeQuery(query, editAutoUpdate = false)
|
||||
}
|
||||
}
|
||||
this.autoUpdateJob.set(autoUpdateJob)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,30 @@
|
|||
package io.github.openflocon.flocondesktop.features.database.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CheckboxDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.isCtrlPressed
|
||||
import androidx.compose.ui.input.key.isMetaPressed
|
||||
|
|
@ -27,15 +41,17 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
|
|||
@Composable
|
||||
fun DatabaseQueryView(
|
||||
query: String,
|
||||
autoUpdate: Boolean,
|
||||
updateQuery: (query: String) -> Unit,
|
||||
executeQuery: (query: String) -> Unit,
|
||||
updateAutoUpdate: (value: Boolean) -> Unit,
|
||||
clearQuery: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = FloconTheme.colorPalette.secondary,
|
||||
color = FloconTheme.colorPalette.primary,
|
||||
shape = FloconTheme.shapes.medium
|
||||
)
|
||||
) {
|
||||
|
|
@ -69,21 +85,66 @@ fun DatabaseQueryView(
|
|||
) {
|
||||
FloconButton(
|
||||
onClick = { executeQuery(query) },
|
||||
containerColor = FloconTheme.colorPalette.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(all = 8.dp)
|
||||
) {
|
||||
Text("Run", modifier = Modifier.padding(horizontal = 10.dp))
|
||||
val contentColor = FloconTheme.colorPalette.onTertiary
|
||||
Row(
|
||||
Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
Icons.Filled.PlayArrow,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
colorFilter = ColorFilter.tint(contentColor)
|
||||
)
|
||||
Text("Run Query", color = contentColor)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Row(
|
||||
Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = autoUpdate,
|
||||
onCheckedChange = {
|
||||
updateAutoUpdate(it)
|
||||
}, colors = CheckboxDefaults.colors(
|
||||
uncheckedColor = FloconTheme.colorPalette.secondary,
|
||||
checkedColor = FloconTheme.colorPalette.secondary,
|
||||
)
|
||||
)
|
||||
Text(
|
||||
"Auto Update",
|
||||
color = FloconTheme.colorPalette.onPrimary,
|
||||
style = FloconTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
FloconButton(
|
||||
onClick = clearQuery,
|
||||
modifier = Modifier
|
||||
.padding(all = 8.dp)
|
||||
Box(
|
||||
modifier = Modifier.clip(RoundedCornerShape(2.dp)).clickable {
|
||||
clearQuery()
|
||||
}.size(40.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("Reset", modifier = Modifier.padding(horizontal = 10.dp))
|
||||
Image(
|
||||
Icons.Filled.Delete,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
colorFilter = ColorFilter.tint(FloconTheme.colorPalette.onPrimary)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +159,8 @@ private fun DatabaseQueryViewPreview() {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
query = "SELECT * FROM TABLE_NAME",
|
||||
updateQuery = {},
|
||||
autoUpdate = true,
|
||||
updateAutoUpdate = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -32,12 +33,22 @@ fun DatabaseTabView(
|
|||
parameters = { parametersOf(params) }
|
||||
)
|
||||
|
||||
DisposableEffect(viewModel) {
|
||||
viewModel.onVisible()
|
||||
onDispose {
|
||||
viewModel.onNotVisible()
|
||||
}
|
||||
}
|
||||
|
||||
val state: DatabaseScreenState by viewModel.state.collectAsStateWithLifecycle()
|
||||
val autoUpdate by viewModel.isAutoUpdateEnabled.collectAsStateWithLifecycle()
|
||||
DatabaseTabViewContent(
|
||||
query = viewModel.query.value,
|
||||
autoUpdate = autoUpdate,
|
||||
updateQuery = viewModel::updateQuery,
|
||||
executeQuery = viewModel::executeQuery,
|
||||
clearQuery = viewModel::clearQuery,
|
||||
updateAutoUpdate = viewModel::updateAutoUpdate,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
|
@ -45,9 +56,11 @@ fun DatabaseTabView(
|
|||
@Composable
|
||||
private fun DatabaseTabViewContent(
|
||||
query: String,
|
||||
autoUpdate: Boolean,
|
||||
updateQuery: (String) -> Unit,
|
||||
executeQuery: () -> Unit,
|
||||
clearQuery: () -> Unit,
|
||||
updateAutoUpdate: (Boolean) -> Unit,
|
||||
state: DatabaseScreenState,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -60,9 +73,11 @@ private fun DatabaseTabViewContent(
|
|||
DatabaseQueryView(
|
||||
query = query,
|
||||
updateQuery = updateQuery,
|
||||
autoUpdate = autoUpdate,
|
||||
executeQuery = {
|
||||
executeQuery()
|
||||
},
|
||||
updateAutoUpdate = updateAutoUpdate,
|
||||
clearQuery = clearQuery,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue