commit 00393136136e6c4b51f88f4c37a93a91a08b87e4
parent 65396a1a5787806e95d036fcecf05895e38ef33b
Author: Iván Ávalos <avalos@disroot.org>
Date: Sat, 14 Feb 2026 22:41:53 +0100
[wallet] move performance stats to settings/ folder
Diffstat:
6 files changed, 340 insertions(+), 342 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/main/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/main/MainViewModel.kt
@@ -59,8 +59,8 @@ import androidx.core.net.toUri
import net.taler.wallet.BuildConfig
import net.taler.wallet.NetworkManager
import net.taler.wallet.donau.DonauManager
-import net.taler.wallet.stats.PerformanceTable
-import net.taler.wallet.stats.TestingGetPerformanceStatsResponse
+import net.taler.wallet.settings.PerformanceTable
+import net.taler.wallet.settings.TestingGetPerformanceStatsResponse
const val TAG = "taler-wallet"
const val OBSERVABILITY_LIMIT = 100
diff --git a/wallet/src/main/java/net/taler/wallet/settings/PerformanceFragment.kt b/wallet/src/main/java/net/taler/wallet/settings/PerformanceFragment.kt
@@ -0,0 +1,245 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2026 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.settings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.Badge
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import kotlinx.serialization.json.Json
+import net.taler.wallet.BottomInsetsSpacer
+import net.taler.wallet.R
+import net.taler.wallet.balances.SectionHeader
+import net.taler.wallet.compose.EmptyComposable
+import net.taler.wallet.compose.ShareButton
+import net.taler.wallet.compose.TalerSurface
+import net.taler.wallet.compose.collectAsStateLifecycleAware
+import net.taler.wallet.main.MainViewModel
+
+class PerformanceFragment: Fragment() {
+ private val model: MainViewModel by activityViewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = ComposeView(requireContext()).apply {
+ setContent {
+ TalerSurface {
+ val stats by model.performanceTable.collectAsStateLifecycleAware()
+
+ if (stats == null) {
+ EmptyComposable()
+ return@TalerSurface
+ }
+
+ stats?.let {
+ PerformanceTableComposable(it,
+ onReload = { model.loadPerformanceStats() },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ model.loadPerformanceStats()
+ }
+}
+
+@Composable
+fun PerformanceTableComposable(
+ stats: PerformanceTable,
+ onReload: () -> Unit,
+) {
+ val json = remember { Json {
+ prettyPrint = true
+ ignoreUnknownKeys = true
+ coerceInputValues = true
+ } }
+
+ LazyColumn {
+ item {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceAround,
+ ) {
+ ShareButton(json.encodeToString(stats))
+
+ Button(onClick = onReload) {
+ Icon(
+ Icons.Default.Refresh,
+ contentDescription = null,
+ modifier = Modifier.size(ButtonDefaults.IconSize),
+ )
+ Spacer(Modifier.size(ButtonDefaults.IconSpacing))
+ Text(stringResource(R.string.reload))
+ }
+ }
+ }
+
+ if (stats.httpFetch.isNotEmpty()) {
+ stickyHeader {
+ SectionHeader { Text(stringResource(R.string.performance_stats_http_fetch)) }
+ }
+
+ itemsIndexed(stats.httpFetch) { i, stat ->
+ PerformanceStatItem(i + 1, stat)
+ }
+ }
+
+ if (stats.dbQuery.isNotEmpty()) {
+ stickyHeader {
+ SectionHeader { Text(stringResource(R.string.performance_stats_db_query)) }
+ }
+
+ itemsIndexed(stats.dbQuery) { i, stat ->
+ PerformanceStatItem(i + 1, stat)
+ }
+ }
+
+
+ if (stats.crypto.isNotEmpty()) {
+ stickyHeader {
+ SectionHeader { Text(stringResource(R.string.performance_stats_crypto)) }
+ }
+
+ itemsIndexed(stats.crypto) { i, stat ->
+ PerformanceStatItem(i + 1, stat)
+ }
+ }
+
+ if (stats.walletRequest.isNotEmpty()) {
+ stickyHeader {
+ SectionHeader { Text(stringResource(R.string.performance_stats_wallet_request)) }
+ }
+
+ itemsIndexed(stats.walletRequest) { i, stat ->
+ PerformanceStatItem(i + 1, stat)
+ }
+ }
+
+ if (stats.walletTask.isNotEmpty()) {
+ stickyHeader {
+ SectionHeader { Text(stringResource(R.string.performance_stats_wallet_task)) }
+ }
+
+ itemsIndexed(stats.walletTask) { i, stat ->
+ PerformanceStatItem(i + 1, stat)
+ }
+ }
+
+ item {
+ BottomInsetsSpacer()
+ }
+ }
+}
+
+@Composable
+fun PerformanceStatItem(
+ ranking: Int,
+ stat: PerformanceStat,
+) {
+ ListItem(
+ leadingContent = {
+ Text(
+ text = stringResource(R.string.ranking, ranking),
+ fontWeight = FontWeight.ExtraBold,
+ style = MaterialTheme.typography.titleLarge,
+ )
+ },
+
+ headlineContent = {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+
+ ) {
+ Box(Modifier.weight(1f, fill = false)) {
+ when (stat) {
+ is PerformanceStat.HttpFetch -> Text(
+ text = stat.url,
+ style = MaterialTheme.typography.bodySmall,
+ )
+
+ is PerformanceStat.DbQuery -> Text(stat.name)
+ is PerformanceStat.Crypto -> Text(stat.operation)
+ is PerformanceStat.WalletRequest -> Text(stat.operation)
+ is PerformanceStat.WalletTask -> Text(
+ text = stat.taskId,
+ style = MaterialTheme.typography.bodySmall,
+ fontFamily = FontFamily.Monospace,
+ )
+ }
+ }
+
+ Badge(Modifier.padding(start = 10.dp)) {
+ Text(stat.count.toString())
+ }
+ }
+ },
+
+ supportingContent = {
+ when(stat) {
+ is PerformanceStat.DbQuery -> Text(
+ text = stat.location,
+ style = MaterialTheme.typography.bodySmall,
+ fontFamily = FontFamily.Monospace,
+ )
+ else -> {}
+ }
+ },
+
+ trailingContent = {
+ Text(stringResource(R.string.millisecond, stat.maxDurationMs))
+ },
+ )
+}
+\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/settings/PerformanceStats.kt b/wallet/src/main/java/net/taler/wallet/settings/PerformanceStats.kt
@@ -0,0 +1,90 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2026 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.settings
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+sealed class PerformanceStat {
+ abstract val maxDurationMs: Int
+ abstract val count: Int
+
+ @Serializable
+ @SerialName("http-fetch")
+ data class HttpFetch(
+ val url: String,
+ override val maxDurationMs: Int,
+ override val count: Int,
+ ): PerformanceStat()
+
+ @Serializable
+ @SerialName("db-query")
+ data class DbQuery(
+ val name: String,
+ val location: String,
+ override val maxDurationMs: Int,
+ override val count: Int,
+ ): PerformanceStat()
+
+ @Serializable
+ @SerialName("crypto")
+ data class Crypto(
+ val operation: String,
+ override val maxDurationMs: Int,
+ override val count: Int,
+ ): PerformanceStat()
+
+ @Serializable
+ @SerialName("wallet-request")
+ data class WalletRequest(
+ val operation: String,
+ override val maxDurationMs: Int,
+ override val count: Int,
+ ): PerformanceStat()
+
+ @Serializable
+ @SerialName("wallet-task")
+ data class WalletTask(
+ val taskId: String,
+ override val maxDurationMs: Int,
+ override val count: Int,
+ ): PerformanceStat()
+}
+
+@Serializable
+data class PerformanceTable(
+ @SerialName("http-fetch")
+ val httpFetch: List<PerformanceStat.HttpFetch> = emptyList(),
+
+ @SerialName("db-query")
+ val dbQuery: List<PerformanceStat.DbQuery> = emptyList(),
+
+ @SerialName("crypto")
+ val crypto: List<PerformanceStat.Crypto> = emptyList(),
+
+ @SerialName("wallet-request")
+ val walletRequest: List<PerformanceStat.WalletRequest> = emptyList(),
+
+ @SerialName("wallet-task")
+ val walletTask: List<PerformanceStat.WalletTask> = emptyList(),
+)
+
+@Serializable
+data class TestingGetPerformanceStatsResponse(
+ val stats: PerformanceTable,
+)
+\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/stats/PerformanceFragment.kt b/wallet/src/main/java/net/taler/wallet/stats/PerformanceFragment.kt
@@ -1,247 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2026 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.stats
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
-import androidx.compose.material3.Badge
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ProvideTextStyle
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import kotlinx.serialization.json.Json
-import net.taler.wallet.BottomInsetsSpacer
-import net.taler.wallet.R
-import net.taler.wallet.balances.SectionHeader
-import net.taler.wallet.compose.EmptyComposable
-import net.taler.wallet.compose.ShareButton
-import net.taler.wallet.compose.TalerSurface
-import net.taler.wallet.compose.collectAsStateLifecycleAware
-import net.taler.wallet.main.MainViewModel
-
-class PerformanceFragment: Fragment() {
- private val model: MainViewModel by activityViewModels()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ) = ComposeView(requireContext()).apply {
- setContent {
- TalerSurface {
- val stats by model.performanceTable.collectAsStateLifecycleAware()
-
- if (stats == null) {
- EmptyComposable()
- return@TalerSurface
- }
-
- stats?.let {
- PerformanceTableComposable(it,
- onReload = { model.loadPerformanceStats() },
- )
- }
- }
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- model.loadPerformanceStats()
- }
-}
-
-@Composable
-fun PerformanceTableComposable(
- stats: PerformanceTable,
- onReload: () -> Unit,
-) {
- val json = remember { Json {
- prettyPrint = true
- ignoreUnknownKeys = true
- coerceInputValues = true
- } }
-
- LazyColumn {
- item {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceAround,
- ) {
- ShareButton(json.encodeToString(stats))
-
- Button(onClick = onReload) {
- Icon(
- Icons.Default.Refresh,
- contentDescription = null,
- modifier = Modifier.size(ButtonDefaults.IconSize),
- )
- Spacer(Modifier.size(ButtonDefaults.IconSpacing))
- Text(stringResource(R.string.reload))
- }
- }
- }
-
- if (stats.httpFetch.isNotEmpty()) {
- stickyHeader {
- SectionHeader { Text(stringResource(R.string.performance_stats_http_fetch)) }
- }
-
- itemsIndexed(stats.httpFetch) { i, stat ->
- PerformanceStatItem(i + 1, stat)
- }
- }
-
- if (stats.dbQuery.isNotEmpty()) {
- stickyHeader {
- SectionHeader { Text(stringResource(R.string.performance_stats_db_query)) }
- }
-
- itemsIndexed(stats.dbQuery) { i, stat ->
- PerformanceStatItem(i + 1, stat)
- }
- }
-
-
- if (stats.crypto.isNotEmpty()) {
- stickyHeader {
- SectionHeader { Text(stringResource(R.string.performance_stats_crypto)) }
- }
-
- itemsIndexed(stats.crypto) { i, stat ->
- PerformanceStatItem(i + 1, stat)
- }
- }
-
- if (stats.walletRequest.isNotEmpty()) {
- stickyHeader {
- SectionHeader { Text(stringResource(R.string.performance_stats_wallet_request)) }
- }
-
- itemsIndexed(stats.walletRequest) { i, stat ->
- PerformanceStatItem(i + 1, stat)
- }
- }
-
- if (stats.walletTask.isNotEmpty()) {
- stickyHeader {
- SectionHeader { Text(stringResource(R.string.performance_stats_wallet_task)) }
- }
-
- itemsIndexed(stats.walletTask) { i, stat ->
- PerformanceStatItem(i + 1, stat)
- }
- }
-
- item {
- BottomInsetsSpacer()
- }
- }
-}
-
-@Composable
-fun PerformanceStatItem(
- ranking: Int,
- stat: PerformanceStat,
-) {
- ListItem(
- leadingContent = {
- Text(
- text = stringResource(R.string.ranking, ranking),
- fontWeight = FontWeight.ExtraBold,
- style = MaterialTheme.typography.titleLarge,
- )
- },
-
- headlineContent = {
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically,
-
- ) {
- Box(Modifier.weight(1f, fill = false)) {
- when (stat) {
- is PerformanceStat.HttpFetch -> Text(
- text = stat.url,
- style = MaterialTheme.typography.bodySmall,
- )
-
- is PerformanceStat.DbQuery -> Text(stat.name)
- is PerformanceStat.Crypto -> Text(stat.operation)
- is PerformanceStat.WalletRequest -> Text(stat.operation)
- is PerformanceStat.WalletTask -> Text(
- text = stat.taskId,
- style = MaterialTheme.typography.bodySmall,
- fontFamily = FontFamily.Monospace,
- )
- }
- }
-
- Badge(Modifier.padding(start = 10.dp)) {
- Text(stat.count.toString())
- }
- }
- },
-
- supportingContent = {
- when(stat) {
- is PerformanceStat.DbQuery -> Text(
- text = stat.location,
- style = MaterialTheme.typography.bodySmall,
- fontFamily = FontFamily.Monospace,
- )
- else -> {}
- }
- },
-
- trailingContent = {
- Text(stringResource(R.string.millisecond, stat.maxDurationMs))
- },
- )
-}
-\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/stats/PerformanceStats.kt b/wallet/src/main/java/net/taler/wallet/stats/PerformanceStats.kt
@@ -1,90 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2026 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.stats
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Serializable
-sealed class PerformanceStat {
- abstract val maxDurationMs: Int
- abstract val count: Int
-
- @Serializable
- @SerialName("http-fetch")
- data class HttpFetch(
- val url: String,
- override val maxDurationMs: Int,
- override val count: Int,
- ): PerformanceStat()
-
- @Serializable
- @SerialName("db-query")
- data class DbQuery(
- val name: String,
- val location: String,
- override val maxDurationMs: Int,
- override val count: Int,
- ): PerformanceStat()
-
- @Serializable
- @SerialName("crypto")
- data class Crypto(
- val operation: String,
- override val maxDurationMs: Int,
- override val count: Int,
- ): PerformanceStat()
-
- @Serializable
- @SerialName("wallet-request")
- data class WalletRequest(
- val operation: String,
- override val maxDurationMs: Int,
- override val count: Int,
- ): PerformanceStat()
-
- @Serializable
- @SerialName("wallet-task")
- data class WalletTask(
- val taskId: String,
- override val maxDurationMs: Int,
- override val count: Int,
- ): PerformanceStat()
-}
-
-@Serializable
-data class PerformanceTable(
- @SerialName("http-fetch")
- val httpFetch: List<PerformanceStat.HttpFetch> = emptyList(),
-
- @SerialName("db-query")
- val dbQuery: List<PerformanceStat.DbQuery> = emptyList(),
-
- @SerialName("crypto")
- val crypto: List<PerformanceStat.Crypto> = emptyList(),
-
- @SerialName("wallet-request")
- val walletRequest: List<PerformanceStat.WalletRequest> = emptyList(),
-
- @SerialName("wallet-task")
- val walletTask: List<PerformanceStat.WalletTask> = emptyList(),
-)
-
-@Serializable
-data class TestingGetPerformanceStatsResponse(
- val stats: PerformanceTable,
-)
-\ No newline at end of file
diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml
@@ -354,7 +354,7 @@
<fragment
android:id="@+id/performanceStats"
- android:name="net.taler.wallet.stats.PerformanceFragment"
+ android:name="net.taler.wallet.settings.PerformanceFragment"
android:label="@string/performance_stats_title" />
<action