mirror of
https://github.com/openflocon/Flocon.git
synced 2026-05-19 18:36:26 +00:00
refact: [NETWORK] multiple network configs (#119)
Co-authored-by: Florent Champigny <florent@bere.al>
This commit is contained in:
parent
4b0b565705
commit
fa1df1fe3c
46 changed files with 940 additions and 368 deletions
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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"),
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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(
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue