refact: [FILTERS] persist them (#68)

Co-authored-by: Florent Champigny <florent@bere.al>
This commit is contained in:
Florent CHAMPIGNY 2025-08-07 14:17:03 +02:00 committed by GitHub
parent 35e110a384
commit a7f102926b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 514 additions and 88 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 27,
"identityHash": "6602c9e782a66f200b014ac2cfb4ef99",
"identityHash": "2064d08d969f8275c5f4dea45aa8505a",
"entities": [
{
"tableName": "FloconHttpRequestEntity",
@ -764,11 +764,48 @@
"createSql": "CREATE INDEX IF NOT EXISTS `index_AnalyticsItemEntity_deviceId_analyticsTableId` ON `${TABLE_NAME}` (`deviceId`, `analyticsTableId`)"
}
]
},
{
"tableName": "network_filter",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `columnName` TEXT NOT NULL, `isEnabled` INTEGER NOT NULL, `itemsAsJson` TEXT NOT NULL, PRIMARY KEY(`deviceId`, `columnName`))",
"fields": [
{
"fieldPath": "deviceId",
"columnName": "deviceId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "columnName",
"columnName": "columnName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isEnabled",
"columnName": "isEnabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "itemsAsJson",
"columnName": "itemsAsJson",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"deviceId",
"columnName"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6602c9e782a66f200b014ac2cfb4ef99')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2064d08d969f8275c5f4dea45aa8505a')"
]
}
}

View file

@ -21,14 +21,16 @@ import io.github.openflocon.flocondesktop.features.files.data.datasources.model.
import io.github.openflocon.flocondesktop.features.images.data.datasources.local.FloconImageDao
import io.github.openflocon.flocondesktop.features.images.data.datasources.local.model.DeviceImageEntity
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.FloconHttpRequestDao
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.NetworkFilterDao
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.model.FloconHttpRequestEntity
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.model.NetworkFilterEntity
import io.github.openflocon.flocondesktop.features.table.data.datasource.local.FloconTableDao
import io.github.openflocon.flocondesktop.features.table.data.datasource.local.model.TableEntity
import io.github.openflocon.flocondesktop.features.table.data.datasource.local.model.TableItemEntity
import kotlinx.coroutines.Dispatchers
@Database(
version = 27,
version = 28,
entities = [
FloconHttpRequestEntity::class,
FileEntity::class,
@ -41,6 +43,7 @@ import kotlinx.coroutines.Dispatchers
SuccessQueryEntity::class,
DeeplinkEntity::class,
AnalyticsItemEntity::class,
NetworkFilterEntity::class,
],
)
@TypeConverters(
@ -56,6 +59,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val queryDao: QueryDao
abstract val deeplinkDao: FloconDeeplinkDao
abstract val analyticsDao: FloconAnalyticsDao
abstract val networkFilterDao: NetworkFilterDao
}
fun getRoomDatabase(): AppDatabase = getDatabaseBuilder()

View file

@ -31,4 +31,7 @@ val roomModule =
single {
get<AppDatabase>().analyticsDao
}
single {
get<AppDatabase>().networkFilterDao
}
}

View file

@ -0,0 +1,41 @@
package io.github.openflocon.flocondesktop.features.network.data
import io.github.openflocon.flocondesktop.DeviceId
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.NetworkFilterLocalDataSource
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkFilterRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
class NetworkFilterRepositoryImpl(
private val networkFilterLocalDataSource: NetworkFilterLocalDataSource,
) : NetworkFilterRepository {
override suspend fun get(
deviceId: DeviceId,
column: NetworkTextFilterColumns,
): TextFilterStateDomainModel? {
return networkFilterLocalDataSource.get(
deviceId = deviceId,
column = column,
)
}
override fun observe(deviceId: DeviceId): Flow<Map<NetworkTextFilterColumns, TextFilterStateDomainModel>> {
return networkFilterLocalDataSource.observe(
deviceId = deviceId,
).distinctUntilChanged()
}
override suspend fun update(
deviceId: DeviceId,
column: NetworkTextFilterColumns,
newValue: TextFilterStateDomainModel
) {
return networkFilterLocalDataSource.update(
deviceId = deviceId,
column = column,
newValue = newValue,
)
}
}

View file

@ -0,0 +1,21 @@
package io.github.openflocon.flocondesktop.features.network.data.datasource.local
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.model.NetworkFilterEntity
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import kotlinx.coroutines.flow.Flow
@Dao
interface NetworkFilterDao {
@Query("SELECT * FROM network_filter WHERE deviceId = :deviceId AND columnName = :column")
suspend fun get(deviceId: String, column: NetworkTextFilterColumns): NetworkFilterEntity?
@Query("SELECT * FROM network_filter WHERE deviceId = :deviceId")
fun observe(deviceId: String): Flow<List<NetworkFilterEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(entity: NetworkFilterEntity)
}

View file

@ -0,0 +1,20 @@
package io.github.openflocon.flocondesktop.features.network.data.datasource.local
import io.github.openflocon.flocondesktop.DeviceId
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import kotlinx.coroutines.flow.Flow
interface NetworkFilterLocalDataSource {
suspend fun get(
deviceId: DeviceId,
column: NetworkTextFilterColumns
): TextFilterStateDomainModel?
fun observe(deviceId: DeviceId): Flow<Map<NetworkTextFilterColumns, TextFilterStateDomainModel>>
suspend fun update(
deviceId: DeviceId,
column: NetworkTextFilterColumns,
newValue: TextFilterStateDomainModel
)
}

View file

@ -0,0 +1,54 @@
package io.github.openflocon.flocondesktop.features.network.data.datasource.local
import io.github.openflocon.flocondesktop.DeviceId
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.mapper.textFilterToDomain
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.mapper.textFilterToEntity
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.model.NetworkFilterEntity
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
class NetworkFilterLocalDataSourceRoom(
private val networkFilterDao: NetworkFilterDao,
) : NetworkFilterLocalDataSource {
override suspend fun get(
deviceId: DeviceId,
column: NetworkTextFilterColumns
): TextFilterStateDomainModel? {
return networkFilterDao.get(
deviceId = deviceId,
column = column,
)?.let {
textFilterToDomain(it)
}
}
override fun observe(deviceId: DeviceId): Flow<Map<NetworkTextFilterColumns, TextFilterStateDomainModel>> {
return networkFilterDao.observe(
deviceId = deviceId,
).map {
it.associate {
it.columnName to textFilterToDomain(it)
}
}
}
override suspend fun update(
deviceId: DeviceId,
column: NetworkTextFilterColumns,
newValue: TextFilterStateDomainModel
) {
networkFilterDao.insertOrUpdate(
textFilterToEntity(
deviceId = deviceId,
column = column,
domain = newValue,
)
)
}
}

View file

@ -0,0 +1,54 @@
package io.github.openflocon.flocondesktop.features.network.data.datasource.local.mapper
import io.github.openflocon.flocondesktop.DeviceId
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.model.FilterItemSavedEntity
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.model.NetworkFilterEntity
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import kotlinx.serialization.json.Json
fun textFilterItemToEntity(item: TextFilterStateDomainModel.FilterItem): FilterItemSavedEntity {
return FilterItemSavedEntity(
text = item.text,
isActive = item.isActive,
isExcluded = item.isExcluded,
)
}
fun textFilterItemToDomain(item: FilterItemSavedEntity): TextFilterStateDomainModel.FilterItem {
return TextFilterStateDomainModel.FilterItem(
text = item.text,
isActive = item.isActive,
isExcluded = item.isExcluded,
)
}
fun textFilterToEntity(
deviceId: DeviceId,
column: NetworkTextFilterColumns,
domain: TextFilterStateDomainModel
): NetworkFilterEntity {
val itemsEntity: List<FilterItemSavedEntity> = domain.items.map {
textFilterItemToEntity(it)
}
return NetworkFilterEntity(
deviceId = deviceId,
columnName = column,
isEnabled = domain.isEnabled,
itemsAsJson = Json.encodeToString(itemsEntity)
)
}
fun textFilterToDomain(
entity: NetworkFilterEntity
): TextFilterStateDomainModel {
val itemsEntity = Json.decodeFromString<List<FilterItemSavedEntity>>(entity.itemsAsJson)
return TextFilterStateDomainModel(
isEnabled = entity.isEnabled,
items = itemsEntity.map {
textFilterItemToDomain(it)
}
)
}

View file

@ -0,0 +1,10 @@
package io.github.openflocon.flocondesktop.features.network.data.datasource.local.model
import kotlinx.serialization.Serializable
@Serializable
data class FilterItemSavedEntity(
val text: String,
val isActive: Boolean,
val isExcluded: Boolean,
)

View file

@ -0,0 +1,17 @@
package io.github.openflocon.flocondesktop.features.network.data.datasource.local.model
import androidx.room.Entity
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
@Entity(
tableName = "network_filter",
primaryKeys = [
"deviceId", "columnName"
]
)
data class NetworkFilterEntity(
val deviceId: String,
val columnName: NetworkTextFilterColumns,
val isEnabled: Boolean,
val itemsAsJson: String,
)

View file

@ -1,8 +1,12 @@
package io.github.openflocon.flocondesktop.features.network.data.di
import io.github.openflocon.flocondesktop.features.network.data.NetworkFilterRepositoryImpl
import io.github.openflocon.flocondesktop.features.network.data.NetworkRepositoryImpl
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.NetworkFilterLocalDataSource
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.NetworkFilterLocalDataSourceRoom
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.NetworkLocalDataSource
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.NetworkLocalDataSourceRoom
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkFilterRepository
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkRepository
import io.github.openflocon.flocondesktop.messages.domain.repository.sub.MessagesReceiverRepository
import org.koin.core.module.dsl.bind
@ -18,4 +22,10 @@ val networkDataModule =
singleOf(::NetworkLocalDataSourceRoom) {
bind<NetworkLocalDataSource>()
}
singleOf(::NetworkFilterRepositoryImpl) {
bind<NetworkFilterRepository>()
}
singleOf(::NetworkFilterLocalDataSourceRoom) {
bind<NetworkFilterLocalDataSource>()
}
}

View file

@ -6,6 +6,9 @@ import io.github.openflocon.flocondesktop.features.network.domain.ObserveHttpReq
import io.github.openflocon.flocondesktop.features.network.domain.RemoveHttpRequestUseCase
import io.github.openflocon.flocondesktop.features.network.domain.RemoveHttpRequestsBeforeUseCase
import io.github.openflocon.flocondesktop.features.network.domain.ResetCurrentDeviceHttpRequestsUseCase
import io.github.openflocon.flocondesktop.features.network.domain.filter.GetNetworkFilterUseCase
import io.github.openflocon.flocondesktop.features.network.domain.filter.ObserveNetworkFilterUseCase
import io.github.openflocon.flocondesktop.features.network.domain.filter.UpdateNetworkFilterUseCase
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module
@ -17,4 +20,7 @@ val networkDomainModule =
factoryOf(::ResetCurrentDeviceHttpRequestsUseCase)
factoryOf(::RemoveHttpRequestsBeforeUseCase)
factoryOf(::RemoveHttpRequestUseCase)
factoryOf(::GetNetworkFilterUseCase)
factoryOf(::ObserveNetworkFilterUseCase)
factoryOf(::UpdateNetworkFilterUseCase)
}

View file

@ -0,0 +1,20 @@
package io.github.openflocon.flocondesktop.features.network.domain.filter
import io.github.openflocon.flocondesktop.core.domain.device.GetCurrentDeviceIdUseCase
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkFilterRepository
class GetNetworkFilterUseCase(
private val getCurrentDeviceIdUseCase: GetCurrentDeviceIdUseCase,
private val networkFilterRepository: NetworkFilterRepository,
) {
suspend operator fun invoke(column: NetworkTextFilterColumns): TextFilterStateDomainModel {
return getCurrentDeviceIdUseCase()?.let { deviceId ->
networkFilterRepository.get(deviceId = deviceId, column = column)
} ?: TextFilterStateDomainModel(
items = listOf(),
isEnabled = true,
)
}
}

View file

@ -0,0 +1,22 @@
package io.github.openflocon.flocondesktop.features.network.domain.filter
import io.github.openflocon.flocondesktop.core.domain.device.ObserveCurrentDeviceIdUseCase
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkFilterRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
class ObserveNetworkFilterUseCase(
private val observeCurrentDeviceIdUseCase: ObserveCurrentDeviceIdUseCase,
private val networkFilterRepository: NetworkFilterRepository,
) {
operator fun invoke(): Flow<Map<NetworkTextFilterColumns, TextFilterStateDomainModel>> {
return observeCurrentDeviceIdUseCase().flatMapLatest { deviceId ->
if (deviceId == null) flowOf(emptyMap())
else networkFilterRepository.observe(deviceId = deviceId)
}.distinctUntilChanged()
}
}

View file

@ -0,0 +1,24 @@
package io.github.openflocon.flocondesktop.features.network.domain.filter
import io.github.openflocon.flocondesktop.core.domain.device.GetCurrentDeviceIdUseCase
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkFilterRepository
class UpdateNetworkFilterUseCase(
private val getCurrentDeviceIdUseCase: GetCurrentDeviceIdUseCase,
private val networkFilterRepository: NetworkFilterRepository,
) {
suspend operator fun invoke(
column: NetworkTextFilterColumns,
newValue: TextFilterStateDomainModel
) {
getCurrentDeviceIdUseCase()?.let { deviceId ->
networkFilterRepository.update(
deviceId = deviceId,
column = column,
newValue = newValue
)
}
}
}

View file

@ -0,0 +1,9 @@
package io.github.openflocon.flocondesktop.features.network.domain.model
enum class NetworkTextFilterColumns {
RequestTime,
Domain,
Query,
Status,
Time,
}

View file

@ -0,0 +1,12 @@
package io.github.openflocon.flocondesktop.features.network.domain.model
data class TextFilterStateDomainModel(
val items: List<FilterItem>,
val isEnabled: Boolean,
) {
data class FilterItem(
val text: String,
val isActive: Boolean,
val isExcluded: Boolean,
)
}

View file

@ -0,0 +1,20 @@
package io.github.openflocon.flocondesktop.features.network.domain.repository
import io.github.openflocon.flocondesktop.DeviceId
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import kotlinx.coroutines.flow.Flow
interface NetworkFilterRepository {
suspend fun get(
deviceId: DeviceId,
column: NetworkTextFilterColumns
): TextFilterStateDomainModel?
fun observe(deviceId: DeviceId): Flow<Map<NetworkTextFilterColumns, TextFilterStateDomainModel>>
suspend fun update(
deviceId: DeviceId,
column: NetworkTextFilterColumns,
newValue: TextFilterStateDomainModel
)
}

View file

@ -1,13 +1,13 @@
package io.github.openflocon.flocondesktop.features.network.ui
import io.github.openflocon.flocondesktop.features.network.domain.model.FloconHttpRequestDomainModel
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.ui.delegate.HeaderDelegate
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkItemViewState
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.ui.model.SortedByUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.NetworkColumnsTypeUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterColumns
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel
class SortAndFilterNetworkItemsProcessor {
operator fun invoke(
@ -15,7 +15,7 @@ class SortAndFilterNetworkItemsProcessor {
filterState: FilterUiState,
sorted: HeaderDelegate.Sorted?,
allowedMethods: List<NetworkMethodUi>,
textFilters: Map<TextFilterColumns, TextFilterState>,
textFilters: Map<NetworkTextFilterColumns, TextFilterStateUiModel>,
): List<NetworkItemViewState> {
return items.asSequence()
.filter { item ->
@ -62,8 +62,8 @@ private fun sort(
return sequence.sortedWith(sortedComparator)
}
private fun TextFilterState.filter(
column: TextFilterColumns,
private fun TextFilterStateUiModel.filter(
column: NetworkTextFilterColumns,
items: List<Pair<FloconHttpRequestDomainModel, NetworkItemViewState>>
): List<Pair<FloconHttpRequestDomainModel, NetworkItemViewState>> {
return items.filter { item ->
@ -71,21 +71,21 @@ private fun TextFilterState.filter(
}
}
private fun TextFilterState.filter(
column: TextFilterColumns,
private fun TextFilterStateUiModel.filter(
column: NetworkTextFilterColumns,
item: Pair<FloconHttpRequestDomainModel, NetworkItemViewState>
): Boolean {
val text = when (column) {
TextFilterColumns.RequestTime -> item.second.dateFormatted
TextFilterColumns.Domain -> item.second.domain
TextFilterColumns.Query -> item.second.type.text
TextFilterColumns.Status -> item.second.status.text
TextFilterColumns.Time -> item.second.timeFormatted
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 TextFilterState.filterByText(text: String): Boolean {
private fun TextFilterStateUiModel.filterByText(text: String): Boolean {
for (filter in this.allFilters) {
if (!filter.filterByText(text))
return false
@ -94,7 +94,7 @@ private fun TextFilterState.filterByText(text: String): Boolean {
return true
}
private fun TextFilterState.FilterItem.filterByText(text: String): Boolean {
private fun TextFilterStateUiModel.FilterItem.filterByText(text: String): Boolean {
if (!this.isActive)
return true

View file

@ -3,6 +3,12 @@ package io.github.openflocon.flocondesktop.features.network.ui.delegate
import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableDelegate
import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableScoped
import io.github.openflocon.flocondesktop.common.coroutines.dispatcherprovider.DispatcherProvider
import io.github.openflocon.flocondesktop.features.network.domain.filter.GetNetworkFilterUseCase
import io.github.openflocon.flocondesktop.features.network.domain.filter.ObserveNetworkFilterUseCase
import io.github.openflocon.flocondesktop.features.network.domain.filter.UpdateNetworkFilterUseCase
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.ui.mapper.toTextFilterDomain
import io.github.openflocon.flocondesktop.features.network.ui.mapper.toTextFilterUi
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.ui.model.SortedByUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.NetworkHeaderUiState
@ -13,20 +19,25 @@ import io.github.openflocon.flocondesktop.features.network.ui.model.header.colum
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.NetworkStatusColumnUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.NetworkTextColumnUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.MethodFilterState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterColumns
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
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.launch
class HeaderDelegate(
private val closeableDelegate: CloseableDelegate,
dispatcherProvider: DispatcherProvider,
private val observeNetworkFilterUseCase: ObserveNetworkFilterUseCase,
private val updateNetworkFilterUseCase: UpdateNetworkFilterUseCase,
private val getNetworkFilterUseCase: GetNetworkFilterUseCase,
) : CloseableScoped by closeableDelegate {
data class Sorted(
@ -34,7 +45,9 @@ class HeaderDelegate(
val sort: SortedByUiModel.Enabled,
)
val textFiltersState = MutableStateFlow<Map<TextFilterColumns, TextFilterState>>(emptyMap())
val textFiltersState: StateFlow<Map<NetworkTextFilterColumns, TextFilterStateUiModel>> = observeNetworkFilterUseCase()
.map { it.mapValues { (key, value) -> toTextFilterUi(value) } }
.stateIn(coroutineScope, started = SharingStarted.WhileSubscribed(5_000), emptyMap())
val sorted = MutableStateFlow<Sorted?>(null)
private val methodFilterState = MutableStateFlow<MethodFilterState>(
@ -76,13 +89,14 @@ class HeaderDelegate(
fun buildHeaderValue(
sorted: Sorted?,
methodFilterState: MethodFilterState,
textFiltersState: Map<TextFilterColumns, TextFilterState>,
textFiltersState: Map<NetworkTextFilterColumns, TextFilterStateUiModel>,
): NetworkHeaderUiState {
return NetworkHeaderUiState(
requestTime = NetworkTextColumnUiModel(
sortedBy = sorted?.takeIf { it.column == NetworkColumnsTypeUiModel.RequestTime }?.sort
?: SortedByUiModel.None,
filter = textFiltersState[TextFilterColumns.RequestTime] ?: TextFilterState.EMPTY,
filter = textFiltersState[NetworkTextFilterColumns.RequestTime]
?: TextFilterStateUiModel.EMPTY,
),
method = NetworkMethodColumnUiModel(
sortedBy = sorted?.takeIf { it.column == NetworkColumnsTypeUiModel.Method }?.sort
@ -92,22 +106,22 @@ class HeaderDelegate(
domain = NetworkTextColumnUiModel(
sortedBy = sorted?.takeIf { it.column == NetworkColumnsTypeUiModel.Domain }?.sort
?: SortedByUiModel.None,
filter = textFiltersState[TextFilterColumns.Domain] ?: TextFilterState.EMPTY,
filter = textFiltersState[NetworkTextFilterColumns.Domain] ?: TextFilterStateUiModel.EMPTY,
),
query = NetworkTextColumnUiModel(
sortedBy = sorted?.takeIf { it.column == NetworkColumnsTypeUiModel.Query }?.sort
?: SortedByUiModel.None,
filter = textFiltersState[TextFilterColumns.Query] ?: TextFilterState.EMPTY,
filter = textFiltersState[NetworkTextFilterColumns.Query] ?: TextFilterStateUiModel.EMPTY,
),
status = NetworkStatusColumnUiModel(
sortedBy = sorted?.takeIf { it.column == NetworkColumnsTypeUiModel.Status }?.sort
?: SortedByUiModel.None,
filter = textFiltersState[TextFilterColumns.Status] ?: TextFilterState.EMPTY,
filter = textFiltersState[NetworkTextFilterColumns.Status] ?: TextFilterStateUiModel.EMPTY,
),
time = NetworkTextColumnUiModel(
sortedBy = sorted?.takeIf { it.column == NetworkColumnsTypeUiModel.Time }?.sort
?: SortedByUiModel.None,
filter = textFiltersState[TextFilterColumns.Time] ?: TextFilterState.EMPTY,
filter = textFiltersState[NetworkTextFilterColumns.Time] ?: TextFilterStateUiModel.EMPTY,
),
)
}
@ -139,42 +153,44 @@ class HeaderDelegate(
}
fun onFilterAction(action: OnFilterAction) {
when (action) {
is OnFilterAction.ClickOnMethod -> {
val clicked = action.methodUi
methodFilterState.update {
it.copy(items = it.items.map { item ->
if (item.method == clicked) {
item.copy(isSelected = !item.isSelected)
} else item
})
coroutineScope.launch {
when (action) {
is OnFilterAction.ClickOnMethod -> {
val clicked = action.methodUi
methodFilterState.update {
it.copy(items = it.items.map { item ->
if (item.method == clicked) {
item.copy(isSelected = !item.isSelected)
} else item
})
}
}
}
is OnFilterAction.TextFilter -> {
textFilterAction(column = action.column, action = action.action)
is OnFilterAction.TextFilter -> {
textFilterAction(column = action.column, action = action.action)
}
}
}
}
private fun textFilterAction(column: TextFilterColumns, action: TextFilterAction) {
val filter = textFiltersState.value[column] ?: TextFilterState(
includedFilters = listOf(),
excludedFilters = listOf(),
isEnabled = true,
)
private suspend fun textFilterAction(
column: NetworkTextFilterColumns,
action: TextFilterAction
) {
val filter: TextFilterStateUiModel = getNetworkFilterUseCase(column).let { toTextFilterUi(it) }
val updated = parformAction(filter, action)
textFiltersState.update {
it + Pair(column, updated)
}
updateNetworkFilterUseCase(
column = column,
newValue = toTextFilterDomain(updated)
)
}
}
private fun parformAction(
filter: TextFilterState,
filter: TextFilterStateUiModel,
action: TextFilterAction
): TextFilterState {
): TextFilterStateUiModel {
return when (action) {
is TextFilterAction.Delete -> {
filter.copy(
@ -185,7 +201,7 @@ private fun parformAction(
is TextFilterAction.Exclude -> {
filter.copy(
excludedFilters = (filter.excludedFilters + TextFilterState.FilterItem(
excludedFilters = (filter.excludedFilters + TextFilterStateUiModel.FilterItem(
text = action.text,
isActive = true,
isExcluded = true
@ -195,7 +211,7 @@ private fun parformAction(
is TextFilterAction.Include -> {
filter.copy(
includedFilters = (filter.includedFilters + TextFilterState.FilterItem(
includedFilters = (filter.includedFilters + TextFilterStateUiModel.FilterItem(
text = action.text,
isActive = true,
isExcluded = false

View file

@ -0,0 +1,35 @@
package io.github.openflocon.flocondesktop.features.network.ui.mapper
import io.github.openflocon.flocondesktop.features.network.domain.model.TextFilterStateDomainModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel
fun toTextFilterUi(textFilter: TextFilterStateDomainModel) : TextFilterStateUiModel {
return TextFilterStateUiModel(
includedFilters = textFilter.items.filter { it.isExcluded.not() }.map { itemToUI(it) },
excludedFilters = textFilter.items.filter { it.isExcluded }.map { itemToUI(it) },
isEnabled = textFilter.isEnabled
)
}
fun toTextFilterDomain(textFilter: TextFilterStateUiModel) : TextFilterStateDomainModel {
return TextFilterStateDomainModel(
items = textFilter.allFilters.map { itemToDomain(it) },
isEnabled = textFilter.isEnabled,
)
}
fun itemToDomain(item: TextFilterStateUiModel.FilterItem): TextFilterStateDomainModel.FilterItem {
return TextFilterStateDomainModel.FilterItem(
text = item.text,
isActive = item.isActive,
isExcluded = item.isExcluded
)
}
fun itemToUI(item: TextFilterStateDomainModel.FilterItem): TextFilterStateUiModel.FilterItem {
return TextFilterStateUiModel.FilterItem(
text = item.text,
isActive = item.isActive,
isExcluded = item.isExcluded
)
}

View file

@ -1,9 +1,9 @@
package io.github.openflocon.flocondesktop.features.network.ui.model.header
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterColumns
sealed interface OnFilterAction {
data class ClickOnMethod(val methodUi: NetworkMethodUi) : OnFilterAction
data class TextFilter(val column: TextFilterColumns, val action: TextFilterAction) : OnFilterAction
data class TextFilter(val column: NetworkTextFilterColumns, val action: TextFilterAction) : OnFilterAction
}

View file

@ -1,10 +1,10 @@
package io.github.openflocon.flocondesktop.features.network.ui.model.header
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel
sealed interface TextFilterAction {
data class Include(val text: String) : TextFilterAction
data class Exclude(val text: String) : TextFilterAction
data class SetIsActive(val item: TextFilterState.FilterItem, val isActive: Boolean) : TextFilterAction
data class Delete(val item: TextFilterState.FilterItem) : TextFilterAction
data class SetIsActive(val item: TextFilterStateUiModel.FilterItem, val isActive: Boolean) : TextFilterAction
data class Delete(val item: TextFilterStateUiModel.FilterItem) : TextFilterAction
}

View file

@ -2,18 +2,18 @@ package io.github.openflocon.flocondesktop.features.network.ui.model.header.colu
import androidx.compose.runtime.Immutable
import io.github.openflocon.flocondesktop.features.network.ui.model.SortedByUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.previewTextFilterState
@Immutable
data class NetworkStatusColumnUiModel(
override val sortedBy: SortedByUiModel,
override val filter: TextFilterState, // TODO maybe later a specific filter
override val filter: TextFilterStateUiModel, // TODO maybe later a specific filter
) : NetworkColumnUiModel {
companion object {
val EMPTY = NetworkStatusColumnUiModel(
sortedBy = SortedByUiModel.None,
filter = TextFilterState.EMPTY,
filter = TextFilterStateUiModel.EMPTY,
)
}
}

View file

@ -2,18 +2,18 @@ package io.github.openflocon.flocondesktop.features.network.ui.model.header.colu
import androidx.compose.runtime.Immutable
import io.github.openflocon.flocondesktop.features.network.ui.model.SortedByUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.previewTextFilterState
@Immutable
data class NetworkTextColumnUiModel(
override val sortedBy: SortedByUiModel,
override val filter: TextFilterState,
override val filter: TextFilterStateUiModel,
) : NetworkColumnUiModel {
companion object {
val EMPTY = NetworkTextColumnUiModel(
sortedBy = SortedByUiModel.None,
filter = TextFilterState.EMPTY,
filter = TextFilterStateUiModel.EMPTY,
)
}
}

View file

@ -1,19 +1,11 @@
package io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter
import androidx.compose.runtime.Immutable
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterState.FilterItem
enum class TextFilterColumns {
RequestTime,
Domain,
Query,
Status,
Time,
}
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel.FilterItem
@Immutable
data class TextFilterState(
data class TextFilterStateUiModel(
val includedFilters: List<FilterItem>,
val excludedFilters: List<FilterItem>,
val isEnabled: Boolean,
@ -30,8 +22,8 @@ data class TextFilterState(
override val isActive: Boolean = allFilters.isNotEmpty() && isEnabled && allFilters.any { it.isActive }
companion object {
val EMPTY = TextFilterState(
companion object Companion {
val EMPTY = TextFilterStateUiModel(
includedFilters = emptyList(),
excludedFilters = emptyList(),
isEnabled = false,
@ -39,7 +31,7 @@ data class TextFilterState(
}
}
fun previewTextFilterState() = TextFilterState(
fun previewTextFilterState() = TextFilterStateUiModel(
isEnabled = true,
includedFilters = listOf(
FilterItem(text = "toInclude", isExcluded = false, isActive = true),

View file

@ -45,7 +45,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import io.github.openflocon.flocondesktop.common.ui.interactions.hover
import io.github.openflocon.flocondesktop.features.network.ui.model.header.TextFilterAction
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterStateUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.previewTextFilterState
import io.github.openflocon.library.designsystem.FloconTheme
import org.jetbrains.compose.ui.tooling.preview.Preview
@ -55,7 +55,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
fun TextFilterDropdown(
expanded: Boolean,
onDismissRequest: () -> Unit,
filterState: TextFilterState,
filterState: TextFilterStateUiModel,
textFilterAction: (TextFilterAction) -> Unit,
) {
DropdownMenu(
@ -74,7 +74,7 @@ fun TextFilterDropdown(
@Composable
private fun TextFilterDropdownContent(
modifier: Modifier = Modifier,
filterState: TextFilterState,
filterState: TextFilterStateUiModel,
textFilterAction: (TextFilterAction) -> Unit,
) {
Column(modifier = modifier) {
@ -163,10 +163,10 @@ private fun TextFilterDropdownContent(
@Composable
private fun FilterItemView(
item: TextFilterState.FilterItem,
item: TextFilterStateUiModel.FilterItem,
modifier: Modifier = Modifier,
changeIsActive: (item: TextFilterState.FilterItem, newValue: Boolean) -> Unit,
clickDelete: (item: TextFilterState.FilterItem) -> Unit,
changeIsActive: (item: TextFilterStateUiModel.FilterItem, newValue: Boolean) -> Unit,
clickDelete: (item: TextFilterStateUiModel.FilterItem) -> Unit,
) {
var isHover by remember { mutableStateOf(false) }
Row(

View file

@ -16,12 +16,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.network.ui.NetworkAction
import io.github.openflocon.flocondesktop.features.network.domain.model.NetworkTextFilterColumns
import io.github.openflocon.flocondesktop.features.network.ui.model.SortedByUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.NetworkHeaderUiState
import io.github.openflocon.flocondesktop.features.network.ui.model.header.OnFilterAction
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.NetworkColumnsTypeUiModel
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.filter.TextFilterColumns
import io.github.openflocon.flocondesktop.features.network.ui.model.header.columns.base.isFiltered
import io.github.openflocon.flocondesktop.features.network.ui.model.header.previewNetworkHeaderUiState
import io.github.openflocon.flocondesktop.features.network.ui.view.NetworkItemColumnWidths
@ -72,7 +71,7 @@ fun NetworkItemHeaderView(
},
filterState = state.requestTime.filter,
textFilterAction = {
onFilterAction(OnFilterAction.TextFilter(TextFilterColumns.RequestTime, it))
onFilterAction(OnFilterAction.TextFilter(NetworkTextFilterColumns.RequestTime, it))
}
)
}
@ -128,7 +127,7 @@ fun NetworkItemHeaderView(
isDropdownExpanded = false
},
textFilterAction = {
onFilterAction(OnFilterAction.TextFilter(TextFilterColumns.Domain, it))
onFilterAction(OnFilterAction.TextFilter(NetworkTextFilterColumns.Domain, it))
}
)
}
@ -154,7 +153,7 @@ fun NetworkItemHeaderView(
isDropdownExpanded = false
},
textFilterAction = {
onFilterAction(OnFilterAction.TextFilter(TextFilterColumns.Query,it))
onFilterAction(OnFilterAction.TextFilter(NetworkTextFilterColumns.Query,it))
}
)
}
@ -181,7 +180,7 @@ fun NetworkItemHeaderView(
isDropdownExpanded = false
},
textFilterAction = {
onFilterAction(OnFilterAction.TextFilter(TextFilterColumns.Status,it))
onFilterAction(OnFilterAction.TextFilter(NetworkTextFilterColumns.Status,it))
}
)
}
@ -207,7 +206,7 @@ fun NetworkItemHeaderView(
isDropdownExpanded = false
},
textFilterAction = {
onFilterAction(OnFilterAction.TextFilter(TextFilterColumns.Time, it))
onFilterAction(OnFilterAction.TextFilter(NetworkTextFilterColumns.Time, it))
}
)
}