refact: [NETWORK] multiple network configs (#119)

Co-authored-by: Florent Champigny <florent@bere.al>
This commit is contained in:
Florent CHAMPIGNY 2025-08-20 14:40:52 +02:00 committed by GitHub
parent 4b0b565705
commit fa1df1fe3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 940 additions and 368 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 39,
"identityHash": "121dfc4cfd03bb71ddd3d8af08dd8939",
"identityHash": "2f04f00102ac26b72949768d76f33930",
"entities": [
{
"tableName": "FloconNetworkCallEntity",
@ -998,8 +998,20 @@
},
{
"tableName": "BadQualityConfigEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `packageName` TEXT NOT NULL, `isEnabled` INTEGER NOT NULL, `errorProbability` REAL NOT NULL, `errors` TEXT NOT NULL, `triggerProbability` REAL NOT NULL, `minLatencyMs` INTEGER NOT NULL, `maxLatencyMs` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `packageName`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `deviceId` TEXT NOT NULL, `packageName` TEXT NOT NULL, `isEnabled` INTEGER NOT NULL, `errorProbability` REAL NOT NULL, `errors` TEXT NOT NULL, `triggerProbability` REAL NOT NULL, `minLatencyMs` INTEGER NOT NULL, `maxLatencyMs` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "deviceId",
"columnName": "deviceId",
@ -1052,15 +1064,26 @@
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"deviceId",
"packageName"
"id"
]
}
},
"indices": [
{
"name": "index_BadQualityConfigEntity_deviceId_packageName",
"unique": false,
"columnNames": [
"deviceId",
"packageName"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_BadQualityConfigEntity_deviceId_packageName` ON `${TABLE_NAME}` (`deviceId`, `packageName`)"
}
]
}
],
"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, '121dfc4cfd03bb71ddd3d8af08dd8939')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2f04f00102ac26b72949768d76f33930')"
]
}
}

View file

@ -36,7 +36,7 @@ import io.github.openflocon.flocondesktop.common.db.converters.MapStringsConvert
import kotlinx.coroutines.Dispatchers
@Database(
version = 39,
version = 41,
entities = [
FloconNetworkCallEntity::class,
FileEntity::class,

View file

@ -1,62 +0,0 @@
package io.github.openflocon.flocondesktop.features.network
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.github.openflocon.domain.common.DispatcherProvider
import io.github.openflocon.domain.feedback.FeedbackDisplayer
import io.github.openflocon.domain.network.usecase.badquality.ObserveNetworkBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.SaveNetworkBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.UpdateNetworkBadQualityIsEnabledUseCase
import io.github.openflocon.flocondesktop.features.network.mapper.toDomain
import io.github.openflocon.flocondesktop.features.network.mapper.toUi
import io.github.openflocon.flocondesktop.features.network.model.badquality.BadQualityConfigUiModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class BadQualityNetworkViewModel(
private val observeNetworkBadQualityUseCase: ObserveNetworkBadQualityUseCase,
private val saveNetworkBadQualityUseCase: SaveNetworkBadQualityUseCase,
private val updateNetworkBadQualityIsEnabledUseCase: UpdateNetworkBadQualityIsEnabledUseCase,
private val dispatcherProvider: DispatcherProvider,
private val feedbackDisplayer: FeedbackDisplayer,
) : ViewModel() {
enum class Event {
Close
}
private val _events = Channel<BadQualityNetworkViewModel.Event?>()
val events: Flow<Event?> = _events.receiveAsFlow()
val viewState = observeNetworkBadQualityUseCase()
.distinctUntilChanged()
.map { toUi(it) }
.flowOn(dispatcherProvider.viewModel)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null,
)
fun changeIsEnabled(enabled: Boolean) {
viewModelScope.launch(dispatcherProvider.viewModel) {
updateNetworkBadQualityIsEnabledUseCase(isEnabled = enabled)
}
}
fun save(uiModel: BadQualityConfigUiModel) {
viewModelScope.launch(dispatcherProvider.viewModel) {
saveNetworkBadQualityUseCase(toDomain(uiModel))
// close
_events.send(Event.Close)
feedbackDisplayer.displayMessage("Saved")
}
}
}

View file

@ -1,6 +1,8 @@
package io.github.openflocon.flocondesktop.features.network
import io.github.openflocon.flocondesktop.features.network.badquality.BadQualityNetworkViewModel
import io.github.openflocon.flocondesktop.features.network.delegate.HeaderDelegate
import io.github.openflocon.flocondesktop.features.network.mock.NetworkMocksViewModel
import io.github.openflocon.flocondesktop.messages.ui.MessagesServerDelegate
import org.koin.core.module.dsl.factoryOf
import org.koin.core.module.dsl.viewModelOf

View file

@ -0,0 +1,101 @@
package io.github.openflocon.flocondesktop.features.network.badquality
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.github.openflocon.domain.common.DispatcherProvider
import io.github.openflocon.domain.feedback.FeedbackDisplayer
import io.github.openflocon.domain.network.models.BadQualityConfigId
import io.github.openflocon.domain.network.usecase.badquality.DeleteBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.ObserveAllNetworkBadQualitiesUseCase
import io.github.openflocon.domain.network.usecase.badquality.ObserveNetworkBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.SaveNetworkBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.SetNetworkBadQualityEnabledConfigUseCase
import io.github.openflocon.flocondesktop.features.network.badquality.edition.mapper.toDomain
import io.github.openflocon.flocondesktop.features.network.badquality.edition.mapper.toUi
import io.github.openflocon.flocondesktop.features.network.badquality.list.mapper.toLineUi
import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.BadQualityConfigUiModel
import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.SelectedBadQualityUiModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class BadQualityNetworkViewModel(
private val observeAllNetworkBadQualitiesUseCase: ObserveAllNetworkBadQualitiesUseCase,
private val observeNetworkBadQualityUseCase: ObserveNetworkBadQualityUseCase,
private val deleteBadQualityUseCase: DeleteBadQualityUseCase,
private val saveNetworkBadQualityUseCase: SaveNetworkBadQualityUseCase,
private val setNetworkBadQualityEnabledConfigUseCase: SetNetworkBadQualityEnabledConfigUseCase,
private val dispatcherProvider: DispatcherProvider,
private val feedbackDisplayer: FeedbackDisplayer,
) : ViewModel() {
enum class Event {
Close
}
private val _events = Channel<BadQualityNetworkViewModel.Event?>()
val events: Flow<Event?> = _events.receiveAsFlow()
val items = observeAllNetworkBadQualitiesUseCase()
.distinctUntilChanged()
.map { it.map { it.toLineUi() } }
.flowOn(dispatcherProvider.viewModel)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
val selectedItem = MutableStateFlow<SelectedBadQualityUiModel?>(null)
fun setEnabledElement(configId: BadQualityConfigId?) {
viewModelScope.launch(dispatcherProvider.viewModel) {
setNetworkBadQualityEnabledConfigUseCase(configId = configId)
}
}
fun delete(id: String) {
viewModelScope.launch(dispatcherProvider.viewModel) {
deleteBadQualityUseCase(id)
}
}
fun select(id: String) {
viewModelScope.launch(dispatcherProvider.viewModel) {
// TODO get
val existing = observeNetworkBadQualityUseCase(id).firstOrNull()?.toUi()
selectedItem.value = existing?.let {
SelectedBadQualityUiModel.Edition(
config = existing
)
} ?: SelectedBadQualityUiModel.Creation
}
}
fun create() {
selectedItem.value = SelectedBadQualityUiModel.Creation
}
fun save(uiModel: BadQualityConfigUiModel) {
viewModelScope.launch(dispatcherProvider.viewModel) {
saveNetworkBadQualityUseCase(uiModel.toDomain())
// close
selectedItem.value = null
feedbackDisplayer.displayMessage("Saved")
}
}
fun closeEdition() {
selectedItem.value = null
}
}

View file

@ -0,0 +1,59 @@
package io.github.openflocon.flocondesktop.features.network.badquality.edition.mapper
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.BadQualityConfigUiModel
import kotlin.time.Instant
fun BadQualityConfigDomainModel.toUi() = BadQualityConfigUiModel(
id = id,
name = name,
createdAt = createdAt.toEpochMilliseconds(),
isEnabled = isEnabled,
latency = latency.toUi(),
errorProbability = errorProbability,
errors = errors.map { error ->
error.toUi()
}
)
private fun BadQualityConfigDomainModel.Error.toUi() = BadQualityConfigUiModel.Error(
weight = weight,
httpCode = httpCode,
body = body,
contentType = contentType,
)
private fun BadQualityConfigDomainModel.LatencyConfig.toUi() =
BadQualityConfigUiModel.LatencyConfig(
triggerProbability = triggerProbability,
minLatencyMs = minLatencyMs,
maxLatencyMs = maxLatencyMs,
)
fun BadQualityConfigUiModel.toDomain() = BadQualityConfigDomainModel(
id = id,
name = name,
createdAt = Instant.fromEpochMilliseconds(createdAt),
isEnabled = isEnabled,
latency = latency.toDomain(),
errorProbability = errorProbability,
errors = errors.map { error ->
error.toDomain()
}
)
private fun BadQualityConfigUiModel.Error.toDomain() = BadQualityConfigDomainModel.Error(
weight = weight,
httpCode = httpCode,
body = body,
contentType = contentType,
)
private fun BadQualityConfigUiModel.LatencyConfig.toDomain() =
BadQualityConfigDomainModel.LatencyConfig(
triggerProbability = triggerProbability,
minLatencyMs = minLatencyMs,
maxLatencyMs = maxLatencyMs,
)

View file

@ -1,8 +1,11 @@
package io.github.openflocon.flocondesktop.features.network.model.badquality
package io.github.openflocon.flocondesktop.features.network.badquality.edition.model
import java.util.UUID
data class BadQualityConfigUiModel(
val id: String,
val name: String,
val createdAt: Long,
val isEnabled: Boolean,
val latency: LatencyConfig,
val errorProbability: Double, // chance of triggering an error
@ -23,6 +26,8 @@ data class BadQualityConfigUiModel(
}
fun previewBadQualityConfigUiModel(errorCount: Int) = BadQualityConfigUiModel(
id = "id",
name = "config_name",
isEnabled = true,
latency = BadQualityConfigUiModel.LatencyConfig(
triggerProbability = 0.1,
@ -30,6 +35,7 @@ fun previewBadQualityConfigUiModel(errorCount: Int) = BadQualityConfigUiModel(
maxLatencyMs = 200,
),
errorProbability = 0.8,
createdAt = System.currentTimeMillis(),
errors = List(errorCount) {
BadQualityConfigUiModel.Error(
weight = 1f,

View file

@ -0,0 +1,18 @@
package io.github.openflocon.flocondesktop.features.network.badquality.edition.model
import androidx.compose.runtime.Immutable
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkUiModel
@Immutable
sealed interface SelectedBadQualityUiModel {
val config: BadQualityConfigUiModel?
@Immutable
data object Creation : SelectedBadQualityUiModel {
override val config: BadQualityConfigUiModel? = null
}
@Immutable
data class Edition(override val config: BadQualityConfigUiModel) : SelectedBadQualityUiModel
}

View file

@ -1,6 +1,6 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package io.github.openflocon.flocondesktop.features.network.view.badquality
package io.github.openflocon.flocondesktop.features.network.badquality.edition.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -25,7 +25,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
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
@ -38,91 +37,68 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.features.network.BadQualityNetworkViewModel
import io.github.openflocon.flocondesktop.features.network.model.badquality.BadQualityConfigUiModel
import io.github.openflocon.flocondesktop.features.network.model.badquality.previewBadQualityConfigUiModel
import io.github.openflocon.flocondesktop.features.network.view.mocks.NetworkMockFieldView
import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.BadQualityConfigUiModel
import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.SelectedBadQualityUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.view.NetworkMockFieldView
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconButton
import io.github.openflocon.library.designsystem.components.FloconDialog
import io.github.openflocon.library.designsystem.components.FloconDialogButtons
import io.github.openflocon.library.designsystem.components.FloconSurface
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
import java.util.UUID
@Composable
fun BadNetworkQualityWindow(
fun BadQualityEditionWindow(
onCloseRequest: () -> Unit,
save: (state: BadQualityConfigUiModel) -> Unit,
state: SelectedBadQualityUiModel,
) {
val viewModel: BadQualityNetworkViewModel = koinViewModel()
val state by viewModel.viewState.collectAsStateWithLifecycle()
val viewModelEvent by viewModel.events.collectAsStateWithLifecycle(null)
LaunchedEffect(viewModelEvent) {
when (viewModelEvent) {
BadQualityNetworkViewModel.Event.Close -> onCloseRequest()
null -> {}
}
}
FloconDialog(
onDismissRequest = onCloseRequest
) {
BadNetworkQualityContent(
BadNetworkQualityEditionContent(
state = state,
save = viewModel::save,
save = save,
close = onCloseRequest,
)
}
}
@Composable
@Preview
private fun BadNetworkQualityContentPreview() {
FloconTheme {
FloconSurface {
BadNetworkQualityContent(
state = previewBadQualityConfigUiModel(
errorCount = 5
),
save = {},
close = {},
)
}
}
}
@Composable
fun BadNetworkQualityContent(
state: BadQualityConfigUiModel?,
fun BadNetworkQualityEditionContent(
state: SelectedBadQualityUiModel,
close: () -> Unit,
save: (state: BadQualityConfigUiModel) -> Unit,
modifier: Modifier = Modifier,
) {
var isEnabled by remember(state) { mutableStateOf<Boolean>(state?.isEnabled ?: true) }
val config = state.config
var name by remember(state) {
mutableStateOf<String>(
config?.name ?: ""
)
}
var triggerProbability by remember(state) {
mutableStateOf<String>(
state?.latency?.triggerProbability?.let { it * 100.0 }?.toString() ?: "100"
config?.latency?.triggerProbability?.let { it * 100.0 }?.toString() ?: "100"
)
}
var minLatencyMs by remember(state) {
mutableStateOf<String>(
state?.latency?.minLatencyMs?.toString() ?: "0"
config?.latency?.minLatencyMs?.toString() ?: "0"
)
}
var maxLatencyMs by remember(state) {
mutableStateOf<String>(
state?.latency?.maxLatencyMs?.toString() ?: "0"
config?.latency?.maxLatencyMs?.toString() ?: "0"
)
}
var errorProbability by remember(state) {
mutableStateOf<String>(
state?.errorProbability?.let { it * 100.0 }?.toString() ?: "100"
config?.errorProbability?.let { it * 100.0 }?.toString() ?: "100"
)
}
var errors by remember(state) { mutableStateOf(state?.errors ?: emptyList()) }
var errors by remember(state) { mutableStateOf(config?.errors ?: emptyList()) }
var selectedErrorToEdit by remember { mutableStateOf<BadQualityConfigUiModel.Error?>(null) }
Column(
@ -131,20 +107,15 @@ fun BadNetworkQualityContent(
.fillMaxSize()
.padding(8.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Switch(
modifier = Modifier.scale(0.6f),
checked = isEnabled,
onCheckedChange = { isEnabled = it }
)
Text(
text = "Enable",
style = FloconTheme.typography.titleMedium
)
}
NetworkMockFieldView(
label = "Name",
placeHolder = "",
value = name,
onValueChange = {
name = it
},
trailingComponent = { }
)
NetworkMockFieldView(
label = "Trigger probability",
placeHolder = "0",
@ -237,7 +208,10 @@ fun BadNetworkQualityContent(
maxLatencyMs.toLong().coerceAtLeast(minLatencyMsValue)
save(
BadQualityConfigUiModel(
isEnabled = isEnabled,
id = config?.id ?: UUID.randomUUID().toString(), // generate a new one
name = name,
isEnabled = config?.isEnabled ?: false, // disabled by default
createdAt = config?.createdAt ?: System.currentTimeMillis(), // generate a new date
latency = BadQualityConfigUiModel.LatencyConfig(
triggerProbability = triggerProbability
.toDoubleOrNull()

View file

@ -0,0 +1,10 @@
package io.github.openflocon.flocondesktop.features.network.badquality.list.mapper
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.flocondesktop.features.network.badquality.list.model.NetworkBadQualityLineUiModel
fun BadQualityConfigDomainModel.toLineUi() = NetworkBadQualityLineUiModel(
id = id,
name = name,
isEnabled = isEnabled,
)

View file

@ -0,0 +1,16 @@
package io.github.openflocon.flocondesktop.features.network.badquality.list.model
import androidx.compose.runtime.Immutable
@Immutable
data class NetworkBadQualityLineUiModel(
val id: String,
val isEnabled: Boolean,
val name: String,
)
fun previewNetworkBadQualityLineUiModel() = NetworkBadQualityLineUiModel(
id = "1",
isEnabled = true,
name = "the name"
)

View file

@ -0,0 +1,83 @@
package io.github.openflocon.flocondesktop.features.network.badquality.list.view
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Switch
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.scale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.github.openflocon.flocondesktop.features.network.badquality.list.model.NetworkBadQualityLineUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.view.MockNetworkMethodView
import io.github.openflocon.flocondesktop.features.network.mock.list.model.MockNetworkLineUiModel
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconIconButton
import io.github.openflocon.library.designsystem.components.FloconSurface
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun BadNetworkLineView(
item: NetworkBadQualityLineUiModel,
onClicked: (id: String) -> Unit,
onDeleteClicked: (id: String) -> Unit,
enableClicked: (id: String, enabled: Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.padding(vertical = 2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.height(12.dp)) {
Switch(
modifier = Modifier.scale(0.6f),
checked = item.isEnabled,
onCheckedChange = {
enableClicked(item.id, it)
},
)
}
Row(
modifier = Modifier.fillMaxWidth()
.clickable {
onClicked(item.id)
},
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = item.name,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = FloconTheme.typography.bodySmall.copy(fontSize = 11.sp),
color = FloconTheme.colorPalette.onSurface,
modifier = Modifier.weight(2f)
.background(
color = FloconTheme.colorPalette.panel.copy(alpha = 0.8f),
shape = RoundedCornerShape(4.dp),
)
.padding(horizontal = 8.dp, vertical = 6.dp),
)
FloconIconButton(
imageVector = Icons.Filled.Delete,
onClick = {
onDeleteClicked(item.id)
},
)
}
}
}

View file

@ -0,0 +1,70 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package io.github.openflocon.flocondesktop.features.network.badquality.list
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.features.network.badquality.BadQualityNetworkViewModel
import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.BadQualityConfigUiModel
import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.SelectedBadQualityUiModel
import io.github.openflocon.flocondesktop.features.network.badquality.edition.view.BadNetworkQualityEditionContent
import io.github.openflocon.flocondesktop.features.network.badquality.edition.view.BadQualityEditionWindow
import io.github.openflocon.flocondesktop.features.network.badquality.list.view.NetworkBadQualityContent
import io.github.openflocon.library.designsystem.components.FloconDialog
import org.koin.compose.viewmodel.koinViewModel
@Composable
fun BadNetworkQualityWindow(
onCloseRequest: () -> Unit,
) {
FloconDialog(
onDismissRequest = onCloseRequest
) {
BadNetworkQualityContent(
onCloseRequest = onCloseRequest,
)
}
}
@Composable
private fun BadNetworkQualityContent(
onCloseRequest: () -> Unit,
) {
val viewModel: BadQualityNetworkViewModel = koinViewModel()
val items by viewModel.items.collectAsStateWithLifecycle()
val viewModelEvent by viewModel.events.collectAsStateWithLifecycle(null)
LaunchedEffect(viewModelEvent) {
when (viewModelEvent) {
BadQualityNetworkViewModel.Event.Close -> onCloseRequest()
null -> {}
}
}
NetworkBadQualityContent(
lines = items,
modifier = Modifier.fillMaxSize(),
onItemClicked = viewModel::select,
onAddItemClicked = viewModel::create,
onDeleteClicked = viewModel::delete,
setEnabled = viewModel::setEnabledElement,
)
val selectedConfig by viewModel.selectedItem.collectAsStateWithLifecycle()
selectedConfig?.let {
BadQualityEditionWindow(
onCloseRequest = viewModel::closeEdition,
save = viewModel::save,
state = it,
)
}
}

View file

@ -0,0 +1,80 @@
package io.github.openflocon.flocondesktop.features.network.badquality.list.view
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
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.foundation.shape.RoundedCornerShape
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.unit.dp
import io.github.openflocon.flocondesktop.features.network.badquality.list.model.NetworkBadQualityLineUiModel
import io.github.openflocon.library.designsystem.FloconTheme
@Composable
fun NetworkBadQualityContent(
lines: List<NetworkBadQualityLineUiModel>,
onItemClicked: (id: String) -> Unit,
onDeleteClicked: (id: String) -> Unit,
setEnabled: (id: String?) -> Unit,
onAddItemClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
Box(
Modifier
.fillMaxWidth()
.background(FloconTheme.colorPalette.panel)
.padding(horizontal = 12.dp, vertical = 4.dp),
) {
Text(
text = "Bad network configs",
modifier = Modifier
.background(FloconTheme.colorPalette.panel)
.padding(all = 12.dp),
style = FloconTheme.typography.titleMedium,
color = FloconTheme.colorPalette.onSurface,
)
Box(
modifier = Modifier.align(Alignment.CenterEnd)
.clip(RoundedCornerShape(12.dp))
.background(FloconTheme.colorPalette.onSurface)
.clickable(onClick = onAddItemClicked)
.padding(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
"Create",
style = FloconTheme.typography.titleSmall,
color = FloconTheme.colorPalette.panel,
)
}
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(lines) {
BadNetworkLineView(
item = it,
onClicked = onItemClicked,
onDeleteClicked = onDeleteClicked,
enableClicked = { id, enabled ->
if(enabled) {
setEnabled(id)
} else {
setEnabled(null)
}
},
modifier = Modifier.fillMaxWidth(),
)
}
}
}
}

View file

@ -1,54 +0,0 @@
package io.github.openflocon.flocondesktop.features.network.mapper
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.flocondesktop.features.network.model.badquality.BadQualityConfigUiModel
fun toUi(model: BadQualityConfigDomainModel?) = model?.let {
BadQualityConfigUiModel(
isEnabled = it.isEnabled,
latency = toUi(it.latency),
errorProbability = it.errorProbability,
errors = it.errors.map { error ->
toUi(error)
}
)
}
private fun toUi(error: BadQualityConfigDomainModel.Error) = BadQualityConfigUiModel.Error(
weight = error.weight,
httpCode = error.httpCode,
body = error.body,
contentType = error.contentType,
)
private fun toUi(model: BadQualityConfigDomainModel.LatencyConfig) =
BadQualityConfigUiModel.LatencyConfig(
triggerProbability = model.triggerProbability,
minLatencyMs = model.minLatencyMs,
maxLatencyMs = model.maxLatencyMs,
)
fun toDomain(model: BadQualityConfigUiModel) = BadQualityConfigDomainModel(
isEnabled = model.isEnabled,
latency = toDomain(model.latency),
errorProbability = model.errorProbability,
errors = model.errors.map { error ->
toDomain(error)
}
)
private fun toDomain(error: BadQualityConfigUiModel.Error) = BadQualityConfigDomainModel.Error(
weight = error.weight,
httpCode = error.httpCode,
body = error.body,
contentType = error.contentType,
)
private fun toDomain(model: BadQualityConfigUiModel.LatencyConfig) =
BadQualityConfigDomainModel.LatencyConfig(
triggerProbability = model.triggerProbability,
minLatencyMs = model.minLatencyMs,
maxLatencyMs = model.maxLatencyMs,
)

View file

@ -1,109 +0,0 @@
package io.github.openflocon.flocondesktop.features.network.mapper
import io.github.openflocon.domain.common.Either
import io.github.openflocon.domain.common.failure
import io.github.openflocon.domain.common.success
import io.github.openflocon.domain.network.models.MockNetworkDomainModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.EditableMockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.HeaderUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkLineUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.SelectedMockUiModel
import java.util.UUID
fun toLineUi(mockDomain: MockNetworkDomainModel): MockNetworkLineUiModel = MockNetworkLineUiModel(
id = mockDomain.id,
isEnabled = mockDomain.isEnabled,
urlPattern = mockDomain.expectation.urlPattern,
method = toMockMethodUi(mockDomain.expectation.method),
)
fun toDomain(uiModel: MockNetworkUiModel): MockNetworkDomainModel = MockNetworkDomainModel(
id = uiModel.id ?: UUID.randomUUID().toString(),
isEnabled = uiModel.isEnabled,
expectation = MockNetworkDomainModel.Expectation(
urlPattern = uiModel.expectation.urlPattern,
method = uiModel.expectation.method.text,
),
response = MockNetworkDomainModel.Response(
httpCode = uiModel.response.httpCode,
body = uiModel.response.body,
mediaType = uiModel.response.mediaType,
delay = uiModel.response.delay,
headers = uiModel.response.headers,
),
)
fun toMockMethodUi(text: String): MockNetworkMethodUi = when (text.lowercase()) {
"get" -> MockNetworkMethodUi.GET
"post" -> MockNetworkMethodUi.POST
"put" -> MockNetworkMethodUi.PUT
"delete" -> MockNetworkMethodUi.DELETE
"patch" -> MockNetworkMethodUi.PATCH
else -> MockNetworkMethodUi.ALL
}
fun toUi(domainModel: MockNetworkDomainModel): MockNetworkUiModel = MockNetworkUiModel(
id = domainModel.id,
expectation = MockNetworkUiModel.Expectation(
urlPattern = domainModel.expectation.urlPattern,
method = toMockMethodUi(domainModel.expectation.method),
),
isEnabled = domainModel.isEnabled,
response = MockNetworkUiModel.Response(
httpCode = domainModel.response.httpCode,
body = domainModel.response.body,
mediaType = domainModel.response.mediaType,
delay = domainModel.response.delay,
headers = domainModel.response.headers,
),
)
fun createEditable(initialMock: SelectedMockUiModel): EditableMockNetworkUiModel = when (initialMock) {
is SelectedMockUiModel.Creation -> createEditable(null)
is SelectedMockUiModel.Edition -> createEditable(initialMock.existing)
}
fun createEditable(initialMock: MockNetworkUiModel?): EditableMockNetworkUiModel = EditableMockNetworkUiModel(
id = initialMock?.id,
isEnabled = initialMock?.isEnabled ?: true, // true by default
expectation = EditableMockNetworkUiModel.Expectation(
urlPattern = initialMock?.expectation?.urlPattern,
method = initialMock?.expectation?.method ?: MockNetworkMethodUi.GET,
),
response = EditableMockNetworkUiModel.Response(
httpCode = initialMock?.response?.httpCode ?: 200,
body = initialMock?.response?.body ?: "",
mediaType = initialMock?.response?.mediaType ?: "application/json",
delay = initialMock?.response?.delay ?: 0,
headers = initialMock?.response?.headers?.map {
HeaderUiModel(
key = it.key,
value = it.value,
)
} ?: emptyList(),
),
)
fun editableToUi(editable: EditableMockNetworkUiModel): Either<Throwable, MockNetworkUiModel> = try {
MockNetworkUiModel(
id = editable.id,
expectation = MockNetworkUiModel.Expectation(
urlPattern = editable.expectation.urlPattern!!,
method = editable.expectation.method,
),
isEnabled = editable.isEnabled,
response = MockNetworkUiModel.Response(
httpCode = editable.response.httpCode,
body = editable.response.body!!,
mediaType = editable.response.mediaType,
delay = editable.response.delay,
headers = editable.response.headers.associate {
it.key to it.value
}.filterNot { it.key.isEmpty() },
),
).success()
} catch (t: Throwable) {
t.failure()
}

View file

@ -1,8 +1,9 @@
package io.github.openflocon.flocondesktop.features.network
package io.github.openflocon.flocondesktop.features.network.mock
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.github.openflocon.domain.common.DispatcherProvider
import io.github.openflocon.domain.feedback.FeedbackDisplayer
import io.github.openflocon.domain.network.models.MockNetworkDomainModel
import io.github.openflocon.domain.network.usecase.mocks.AddNetworkMocksUseCase
import io.github.openflocon.domain.network.usecase.mocks.DeleteNetworkMocksUseCase
@ -10,13 +11,12 @@ import io.github.openflocon.domain.network.usecase.mocks.GenerateNetworkMockFrom
import io.github.openflocon.domain.network.usecase.mocks.GetNetworkMockByIdUseCase
import io.github.openflocon.domain.network.usecase.mocks.ObserveNetworkMocksUseCase
import io.github.openflocon.domain.network.usecase.mocks.UpdateNetworkMockIsEnabledUseCase
import io.github.openflocon.domain.feedback.FeedbackDisplayer
import io.github.openflocon.flocondesktop.features.network.mapper.toDomain
import io.github.openflocon.flocondesktop.features.network.mapper.toLineUi
import io.github.openflocon.flocondesktop.features.network.mapper.toUi
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockEditionWindowUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.SelectedMockUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.mapper.toDomain
import io.github.openflocon.flocondesktop.features.network.mock.edition.mapper.toUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockEditionWindowUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.SelectedMockUiModel
import io.github.openflocon.flocondesktop.features.network.mock.list.mapper.toLineUi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
@ -39,7 +39,7 @@ class NetworkMocksViewModel(
val items = observeNetworkMocksUseCase()
.distinctUntilChanged()
.map { it.map { toLineUi(it) } }
.map { it.map { it.toLineUi() } }
.flowOn(dispatcherProvider.viewModel)
.stateIn(
scope = viewModelScope,

View file

@ -0,0 +1,96 @@
package io.github.openflocon.flocondesktop.features.network.mock.edition.mapper
import io.github.openflocon.domain.common.Either
import io.github.openflocon.domain.common.failure
import io.github.openflocon.domain.common.success
import io.github.openflocon.domain.network.models.MockNetworkDomainModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.EditableMockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.HeaderUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.SelectedMockUiModel
import io.github.openflocon.flocondesktop.features.network.mock.list.mapper.toMockMethodUi
import java.util.UUID
fun toDomain(uiModel: MockNetworkUiModel): MockNetworkDomainModel = MockNetworkDomainModel(
id = uiModel.id ?: UUID.randomUUID().toString(),
isEnabled = uiModel.isEnabled,
expectation = MockNetworkDomainModel.Expectation(
urlPattern = uiModel.expectation.urlPattern,
method = uiModel.expectation.method.text,
),
response = MockNetworkDomainModel.Response(
httpCode = uiModel.response.httpCode,
body = uiModel.response.body,
mediaType = uiModel.response.mediaType,
delay = uiModel.response.delay,
headers = uiModel.response.headers,
),
)
fun toUi(domainModel: MockNetworkDomainModel): MockNetworkUiModel = MockNetworkUiModel(
id = domainModel.id,
expectation = MockNetworkUiModel.Expectation(
urlPattern = domainModel.expectation.urlPattern,
method = toMockMethodUi(domainModel.expectation.method),
),
isEnabled = domainModel.isEnabled,
response = MockNetworkUiModel.Response(
httpCode = domainModel.response.httpCode,
body = domainModel.response.body,
mediaType = domainModel.response.mediaType,
delay = domainModel.response.delay,
headers = domainModel.response.headers,
),
)
fun createEditable(initialMock: SelectedMockUiModel): EditableMockNetworkUiModel =
when (initialMock) {
is SelectedMockUiModel.Creation -> createEditable(null)
is SelectedMockUiModel.Edition -> createEditable(initialMock.existing)
}
fun createEditable(initialMock: MockNetworkUiModel?): EditableMockNetworkUiModel =
EditableMockNetworkUiModel(
id = initialMock?.id,
isEnabled = initialMock?.isEnabled ?: true, // true by default
expectation = EditableMockNetworkUiModel.Expectation(
urlPattern = initialMock?.expectation?.urlPattern,
method = initialMock?.expectation?.method ?: MockNetworkMethodUi.GET,
),
response = EditableMockNetworkUiModel.Response(
httpCode = initialMock?.response?.httpCode ?: 200,
body = initialMock?.response?.body ?: "",
mediaType = initialMock?.response?.mediaType ?: "application/json",
delay = initialMock?.response?.delay ?: 0,
headers = initialMock?.response?.headers?.map {
HeaderUiModel(
key = it.key,
value = it.value,
)
} ?: emptyList(),
),
)
fun editableToUi(editable: EditableMockNetworkUiModel): Either<Throwable, MockNetworkUiModel> =
try {
MockNetworkUiModel(
id = editable.id,
expectation = MockNetworkUiModel.Expectation(
urlPattern = editable.expectation.urlPattern!!,
method = editable.expectation.method,
),
isEnabled = editable.isEnabled,
response = MockNetworkUiModel.Response(
httpCode = editable.response.httpCode,
body = editable.response.body!!,
mediaType = editable.response.mediaType,
delay = editable.response.delay,
headers = editable.response.headers.associate {
it.key to it.value
}.filterNot { it.key.isEmpty() },
),
).success()
} catch (t: Throwable) {
t.failure()
}

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.model.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.model
import java.util.UUID

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.model.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.model
import androidx.compose.runtime.Immutable
import kotlin.uuid.ExperimentalUuidApi

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.model.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.model
enum class MockNetworkMethodUi(val text: String) {
GET("GET"),

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.model.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.model
import androidx.compose.runtime.Immutable

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.view.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.view
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.view.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -12,7 +12,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
@Composable
fun MockNetworkMethodDropdown(

View file

@ -1,11 +1,11 @@
package io.github.openflocon.flocondesktop.features.network.view.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.view
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.view.components.NetworkTag
import io.github.openflocon.flocondesktop.features.network.view.components.deleteMethodBackground
import io.github.openflocon.flocondesktop.features.network.view.components.deleteMethodText

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.view.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -39,14 +39,13 @@ import androidx.compose.ui.util.fastForEach
import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow
import io.github.openflocon.flocondesktop.common.ui.window.FloconWindowState
import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState
import io.github.openflocon.flocondesktop.features.network.mapper.createEditable
import io.github.openflocon.flocondesktop.features.network.mapper.editableToUi
import io.github.openflocon.flocondesktop.features.network.model.mocks.HeaderUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.SelectedMockUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.mapper.createEditable
import io.github.openflocon.flocondesktop.features.network.mock.edition.mapper.editableToUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.HeaderUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkUiModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.SelectedMockUiModel
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconSurface
import org.jetbrains.compose.ui.tooling.preview.Preview
import kotlin.collections.plus
@Composable

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.view.mocks
package io.github.openflocon.flocondesktop.features.network.mock.edition.view
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement

View file

@ -0,0 +1,21 @@
package io.github.openflocon.flocondesktop.features.network.mock.list.mapper
import io.github.openflocon.domain.network.models.MockNetworkDomainModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.mock.list.model.MockNetworkLineUiModel
fun MockNetworkDomainModel.toLineUi(): MockNetworkLineUiModel = MockNetworkLineUiModel(
id = id,
isEnabled = isEnabled,
urlPattern = expectation.urlPattern,
method = toMockMethodUi(expectation.method),
)
fun toMockMethodUi(text: String): MockNetworkMethodUi = when (text.lowercase()) {
"get" -> MockNetworkMethodUi.GET
"post" -> MockNetworkMethodUi.POST
"put" -> MockNetworkMethodUi.PUT
"delete" -> MockNetworkMethodUi.DELETE
"patch" -> MockNetworkMethodUi.PATCH
else -> MockNetworkMethodUi.ALL
}

View file

@ -1,6 +1,7 @@
package io.github.openflocon.flocondesktop.features.network.model.mocks
package io.github.openflocon.flocondesktop.features.network.mock.list.model
import androidx.compose.runtime.Immutable
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
@Immutable
data class MockNetworkLineUiModel(

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.view.mocks
package io.github.openflocon.flocondesktop.features.network.mock.list.view
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -20,8 +20,9 @@ import androidx.compose.ui.draw.scale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkLineUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.mock.edition.view.MockNetworkMethodView
import io.github.openflocon.flocondesktop.features.network.mock.list.model.MockNetworkLineUiModel
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconIconButton
import io.github.openflocon.library.designsystem.components.FloconSurface

View file

@ -1,4 +1,4 @@
package io.github.openflocon.flocondesktop.features.network.view.mocks
package io.github.openflocon.flocondesktop.features.network.mock.list.view
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -22,9 +22,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.features.network.NetworkMocksViewModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.MockNetworkLineUiModel
import io.github.openflocon.flocondesktop.features.network.model.mocks.previewMockNetworkLineUiModel
import io.github.openflocon.flocondesktop.features.network.mock.NetworkMocksViewModel
import io.github.openflocon.flocondesktop.features.network.mock.edition.view.NetworkEditionWindow
import io.github.openflocon.flocondesktop.features.network.mock.list.model.MockNetworkLineUiModel
import io.github.openflocon.flocondesktop.features.network.mock.list.model.previewMockNetworkLineUiModel
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconSurface
import org.jetbrains.compose.ui.tooling.preview.Preview

View file

@ -33,10 +33,10 @@ import io.github.openflocon.flocondesktop.features.network.model.NetworkItemView
import io.github.openflocon.flocondesktop.features.network.model.previewGraphQlItemViewState
import io.github.openflocon.flocondesktop.features.network.model.previewNetworkItemViewState
import io.github.openflocon.flocondesktop.features.network.previewNetworkUiState
import io.github.openflocon.flocondesktop.features.network.view.badquality.BadNetworkQualityWindow
import io.github.openflocon.flocondesktop.features.network.badquality.list.BadNetworkQualityWindow
import io.github.openflocon.flocondesktop.features.network.view.header.NetworkFilter
import io.github.openflocon.flocondesktop.features.network.view.header.NetworkItemHeaderView
import io.github.openflocon.flocondesktop.features.network.view.mocks.NetworkMocksWindow
import io.github.openflocon.flocondesktop.features.network.mock.list.view.NetworkMocksWindow
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconPanel
import io.github.openflocon.library.designsystem.components.FloconSurface

View file

@ -2,6 +2,7 @@ package io.github.openflocon.data.core.network.datasource
import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigId
import kotlinx.coroutines.flow.Flow
interface NetworkQualityLocalDataSource {
@ -10,15 +11,32 @@ interface NetworkQualityLocalDataSource {
config: BadQualityConfigDomainModel
)
suspend fun getNetworkQuality(deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel) : BadQualityConfigDomainModel?
suspend fun getNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
) : BadQualityConfigDomainModel?
fun observe(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
): Flow<BadQualityConfigDomainModel?>
suspend fun updateIsEnabled(
suspend fun delete(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
isEnabled: Boolean,
configId: BadQualityConfigId,
)
fun observeAll(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
): Flow<List<BadQualityConfigDomainModel>>
suspend fun setEnabledConfig(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId?,
)
suspend fun getTheOnlyEnabledNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel
): BadQualityConfigDomainModel?
}

View file

@ -10,8 +10,8 @@ import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainMod
import io.github.openflocon.domain.messages.models.FloconIncomingMessageDomainModel
import io.github.openflocon.domain.messages.repository.MessagesReceiverRepository
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigId
import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel
import io.github.openflocon.domain.network.models.FloconNetworkResponseDomainModel
import io.github.openflocon.domain.network.models.FloconNetworkResponseOnlyDomainModel
import io.github.openflocon.domain.network.models.MockNetworkDomainModel
import io.github.openflocon.domain.network.repository.NetworkBadQualityRepository
@ -295,30 +295,62 @@ class NetworkRepositoryImpl(
}
}
override suspend fun getNetworkQuality(deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel): BadQualityConfigDomainModel? {
override suspend fun getNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
): BadQualityConfigDomainModel? {
return withContext(dispatcherProvider.data) {
networkQualityLocalDataSource.getNetworkQuality(
deviceIdAndPackageName = deviceIdAndPackageName,
configId = configId,
)
}
}
override suspend fun getTheOnlyEnabledNetworkQuality(deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel): BadQualityConfigDomainModel? {
return withContext(dispatcherProvider.data) {
networkQualityLocalDataSource.getTheOnlyEnabledNetworkQuality(
deviceIdAndPackageName = deviceIdAndPackageName,
)
}
}
override suspend fun deleteNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
) {
return withContext(dispatcherProvider.data) {
networkQualityLocalDataSource.delete(
deviceIdAndPackageName = deviceIdAndPackageName,
configId = configId,
)
}
}
override fun observeNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
): Flow<BadQualityConfigDomainModel?> {
return networkQualityLocalDataSource.observe(
deviceIdAndPackageName = deviceIdAndPackageName,
)
configId = configId,
).flowOn(dispatcherProvider.data)
}
override suspend fun setNetworkQualityIsEnabled(
override fun observeAllNetworkQualities(deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel): Flow<List<BadQualityConfigDomainModel>> {
return networkQualityLocalDataSource.observeAll(
deviceIdAndPackageName = deviceIdAndPackageName,
).flowOn(dispatcherProvider.data)
}
override suspend fun setEnabledConfig(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
isEnabled: Boolean,
configId: BadQualityConfigId?,
) {
withContext(dispatcherProvider.data) {
networkQualityLocalDataSource.updateIsEnabled(
networkQualityLocalDataSource.setEnabledConfig(
deviceIdAndPackageName = deviceIdAndPackageName,
isEnabled = isEnabled,
configId = configId,
)
}
}

View file

@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.github.openflocon.data.local.network.models.badquality.BadQualityConfigEntity
import io.github.openflocon.domain.device.models.DeviceId
import kotlinx.coroutines.flow.Flow
@Dao
@ -16,24 +17,57 @@ interface NetworkBadQualityConfigDao {
SELECT *
FROM BadQualityConfigEntity
WHERE deviceId = :deviceId AND packageName = :packageName
AND id = :configId
LIMIT 1
""")
suspend fun get(deviceId: String, packageName: String): BadQualityConfigEntity?
suspend fun get(deviceId: String, packageName: String, configId: String): BadQualityConfigEntity?
@Query("""
SELECT *
FROM BadQualityConfigEntity
WHERE deviceId = :deviceId AND packageName = :packageName
AND isEnabled = 1
LIMIT 1
""")
fun observe(deviceId: String, packageName: String): Flow<BadQualityConfigEntity?>
suspend fun getTheOnlyEnabledNetworkQuality(deviceId: DeviceId, packageName: String) : BadQualityConfigEntity?
@Query("""
SELECT *
FROM BadQualityConfigEntity
WHERE deviceId = :deviceId AND packageName = :packageName
AND id = :configId
LIMIT 1
""")
fun observe(deviceId: String, packageName: String, configId: String): Flow<BadQualityConfigEntity?>
@Query("""
SELECT *
FROM BadQualityConfigEntity
WHERE deviceId = :deviceId AND packageName = :packageName
ORDER BY createdAt
""")
fun observeAll(deviceId: String, packageName: String): Flow<List<BadQualityConfigEntity>>
@Query("""
UPDATE BadQualityConfigEntity
SET isEnabled = :isEnabled
SET isEnabled = CASE
WHEN id = :configId THEN 1
ELSE 0
END
WHERE deviceId = :deviceId
AND packageName = :packageName
""")
suspend fun updateIsEnabled(deviceId: String, packageName: String, isEnabled: Boolean)
suspend fun setEnabledConfig(
deviceId: String,
packageName: String,
configId: String?,
)
@Query("""
DELETE FROM BadQualityConfigEntity
WHERE deviceId = :deviceId AND packageName = :packageName
AND id = :configId
"""
)
suspend fun delete(deviceId: DeviceId, packageName: String, configId: String)
}

View file

@ -6,6 +6,7 @@ import io.github.openflocon.data.local.network.mapper.toDomain
import io.github.openflocon.data.local.network.mapper.toEntity
import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@ -29,10 +30,14 @@ class BadQualityConfigLocalDataSourceImpl(
)
}
override suspend fun getNetworkQuality(deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel): BadQualityConfigDomainModel? {
override suspend fun getNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
): BadQualityConfigDomainModel? {
return networkBadQualityConfigDao.get(
deviceId = deviceIdAndPackageName.deviceId,
packageName = deviceIdAndPackageName.packageName
packageName = deviceIdAndPackageName.packageName,
configId = configId
)?.let {
toDomain(
json = json,
@ -42,11 +47,13 @@ class BadQualityConfigLocalDataSourceImpl(
}
override fun observe(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
): Flow<BadQualityConfigDomainModel?> {
return networkBadQualityConfigDao.observe(
deviceId = deviceIdAndPackageName.deviceId,
packageName = deviceIdAndPackageName.packageName
packageName = deviceIdAndPackageName.packageName,
configId = configId,
).map {
it?.let {
toDomain(
@ -57,14 +64,56 @@ class BadQualityConfigLocalDataSourceImpl(
}.distinctUntilChanged()
}
override suspend fun updateIsEnabled(
override fun observeAll(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
isEnabled: Boolean,
) {
networkBadQualityConfigDao.updateIsEnabled(
): Flow<List<BadQualityConfigDomainModel>> {
return networkBadQualityConfigDao.observeAll(
deviceId = deviceIdAndPackageName.deviceId,
packageName = deviceIdAndPackageName.packageName,
isEnabled = isEnabled
).map { list ->
list.map {
toDomain(
json = json,
entity = it
)
}
}.distinctUntilChanged()
}
override suspend fun setEnabledConfig(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId?,
) {
networkBadQualityConfigDao.setEnabledConfig(
deviceId = deviceIdAndPackageName.deviceId,
packageName = deviceIdAndPackageName.packageName,
configId = configId,
)
}
override suspend fun getTheOnlyEnabledNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
): BadQualityConfigDomainModel? {
return networkBadQualityConfigDao.getTheOnlyEnabledNetworkQuality(
deviceId = deviceIdAndPackageName.deviceId,
packageName = deviceIdAndPackageName.packageName,
)?.let {
toDomain(
json = json,
entity = it
)
}
}
override suspend fun delete(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId
) {
networkBadQualityConfigDao.delete(
deviceId = deviceIdAndPackageName.deviceId,
packageName = deviceIdAndPackageName.packageName,
configId = configId
)
}
}

View file

@ -6,6 +6,7 @@ import io.github.openflocon.data.local.network.models.badquality.LatencyConfigEm
import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import kotlinx.serialization.json.Json
import kotlin.time.Instant
fun toDomain(json: Json, entity: BadQualityConfigEntity): BadQualityConfigDomainModel {
val errors = try {
@ -16,6 +17,9 @@ fun toDomain(json: Json, entity: BadQualityConfigEntity): BadQualityConfigDomain
emptyList()
}
return BadQualityConfigDomainModel(
id = entity.id,
name = entity.name,
createdAt = Instant.fromEpochMilliseconds(entity.createdAt),
isEnabled = entity.isEnabled,
latency = BadQualityConfigDomainModel.LatencyConfig(
triggerProbability = entity.latency.triggerProbability,
@ -52,6 +56,9 @@ fun toEntity(
"[]"
}
return BadQualityConfigEntity(
id = config.id,
name = config.name,
createdAt = config.createdAt.toEpochMilliseconds(),
deviceId = deviceIdAndPackageName.deviceId,
packageName = deviceIdAndPackageName.packageName,
isEnabled = config.isEnabled,

View file

@ -2,10 +2,19 @@ package io.github.openflocon.data.local.network.models.badquality
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(primaryKeys = ["deviceId", "packageName"])
@Entity(
indices = [
Index(value = ["deviceId", "packageName"]),
],
)
data class BadQualityConfigEntity(
@PrimaryKey val id: String,
val name: String,
val deviceId: String,
val createdAt: Long,
val packageName: String,
val isEnabled: Boolean,
@Embedded val latency: LatencyConfigEmbedded,

View file

@ -9,10 +9,12 @@ import io.github.openflocon.domain.network.usecase.RemoveHttpRequestUseCase
import io.github.openflocon.domain.network.usecase.RemoveHttpRequestsBeforeUseCase
import io.github.openflocon.domain.network.usecase.ResetCurrentDeviceHttpRequestsUseCase
import io.github.openflocon.domain.network.usecase.UpdateNetworkFilterUseCase
import io.github.openflocon.domain.network.usecase.badquality.DeleteBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.ObserveAllNetworkBadQualitiesUseCase
import io.github.openflocon.domain.network.usecase.badquality.ObserveNetworkBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.SaveNetworkBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.SetupNetworkBadQualityUseCase
import io.github.openflocon.domain.network.usecase.badquality.UpdateNetworkBadQualityIsEnabledUseCase
import io.github.openflocon.domain.network.usecase.badquality.SetNetworkBadQualityEnabledConfigUseCase
import io.github.openflocon.domain.network.usecase.mocks.AddNetworkMocksUseCase
import io.github.openflocon.domain.network.usecase.mocks.DeleteNetworkMocksUseCase
import io.github.openflocon.domain.network.usecase.mocks.GenerateNetworkMockFromNetworkCallUseCase
@ -45,6 +47,8 @@ internal val networkModule = module {
// bad quality
factoryOf(::ObserveNetworkBadQualityUseCase)
factoryOf(::SaveNetworkBadQualityUseCase)
factoryOf(::DeleteBadQualityUseCase)
factoryOf(::SetupNetworkBadQualityUseCase)
factoryOf(::UpdateNetworkBadQualityIsEnabledUseCase)
factoryOf(::SetNetworkBadQualityEnabledConfigUseCase)
factoryOf(::ObserveAllNetworkBadQualitiesUseCase)
}

View file

@ -1,7 +1,14 @@
package io.github.openflocon.domain.network.models
import kotlin.time.Instant
typealias BadQualityConfigId = String
data class BadQualityConfigDomainModel(
val id: BadQualityConfigId,
val isEnabled: Boolean,
val name: String,
val createdAt: Instant,
val latency: LatencyConfig,
val errorProbability: Double, // chance of triggering an error
val errors: List<Error>, // list of errors

View file

@ -2,6 +2,7 @@ package io.github.openflocon.domain.network.repository
import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigId
import kotlinx.coroutines.flow.Flow
interface NetworkBadQualityRepository {
@ -19,14 +20,29 @@ interface NetworkBadQualityRepository {
suspend fun getNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
) : BadQualityConfigDomainModel?
suspend fun getTheOnlyEnabledNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
) : BadQualityConfigDomainModel?
suspend fun deleteNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
)
fun observeNetworkQuality(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId,
) : Flow<BadQualityConfigDomainModel?>
suspend fun setNetworkQualityIsEnabled(
fun observeAllNetworkQualities(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
isEnabled: Boolean,
) : Flow<List<BadQualityConfigDomainModel>>
suspend fun setEnabledConfig(
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel,
configId: BadQualityConfigId?,
)
}

View file

@ -2,18 +2,19 @@ package io.github.openflocon.domain.network.usecase.badquality
import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigId
import io.github.openflocon.domain.network.repository.NetworkBadQualityRepository
import io.github.openflocon.domain.network.repository.NetworkMocksRepository
class UpdateNetworkBadQualityIsEnabledUseCase(
class DeleteBadQualityUseCase(
private val getCurrentDeviceIdAndPackageNameUseCase: GetCurrentDeviceIdAndPackageNameUseCase,
private val networkBadQualityRepository: NetworkBadQualityRepository,
private val setupNetworkBadQualityUseCase: SetupNetworkBadQualityUseCase,
) {
suspend operator fun invoke(isEnabled: Boolean) {
suspend operator fun invoke(configId: BadQualityConfigId) {
getCurrentDeviceIdAndPackageNameUseCase()?.let { deviceIdAndPackageName ->
networkBadQualityRepository.setNetworkQualityIsEnabled(
isEnabled = isEnabled,
networkBadQualityRepository.deleteNetworkQuality(
configId = configId,
deviceIdAndPackageName = deviceIdAndPackageName,
)
// then send to device

View file

@ -0,0 +1,27 @@
package io.github.openflocon.domain.network.usecase.badquality
import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdAndPackageNameUseCase
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.domain.network.repository.NetworkBadQualityRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
class ObserveAllNetworkBadQualitiesUseCase(
private val networkBadQualityRepository: NetworkBadQualityRepository,
private val observeCurrentDeviceIdAndPackageNameUseCase: ObserveCurrentDeviceIdAndPackageNameUseCase,
) {
operator fun invoke(): Flow<List<BadQualityConfigDomainModel>> =
observeCurrentDeviceIdAndPackageNameUseCase()
.flatMapLatest { current ->
if (current == null) {
flowOf(emptyList())
} else {
networkBadQualityRepository.observeAllNetworkQualities(
deviceIdAndPackageName = current,
)
}
}
.distinctUntilChanged()
}

View file

@ -2,6 +2,7 @@ package io.github.openflocon.domain.network.usecase.badquality
import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdAndPackageNameUseCase
import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel
import io.github.openflocon.domain.network.models.BadQualityConfigId
import io.github.openflocon.domain.network.models.MockNetworkDomainModel
import io.github.openflocon.domain.network.repository.NetworkBadQualityRepository
import io.github.openflocon.domain.network.repository.NetworkMocksRepository
@ -14,13 +15,18 @@ class ObserveNetworkBadQualityUseCase(
private val networkBadQualityRepository: NetworkBadQualityRepository,
private val observeCurrentDeviceIdAndPackageNameUseCase: ObserveCurrentDeviceIdAndPackageNameUseCase,
) {
operator fun invoke(): Flow<BadQualityConfigDomainModel?> =
operator fun invoke(
configId: BadQualityConfigId,
): Flow<BadQualityConfigDomainModel?> =
observeCurrentDeviceIdAndPackageNameUseCase()
.flatMapLatest { current ->
if (current == null) {
flowOf(null)
} else {
networkBadQualityRepository.observeNetworkQuality(deviceIdAndPackageName = current)
networkBadQualityRepository.observeNetworkQuality(
deviceIdAndPackageName = current,
configId = configId,
)
}
}
.distinctUntilChanged()

View file

@ -0,0 +1,24 @@
package io.github.openflocon.domain.network.usecase.badquality
import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase
import io.github.openflocon.domain.network.models.BadQualityConfigId
import io.github.openflocon.domain.network.repository.NetworkBadQualityRepository
class SetNetworkBadQualityEnabledConfigUseCase(
private val getCurrentDeviceIdAndPackageNameUseCase: GetCurrentDeviceIdAndPackageNameUseCase,
private val networkBadQualityRepository: NetworkBadQualityRepository,
private val setupNetworkBadQualityUseCase: SetupNetworkBadQualityUseCase,
) {
suspend operator fun invoke(
configId: BadQualityConfigId?, // null to enable none
) {
getCurrentDeviceIdAndPackageNameUseCase()?.let { deviceIdAndPackageName ->
networkBadQualityRepository.setEnabledConfig(
configId = configId,
deviceIdAndPackageName = deviceIdAndPackageName,
)
// then send to device
setupNetworkBadQualityUseCase()
}
}
}

View file

@ -11,7 +11,9 @@ class SetupNetworkBadQualityUseCase(
suspend operator fun invoke() {
getCurrentDeviceIdAndPackageNameUseCase()?.let { deviceIdAndPackageName ->
networkBadQualityRepository.setupBadNetworkQuality(
config = networkBadQualityRepository.getNetworkQuality(deviceIdAndPackageName),
config = networkBadQualityRepository.getTheOnlyEnabledNetworkQuality(
deviceIdAndPackageName = deviceIdAndPackageName,
),
deviceIdAndPackageName = deviceIdAndPackageName,
)
}