From 57cc3e9711929ba62ca712dd5f9d03220830ff59 Mon Sep 17 00:00:00 2001 From: Florent CHAMPIGNY Date: Fri, 3 Oct 2025 15:38:18 +0200 Subject: [PATCH] refact: [NETWORK] filter from data (#297) Co-authored-by: Florent Champigny --- .../features/network/list/NetworkViewModel.kt | 60 ++++++++++--- .../network/list/delegate/HeaderDelegate.kt | 2 - .../network/list/mapper/FilterMapper.kt | 2 - .../list/model/header/TextFilterAction.kt | 4 +- .../base/filter/TextFilterStateUiModel.kt | 9 +- .../processor/FilterNetworkItemsProcessor.kt | 87 ------------------- .../network/list/view/NetworkItemView.kt | 8 +- .../list/view/filters/TextFilterDropdown.kt | 48 ++-------- .../NetworkFilterLocalDataSourceRoom.kt | 15 ++-- .../datasource/NetworkLocalDataSourceRoom.kt | 45 +++++++++- .../data/local/network/mapper/FilterMapper.kt | 13 ++- .../network/models/FilterItemSavedEntity.kt | 1 - .../models/TextFilterStateDomainModel.kt | 1 - .../models/NetworkFilterDomainModel.kt | 13 ++- 14 files changed, 133 insertions(+), 175 deletions(-) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt index fcbcde8d..139d0226 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt @@ -13,6 +13,8 @@ import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel import io.github.openflocon.domain.network.models.MockNetworkDomainModel import io.github.openflocon.domain.network.models.NetworkFilterDomainModel +import io.github.openflocon.domain.network.models.NetworkFilterDomainModel.Filters +import io.github.openflocon.domain.network.models.NetworkSortDomainModel import io.github.openflocon.domain.network.models.NetworkTextFilterColumns import io.github.openflocon.domain.network.usecase.DecodeJwtTokenUseCase import io.github.openflocon.domain.network.usecase.ExportNetworkCallsToCsvUseCase @@ -118,17 +120,20 @@ class NetworkViewModel( .flowOn(dispatcherProvider.viewModel) .stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(5_000), null) + private val filter = combines( + snapshotFlow { _filterText.value }.map { it.takeIf { it.isNotBlank() } }, + headerDelegate.textFiltersState.map { it.toDomain() } + ).map { (textFilters, filterOnAllColumns) -> + NetworkFilterDomainModel( + filterOnAllColumns = textFilters, + filters = filterOnAllColumns, + ) + } + private val sortAndFilter = combines( headerDelegate.sorted.map { it?.toDomain() }.distinctUntilChanged(), - snapshotFlow { _filterText.value } - .map { it.takeIf { it.isNotBlank() } } - .map { - NetworkFilterDomainModel( - filterOnAllColumns = it - ) - } - .distinctUntilChanged() - ) + filter, + ).distinctUntilChanged() private val networkItems: Flow> = sortAndFilter.flatMapLatest { (sorted, filter) -> @@ -171,14 +176,11 @@ class NetworkViewModel( private val filteredItems: Flow> = combine( items, - contentState, filterConfig, - ) { items, content, config -> + ) { items, config -> filterNetworkItemsProcessor( items = items, - filterState = config.filterState, allowedMethods = config.allowedMethods, - textFilters = config.textFilters, ) } .flowOn(dispatcherProvider.viewModel.limitedParallelism(1)) @@ -438,3 +440,35 @@ class NetworkViewModel( } } } + +private fun Map.toDomain(): List? { + return buildList { + this@toDomain.forEach { (column, filter) -> + if (filter.isEnabled) { + val includedFilters = filter.includedFilters.mapNotNull { + it.toDomain() + } + val excludedFilters = filter.excludedFilters.mapNotNull { + it.toDomain() + } + if (includedFilters.isNotEmpty() || excludedFilters.isNotEmpty()) { + add( + Filters( + column = column, + includedFilters = includedFilters, + excludedFilters = excludedFilters, + ) + ) + } + } + } + } +} + +private fun TextFilterStateUiModel.FilterItem.toDomain(): NetworkFilterDomainModel.Filters.FilterItem? { + return if(isActive) { + NetworkFilterDomainModel.Filters.FilterItem( + text = text, + ) + } else null +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/HeaderDelegate.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/HeaderDelegate.kt index 95956c1f..3101aa88 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/HeaderDelegate.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/HeaderDelegate.kt @@ -206,7 +206,6 @@ private fun TextFilterStateUiModel.performAction( text = action.text, isActive = true, isExcluded = true, - isRegex = action.isRegex, ) val exists = excludedFilters.any { it.text == newItem.text } if (exists) { @@ -227,7 +226,6 @@ private fun TextFilterStateUiModel.performAction( text = action.text, isActive = true, isExcluded = false, - isRegex = action.isRegex, ) val exists = includedFilters.any { it.text == newItem.text } if (exists) { diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/FilterMapper.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/FilterMapper.kt index ba95c0cc..a06e38ad 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/FilterMapper.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/FilterMapper.kt @@ -22,14 +22,12 @@ fun itemToDomain(item: TextFilterStateUiModel.FilterItem): TextFilterStateDomain text = item.text, isActive = item.isActive, isExcluded = item.isExcluded, - isRegex = item.isRegex, ) fun itemToUI(item: TextFilterStateDomainModel.FilterItem): TextFilterStateUiModel.FilterItem = TextFilterStateUiModel.FilterItem( text = item.text, isActive = item.isActive, isExcluded = item.isExcluded, - isRegex = item.isRegex, ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/TextFilterAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/TextFilterAction.kt index c2c2b357..55ff227e 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/TextFilterAction.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/TextFilterAction.kt @@ -3,8 +3,8 @@ package io.github.openflocon.flocondesktop.features.network.list.model.header import io.github.openflocon.flocondesktop.features.network.list.model.header.columns.base.filter.TextFilterStateUiModel sealed interface TextFilterAction { - data class Include(val text: String, val isRegex: Boolean) : TextFilterAction - data class Exclude(val text: String, val isRegex: Boolean) : TextFilterAction + data class Include(val text: String) : TextFilterAction + data class Exclude(val text: String) : TextFilterAction data class SetIsActive(val item: TextFilterStateUiModel.FilterItem, val isActive: Boolean) : TextFilterAction data class Delete(val item: TextFilterStateUiModel.FilterItem) : TextFilterAction } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/columns/base/filter/TextFilterStateUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/columns/base/filter/TextFilterStateUiModel.kt index 72a3be23..ea5adcd7 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/columns/base/filter/TextFilterStateUiModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/header/columns/base/filter/TextFilterStateUiModel.kt @@ -17,7 +17,6 @@ data class TextFilterStateUiModel( val text: String, val isActive: Boolean, val isExcluded: Boolean, - val isRegex: Boolean, ) override val isActive: Boolean = allFilters.isNotEmpty() && isEnabled && allFilters.any { it.isActive } @@ -34,11 +33,11 @@ data class TextFilterStateUiModel( fun previewTextFilterState() = TextFilterStateUiModel( isEnabled = true, includedFilters = listOf( - FilterItem(text = "toInclude", isExcluded = false, isActive = true, isRegex = false), - FilterItem(text = "toInclude2", isExcluded = false, isActive = true, isRegex = true), + FilterItem(text = "toInclude", isExcluded = false, isActive = true), + FilterItem(text = "toInclude2", isExcluded = false, isActive = true), ), excludedFilters = listOf( - FilterItem(text = "toExclude", isExcluded = true, isActive = true, isRegex = false), - FilterItem(text = "toExclude2", isExcluded = true, isActive = false, isRegex = true), + FilterItem(text = "toExclude", isExcluded = true, isActive = true), + FilterItem(text = "toExclude2", isExcluded = true, isActive = false), ), ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/processor/FilterNetworkItemsProcessor.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/processor/FilterNetworkItemsProcessor.kt index c821b7fc..e1bda5a5 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/processor/FilterNetworkItemsProcessor.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/processor/FilterNetworkItemsProcessor.kt @@ -1,104 +1,17 @@ package io.github.openflocon.flocondesktop.features.network.list.processor import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel -import io.github.openflocon.domain.network.models.NetworkTextFilterColumns -import io.github.openflocon.flocondesktop.features.network.list.model.TopBarUiState import io.github.openflocon.flocondesktop.features.network.list.model.NetworkItemViewState import io.github.openflocon.flocondesktop.features.network.list.model.NetworkMethodUi -import io.github.openflocon.flocondesktop.features.network.list.model.header.columns.base.filter.TextFilterStateUiModel class FilterNetworkItemsProcessor { operator fun invoke( items: List>, - filterState: TopBarUiState, allowedMethods: List, - textFilters: Map, ): List = items.asSequence() .filter { item -> item.second.method in allowedMethods } - .filter { item -> - val filters = textFilters.filter { it.value.isActive } - if(filters.isEmpty()) true - else { - filters.any { (column, textFilter) -> - textFilter.filter(column, item) - } - } - } .map { it.second } .toList() } - -private fun TextFilterStateUiModel.filter( - column: NetworkTextFilterColumns, - item: Pair, -): Boolean { - val text = when (column) { - NetworkTextFilterColumns.RequestTime -> item.second.dateFormatted - NetworkTextFilterColumns.Domain -> item.second.domain - NetworkTextFilterColumns.Query -> item.second.type.text - NetworkTextFilterColumns.Status -> item.second.status.text - NetworkTextFilterColumns.Time -> item.second.timeFormatted - } - return filterByText(text) -} - -private fun TextFilterStateUiModel.filterByText(text: String?): Boolean { - // 1. If text is null, it always passes the filter (default behavior). - if (text == null) { - return true - } - - val activeFilters = this.allFilters.filter { it.isActive } - - // 2. If there are no active filters, it always passes. - if (activeFilters.isEmpty()) { - return true - } - - val (excludedFilters, includedFilters) = activeFilters.partition { it.isExcluded } - - // --- Step 1: Exclusion Check --- - - // 4. Check if the text matches ANY exclusion filter. - // If ANY active 'isExcluded' filter matches, the item MUST be rejected (return false). - val isExcluded = excludedFilters.any { it.matches(text) } - - if (isExcluded) { - return false // Excluded, so it fails the filter immediately. - } - - // --- Step 2: Inclusion Check --- - - // 5. If there are no inclusion filters, the item passes (as it wasn't excluded). - if (includedFilters.isEmpty()) { - return true - } - - // 6. If not excluded, check if the text matches AT LEAST ONE inclusion filter. - // If ANY non-excluded filter matches, the item is kept (return true). - return includedFilters.any { it.matches(text) } -} - -private fun TextFilterStateUiModel.FilterItem.matches(text: String): Boolean { - return if (this.isRegex) { - Regex(this.text, setOf(RegexOption.IGNORE_CASE)).containsMatchIn(text) - } else { - text.contains(this.text, ignoreCase = true) - } -} - -/** - * lookup if the request or response body contains the text - */ -private fun FloconNetworkCallDomainModel.contains(text: String) : Boolean { - return request.body?.contains(text, ignoreCase = true) == true || response?.contains(text) == true -} - -private fun FloconNetworkCallDomainModel.Response.contains(text: String) : Boolean { - return when(this) { - is FloconNetworkCallDomainModel.Response.Failure -> issue.contains(text, ignoreCase = true) - is FloconNetworkCallDomainModel.Response.Success -> body?.contains(text, ignoreCase = true) == true - } -} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt index bd6f2222..fd65e346 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt @@ -175,7 +175,7 @@ private fun contextualActions( NetworkAction.HeaderAction.FilterAction( OnFilterAction.TextFilter( column = NetworkTextFilterColumns.Domain, - action = TextFilterAction.Include(text = state.domain, isRegex = false) + action = TextFilterAction.Include(text = state.domain) ) ) ) @@ -185,7 +185,7 @@ private fun contextualActions( NetworkAction.HeaderAction.FilterAction( OnFilterAction.TextFilter( column = NetworkTextFilterColumns.Query, - action = TextFilterAction.Include(text = state.type.query, isRegex = false) + action = TextFilterAction.Include(text = state.type.query) ) ) ) @@ -197,7 +197,7 @@ private fun contextualActions( NetworkAction.HeaderAction.FilterAction( OnFilterAction.TextFilter( column = NetworkTextFilterColumns.Domain, - action = TextFilterAction.Exclude(text = state.domain, isRegex = false) + action = TextFilterAction.Exclude(text = state.domain) ) ) ) @@ -207,7 +207,7 @@ private fun contextualActions( NetworkAction.HeaderAction.FilterAction( OnFilterAction.TextFilter( column = NetworkTextFilterColumns.Query, - action = TextFilterAction.Exclude(text = state.type.query, isRegex = false) + action = TextFilterAction.Exclude(text = state.type.query) ) ) ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt index 7953fb53..7ff9873e 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt @@ -58,11 +58,11 @@ fun TextFilterDropdownContent( modifier = modifier.fillMaxWidth() ) { TextFilterFieldView( - submitTextField = { text, toInclude, isRegex -> + submitTextField = { text, toInclude -> if (toInclude) { - textFilterAction.invoke(TextFilterAction.Include(text, isRegex = isRegex)) + textFilterAction.invoke(TextFilterAction.Include(text)) } else { - textFilterAction.invoke(TextFilterAction.Exclude(text, isRegex = isRegex)) + textFilterAction.invoke(TextFilterAction.Exclude(text)) } }, modifier = Modifier @@ -196,11 +196,10 @@ private fun FilterItemView( @Composable private fun TextFilterFieldView( - submitTextField: (value: String, toInclude: Boolean, isRegex: Boolean) -> Unit, + submitTextField: (value: String, toInclude: Boolean) -> Unit, modifier: Modifier = Modifier, ) { var value by remember { mutableStateOf("") } - var isRegex by remember { mutableStateOf(false) } Row( modifier = modifier, @@ -213,7 +212,7 @@ private fun TextFilterFieldView( keyboardActions = KeyboardActions( onDone = { // default action -> add as "include filter" - submitTextField(value, true, isRegex) + submitTextField(value, true) value = "" // reset }, ), @@ -221,15 +220,6 @@ private fun TextFilterFieldView( textStyle = FloconTheme.typography.bodySmall.copy( color = FloconTheme.colorPalette.onSecondary, ), - trailingComponent = { - RegexFilterButton( - modifier = Modifier.size(24.dp), - isRegex = isRegex, - onClick = { - isRegex = !isRegex - }, - ) - }, containerColor = FloconTheme.colorPalette.secondary, modifier = Modifier .weight(1f) @@ -238,7 +228,7 @@ private fun TextFilterFieldView( FloconSmallIconButton( enabled = value.isNotEmpty(), onClick = { - submitTextField(value, true, isRegex) + submitTextField(value, true) value = "" // reset } ) { @@ -247,7 +237,7 @@ private fun TextFilterFieldView( FloconSmallIconButton( enabled = value.isNotEmpty(), onClick = { - submitTextField(value, false, isRegex) + submitTextField(value, false) value = "" // reset } ) { @@ -281,30 +271,6 @@ private fun TextFilterButton( } } - -@Composable -private fun RegexFilterButton( - onClick: () -> Unit, - isRegex: Boolean, - modifier: Modifier = Modifier, -) { - Box( - modifier = modifier - .size(20.dp) - .clip(RoundedCornerShape(6.dp)) - .graphicsLayer { alpha = if (isRegex) 1f else 0.5f } - .background(Color.White) - .clickable(onClick = onClick), - contentAlignment = Alignment.Center, - ) { - Text( - ".*", - fontSize = 11.sp, - color = FloconTheme.colorPalette.surface, - ) - } -} - @Preview @Composable private fun TextFilterDropdownContentPreview() { diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkFilterLocalDataSourceRoom.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkFilterLocalDataSourceRoom.kt index fc52471f..8ae881fd 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkFilterLocalDataSourceRoom.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkFilterLocalDataSourceRoom.kt @@ -4,16 +4,16 @@ import io.github.openflocon.data.core.network.datasource.NetworkFilterLocalDataS import io.github.openflocon.data.local.network.dao.NetworkFilterDao import io.github.openflocon.data.local.network.mapper.textFilterToDomain import io.github.openflocon.data.local.network.mapper.textFilterToEntity -import io.github.openflocon.domain.device.models.AppPackageName -import io.github.openflocon.domain.device.models.DeviceId import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel import io.github.openflocon.domain.models.TextFilterStateDomainModel import io.github.openflocon.domain.network.models.NetworkTextFilterColumns import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.serialization.json.Json class NetworkFilterLocalDataSourceRoom( private val networkFilterDao: NetworkFilterDao, + private val json: Json, ) : NetworkFilterLocalDataSource { override suspend fun get( @@ -23,9 +23,9 @@ class NetworkFilterLocalDataSourceRoom( deviceId = deviceAndApp.deviceId, packageName = deviceAndApp.packageName, column = column, - )?.let { - textFilterToDomain(it) - } + )?.textFilterToDomain( + json = json, + ) override fun observe( deviceAndApp: DeviceIdAndPackageNameDomainModel, @@ -34,7 +34,9 @@ class NetworkFilterLocalDataSourceRoom( packageName = deviceAndApp.packageName, ).map { it.associate { - it.columnName to textFilterToDomain(it) + it.columnName to it.textFilterToDomain( + json = json, + ) } } @@ -49,6 +51,7 @@ class NetworkFilterLocalDataSourceRoom( packageName = deviceAndApp.packageName, column = column, domain = newValue, + json = json, ), ) } diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkLocalDataSourceRoom.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkLocalDataSourceRoom.kt index 75d709b2..1144199d 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkLocalDataSourceRoom.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkLocalDataSourceRoom.kt @@ -10,6 +10,7 @@ import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainMod import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel import io.github.openflocon.domain.network.models.NetworkFilterDomainModel import io.github.openflocon.domain.network.models.NetworkSortDomainModel +import io.github.openflocon.domain.network.models.NetworkTextFilterColumns import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -63,24 +64,54 @@ class NetworkLocalDataSourceRoom( columns.forEachIndexed { index, column -> appendLine("$column LIKE ? COLLATE NOCASE") args.add("%$filterText%") - if(index != columns.lastIndex) { + if (index != columns.lastIndex) { appendLine("OR") } } appendLine(")") } + + filter.filters?.forEach { filter -> + filter.includedFilters.takeIf { it.isNotEmpty() }?.let { included -> + appendLine("AND (") + included.forEachIndexed { includedFilterIndex, filterItem -> + appendLine("\t${filter.column.roomColumnName()} LIKE ? COLLATE NOCASE") + args.add("%${filterItem.text}%") + + if (includedFilterIndex != included.lastIndex) { + appendLine("\tOR") + } + } + appendLine(")") + } + filter.excludedFilters.takeIf { it.isNotEmpty() }?.let { excluded -> + appendLine("AND (") + excluded.forEachIndexed { excludedFilterIndex, filterItem -> + appendLine("\t${filter.column.roomColumnName()} NOT LIKE ? COLLATE NOCASE") + args.add("%${filterItem.text}%") + + if (excludedFilterIndex != excluded.lastIndex) { + appendLine("\tAND") + } + } + appendLine(")") + } + } + appendLine("ORDER BY $safeColumnName $sortOrder") }.trimIndent() val roomQuery = RoomRawQuery(sqlQuery, onBindStatement = { args.forEachIndexed { index, item -> - when(item) { + when (item) { is String -> { it.bindText(index + 1, item) } + is Int -> { it.bindInt(index + 1, item) } + is Long -> { it.bindLong(index + 1, item) } @@ -115,7 +146,7 @@ class NetworkLocalDataSourceRoom( override suspend fun getCall( deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel, callId: String, - ): FloconNetworkCallDomainModel? { + ): FloconNetworkCallDomainModel? { return floconNetworkDao .getCallById( deviceId = deviceIdAndPackageName.deviceId, @@ -182,6 +213,14 @@ class NetworkLocalDataSourceRoom( } } +// TODO maybe merge with NetworkSortDomainModel.Column +private fun NetworkTextFilterColumns.roomColumnName(): String = when (this) { + NetworkTextFilterColumns.RequestTime -> "request_startTimeFormatted" + NetworkTextFilterColumns.Domain -> "request_domainFormatted" + NetworkTextFilterColumns.Query -> "request_queryFormatted" + NetworkTextFilterColumns.Status -> "response_statusFormatted" + NetworkTextFilterColumns.Time -> "response_durationMs" +} private fun NetworkSortDomainModel.Column.roomColumnName(): String = when (this) { NetworkSortDomainModel.Column.RequestStartTimeFormatted -> "request_startTimeFormatted" diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/FilterMapper.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/FilterMapper.kt index f565e2f8..01c16683 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/FilterMapper.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/FilterMapper.kt @@ -12,17 +12,16 @@ fun textFilterItemToEntity(item: TextFilterStateDomainModel.FilterItem): FilterI text = item.text, isActive = item.isActive, isExcluded = item.isExcluded, - isRegex = item.isRegex, ) fun textFilterItemToDomain(item: FilterItemSavedEntity): TextFilterStateDomainModel.FilterItem = TextFilterStateDomainModel.FilterItem( text = item.text, isActive = item.isActive, isExcluded = item.isExcluded, - isRegex = item.isRegex, ) fun textFilterToEntity( + json: Json, deviceId: DeviceId, packageName: AppPackageName, column: NetworkTextFilterColumns, @@ -36,16 +35,16 @@ fun textFilterToEntity( packageName = packageName, columnName = column, isEnabled = domain.isEnabled, - itemsAsJson = Json.encodeToString(itemsEntity), + itemsAsJson = json.encodeToString(itemsEntity), ) } -fun textFilterToDomain( - entity: NetworkFilterEntity, +fun NetworkFilterEntity.textFilterToDomain( + json: Json, ): TextFilterStateDomainModel { - val itemsEntity = Json.decodeFromString>(entity.itemsAsJson) + val itemsEntity = json.decodeFromString>(itemsAsJson) return TextFilterStateDomainModel( - isEnabled = entity.isEnabled, + isEnabled = isEnabled, items = itemsEntity.map { textFilterItemToDomain(it) }, diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/FilterItemSavedEntity.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/FilterItemSavedEntity.kt index 30434d84..f8833a99 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/FilterItemSavedEntity.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/FilterItemSavedEntity.kt @@ -7,5 +7,4 @@ data class FilterItemSavedEntity( val text: String, val isActive: Boolean, val isExcluded: Boolean, - val isRegex: Boolean, ) diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/models/TextFilterStateDomainModel.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/models/TextFilterStateDomainModel.kt index 564c57cc..3384e117 100644 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/models/TextFilterStateDomainModel.kt +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/models/TextFilterStateDomainModel.kt @@ -8,6 +8,5 @@ data class TextFilterStateDomainModel( val text: String, val isActive: Boolean, val isExcluded: Boolean, - val isRegex: Boolean, ) } diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/NetworkFilterDomainModel.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/NetworkFilterDomainModel.kt index e9dd4411..332138ae 100644 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/NetworkFilterDomainModel.kt +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/NetworkFilterDomainModel.kt @@ -2,4 +2,15 @@ package io.github.openflocon.domain.network.models data class NetworkFilterDomainModel( val filterOnAllColumns: String?, -) + val filters: List?, +) { + data class Filters( + val column: NetworkTextFilterColumns, + val includedFilters: List, + val excludedFilters: List, + ) { + data class FilterItem( + val text: String, + ) + } +}