From e33bc723687328eec69e7580dd3e5698d4679167 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Thu, 28 Mar 2024 13:23:44 -0600 Subject: [wallet] Make observability log atomic and improve dialog UI bug 0008509 --- .../main/java/net/taler/wallet/MainViewModel.kt | 26 ++++++--- .../net/taler/wallet/events/ObservabilityDialog.kt | 61 ++++++++++++++-------- .../net/taler/wallet/events/ObservabilityEvent.kt | 3 ++ 3 files changed, 61 insertions(+), 29 deletions(-) (limited to 'wallet/src/main') diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt index b4da875..4b53d15 100644 --- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt +++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt @@ -24,6 +24,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import net.taler.common.Amount @@ -57,6 +60,11 @@ private val transactionNotifications = listOf( "transaction-state-transition", ) +private val observabilityNotifications = listOf( + "task-observability-event", + "request-observability-event", +) + class MainViewModel( app: Application, ) : AndroidViewModel(app), VersionReceiver, NotificationReceiver { @@ -89,8 +97,8 @@ class MainViewModel( private val mTransactionsEvent = MutableLiveData>() val transactionsEvent: LiveData> = mTransactionsEvent - private val mObservabilityLog = MutableLiveData>(emptyList()) - val observabilityLog: LiveData> = mObservabilityLog + private val mObservabilityLog = MutableStateFlow>(emptyList()) + val observabilityLog: StateFlow> = mObservabilityLog private val mScanCodeEvent = MutableLiveData>() val scanCodeEvent: LiveData> = mScanCodeEvent @@ -113,14 +121,16 @@ class MainViewModel( balanceManager.loadBalances() } - if (payload.type == "task-observability-event" + if (payload.type in observabilityNotifications && payload.event != null && devMode.value == true) { - val logs = mObservabilityLog.value - ?.takeLast(OBSERVABILITY_LIMIT) - ?.toMutableList() ?: mutableListOf() - logs.add(payload.event) - mObservabilityLog.postValue(logs) + mObservabilityLog.getAndUpdate { logs -> + logs.takeLast(OBSERVABILITY_LIMIT) + .toMutableList().apply { + add(payload.event) + } + + } } if (payload.type in transactionNotifications) viewModelScope.launch(Dispatchers.Main) { diff --git a/wallet/src/main/java/net/taler/wallet/events/ObservabilityDialog.kt b/wallet/src/main/java/net/taler/wallet/events/ObservabilityDialog.kt index eae5758..91733c3 100644 --- a/wallet/src/main/java/net/taler/wallet/events/ObservabilityDialog.kt +++ b/wallet/src/main/java/net/taler/wallet/events/ObservabilityDialog.kt @@ -20,31 +20,34 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState 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.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels import kotlinx.serialization.ExperimentalSerializationApi @@ -52,8 +55,10 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import net.taler.wallet.MainViewModel import net.taler.wallet.R -import net.taler.wallet.compose.copyToClipBoard +import net.taler.wallet.compose.CopyToClipboardButton import net.taler.wallet.events.ObservabilityDialog.Companion.json +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle class ObservabilityDialog: DialogFragment() { private val model: MainViewModel by activityViewModels() @@ -64,8 +69,8 @@ class ObservabilityDialog: DialogFragment() { savedInstanceState: Bundle? ): View = ComposeView(requireContext()).apply { setContent { - val events by model.observabilityLog.observeAsState() - ObservabilityComposable(events?.reversed() ?: emptyList()) { + val events by model.observabilityLog.collectAsState() + ObservabilityComposable(events.reversed()) { dismiss() } } @@ -122,24 +127,38 @@ fun ObservabilityItem( val context = LocalContext.current val title = event.getTitle(context) val body = json.encodeToString(event.body) + val timestamp = DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.MEDIUM) + .format(event.timestamp) ListItem( modifier = Modifier.fillMaxWidth(), headlineContent = { Text(title) }, + overlineContent = { Text(timestamp) }, supportingContent = if (!showJson) null else { -> - Text( - text = body, - fontFamily = FontFamily.Monospace, - style = MaterialTheme.typography.bodySmall, - ) - }, - trailingContent = if(!showJson) null else { -> - IconButton(onClick = { - copyToClipBoard(context, "Event", body) - }) { - Icon( - Icons.Default.ContentCopy, - contentDescription = stringResource(R.string.copy), + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier.background( + MaterialTheme.colorScheme.secondaryContainer, + shape = MaterialTheme.shapes.small, + ) + ) { + Text( + modifier = Modifier + .padding(10.dp) + .fillMaxWidth(), + text = body, + fontFamily = FontFamily.Monospace, + style = MaterialTheme.typography.bodySmall, + ) + } + + CopyToClipboardButton( + label = "Event", + content = body, + colors = ButtonDefaults.textButtonColors(), ) } }, diff --git a/wallet/src/main/java/net/taler/wallet/events/ObservabilityEvent.kt b/wallet/src/main/java/net/taler/wallet/events/ObservabilityEvent.kt index cb710f7..a726045 100644 --- a/wallet/src/main/java/net/taler/wallet/events/ObservabilityEvent.kt +++ b/wallet/src/main/java/net/taler/wallet/events/ObservabilityEvent.kt @@ -28,11 +28,13 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import net.taler.wallet.R +import java.time.LocalDateTime @Serializable(with = ObservabilityEventSerializer::class) class ObservabilityEvent( val body: JsonObject, + val timestamp: LocalDateTime, val type: String, ) { @@ -78,6 +80,7 @@ class ObservabilityEventSerializer: KSerializer { return ObservabilityEvent( body = jsonObject, + timestamp = LocalDateTime.now(), type = type, ) } -- cgit v1.2.3