mirror of
https://github.com/openflocon/Flocon.git
synced 2026-05-06 05:18:41 +00:00
Feat grpc into network (#21)
* feat: [NETWORK] merge with grpc * formatted * formatted * formatted --------- Co-authored-by: Florent Champigny <florent@bere.al>
This commit is contained in:
parent
705668c15b
commit
66920edcd4
78 changed files with 404 additions and 2157 deletions
|
|
@ -1,4 +1,3 @@
|
|||
import com.android.build.gradle.ProguardFiles.getDefaultProguardFile
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ import io.github.openflocon.flocondesktop.features.deeplinks.data.datasource.roo
|
|||
import io.github.openflocon.flocondesktop.features.deeplinks.data.datasource.room.model.DeeplinkEntity
|
||||
import io.github.openflocon.flocondesktop.features.files.data.datasources.FloconFileDao
|
||||
import io.github.openflocon.flocondesktop.features.files.data.datasources.model.FileEntity
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.GrpcDao
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcCallEntity
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcResponseEntity
|
||||
import io.github.openflocon.flocondesktop.features.images.data.datasources.local.FloconImageDao
|
||||
import io.github.openflocon.flocondesktop.features.images.data.datasources.local.model.DeviceImageEntity
|
||||
import io.github.openflocon.flocondesktop.features.network.data.datasource.local.FloconHttpRequestDao
|
||||
|
|
@ -31,7 +28,7 @@ import io.github.openflocon.flocondesktop.features.table.data.datasource.local.m
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@Database(
|
||||
version = 24,
|
||||
version = 27,
|
||||
entities = [
|
||||
FloconHttpRequestEntity::class,
|
||||
FileEntity::class,
|
||||
|
|
@ -43,8 +40,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
DeviceImageEntity::class,
|
||||
SuccessQueryEntity::class,
|
||||
DeeplinkEntity::class,
|
||||
GrpcCallEntity::class,
|
||||
GrpcResponseEntity::class,
|
||||
AnalyticsItemEntity::class,
|
||||
],
|
||||
)
|
||||
|
|
@ -60,7 +55,6 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
abstract val imageDao: FloconImageDao
|
||||
abstract val queryDao: QueryDao
|
||||
abstract val deeplinkDao: FloconDeeplinkDao
|
||||
abstract val grpcDao: GrpcDao
|
||||
abstract val analyticsDao: FloconAnalyticsDao
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ val roomModule =
|
|||
single {
|
||||
get<AppDatabase>().deeplinkDao
|
||||
}
|
||||
single {
|
||||
get<AppDatabase>().grpcDao
|
||||
}
|
||||
single {
|
||||
get<AppDatabase>().analyticsDao
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import io.github.openflocon.flocondesktop.features.dashboard.di.dashboardModule
|
|||
import io.github.openflocon.flocondesktop.features.database.di.databaseModule
|
||||
import io.github.openflocon.flocondesktop.features.deeplinks.di.deeplinkModule
|
||||
import io.github.openflocon.flocondesktop.features.files.di.filesModule
|
||||
import io.github.openflocon.flocondesktop.features.grpc.di.grpcModule
|
||||
import io.github.openflocon.flocondesktop.features.images.di.imagesModule
|
||||
import io.github.openflocon.flocondesktop.features.network.di.networkModule
|
||||
import io.github.openflocon.flocondesktop.features.sharedpreferences.di.sharedPreferencesModule
|
||||
|
|
@ -19,7 +18,6 @@ val featuresModule =
|
|||
analyticsModule,
|
||||
databaseModule,
|
||||
filesModule,
|
||||
grpcModule,
|
||||
imagesModule,
|
||||
messagesModule,
|
||||
networkModule,
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data
|
||||
|
||||
import io.github.openflocon.flocondesktop.DeviceId
|
||||
import io.github.openflocon.flocondesktop.FloconIncomingMessageDataModel
|
||||
import io.github.openflocon.flocondesktop.Protocol
|
||||
import io.github.openflocon.flocondesktop.common.coroutines.dispatcherprovider.DispatcherProvider
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.LocalGrpcDataSource
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice.GrpcRequestDataModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice.GrpcResponseDataModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.repository.GRPCRepository
|
||||
import io.github.openflocon.flocondesktop.messages.domain.repository.sub.MessagesReceiverRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class GRPCRepositoryImpl(
|
||||
private val dispatcherProvider: DispatcherProvider,
|
||||
private val localGrpcDataSource: LocalGrpcDataSource,
|
||||
) : GRPCRepository,
|
||||
MessagesReceiverRepository {
|
||||
|
||||
private val grpcParser =
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
override val pluginName = listOf(Protocol.FromDevice.GRPC.Plugin)
|
||||
|
||||
override suspend fun onMessageReceived(
|
||||
deviceId: String,
|
||||
message: FloconIncomingMessageDataModel,
|
||||
) {
|
||||
withContext(dispatcherProvider.data) {
|
||||
when (message.method) {
|
||||
Protocol.FromDevice.GRPC.Method.LogNetworkRequest -> decodeGrpcRequest(message)?.let {
|
||||
toDomain(it)
|
||||
}?.let {
|
||||
localGrpcDataSource.saveRequest(
|
||||
deviceId = deviceId,
|
||||
callId = it.callId,
|
||||
request = it.request,
|
||||
)
|
||||
}
|
||||
|
||||
Protocol.FromDevice.GRPC.Method.LogNetworkResponse -> decodeGrpcResponse(message)?.let {
|
||||
toDomain(
|
||||
it,
|
||||
)
|
||||
}?.let {
|
||||
localGrpcDataSource.saveResponse(
|
||||
deviceId = deviceId,
|
||||
callId = it.callId,
|
||||
response = it.response,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeGrpcRequest(message: FloconIncomingMessageDataModel): GrpcRequestDataModel? = try {
|
||||
grpcParser.decodeFromString<GrpcRequestDataModel>(message.body)
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
null
|
||||
}
|
||||
|
||||
private fun decodeGrpcResponse(message: FloconIncomingMessageDataModel): GrpcResponseDataModel? = try {
|
||||
grpcParser.decodeFromString<GrpcResponseDataModel>(message.body)
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
null
|
||||
}
|
||||
|
||||
override fun observeCalls(deviceId: DeviceId): Flow<List<GrpcCallDomainModel>> = localGrpcDataSource.observeCalls(
|
||||
deviceId = deviceId,
|
||||
).flowOn(dispatcherProvider.data)
|
||||
|
||||
override fun observeCall(
|
||||
currentDeviceId: DeviceId,
|
||||
callId: GrpcCallId,
|
||||
): Flow<GrpcCallDomainModel?> = localGrpcDataSource.observeCall(
|
||||
deviceId = currentDeviceId,
|
||||
callId = callId,
|
||||
).flowOn(dispatcherProvider.data)
|
||||
|
||||
override suspend fun deleteCall(deviceId: DeviceId, callId: GrpcCallId) = withContext(dispatcherProvider.data) {
|
||||
localGrpcDataSource.deleteCall(deviceId = deviceId, callId = callId)
|
||||
}
|
||||
override suspend fun deleteCallsBefore(deviceId: DeviceId, callId: GrpcCallId) = withContext(dispatcherProvider.data) {
|
||||
localGrpcDataSource.deleteCallsBefore(deviceId = deviceId, callId = callId)
|
||||
}
|
||||
|
||||
override suspend fun deleteCallsForDevice(deviceId: DeviceId) = withContext(dispatcherProvider.data) {
|
||||
localGrpcDataSource.clearDeviceCalls(deviceId = deviceId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.datasource
|
||||
|
||||
import io.github.openflocon.flocondesktop.DeviceId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcResponseDomainModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface LocalGrpcDataSource {
|
||||
suspend fun saveRequest(deviceId: DeviceId, callId: GrpcCallId, request: GrpcRequestDomainModel)
|
||||
suspend fun saveResponse(deviceId: DeviceId, callId: GrpcCallId, response: GrpcResponseDomainModel)
|
||||
fun observeCalls(deviceId: DeviceId): Flow<List<GrpcCallDomainModel>>
|
||||
fun observeCall(deviceId: DeviceId, callId: GrpcCallId): Flow<GrpcCallDomainModel?>
|
||||
suspend fun clearDeviceCalls(deviceId: DeviceId)
|
||||
|
||||
suspend fun deleteCall(deviceId: DeviceId, callId: GrpcCallId)
|
||||
suspend fun deleteCallsBefore(deviceId: DeviceId, callId: GrpcCallId)
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.datasource.room
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcCallEntity
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcCallWithDetails
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcResponseEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface GrpcDao {
|
||||
@Insert
|
||||
suspend fun insertGrpcCall(call: GrpcCallEntity)
|
||||
|
||||
@Insert
|
||||
suspend fun insertGrpcResponse(response: GrpcResponseEntity)
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM GrpcCallEntity
|
||||
WHERE deviceId = :deviceId
|
||||
ORDER BY timestamp ASC
|
||||
""",
|
||||
)
|
||||
fun observeCallsWithDetails(deviceId: String): Flow<List<GrpcCallWithDetails>>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM GrpcCallEntity
|
||||
WHERE deviceId = :deviceId
|
||||
AND callId = :callId
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
fun observeCallWithDetails(deviceId: String, callId: String): Flow<GrpcCallWithDetails?>
|
||||
|
||||
@Query("DELETE FROM GrpcCallEntity WHERE deviceId = :deviceId")
|
||||
suspend fun clearDeviceRequests(deviceId: String)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM GrpcResponseEntity
|
||||
WHERE response_call_id IN (
|
||||
SELECT callId
|
||||
FROM GrpcCallEntity
|
||||
WHERE deviceId = :deviceId
|
||||
)
|
||||
""",
|
||||
)
|
||||
suspend fun clearDeviceResponses(deviceId: String)
|
||||
|
||||
@Transaction
|
||||
suspend fun clearDeviceData(deviceId: String) {
|
||||
clearDeviceResponses(deviceId)
|
||||
clearDeviceRequests(deviceId)
|
||||
}
|
||||
|
||||
@Query("DELETE FROM GrpcCallEntity WHERE callId = :requestId")
|
||||
suspend fun deleteRequestById(requestId: String)
|
||||
|
||||
@Query("DELETE FROM GrpcResponseEntity WHERE response_call_id = :requestId")
|
||||
suspend fun deleteResponseById(requestId: String)
|
||||
|
||||
@Transaction
|
||||
suspend fun deleteCallById(requestId: String) {
|
||||
deleteResponseById(requestId)
|
||||
deleteRequestById(requestId)
|
||||
}
|
||||
|
||||
@Transaction
|
||||
suspend fun deleteCallsBeforeTimestamp(deviceId: String, timestamp: Long) {
|
||||
// First, get call IDs of requests to be deleted
|
||||
val callIdsToDelete = getCallIdsBeforeTimestamp(deviceId, timestamp)
|
||||
|
||||
// Delete associated headers and responses
|
||||
for (callId in callIdsToDelete) {
|
||||
deleteCallById(callId)
|
||||
}
|
||||
}
|
||||
|
||||
@Query("SELECT callId FROM GrpcCallEntity WHERE deviceId = :deviceId AND timestamp < :timestamp")
|
||||
suspend fun getCallIdsBeforeTimestamp(deviceId: String, timestamp: Long): List<String>
|
||||
|
||||
@Query("DELETE FROM GrpcCallEntity")
|
||||
suspend fun deleteAllRequests()
|
||||
|
||||
@Query("DELETE FROM GrpcResponseEntity")
|
||||
suspend fun deleteAllResponses()
|
||||
|
||||
@Transaction
|
||||
suspend fun clearAllData() {
|
||||
deleteAllResponses()
|
||||
deleteAllRequests()
|
||||
}
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT timestamp
|
||||
FROM GrpcCallEntity
|
||||
WHERE deviceId = :deviceId
|
||||
AND callId = :callId
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun getCallTimestamp(deviceId: String, callId: String): Long?
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.datasource.room
|
||||
|
||||
import io.github.openflocon.flocondesktop.DeviceId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.LocalGrpcDataSource
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcCallEntity
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcResponseEntity
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcResponseDomainModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class LocalGrpcDataSourceImpl(
|
||||
private val grpcDao: GrpcDao,
|
||||
) : LocalGrpcDataSource {
|
||||
|
||||
override suspend fun saveRequest(
|
||||
deviceId: String,
|
||||
callId: String,
|
||||
request: GrpcRequestDomainModel,
|
||||
) {
|
||||
val headersMap = request.headers.associate { it.key to it.value }
|
||||
val requestEntity = GrpcCallEntity(
|
||||
callId = callId,
|
||||
deviceId = deviceId,
|
||||
request = GrpcCallEntity.Request(
|
||||
timestamp = request.timestamp,
|
||||
authority = request.authority,
|
||||
method = request.method,
|
||||
data = request.data,
|
||||
headers = headersMap,
|
||||
),
|
||||
)
|
||||
grpcDao.insertGrpcCall(requestEntity)
|
||||
}
|
||||
|
||||
override suspend fun saveResponse(
|
||||
deviceId: String,
|
||||
callId: String,
|
||||
response: GrpcResponseDomainModel,
|
||||
) {
|
||||
val headersMap = response.headers.associate { it.key to it.value }
|
||||
val responseEntity = GrpcResponseEntity(
|
||||
callId = callId,
|
||||
responseTimestamp = response.timestamp,
|
||||
status = response.status,
|
||||
resultType = when (response.result) {
|
||||
is GrpcResponseDomainModel.CallResult.Success -> "success"
|
||||
is GrpcResponseDomainModel.CallResult.Error -> "error"
|
||||
},
|
||||
resultData = when (response.result) {
|
||||
is GrpcResponseDomainModel.CallResult.Success -> response.result.data
|
||||
is GrpcResponseDomainModel.CallResult.Error -> response.result.cause
|
||||
},
|
||||
headers = headersMap,
|
||||
)
|
||||
grpcDao.insertGrpcResponse(responseEntity)
|
||||
}
|
||||
|
||||
override fun observeCalls(deviceId: String): Flow<List<GrpcCallDomainModel>> = grpcDao.observeCallsWithDetails(deviceId).map { entities ->
|
||||
entities.map { it.toDomainModel() }
|
||||
}
|
||||
|
||||
override fun observeCall(deviceId: DeviceId, callId: GrpcCallId) = grpcDao.observeCallWithDetails(deviceId, callId = callId).map {
|
||||
it?.toDomainModel()
|
||||
}
|
||||
|
||||
override suspend fun clearDeviceCalls(deviceId: String) {
|
||||
grpcDao.clearDeviceData(deviceId)
|
||||
}
|
||||
|
||||
override suspend fun deleteCall(deviceId: String, callId: GrpcCallId) {
|
||||
grpcDao.deleteCallById(callId)
|
||||
}
|
||||
|
||||
override suspend fun deleteCallsBefore(deviceId: String, callId: GrpcCallId) {
|
||||
val timestamp = grpcDao.getCallTimestamp(deviceId = deviceId, callId = callId) ?: return
|
||||
grpcDao.deleteCallsBeforeTimestamp(
|
||||
deviceId = deviceId,
|
||||
timestamp = timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
grpcDao.clearAllData()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.datasource.room
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model.GrpcCallWithDetails
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcHeaderDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcResponseDomainModel
|
||||
|
||||
fun GrpcCallWithDetails.toDomainModel(): GrpcCallDomainModel {
|
||||
val requestDomain = with(call.request) {
|
||||
GrpcRequestDomainModel(
|
||||
timestamp = timestamp,
|
||||
authority = authority,
|
||||
method = method,
|
||||
headers = headers
|
||||
.map { GrpcHeaderDomainModel(key = it.key, value = it.value) },
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
|
||||
val responseDomain = response?.let {
|
||||
val callResult = when (it.resultType) {
|
||||
"success" -> GrpcResponseDomainModel.CallResult.Success(it.resultData ?: "")
|
||||
"error" -> GrpcResponseDomainModel.CallResult.Error(
|
||||
it.resultData ?: "Unknown error",
|
||||
)
|
||||
|
||||
else -> error("Unknown result type")
|
||||
}
|
||||
GrpcResponseDomainModel(
|
||||
timestamp = it.responseTimestamp,
|
||||
status = it.status,
|
||||
headers = response.headers
|
||||
.map { header ->
|
||||
GrpcHeaderDomainModel(
|
||||
key = header.key,
|
||||
value = header.value,
|
||||
)
|
||||
},
|
||||
result = callResult,
|
||||
)
|
||||
}
|
||||
|
||||
return GrpcCallDomainModel(
|
||||
id = call.callId,
|
||||
request = requestDomain,
|
||||
response = responseDomain,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
indices = [
|
||||
Index(value = ["deviceId"]),
|
||||
Index(value = ["callId"], unique = true),
|
||||
],
|
||||
)
|
||||
data class GrpcCallEntity(
|
||||
@PrimaryKey val callId: String, // GrpcCallId will be the primary key here
|
||||
val deviceId: String,
|
||||
@Embedded val request: GrpcCallEntity.Request,
|
||||
) {
|
||||
data class Request(
|
||||
val timestamp: Long,
|
||||
val authority: String,
|
||||
val method: String,
|
||||
val data: String?,
|
||||
val headers: Map<String, String>,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
|
||||
data class GrpcCallWithDetails(
|
||||
@Embedded val call: GrpcCallEntity,
|
||||
@Relation(
|
||||
parentColumn = "callId",
|
||||
entityColumn = "response_call_id",
|
||||
)
|
||||
val response: GrpcResponseEntity?, // Response can be null if not yet received
|
||||
)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity
|
||||
data class GrpcResponseEntity(
|
||||
@PrimaryKey @ColumnInfo(name = "response_call_id") val callId: String, // Linked to GrpcCallEntity's callId
|
||||
val responseTimestamp: Long,
|
||||
val status: String,
|
||||
val resultType: String, // "success" or "error"
|
||||
val resultData: String?, // Data for success, cause for error
|
||||
val headers: Map<String, String>,
|
||||
)
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.di
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.GRPCRepositoryImpl
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.LocalGrpcDataSource
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.datasource.room.LocalGrpcDataSourceImpl
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.repository.GRPCRepository
|
||||
import io.github.openflocon.flocondesktop.messages.domain.repository.sub.MessagesReceiverRepository
|
||||
import org.koin.core.module.dsl.bind
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val grpcDataModule =
|
||||
module {
|
||||
factoryOf(::GRPCRepositoryImpl) {
|
||||
bind<GRPCRepository>()
|
||||
bind<MessagesReceiverRepository>()
|
||||
}
|
||||
singleOf(::LocalGrpcDataSourceImpl) {
|
||||
bind<LocalGrpcDataSource>()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.model.GrpcRequestDomainModelWrapper
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.model.GrpcResponseDomainModelWrapper
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice.GrpcHeaderDataModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice.GrpcRequestDataModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice.GrpcResponseDataModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcHeaderDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcResponseDomainModel
|
||||
|
||||
fun toDomain(dataModel: GrpcRequestDataModel): GrpcRequestDomainModelWrapper? = GrpcRequestDomainModelWrapper(
|
||||
callId = dataModel.id,
|
||||
request = GrpcRequestDomainModel(
|
||||
timestamp = dataModel.timestamp,
|
||||
authority = dataModel.authority,
|
||||
method = dataModel.method,
|
||||
headers = dataModel.headers.map {
|
||||
toDomain(it)
|
||||
},
|
||||
data = dataModel.data,
|
||||
),
|
||||
)
|
||||
|
||||
fun toDomain(dataModel: GrpcResponseDataModel): GrpcResponseDomainModelWrapper? {
|
||||
return GrpcResponseDomainModelWrapper(
|
||||
callId = dataModel.id,
|
||||
response = GrpcResponseDomainModel(
|
||||
timestamp = dataModel.timestamp,
|
||||
status = dataModel.status,
|
||||
headers = dataModel.headers.map {
|
||||
toDomain(it)
|
||||
},
|
||||
result = when {
|
||||
dataModel.data != null -> GrpcResponseDomainModel.CallResult.Success(
|
||||
data = dataModel.data,
|
||||
)
|
||||
dataModel.cause != null -> GrpcResponseDomainModel.CallResult.Error(
|
||||
cause = dataModel.cause,
|
||||
)
|
||||
else -> return null
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun toDomain(model: GrpcHeaderDataModel): GrpcHeaderDomainModel = GrpcHeaderDomainModel(
|
||||
key = model.key,
|
||||
value = model.value,
|
||||
)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.model
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcRequestDomainModel
|
||||
|
||||
data class GrpcRequestDomainModelWrapper(
|
||||
val callId: GrpcCallId,
|
||||
val request: GrpcRequestDomainModel,
|
||||
)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.model
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcResponseDomainModel
|
||||
|
||||
data class GrpcResponseDomainModelWrapper(
|
||||
val callId: GrpcCallId,
|
||||
val response: GrpcResponseDomainModel,
|
||||
)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GrpcHeaderDataModel(
|
||||
val key: String,
|
||||
val value: String,
|
||||
)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GrpcRequestDataModel(
|
||||
val id: String,
|
||||
val timestamp: Long,
|
||||
val authority: String,
|
||||
val method: String,
|
||||
val headers: List<GrpcHeaderDataModel>,
|
||||
val data: String? = null,
|
||||
)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.data.model.fromdevice
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GrpcResponseDataModel(
|
||||
val id: String,
|
||||
val timestamp: Long,
|
||||
val status: String,
|
||||
val cause: String? = null,
|
||||
val headers: List<GrpcHeaderDataModel>,
|
||||
val data: String? = null,
|
||||
)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.di
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.data.di.grpcDataModule
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.di.grpcDomainModule
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.di.grpcUiModule
|
||||
import org.koin.dsl.module
|
||||
|
||||
val grpcModule =
|
||||
module {
|
||||
includes(
|
||||
grpcDataModule,
|
||||
grpcDomainModule,
|
||||
grpcUiModule,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain
|
||||
|
||||
import io.github.openflocon.flocondesktop.core.domain.device.GetCurrentDeviceIdUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.repository.GRPCRepository
|
||||
|
||||
class DeleteGrpcCallBeforeUseCase(
|
||||
private val grpcRepository: GRPCRepository,
|
||||
private val getCurrentDeviceIdUseCase: GetCurrentDeviceIdUseCase,
|
||||
) {
|
||||
suspend operator fun invoke(callId: GrpcCallId) {
|
||||
val deviceId = getCurrentDeviceIdUseCase() ?: return
|
||||
grpcRepository.deleteCallsBefore(deviceId = deviceId, callId = callId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain
|
||||
|
||||
import io.github.openflocon.flocondesktop.core.domain.device.GetCurrentDeviceIdUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.repository.GRPCRepository
|
||||
|
||||
class DeleteGrpcCallUseCase(
|
||||
private val grpcRepository: GRPCRepository,
|
||||
private val getCurrentDeviceIdUseCase: GetCurrentDeviceIdUseCase,
|
||||
) {
|
||||
suspend operator fun invoke(callId: GrpcCallId) {
|
||||
val deviceId = getCurrentDeviceIdUseCase() ?: return
|
||||
grpcRepository.deleteCall(deviceId, callId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain
|
||||
|
||||
import io.github.openflocon.flocondesktop.core.domain.device.ObserveCurrentDeviceIdUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.repository.GRPCRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
class ObserveGrpcCallByIdUseCase(
|
||||
private val grpcRepository: GRPCRepository,
|
||||
private val observeCurrentDeviceIdUseCase: ObserveCurrentDeviceIdUseCase,
|
||||
) {
|
||||
operator fun invoke(callId: GrpcCallId): Flow<GrpcCallDomainModel?> = observeCurrentDeviceIdUseCase().flatMapLatest { currentDeviceId ->
|
||||
if (currentDeviceId == null) {
|
||||
flowOf(null)
|
||||
} else {
|
||||
grpcRepository.observeCall(currentDeviceId, callId = callId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain
|
||||
|
||||
import io.github.openflocon.flocondesktop.core.domain.device.ObserveCurrentDeviceIdUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.repository.GRPCRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
class ObserveGrpcCallsUseCase(
|
||||
private val grpcRepository: GRPCRepository,
|
||||
private val observeCurrentDeviceIdUseCase: ObserveCurrentDeviceIdUseCase,
|
||||
) {
|
||||
operator fun invoke(): Flow<List<GrpcCallDomainModel>> = observeCurrentDeviceIdUseCase().flatMapLatest { currentDeviceId ->
|
||||
if (currentDeviceId == null) {
|
||||
flowOf(emptyList())
|
||||
} else {
|
||||
grpcRepository.observeCalls(currentDeviceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain
|
||||
|
||||
import io.github.openflocon.flocondesktop.core.domain.device.GetCurrentDeviceIdUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.repository.GRPCRepository
|
||||
|
||||
class ResetCurrentDeviceGrpcCallsUseCase(
|
||||
private val grpcRepository: GRPCRepository,
|
||||
private val getCurrentDeviceIdUseCase: GetCurrentDeviceIdUseCase,
|
||||
) {
|
||||
suspend operator fun invoke() {
|
||||
val deviceId = getCurrentDeviceIdUseCase() ?: return
|
||||
grpcRepository.deleteCallsForDevice(deviceId = deviceId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain.di
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.DeleteGrpcCallBeforeUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.DeleteGrpcCallUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.ObserveGrpcCallByIdUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.ObserveGrpcCallsUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.ResetCurrentDeviceGrpcCallsUseCase
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val grpcDomainModule =
|
||||
module {
|
||||
factoryOf(::ObserveGrpcCallsUseCase)
|
||||
factoryOf(::ObserveGrpcCallByIdUseCase)
|
||||
factoryOf(::DeleteGrpcCallUseCase)
|
||||
factoryOf(::DeleteGrpcCallBeforeUseCase)
|
||||
factoryOf(::ResetCurrentDeviceGrpcCallsUseCase)
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain.model
|
||||
|
||||
data class GrpcCallDomainModel(
|
||||
val id: String,
|
||||
val request: GrpcRequestDomainModel,
|
||||
val response: GrpcResponseDomainModel?,
|
||||
)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain.model
|
||||
|
||||
typealias GrpcCallId = String
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain.model
|
||||
|
||||
data class GrpcHeaderDomainModel(
|
||||
val key: String,
|
||||
val value: String,
|
||||
)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain.model
|
||||
|
||||
data class GrpcRequestDomainModel(
|
||||
val timestamp: Long,
|
||||
val authority: String,
|
||||
val method: String,
|
||||
val headers: List<GrpcHeaderDomainModel>,
|
||||
val data: String?,
|
||||
)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain.model
|
||||
|
||||
data class GrpcResponseDomainModel(
|
||||
val timestamp: Long,
|
||||
val status: String,
|
||||
val headers: List<GrpcHeaderDomainModel>,
|
||||
val result: CallResult,
|
||||
) {
|
||||
sealed interface CallResult {
|
||||
data class Success(
|
||||
val data: String,
|
||||
) : CallResult
|
||||
|
||||
data class Error(
|
||||
val cause: String,
|
||||
) : CallResult
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.domain.repository
|
||||
|
||||
import io.github.openflocon.flocondesktop.DeviceId
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface GRPCRepository {
|
||||
fun observeCalls(deviceId: DeviceId): Flow<List<GrpcCallDomainModel>>
|
||||
fun observeCall(currentDeviceId: DeviceId, callId: GrpcCallId): Flow<GrpcCallDomainModel?>
|
||||
|
||||
suspend fun deleteCall(deviceId: DeviceId, callId: GrpcCallId)
|
||||
suspend fun deleteCallsBefore(deviceId: DeviceId, callId: GrpcCallId)
|
||||
suspend fun deleteCallsForDevice(deviceId: DeviceId)
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.github.openflocon.flocondesktop.common.coroutines.dispatcherprovider.DispatcherProvider
|
||||
import io.github.openflocon.flocondesktop.common.ui.feedback.FeedbackDisplayer
|
||||
import io.github.openflocon.flocondesktop.copyToClipboard
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.DeleteGrpcCallBeforeUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.DeleteGrpcCallUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.ObserveGrpcCallByIdUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.ObserveGrpcCallsUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.ResetCurrentDeviceGrpcCallsUseCase
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.mapper.toDetailUi
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.mapper.toUi
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcDetailViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.OnGrpcItemUserAction
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
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.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GRPCViewModel(
|
||||
private val dispatcherProvider: DispatcherProvider,
|
||||
private val feedbackDisplayer: FeedbackDisplayer,
|
||||
observeGrpcCallsUseCase: ObserveGrpcCallsUseCase,
|
||||
private val observeGrpcCallByIdUseCase: ObserveGrpcCallByIdUseCase,
|
||||
private val deleteGrpcCallBeforeUseCase: DeleteGrpcCallBeforeUseCase,
|
||||
private val deleteGrpcCallUseCase: DeleteGrpcCallUseCase,
|
||||
private val resetCurrentDeviceGrpcCallsUseCase: ResetCurrentDeviceGrpcCallsUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<List<GrpcItemViewState>> =
|
||||
observeGrpcCallsUseCase()
|
||||
.map { list -> list.map { toUi(it) } }
|
||||
.flowOn(dispatcherProvider.viewModel)
|
||||
.stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(5_000), emptyList())
|
||||
|
||||
private val clickedCallId = MutableStateFlow<String?>(null)
|
||||
|
||||
val detailState: StateFlow<GrpcDetailViewState?> =
|
||||
clickedCallId
|
||||
.flatMapLatest { id ->
|
||||
if (id == null) {
|
||||
flowOf(null)
|
||||
} else {
|
||||
observeGrpcCallByIdUseCase(id)
|
||||
.distinctUntilChanged()
|
||||
.map {
|
||||
it?.let {
|
||||
toDetailUi(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatcherProvider.viewModel)
|
||||
.stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(5_000), null)
|
||||
|
||||
fun onGrpcItemUserAction(action: OnGrpcItemUserAction) {
|
||||
viewModelScope.launch {
|
||||
when (action) {
|
||||
is OnGrpcItemUserAction.OnClicked -> {
|
||||
clickedCallId.update {
|
||||
if (it == action.item.callId) {
|
||||
null
|
||||
} else {
|
||||
action.item.callId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnGrpcItemUserAction.Remove -> {
|
||||
deleteGrpcCallUseCase(callId = action.item.callId)
|
||||
}
|
||||
|
||||
is OnGrpcItemUserAction.RemoveLinesAbove -> {
|
||||
deleteGrpcCallBeforeUseCase(callId = action.item.callId)
|
||||
}
|
||||
|
||||
is OnGrpcItemUserAction.CopyMethod -> {
|
||||
val domainModel = observeGrpcCallByIdUseCase(action.item.callId).firstOrNull()
|
||||
?: return@launch
|
||||
copyToClipboard(domainModel.request.method)
|
||||
}
|
||||
|
||||
is OnGrpcItemUserAction.CopyUrl -> {
|
||||
val domainModel = observeGrpcCallByIdUseCase(action.item.callId).firstOrNull()
|
||||
?: return@launch
|
||||
copyToClipboard(domainModel.request.authority)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onCopyText(text: String) {
|
||||
copyToClipboard(text)
|
||||
feedbackDisplayer.displayMessage("copied")
|
||||
}
|
||||
|
||||
fun closeDetailPanel() {
|
||||
viewModelScope.launch {
|
||||
clickedCallId.update { null }
|
||||
}
|
||||
}
|
||||
|
||||
fun onReset() {
|
||||
viewModelScope.launch(dispatcherProvider.viewModel) {
|
||||
resetCurrentDeviceGrpcCallsUseCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.di
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.GRPCViewModel
|
||||
import org.koin.core.module.dsl.viewModelOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val grpcUiModule =
|
||||
module {
|
||||
viewModelOf(::GRPCViewModel)
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.mapper
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcCallDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcHeaderDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.domain.model.GrpcResponseDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcDetailViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemViewState
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.mapper.formatDuration
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.mapper.formatTimestamp
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkDetailHeaderUi
|
||||
|
||||
fun toUi(domainModel: GrpcCallDomainModel): GrpcItemViewState = GrpcItemViewState(
|
||||
callId = domainModel.id,
|
||||
method = domainModel.request.method,
|
||||
url = domainModel.request.authority,
|
||||
status = if (domainModel.response == null) {
|
||||
GrpcItemViewState.StatusViewState.Waiting(text = "Waiting")
|
||||
} else {
|
||||
when (domainModel.response.result) {
|
||||
is GrpcResponseDomainModel.CallResult.Error ->
|
||||
GrpcItemViewState.StatusViewState.Failure(domainModel.response.result.cause)
|
||||
|
||||
is GrpcResponseDomainModel.CallResult.Success ->
|
||||
GrpcItemViewState.StatusViewState.Success(text = "Success")
|
||||
}
|
||||
},
|
||||
requestTimeFormatted = formatTimestamp(domainModel.request.timestamp),
|
||||
durationFormatted = domainModel.response?.let {
|
||||
val duration = it.timestamp - domainModel.request.timestamp
|
||||
formatDuration(duration.toDouble())
|
||||
},
|
||||
)
|
||||
|
||||
fun toDetailUi(domainModel: GrpcCallDomainModel): GrpcDetailViewState = GrpcDetailViewState(
|
||||
method = domainModel.request.method,
|
||||
url = domainModel.request.authority,
|
||||
status = if (domainModel.response == null) {
|
||||
GrpcItemViewState.StatusViewState.Waiting(text = "Waiting")
|
||||
} else {
|
||||
when (domainModel.response.result) {
|
||||
is GrpcResponseDomainModel.CallResult.Error ->
|
||||
GrpcItemViewState.StatusViewState.Failure(domainModel.response.result.cause)
|
||||
|
||||
is GrpcResponseDomainModel.CallResult.Success ->
|
||||
GrpcItemViewState.StatusViewState.Success(text = "Success")
|
||||
}
|
||||
},
|
||||
requestTimeFormatted = formatTimestamp(domainModel.request.timestamp),
|
||||
durationFormatted = domainModel.response?.let {
|
||||
val duration = it.timestamp - domainModel.request.timestamp
|
||||
formatDuration(duration.toDouble())
|
||||
},
|
||||
requestBody = domainModel.request.data,
|
||||
requestHeaders = domainModel.request.headers.map {
|
||||
toUi(it)
|
||||
},
|
||||
response = domainModel.response?.let {
|
||||
GrpcDetailViewState.ResponseViewState(
|
||||
headers = it.headers.map {
|
||||
toUi(it)
|
||||
},
|
||||
result = when (it.result) {
|
||||
is GrpcResponseDomainModel.CallResult.Error -> GrpcDetailViewState.DetailPayload.Failure(it.result.cause)
|
||||
is GrpcResponseDomainModel.CallResult.Success -> GrpcDetailViewState.DetailPayload.Success(it.result.data)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
fun toUi(header: GrpcHeaderDomainModel) = NetworkDetailHeaderUi(
|
||||
name = header.key,
|
||||
value = header.value,
|
||||
)
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkDetailHeaderUi
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.previewNetworkDetailHeaderUi
|
||||
|
||||
@Immutable
|
||||
data class GrpcDetailViewState(
|
||||
val url: String, // authority
|
||||
|
||||
val requestTimeFormatted: String,
|
||||
val durationFormatted: String?,
|
||||
|
||||
val method: String,
|
||||
val status: GrpcItemViewState.StatusViewState,
|
||||
|
||||
// request
|
||||
val requestBody: String?,
|
||||
val requestHeaders: List<NetworkDetailHeaderUi>,
|
||||
|
||||
val response: ResponseViewState?,
|
||||
) {
|
||||
data class ResponseViewState(
|
||||
val headers: List<NetworkDetailHeaderUi>,
|
||||
val result: DetailPayload,
|
||||
)
|
||||
|
||||
sealed interface DetailPayload {
|
||||
data class Success(val body: String) : DetailPayload
|
||||
data class Failure(val cause: String) : DetailPayload
|
||||
}
|
||||
}
|
||||
|
||||
fun previewGrpcDetailViewState() = GrpcDetailViewState(
|
||||
url = "google.com.test",
|
||||
requestTimeFormatted = "00:00:00.0000",
|
||||
durationFormatted = "333ms",
|
||||
method = "public.get.methodName",
|
||||
status = GrpcItemViewState.StatusViewState.Success("OK"),
|
||||
requestBody = "request body",
|
||||
requestHeaders = listOf(
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
),
|
||||
response = GrpcDetailViewState.ResponseViewState(
|
||||
headers = listOf(
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
previewNetworkDetailHeaderUi(),
|
||||
),
|
||||
result = GrpcDetailViewState.DetailPayload.Success("response body"),
|
||||
),
|
||||
)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Immutable
|
||||
data class GrpcItemColumnWidths(
|
||||
val requestTimeFormatted: Dp = 90.dp,
|
||||
val url: Float = 1f, // weight
|
||||
val method: Float = 2f, // weight
|
||||
val status: Dp = 80.dp,
|
||||
val durationFormatted: Dp = 65.dp,
|
||||
)
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class GrpcItemViewState(
|
||||
val callId: String,
|
||||
|
||||
val requestTimeFormatted: String,
|
||||
val url: String, // authority
|
||||
val method: String,
|
||||
val status: StatusViewState,
|
||||
val durationFormatted: String?,
|
||||
) {
|
||||
@Immutable
|
||||
sealed interface StatusViewState {
|
||||
val text: String
|
||||
|
||||
@Immutable
|
||||
data class Success(override val text: String) : StatusViewState
|
||||
|
||||
@Immutable
|
||||
data class Waiting(override val text: String) : StatusViewState
|
||||
|
||||
@Immutable
|
||||
data class Failure(override val text: String) : StatusViewState
|
||||
}
|
||||
|
||||
fun contains(text: String): Boolean = listOf(callId, requestTimeFormatted, url, method, status.text, durationFormatted).any {
|
||||
it?.contains(text, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
|
||||
fun previewGrpcItemViewState(): GrpcItemViewState = GrpcItemViewState(
|
||||
callId = "0",
|
||||
requestTimeFormatted = "00:00:00.0000",
|
||||
url = "google.com.test",
|
||||
method = "public.get.methodName",
|
||||
status = GrpcItemViewState.StatusViewState.Success("OK"),
|
||||
durationFormatted = "333ms",
|
||||
)
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.model
|
||||
|
||||
sealed interface OnGrpcItemUserAction {
|
||||
data class OnClicked(
|
||||
val item: GrpcItemViewState,
|
||||
) : OnGrpcItemUserAction
|
||||
|
||||
data class CopyUrl(
|
||||
val item: GrpcItemViewState,
|
||||
) : OnGrpcItemUserAction
|
||||
|
||||
data class CopyMethod(
|
||||
val item: GrpcItemViewState,
|
||||
) : OnGrpcItemUserAction
|
||||
|
||||
data class Remove(
|
||||
val item: GrpcItemViewState,
|
||||
) : OnGrpcItemUserAction
|
||||
|
||||
data class RemoveLinesAbove(
|
||||
val item: GrpcItemViewState,
|
||||
) : OnGrpcItemUserAction
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.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.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.github.openflocon.flocondesktop.common.ui.FloconColors
|
||||
import io.github.openflocon.flocondesktop.common.ui.FloconTheme
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.GRPCViewModel
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcDetailViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemColumnWidths
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.OnGrpcItemUserAction
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.previewGrpcItemViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.view.header.GrpcFilterBar
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.view.header.GrpcItemHeaderView
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun GRPCScreen(modifier: Modifier = Modifier) {
|
||||
val viewModel: GRPCViewModel = koinViewModel()
|
||||
val state: List<GrpcItemViewState> by viewModel.state.collectAsStateWithLifecycle()
|
||||
val detailState: GrpcDetailViewState? by viewModel.detailState.collectAsStateWithLifecycle()
|
||||
|
||||
GRPCScreen(
|
||||
grpcItems = state,
|
||||
modifier = modifier,
|
||||
detailState = detailState,
|
||||
onReset = viewModel::onReset,
|
||||
closeDetailPanel = viewModel::closeDetailPanel,
|
||||
onCopyText = viewModel::onCopyText,
|
||||
onGrpcItemUserAction = viewModel::onGrpcItemUserAction,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GRPCScreen(
|
||||
grpcItems: List<GrpcItemViewState>,
|
||||
onGrpcItemUserAction: (OnGrpcItemUserAction) -> Unit,
|
||||
onReset: () -> Unit,
|
||||
detailState: GrpcDetailViewState?,
|
||||
closeDetailPanel: () -> Unit,
|
||||
onCopyText: (text: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val columnWidths: GrpcItemColumnWidths =
|
||||
remember { GrpcItemColumnWidths() } // Default widths provided
|
||||
|
||||
var filteredItems by remember { mutableStateOf<List<GrpcItemViewState>>(emptyList()) }
|
||||
|
||||
Surface(modifier = modifier) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
Text(
|
||||
text = "Grpc",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(FloconColors.pannel)
|
||||
.padding(all = 12.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
GrpcFilterBar(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.background(FloconColors.pannel)
|
||||
.padding(horizontal = 12.dp),
|
||||
grpcItems = grpcItems,
|
||||
onResetClicked = onReset,
|
||||
onItemsChange = {
|
||||
filteredItems = it
|
||||
},
|
||||
)
|
||||
GrpcItemHeaderView(
|
||||
columnWidths = columnWidths,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.clickable(
|
||||
interactionSource = null,
|
||||
indication = null,
|
||||
enabled = detailState != null,
|
||||
) {
|
||||
closeDetailPanel()
|
||||
},
|
||||
) {
|
||||
items(filteredItems) {
|
||||
GrpcItemView(
|
||||
state = it,
|
||||
columnWidths = columnWidths,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onUserAction = onGrpcItemUserAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
detailState?.let {
|
||||
GrpcDetailView(
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.fillMaxHeight()
|
||||
.width(500.dp),
|
||||
state = it,
|
||||
onCopy = onCopyText,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun GRPCScreenPreview() {
|
||||
FloconTheme {
|
||||
GRPCScreen(
|
||||
grpcItems = List(10) {
|
||||
previewGrpcItemViewState()
|
||||
},
|
||||
onReset = {},
|
||||
detailState = null,
|
||||
closeDetailPanel = {},
|
||||
onCopyText = {},
|
||||
onGrpcItemUserAction = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,258 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.view
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.github.openflocon.flocondesktop.common.ui.FloconColors
|
||||
import io.github.openflocon.flocondesktop.common.ui.FloconTheme
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcDetailViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.previewGrpcDetailViewState
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.detail.CodeBlockView
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.detail.DetailHeadersView
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.detail.DetailLineTextView
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.detail.DetailLineView
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.detail.DetailSectionTitleView
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.detail.ExpandedSectionView
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun GrpcDetailView(
|
||||
state: GrpcDetailViewState,
|
||||
onCopy: (String) -> Unit, // Le lambda onCopy remplace ClipboardManager
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
var isRequestExpanded by remember { mutableStateOf(true) }
|
||||
var isRequestBodyExpanded by remember { mutableStateOf(true) }
|
||||
var isRequestHeadersExpanded by remember { mutableStateOf(true) }
|
||||
|
||||
var isResponseExpanded by remember { mutableStateOf(true) }
|
||||
var isResponseHeadersExpanded by remember { mutableStateOf(true) }
|
||||
var isResponseBodyExpanded by remember { mutableStateOf(true) }
|
||||
|
||||
val linesLabelWidth: Dp = 130.dp
|
||||
val headersLabelWidth: Dp = 150.dp
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
modifier
|
||||
.background(FloconColors.background)
|
||||
.verticalScroll(scrollState) // Rendre le contenu défilable
|
||||
.padding(all = 12.dp),
|
||||
) {
|
||||
DetailSectionTitleView(
|
||||
isExpanded = isRequestExpanded,
|
||||
title = "Request",
|
||||
onCopy = null,
|
||||
onToggle = {
|
||||
isRequestExpanded = it
|
||||
},
|
||||
)
|
||||
ExpandedSectionView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isExpanded = isRequestExpanded,
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
).padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
DetailLineTextView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = "Url",
|
||||
value = state.url,
|
||||
labelWidth = linesLabelWidth,
|
||||
)
|
||||
DetailLineTextView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = "Method",
|
||||
labelWidth = linesLabelWidth,
|
||||
value = state.method,
|
||||
)
|
||||
DetailLineView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = "Status",
|
||||
labelWidth = linesLabelWidth,
|
||||
) {
|
||||
GrpcStatusView(status = state.status)
|
||||
}
|
||||
DetailLineTextView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = "Request Time",
|
||||
value = state.requestTimeFormatted,
|
||||
labelWidth = linesLabelWidth,
|
||||
)
|
||||
state.durationFormatted?.let {
|
||||
DetailLineTextView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = "Duration",
|
||||
value = it,
|
||||
labelWidth = linesLabelWidth,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// headers
|
||||
DetailSectionTitleView(
|
||||
isExpanded = isRequestHeadersExpanded,
|
||||
title = "Request Headers",
|
||||
onCopy = null,
|
||||
onToggle = {
|
||||
isRequestHeadersExpanded = it
|
||||
},
|
||||
)
|
||||
ExpandedSectionView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isExpanded = isRequestHeadersExpanded,
|
||||
) {
|
||||
DetailHeadersView(
|
||||
headers = state.requestHeaders,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
labelWidth = headersLabelWidth,
|
||||
)
|
||||
}
|
||||
|
||||
// body
|
||||
DetailSectionTitleView(
|
||||
isExpanded = isRequestBodyExpanded,
|
||||
title = "Request Body",
|
||||
onCopy = {
|
||||
onCopy(state.requestBody ?: "")
|
||||
},
|
||||
onToggle = {
|
||||
isRequestBodyExpanded = it
|
||||
},
|
||||
)
|
||||
ExpandedSectionView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isExpanded = isRequestBodyExpanded,
|
||||
) {
|
||||
CodeBlockView(
|
||||
code = state.requestBody ?: "",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp)
|
||||
.padding(vertical = 12.dp),
|
||||
)
|
||||
|
||||
state.response?.let { response ->
|
||||
DetailSectionTitleView(
|
||||
isExpanded = isResponseExpanded,
|
||||
title = "Response",
|
||||
onCopy = null,
|
||||
onToggle = {
|
||||
isResponseExpanded = it
|
||||
},
|
||||
)
|
||||
|
||||
ExpandedSectionView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isExpanded = isResponseExpanded,
|
||||
) {
|
||||
// headers
|
||||
DetailSectionTitleView(
|
||||
isExpanded = isResponseHeadersExpanded,
|
||||
title = "Response Headers",
|
||||
onCopy = null,
|
||||
onToggle = {
|
||||
isResponseHeadersExpanded = it
|
||||
},
|
||||
)
|
||||
ExpandedSectionView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isExpanded = isResponseHeadersExpanded,
|
||||
) {
|
||||
DetailHeadersView(
|
||||
headers = response.headers,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
labelWidth = headersLabelWidth,
|
||||
)
|
||||
}
|
||||
|
||||
when (val r = response.result) {
|
||||
is GrpcDetailViewState.DetailPayload.Failure -> {
|
||||
// body
|
||||
DetailSectionTitleView(
|
||||
isExpanded = isResponseBodyExpanded,
|
||||
title = "Response Error",
|
||||
onCopy = {
|
||||
onCopy(r.cause)
|
||||
},
|
||||
onToggle = {
|
||||
isResponseBodyExpanded = it
|
||||
},
|
||||
)
|
||||
ExpandedSectionView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isExpanded = isResponseBodyExpanded,
|
||||
) {
|
||||
CodeBlockView(
|
||||
code = r.cause,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
is GrpcDetailViewState.DetailPayload.Success -> {
|
||||
// body
|
||||
DetailSectionTitleView(
|
||||
isExpanded = isResponseBodyExpanded,
|
||||
title = "Response Body",
|
||||
onCopy = {
|
||||
onCopy(r.body)
|
||||
},
|
||||
onToggle = {
|
||||
isResponseBodyExpanded = it
|
||||
},
|
||||
)
|
||||
ExpandedSectionView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isExpanded = isResponseBodyExpanded,
|
||||
) {
|
||||
CodeBlockView(
|
||||
code = r.body,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun GrpcDetailViewPreview() {
|
||||
FloconTheme {
|
||||
GrpcDetailView(
|
||||
state = previewGrpcDetailViewState(),
|
||||
modifier = Modifier.padding(16.dp), // Padding pour la preview
|
||||
onCopy = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.view
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
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.common.ui.ContextualItem
|
||||
import io.github.openflocon.flocondesktop.common.ui.ContextualView
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemColumnWidths
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemViewState
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.OnGrpcItemUserAction
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.previewGrpcItemViewState
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun GrpcItemView(
|
||||
state: GrpcItemViewState,
|
||||
columnWidths: GrpcItemColumnWidths = GrpcItemColumnWidths(), // Default widths provided
|
||||
onUserAction: (OnGrpcItemUserAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// Use MaterialTheme.typography for consistent text sizes
|
||||
val bodySmall = MaterialTheme.typography.bodySmall // Typically 12.sp or similar
|
||||
val labelSmall = MaterialTheme.typography.labelSmall // Even smaller, good for labels/tags
|
||||
|
||||
ContextualView(
|
||||
listOf(
|
||||
ContextualItem(
|
||||
id = "copy_url",
|
||||
text = "Copy url",
|
||||
),
|
||||
ContextualItem(
|
||||
id = "copy_method",
|
||||
text = "Copy Method",
|
||||
),
|
||||
ContextualItem(
|
||||
id = "remove",
|
||||
text = "Remove",
|
||||
),
|
||||
ContextualItem(
|
||||
id = "remove_lines_above",
|
||||
text = "Remove lines above ",
|
||||
),
|
||||
),
|
||||
onSelect = {
|
||||
when (it.id) {
|
||||
"copy_url" -> onUserAction(OnGrpcItemUserAction.CopyUrl(state))
|
||||
"copy_method" -> onUserAction(OnGrpcItemUserAction.CopyMethod(state))
|
||||
"remove" -> onUserAction(OnGrpcItemUserAction.Remove(state))
|
||||
"remove_lines_above" -> onUserAction(OnGrpcItemUserAction.RemoveLinesAbove(state))
|
||||
}
|
||||
},
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp) // Padding for the entire item
|
||||
.clip(shape = RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = {
|
||||
onUserAction(OnGrpcItemUserAction.OnClicked(state))
|
||||
})
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surface, // Use surface color for the item background
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
// Inner padding for content
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
// Date - Fixed width from data class
|
||||
Box(
|
||||
modifier = Modifier.width(columnWidths.requestTimeFormatted),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
state.requestTimeFormatted,
|
||||
style = bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
}
|
||||
|
||||
// Request Url - Fixed width from data class
|
||||
Box(
|
||||
modifier = Modifier.weight(columnWidths.url),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
state.url,
|
||||
style = bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
}
|
||||
|
||||
// Method - Takes remaining space (weight)
|
||||
Box(
|
||||
modifier = Modifier.weight(columnWidths.method),
|
||||
) {
|
||||
Text(
|
||||
state.method,
|
||||
style = bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
|
||||
// Status - Fixed width from data class
|
||||
|
||||
// TODO add a badge here
|
||||
Box(
|
||||
modifier = Modifier.width(columnWidths.status),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
GrpcStatusView(
|
||||
state.status,
|
||||
)
|
||||
}
|
||||
|
||||
// Duration - Fixed width from data class
|
||||
Box(
|
||||
modifier = Modifier.width(columnWidths.durationFormatted),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
state.durationFormatted ?: "", // reserve this space
|
||||
style = bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun ItemViewPreview() {
|
||||
MaterialTheme {
|
||||
GrpcItemView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = previewGrpcItemViewState(),
|
||||
onUserAction = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.view
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.github.openflocon.flocondesktop.common.ui.FloconTheme
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemViewState
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
// Custom colors for networkStatusUi/method views to integrate better with the theme
|
||||
val successBackground = Color(0xFF28A745).copy(alpha = 0.3f) // Muted green for success
|
||||
val successText = Color(0xFF28A745) // Brighter green for text
|
||||
|
||||
val errorBackground = Color(0xFFDC3545).copy(alpha = 0.3f) // Muted red for error
|
||||
val errorText = Color(0xFFDC3545) // Brighter red for text
|
||||
|
||||
private val waitingBackground = Color(0xFF6C757D).copy(alpha = 0.3f) // Muted gray for OTHER
|
||||
private val waitingText = Color(0xFF6C757D)
|
||||
|
||||
@Composable
|
||||
fun GrpcStatusView(
|
||||
status: GrpcItemViewState.StatusViewState,
|
||||
textSize: TextUnit = 12.sp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.background(
|
||||
color = when (status) {
|
||||
is GrpcItemViewState.StatusViewState.Failure -> errorBackground
|
||||
is GrpcItemViewState.StatusViewState.Success -> successBackground
|
||||
is GrpcItemViewState.StatusViewState.Waiting -> waitingBackground
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp), // Pill shape
|
||||
).padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
// Padding inside the tag
|
||||
contentAlignment = Alignment.Center, // Center content if Box is larger than text
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = status.text,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = textSize,
|
||||
color = when (status) {
|
||||
is GrpcItemViewState.StatusViewState.Failure -> errorText
|
||||
is GrpcItemViewState.StatusViewState.Success -> successText
|
||||
is GrpcItemViewState.StatusViewState.Waiting -> waitingText
|
||||
},
|
||||
style = MaterialTheme.typography.labelSmall, // Use typography for consistency
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun StatusView_Preview() {
|
||||
FloconTheme {
|
||||
GrpcStatusView(
|
||||
status =
|
||||
GrpcItemViewState.StatusViewState.Success("OK"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun StatusView_Failure_Preview() {
|
||||
FloconTheme {
|
||||
GrpcStatusView(
|
||||
status =
|
||||
GrpcItemViewState.StatusViewState.Failure("Error"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun StatusView_Waiting_Preview() {
|
||||
FloconTheme {
|
||||
GrpcStatusView(
|
||||
status =
|
||||
GrpcItemViewState.StatusViewState.Waiting("Waiting"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.view.header
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import flocondesktop.composeapp.generated.resources.Res
|
||||
import flocondesktop.composeapp.generated.resources.bin
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemViewState
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.components.FilterBar
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Composable
|
||||
fun GrpcFilterBar(
|
||||
grpcItems: List<GrpcItemViewState>,
|
||||
onItemsChange: (List<GrpcItemViewState>) -> Unit,
|
||||
onResetClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var filterText by remember {
|
||||
mutableStateOf("")
|
||||
}
|
||||
val onItemsChangeCallback by rememberUpdatedState(onItemsChange)
|
||||
val filteredGrpcItems: List<GrpcItemViewState> =
|
||||
remember(grpcItems, filterText) {
|
||||
if (filterText.isBlank()) {
|
||||
grpcItems
|
||||
} else {
|
||||
grpcItems.filter {
|
||||
it.contains(filterText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(filteredGrpcItems) {
|
||||
onItemsChangeCallback(filteredGrpcItems)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
FilterBar(
|
||||
placeholderText = "Filter",
|
||||
modifier = Modifier.weight(1f),
|
||||
onTextChange = {
|
||||
filterText = it
|
||||
},
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.clickable(onClick = onResetClicked)
|
||||
.padding(all = 8.dp),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(Res.drawable.bin),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.features.grpc.ui.view.header
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.github.openflocon.flocondesktop.common.ui.FloconColors
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.model.GrpcItemColumnWidths
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.components.HeaderLabelItem
|
||||
|
||||
@Composable
|
||||
fun GrpcItemHeaderView(
|
||||
modifier: Modifier = Modifier,
|
||||
columnWidths: GrpcItemColumnWidths = GrpcItemColumnWidths(), // Default widths provided
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.background(FloconColors.pannel)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp) // Padding for the entire item
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
// Date - Fixed width from data class
|
||||
HeaderLabelItem(
|
||||
modifier = Modifier.width(columnWidths.requestTimeFormatted),
|
||||
text = "Request Time",
|
||||
)
|
||||
HeaderLabelItem(
|
||||
modifier = Modifier.weight(columnWidths.url),
|
||||
text = "Url",
|
||||
)
|
||||
HeaderLabelItem(
|
||||
modifier = Modifier.weight(columnWidths.method),
|
||||
contentAlignment = Alignment.TopStart,
|
||||
text = "Method",
|
||||
)
|
||||
HeaderLabelItem(
|
||||
modifier = Modifier.width(columnWidths.status),
|
||||
text = "Status",
|
||||
)
|
||||
HeaderLabelItem(
|
||||
modifier = Modifier.width(columnWidths.durationFormatted),
|
||||
text = "Duration",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -84,7 +84,6 @@ object FloconHttpRequestGenerator {
|
|||
),
|
||||
response = FloconHttpRequestDomainModel.Response(
|
||||
body = responseBodyContent,
|
||||
httpCode = 200,
|
||||
byteSize = 1500,
|
||||
headers =
|
||||
mapOf(
|
||||
|
|
@ -93,7 +92,9 @@ object FloconHttpRequestGenerator {
|
|||
"X-Response-ID" to "res-$index",
|
||||
),
|
||||
),
|
||||
type = FloconHttpRequestDomainModel.Type.Http,
|
||||
type = FloconHttpRequestDomainModel.Type.Http(
|
||||
httpCode = 200,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import io.github.openflocon.flocondesktop.features.network.data.datasource.local
|
|||
import io.github.openflocon.flocondesktop.features.network.data.model.FloconHttpRequestDataModel
|
||||
import io.github.openflocon.flocondesktop.features.network.data.parser.graphql.computeIsGraphQlSuccess
|
||||
import io.github.openflocon.flocondesktop.features.network.data.parser.graphql.extractGraphQl
|
||||
import io.github.openflocon.flocondesktop.features.network.data.parser.graphql.model.GraphQlResponseBody
|
||||
import io.github.openflocon.flocondesktop.features.network.domain.model.FloconHttpRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkImageRepository
|
||||
import io.github.openflocon.flocondesktop.features.network.domain.repository.NetworkRepository
|
||||
|
|
@ -112,22 +111,30 @@ class NetworkRepositoryImpl(
|
|||
byteSize = decoded.requestSize ?: 0L,
|
||||
),
|
||||
response = FloconHttpRequestDomainModel.Response(
|
||||
httpCode = decoded.responseHttpCode!!,
|
||||
contentType = decoded.responseContentType,
|
||||
body = decoded.responseBody,
|
||||
headers = decoded.responseHeaders!!,
|
||||
byteSize = decoded.responseSize ?: 0L,
|
||||
),
|
||||
type = when {
|
||||
graphQl != null -> FloconHttpRequestDomainModel.Type.GraphQl(
|
||||
query = graphQl.request.queryName ?: "anonymous",
|
||||
operationType = graphQl.request.operationType,
|
||||
isSuccess = computeIsGraphQlSuccess(
|
||||
responseHttpCode = decoded.responseHttpCode,
|
||||
response = graphQl.response,
|
||||
)
|
||||
decoded.floconNetworkType == "grpc" -> FloconHttpRequestDomainModel.Type.Grpc(
|
||||
responseStatus = decoded.responseGrpcStatus!!,
|
||||
)
|
||||
graphQl != null -> {
|
||||
val httpCode = decoded.responseHttpCode!! // mandatory for graphQl
|
||||
FloconHttpRequestDomainModel.Type.GraphQl(
|
||||
query = graphQl.request.queryName ?: "anonymous",
|
||||
operationType = graphQl.request.operationType,
|
||||
isSuccess = computeIsGraphQlSuccess(
|
||||
responseHttpCode = httpCode,
|
||||
response = graphQl.response,
|
||||
),
|
||||
httpCode = httpCode,
|
||||
)
|
||||
}
|
||||
else -> FloconHttpRequestDomainModel.Type.Http(
|
||||
httpCode = decoded.responseHttpCode!!, // mandatory for http
|
||||
)
|
||||
else -> FloconHttpRequestDomainModel.Type.Http
|
||||
},
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class NetworkLocalDataSourceRoom(
|
|||
override fun observeRequests(deviceId: DeviceId): Flow<List<FloconHttpRequestDomainModel>> = floconHttpRequestDao
|
||||
.observeRequests(deviceId)
|
||||
.map { entities ->
|
||||
entities.map { it.toDomainModel() }
|
||||
entities.mapNotNull { it.toDomainModel() }
|
||||
}.flowOn(dispatcherProvider.data)
|
||||
|
||||
override suspend fun save(
|
||||
|
|
|
|||
|
|
@ -8,13 +8,32 @@ fun FloconHttpRequestDomainModel.toEntity(deviceId: String): FloconHttpRequestEn
|
|||
uuid = this.uuid,
|
||||
infos = this.toInfosEntity(),
|
||||
deviceId = deviceId,
|
||||
http = when (val t = this.type) {
|
||||
is FloconHttpRequestDomainModel.Type.Http -> FloconHttpRequestEntity.HttpEmbedded(
|
||||
responseHttpCode = t.httpCode,
|
||||
)
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl,
|
||||
is FloconHttpRequestDomainModel.Type.Grpc,
|
||||
-> null
|
||||
},
|
||||
graphql = when (val t = this.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> FloconHttpRequestEntity.FloconHttpRequestGraphQlEntity(
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> FloconHttpRequestEntity.GraphQlEmbedded(
|
||||
query = t.query,
|
||||
operationType = t.operationType,
|
||||
isSuccess = t.isSuccess,
|
||||
responseHttpCode = t.httpCode,
|
||||
)
|
||||
else -> null
|
||||
is FloconHttpRequestDomainModel.Type.Http,
|
||||
is FloconHttpRequestDomainModel.Type.Grpc,
|
||||
-> null
|
||||
},
|
||||
grpc = when (val t = this.type) {
|
||||
is FloconHttpRequestDomainModel.Type.Grpc -> FloconHttpRequestEntity.GrpcEmbedded(
|
||||
responseStatus = t.responseStatus,
|
||||
)
|
||||
is FloconHttpRequestDomainModel.Type.Http,
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl,
|
||||
-> null
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -26,37 +45,47 @@ private fun FloconHttpRequestDomainModel.toInfosEntity(): FloconHttpRequestInfos
|
|||
requestHeaders = this.request.headers,
|
||||
requestBody = this.request.body,
|
||||
requestByteSize = this.request.byteSize,
|
||||
responseHttpCode = this.response.httpCode,
|
||||
responseContentType = this.response.contentType,
|
||||
responseBody = this.response.body,
|
||||
responseHeaders = this.response.headers,
|
||||
responseByteSize = this.response.byteSize,
|
||||
)
|
||||
|
||||
fun FloconHttpRequestEntity.toDomainModel(): FloconHttpRequestDomainModel = FloconHttpRequestDomainModel(
|
||||
uuid = this.uuid,
|
||||
url = this.infos.url,
|
||||
durationMs = this.infos.durationMs,
|
||||
request = FloconHttpRequestDomainModel.Request(
|
||||
method = this.infos.method,
|
||||
startTime = this.infos.startTime,
|
||||
headers = this.infos.requestHeaders,
|
||||
body = this.infos.requestBody,
|
||||
byteSize = this.infos.requestByteSize,
|
||||
),
|
||||
response = FloconHttpRequestDomainModel.Response(
|
||||
httpCode = this.infos.responseHttpCode,
|
||||
contentType = this.infos.responseContentType,
|
||||
body = this.infos.responseBody,
|
||||
headers = this.infos.responseHeaders,
|
||||
byteSize = this.infos.responseByteSize,
|
||||
),
|
||||
type = when {
|
||||
this.graphql != null -> FloconHttpRequestDomainModel.Type.GraphQl(
|
||||
query = this.graphql.query,
|
||||
operationType = this.graphql.operationType,
|
||||
isSuccess = this.graphql.isSuccess,
|
||||
)
|
||||
else -> FloconHttpRequestDomainModel.Type.Http
|
||||
},
|
||||
)
|
||||
fun FloconHttpRequestEntity.toDomainModel(): FloconHttpRequestDomainModel? {
|
||||
return FloconHttpRequestDomainModel(
|
||||
uuid = this.uuid,
|
||||
url = this.infos.url,
|
||||
durationMs = this.infos.durationMs,
|
||||
request = FloconHttpRequestDomainModel.Request(
|
||||
method = this.infos.method,
|
||||
startTime = this.infos.startTime,
|
||||
headers = this.infos.requestHeaders,
|
||||
body = this.infos.requestBody,
|
||||
byteSize = this.infos.requestByteSize,
|
||||
),
|
||||
response = FloconHttpRequestDomainModel.Response(
|
||||
contentType = this.infos.responseContentType,
|
||||
body = this.infos.responseBody,
|
||||
headers = this.infos.responseHeaders,
|
||||
byteSize = this.infos.responseByteSize,
|
||||
),
|
||||
type = when {
|
||||
this.graphql != null -> FloconHttpRequestDomainModel.Type.GraphQl(
|
||||
query = this.graphql.query,
|
||||
operationType = this.graphql.operationType,
|
||||
isSuccess = this.graphql.isSuccess,
|
||||
httpCode = this.graphql.responseHttpCode,
|
||||
)
|
||||
|
||||
this.http != null -> FloconHttpRequestDomainModel.Type.Http(
|
||||
httpCode = this.http.responseHttpCode,
|
||||
)
|
||||
|
||||
this.grpc != null -> FloconHttpRequestDomainModel.Type.Grpc(
|
||||
responseStatus = this.grpc.responseStatus,
|
||||
)
|
||||
|
||||
else -> return null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,27 @@ data class FloconHttpRequestEntity(
|
|||
val infos: FloconHttpRequestInfosEntity,
|
||||
// if it's a graphql method, this item is not null
|
||||
@Embedded(prefix = "graphql_")
|
||||
val graphql: FloconHttpRequestGraphQlEntity?,
|
||||
val graphql: GraphQlEmbedded?,
|
||||
|
||||
@Embedded(prefix = "http_")
|
||||
val http: HttpEmbedded?,
|
||||
|
||||
@Embedded(prefix = "grpc_")
|
||||
val grpc: GrpcEmbedded?,
|
||||
) {
|
||||
data class FloconHttpRequestGraphQlEntity(
|
||||
data class GraphQlEmbedded(
|
||||
val query: String,
|
||||
val operationType: String,
|
||||
val isSuccess: Boolean,
|
||||
val responseHttpCode: Int,
|
||||
)
|
||||
|
||||
data class HttpEmbedded(
|
||||
val responseHttpCode: Int,
|
||||
)
|
||||
|
||||
data class GrpcEmbedded(
|
||||
val responseStatus: String,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +45,6 @@ data class FloconHttpRequestInfosEntity(
|
|||
val requestHeaders: Map<String, String>,
|
||||
val requestBody: String?,
|
||||
val requestByteSize: Long,
|
||||
val responseHttpCode: Int,
|
||||
val responseContentType: String?,
|
||||
val responseBody: String?,
|
||||
val responseHeaders: Map<String, String>,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class FloconHttpRequestDataModel(
|
||||
val floconNetworkType: String? = null,
|
||||
|
||||
val url: String? = null,
|
||||
val method: String? = null,
|
||||
val startTime: Long? = null,
|
||||
|
|
@ -18,4 +20,5 @@ data class FloconHttpRequestDataModel(
|
|||
val responseBody: String? = null,
|
||||
val responseHeaders: Map<String, String>? = null,
|
||||
val responseSize: Long? = null,
|
||||
val responseGrpcStatus: String? = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ private fun extractOperationName(query: String): String? {
|
|||
|
||||
fun computeIsGraphQlSuccess(
|
||||
responseHttpCode: Int,
|
||||
response: GraphQlResponseBody?
|
||||
response: GraphQlResponseBody?,
|
||||
): Boolean {
|
||||
if(responseHttpCode !in 200..299) return false
|
||||
if (responseHttpCode !in 200..299) return false
|
||||
return response?.errors?.takeUnless { it.isEmpty() } == null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ data class FloconHttpRequestDomainModel(
|
|||
)
|
||||
|
||||
data class Response(
|
||||
val httpCode: Int, // ex: 200
|
||||
val contentType: String? = null,
|
||||
val body: String? = null,
|
||||
val headers: Map<String, String>,
|
||||
|
|
@ -26,10 +25,16 @@ data class FloconHttpRequestDomainModel(
|
|||
|
||||
sealed interface Type {
|
||||
data class GraphQl(
|
||||
val httpCode: Int, // ex: 200
|
||||
val query: String,
|
||||
val operationType: String,
|
||||
val isSuccess: Boolean,
|
||||
) : Type
|
||||
data object Http : Type
|
||||
data class Http(
|
||||
val httpCode: Int, // ex: 200
|
||||
) : Type
|
||||
data class Grpc(
|
||||
val responseStatus: String,
|
||||
) : Type
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package io.github.openflocon.flocondesktop.features.network.ui.mapper
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.network.domain.model.FloconHttpRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkMethodUi
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkMethodUi.OTHER
|
||||
|
||||
fun getMethodUi(httpRequest: FloconHttpRequestDomainModel): NetworkMethodUi = when (val t = httpRequest.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> when (t.operationType.lowercase()) {
|
||||
"query" -> NetworkMethodUi.GraphQl.QUERY
|
||||
"mutation" -> NetworkMethodUi.GraphQl.MUTATION
|
||||
else -> OTHER(t.operationType, icon = null)
|
||||
}
|
||||
is FloconHttpRequestDomainModel.Type.Http -> toHttpMethodUi(httpRequest.request.method)
|
||||
is FloconHttpRequestDomainModel.Type.Grpc -> NetworkMethodUi.Grpc
|
||||
}
|
||||
|
||||
fun toHttpMethodUi(httpMethod: String): NetworkMethodUi = when (httpMethod.lowercase()) {
|
||||
"get" -> NetworkMethodUi.Http.GET
|
||||
"put" -> NetworkMethodUi.Http.PUT
|
||||
"post" -> NetworkMethodUi.Http.POST
|
||||
"delete" -> NetworkMethodUi.Http.DELETE
|
||||
else -> NetworkMethodUi.OTHER(httpMethod, icon = null)
|
||||
}
|
||||
|
|
@ -5,11 +5,12 @@ import io.github.openflocon.flocondesktop.common.ui.JsonPrettyPrinter
|
|||
import io.github.openflocon.flocondesktop.features.network.domain.model.FloconHttpRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkDetailHeaderUi
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkDetailViewState
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkStatusUi
|
||||
|
||||
fun toDetailUi(request: FloconHttpRequestDomainModel): NetworkDetailViewState = NetworkDetailViewState(
|
||||
fullUrl = request.url,
|
||||
method = toMethodUi(request.request.method),
|
||||
status = toNetworkStatusUi(request.response.httpCode),
|
||||
method = toDetailMethodUi(request),
|
||||
status = toDetailNetworkStatusUi(request.type),
|
||||
requestTimeFormatted = request.request.startTime.let { formatTimestamp(it) },
|
||||
durationFormatted = formatDuration(request.durationMs),
|
||||
// request
|
||||
|
|
@ -24,6 +25,13 @@ fun toDetailUi(request: FloconHttpRequestDomainModel): NetworkDetailViewState =
|
|||
graphQlSection = graphQlSection(request),
|
||||
)
|
||||
|
||||
private fun toDetailNetworkStatusUi(type: FloconHttpRequestDomainModel.Type): NetworkStatusUi = when (type) {
|
||||
is FloconHttpRequestDomainModel.Type.Grpc -> toGrpcNetworkStatusUi(type)
|
||||
// here for grphql we want the http code, the graphql status will be displayed on the specific graphql section
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> toNetworkStatusUi(code = type.httpCode)
|
||||
is FloconHttpRequestDomainModel.Type.Http -> toNetworkStatusUi(code = type.httpCode)
|
||||
}
|
||||
|
||||
fun graphQlSection(request: FloconHttpRequestDomainModel): NetworkDetailViewState.GraphQlSection? = (request.type as? FloconHttpRequestDomainModel.Type.GraphQl)?.let {
|
||||
NetworkDetailViewState.GraphQlSection(
|
||||
queryName = request.type.query,
|
||||
|
|
@ -43,3 +51,12 @@ fun toNetworkHeadersUi(headers: Map<String, String>?): List<NetworkDetailHeaderU
|
|||
)
|
||||
}.sortedBy { it.name }
|
||||
} ?: emptyList()
|
||||
|
||||
fun toDetailMethodUi(request: FloconHttpRequestDomainModel): NetworkDetailViewState.Method = when (val t = request.type) {
|
||||
is FloconHttpRequestDomainModel.Type.Grpc -> NetworkDetailViewState.Method.MethodName(
|
||||
name = request.request.method,
|
||||
)
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl,
|
||||
is FloconHttpRequestDomainModel.Type.Http,
|
||||
-> NetworkDetailViewState.Method.Http(toHttpMethodUi(request.request.method))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ package io.github.openflocon.flocondesktop.features.network.ui.mapper
|
|||
import io.github.openflocon.flocondesktop.common.ui.ByteFormatter
|
||||
import io.github.openflocon.flocondesktop.features.network.domain.model.FloconHttpRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkItemViewState
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkMethodUi
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkStatusUi
|
||||
import io.ktor.http.Url
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
|
|
@ -55,56 +53,10 @@ fun toUi(httpRequest: FloconHttpRequestDomainModel): NetworkItemViewState = Netw
|
|||
status = getStatusUi(httpRequest),
|
||||
)
|
||||
|
||||
fun toTypeUi(httpRequest: FloconHttpRequestDomainModel): NetworkItemViewState.NetworkTypeUi = when (val t = httpRequest.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> NetworkItemViewState.NetworkTypeUi.GraphQl(
|
||||
queryName = t.query,
|
||||
)
|
||||
|
||||
FloconHttpRequestDomainModel.Type.Http -> {
|
||||
val query = extractPath(httpRequest.url)
|
||||
NetworkItemViewState.NetworkTypeUi.Url(
|
||||
query = query,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMethodUi(httpRequest: FloconHttpRequestDomainModel): NetworkMethodUi = when (val t = httpRequest.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> when (t.operationType.lowercase()) {
|
||||
"query" -> NetworkMethodUi.GraphQl.QUERY
|
||||
"mutation" -> NetworkMethodUi.GraphQl.MUTATION
|
||||
else -> NetworkMethodUi.OTHER(t.operationType, icon = null)
|
||||
}
|
||||
is FloconHttpRequestDomainModel.Type.Http -> toMethodUi(httpRequest.request.method)
|
||||
}
|
||||
|
||||
fun getStatusUi(httpRequest: FloconHttpRequestDomainModel): NetworkStatusUi = when (val t = httpRequest.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> toGraphQlNetworkStatusUi(isSuccess = t.isSuccess)
|
||||
is FloconHttpRequestDomainModel.Type.Http -> toNetworkStatusUi(httpRequest.response.httpCode)
|
||||
}
|
||||
|
||||
fun getDomainUi(httpRequest: FloconHttpRequestDomainModel): String = when (val t = httpRequest.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> extractDomainAndPath(httpRequest.url)
|
||||
is FloconHttpRequestDomainModel.Type.Http -> extractDomain(httpRequest.url)
|
||||
}
|
||||
|
||||
fun toNetworkStatusUi(code: Int): NetworkStatusUi = NetworkStatusUi(
|
||||
text = code.toString(),
|
||||
isSuccess = code >= 200 && code < 300,
|
||||
)
|
||||
|
||||
fun toGraphQlNetworkStatusUi(isSuccess: Boolean): NetworkStatusUi {
|
||||
return NetworkStatusUi(
|
||||
text = if (isSuccess) "Success" else "Error",
|
||||
isSuccess = isSuccess,
|
||||
)
|
||||
}
|
||||
|
||||
fun toMethodUi(httpMethod: String): NetworkMethodUi = when (httpMethod.lowercase()) {
|
||||
"get" -> NetworkMethodUi.Http.GET
|
||||
"put" -> NetworkMethodUi.Http.PUT
|
||||
"post" -> NetworkMethodUi.Http.POST
|
||||
"delete" -> NetworkMethodUi.Http.DELETE
|
||||
else -> NetworkMethodUi.OTHER(httpMethod, icon = null)
|
||||
is FloconHttpRequestDomainModel.Type.Grpc -> extractDomain(httpRequest.url)
|
||||
}
|
||||
|
||||
fun formatDuration(duration: Double): String = duration.milliseconds.toString(unit = DurationUnit.MILLISECONDS)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package io.github.openflocon.flocondesktop.features.network.ui.mapper
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.network.domain.model.FloconHttpRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkStatusUi
|
||||
|
||||
fun getStatusUi(httpRequest: FloconHttpRequestDomainModel): NetworkStatusUi = when (val t = httpRequest.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> toGraphQlNetworkStatusUi(isSuccess = t.isSuccess)
|
||||
is FloconHttpRequestDomainModel.Type.Http -> toNetworkStatusUi(t.httpCode)
|
||||
is FloconHttpRequestDomainModel.Type.Grpc -> toGrpcNetworkStatusUi(t)
|
||||
}
|
||||
|
||||
fun toNetworkStatusUi(code: Int): NetworkStatusUi = NetworkStatusUi(
|
||||
text = code.toString(),
|
||||
isSuccess = code >= 200 && code < 300,
|
||||
)
|
||||
|
||||
fun toGraphQlNetworkStatusUi(isSuccess: Boolean): NetworkStatusUi = NetworkStatusUi(
|
||||
text = if (isSuccess) "Success" else "Error",
|
||||
isSuccess = isSuccess,
|
||||
)
|
||||
|
||||
fun toGrpcNetworkStatusUi(type: FloconHttpRequestDomainModel.Type.Grpc): NetworkStatusUi {
|
||||
val isSuccess = type.responseStatus == "OK"
|
||||
return NetworkStatusUi(
|
||||
text = type.responseStatus,
|
||||
isSuccess = isSuccess,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package io.github.openflocon.flocondesktop.features.network.ui.mapper
|
||||
|
||||
import io.github.openflocon.flocondesktop.features.network.domain.model.FloconHttpRequestDomainModel
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkItemViewState
|
||||
|
||||
fun toTypeUi(networkRequest: FloconHttpRequestDomainModel): NetworkItemViewState.NetworkTypeUi = when (val t = networkRequest.type) {
|
||||
is FloconHttpRequestDomainModel.Type.GraphQl -> NetworkItemViewState.NetworkTypeUi.GraphQl(
|
||||
queryName = t.query,
|
||||
)
|
||||
|
||||
is FloconHttpRequestDomainModel.Type.Http -> {
|
||||
val query = extractPath(networkRequest.url)
|
||||
NetworkItemViewState.NetworkTypeUi.Url(
|
||||
query = query,
|
||||
)
|
||||
}
|
||||
|
||||
is FloconHttpRequestDomainModel.Type.Grpc -> {
|
||||
NetworkItemViewState.NetworkTypeUi.Grpc(
|
||||
method = networkRequest.request.method,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@ data class NetworkDetailViewState(
|
|||
val fullUrl: String,
|
||||
val requestTimeFormatted: String,
|
||||
val durationFormatted: String,
|
||||
val method: NetworkMethodUi,
|
||||
|
||||
val method: Method,
|
||||
val status: NetworkStatusUi,
|
||||
|
||||
val graphQlSection: GraphQlSection?,
|
||||
|
|
@ -21,9 +22,19 @@ data class NetworkDetailViewState(
|
|||
val responseSize: String,
|
||||
val responseHeaders: List<NetworkDetailHeaderUi>,
|
||||
) {
|
||||
@Immutable
|
||||
data class GraphQlSection(
|
||||
val queryName: String,
|
||||
val method: NetworkMethodUi,
|
||||
val status: NetworkStatusUi,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed interface Method {
|
||||
@Immutable
|
||||
data class Http(val method: NetworkMethodUi) : Method
|
||||
|
||||
@Immutable
|
||||
data class MethodName(val name: String) : Method
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ data class NetworkItemViewState(
|
|||
) : NetworkTypeUi {
|
||||
override fun contains(text: String): Boolean = queryName.contains(text, ignoreCase = true)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class Grpc(
|
||||
val method: String,
|
||||
) : NetworkTypeUi {
|
||||
override fun contains(text: String): Boolean = method.contains(text, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ sealed interface NetworkMethodUi {
|
|||
}
|
||||
}
|
||||
|
||||
data object Grpc : NetworkMethodUi {
|
||||
override val text = "gRPC"
|
||||
override val icon = null
|
||||
}
|
||||
|
||||
data class OTHER(
|
||||
override val text: String,
|
||||
override val icon: DrawableResource?,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -93,7 +94,19 @@ fun NetworkDetailView(
|
|||
label = "Method",
|
||||
labelWidth = linesLabelWidth,
|
||||
) {
|
||||
MethodView(method = state.method)
|
||||
when (val m = state.method) {
|
||||
is NetworkDetailViewState.Method.Http -> MethodView(method = m.method)
|
||||
is NetworkDetailViewState.Method.MethodName -> {
|
||||
Text(
|
||||
text = m.name,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.weight(2f)
|
||||
.background(color = FloconColors.pannel.copy(alpha = 0.8f), shape = RoundedCornerShape(4.dp))
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DetailLineView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
@ -269,7 +282,7 @@ private fun NetworkDetailViewPreview() {
|
|||
state =
|
||||
NetworkDetailViewState(
|
||||
fullUrl = "http://www.google.com",
|
||||
method = NetworkMethodUi.Http.GET,
|
||||
method = NetworkDetailViewState.Method.Http(NetworkMethodUi.Http.GET),
|
||||
status =
|
||||
NetworkStatusUi(
|
||||
text = "200",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
|
|||
data class NetworkItemColumnWidths(
|
||||
val dateWidth: Dp = 90.dp,
|
||||
val methodWidth: Dp = 70.dp,
|
||||
val statusCodeWidth: Dp = 60.dp,
|
||||
val statusCodeWidth: Dp = 65.dp,
|
||||
val requestSizeWidth: Dp = 65.dp,
|
||||
val responseSizeWidth: Dp = 65.dp,
|
||||
val timeWidth: Dp = 60.dp,
|
||||
|
|
@ -151,6 +151,19 @@ fun NetworkItemView(
|
|||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
)
|
||||
}
|
||||
|
||||
is NetworkItemViewState.NetworkTypeUi.Grpc -> {
|
||||
Text(
|
||||
text = type.method,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.weight(2f)
|
||||
.background(color = FloconColors.pannel.copy(alpha = 0.8f), shape = RoundedCornerShape(4.dp))
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,11 @@ private val deleteMethodText = Color(0xFFDC3545)
|
|||
private val otherMethodBackground = Color(0xFF6C757D).copy(alpha = 0.3f) // Muted gray for OTHER
|
||||
private val otherMethodText = Color(0xFF6C757D)
|
||||
|
||||
private val grpcQueryMethodBackground = Color(0XAAE235A9).copy(alpha = 0.8f) // Muted gray for OTHER
|
||||
private val grpcQueryMethodText = Color(0XAAFFFFFF)
|
||||
private val graphQlQueryMethodBackground = Color(0XAAE235A9).copy(alpha = 0.8f) // Muted gray for OTHER
|
||||
private val graphQlQueryMethodText = Color(0XAAFFFFFF)
|
||||
|
||||
private val grpcMethodBackground = Color(0xff71CCCB)
|
||||
private val grpcMethodText = Color(0xff244B5A)
|
||||
|
||||
@Composable
|
||||
fun MethodView(
|
||||
|
|
@ -56,8 +59,9 @@ fun MethodView(
|
|||
is NetworkMethodUi.OTHER -> otherMethodBackground to otherMethodText
|
||||
is NetworkMethodUi.Http.POST -> postMethodBackground to postMethodText
|
||||
is NetworkMethodUi.Http.PUT -> putMethodBackground to putMethodText
|
||||
is NetworkMethodUi.GraphQl.QUERY -> grpcQueryMethodBackground to grpcQueryMethodText
|
||||
is NetworkMethodUi.GraphQl.MUTATION -> grpcQueryMethodBackground to grpcQueryMethodText
|
||||
is NetworkMethodUi.GraphQl.QUERY -> graphQlQueryMethodBackground to graphQlQueryMethodText
|
||||
is NetworkMethodUi.GraphQl.MUTATION -> graphQlQueryMethodBackground to graphQlQueryMethodText
|
||||
is NetworkMethodUi.Grpc -> grpcMethodBackground to grpcMethodText
|
||||
}
|
||||
|
||||
NetworkTag(
|
||||
|
|
@ -123,3 +127,11 @@ private fun MethodView_GraphQlQuery_Preview() {
|
|||
MethodView(method = NetworkMethodUi.GraphQl.QUERY)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun MethodView_Ggrpc_Preview() {
|
||||
FloconTheme {
|
||||
MethodView(method = NetworkMethodUi.Grpc)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package io.github.openflocon.flocondesktop.features.network.ui.view.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.github.openflocon.flocondesktop.common.ui.FloconTheme
|
||||
|
|
@ -25,18 +27,22 @@ fun StatusView(
|
|||
textSize: TextUnit = 12.sp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = status.text.toString(),
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = textSize,
|
||||
color =
|
||||
when (status.isSuccess) {
|
||||
true -> successTagText
|
||||
false -> errorTagText
|
||||
},
|
||||
style = MaterialTheme.typography.labelSmall, // Use typography for consistency
|
||||
)
|
||||
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
||||
BasicText(
|
||||
text = status.text.toString(),
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
maxFontSize = textSize,
|
||||
minFontSize = 8.sp,
|
||||
),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.labelSmall.copy(
|
||||
color = when (status.isSuccess) {
|
||||
true -> successTagText
|
||||
false -> errorTagText
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import io.github.openflocon.flocondesktop.features.dashboard.ui.view.DashboardSc
|
|||
import io.github.openflocon.flocondesktop.features.database.ui.view.DatabaseScreen
|
||||
import io.github.openflocon.flocondesktop.features.deeplinks.ui.view.DeeplinkScreen
|
||||
import io.github.openflocon.flocondesktop.features.files.ui.view.FilesScreen
|
||||
import io.github.openflocon.flocondesktop.features.grpc.ui.view.GRPCScreen
|
||||
import io.github.openflocon.flocondesktop.features.images.ui.view.ImagesScreen
|
||||
import io.github.openflocon.flocondesktop.features.network.ui.view.NetworkScreen
|
||||
import io.github.openflocon.flocondesktop.features.sharedpreferences.ui.view.SharedPreferencesScreen
|
||||
|
|
@ -93,12 +92,6 @@ private fun MainScreen(
|
|||
.fillMaxSize(),
|
||||
)
|
||||
|
||||
SubScreen.GRPC ->
|
||||
GRPCScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
)
|
||||
|
||||
SubScreen.Files ->
|
||||
FilesScreen(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ fun buildLeftPanelState(selectedId: String?) = LeftPanelState(
|
|||
items = listOf(
|
||||
item(subScreen = SubScreen.Network, selectedId = selectedId),
|
||||
item(subScreen = SubScreen.Images, selectedId = selectedId),
|
||||
item(subScreen = SubScreen.GRPC, selectedId = selectedId),
|
||||
),
|
||||
),
|
||||
LeftPannelSection(
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ enum class SubScreen {
|
|||
Network,
|
||||
Images, // network images
|
||||
|
||||
GRPC,
|
||||
|
||||
// storage
|
||||
Database,
|
||||
Files, // device files (context.cache, context.files)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import flocondesktop.composeapp.generated.resources.dashboard
|
|||
import flocondesktop.composeapp.generated.resources.database
|
||||
import flocondesktop.composeapp.generated.resources.deeplinks
|
||||
import flocondesktop.composeapp.generated.resources.files
|
||||
import flocondesktop.composeapp.generated.resources.grpc
|
||||
import flocondesktop.composeapp.generated.resources.images
|
||||
import flocondesktop.composeapp.generated.resources.network
|
||||
import flocondesktop.composeapp.generated.resources.settings
|
||||
|
|
@ -23,7 +22,6 @@ fun SubScreen.displayName(): String = when (this) {
|
|||
SubScreen.Files -> "Files"
|
||||
SubScreen.Tables -> "Tables"
|
||||
SubScreen.Images -> "Images"
|
||||
SubScreen.GRPC -> "gRPC"
|
||||
SubScreen.SharedPreferences -> "SharedPreferences"
|
||||
SubScreen.Dashboard -> "Dashboard"
|
||||
SubScreen.Settings -> "Settings"
|
||||
|
|
@ -37,7 +35,6 @@ fun SubScreen.icon(): DrawableResource = when (this) {
|
|||
SubScreen.Database -> Res.drawable.database
|
||||
SubScreen.Files -> Res.drawable.files
|
||||
SubScreen.Tables -> Res.drawable.tables
|
||||
SubScreen.GRPC -> Res.drawable.grpc
|
||||
SubScreen.Images -> Res.drawable.images
|
||||
SubScreen.SharedPreferences -> Res.drawable.sharedpreference
|
||||
SubScreen.Settings -> Res.drawable.settings
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue