taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

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:
Mwallet/src/main/java/net/taler/wallet/main/MainViewModel.kt | 4++--
Awallet/src/main/java/net/taler/wallet/settings/PerformanceFragment.kt | 246+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awallet/src/main/java/net/taler/wallet/settings/PerformanceStats.kt | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dwallet/src/main/java/net/taler/wallet/stats/PerformanceFragment.kt | 248-------------------------------------------------------------------------------
Dwallet/src/main/java/net/taler/wallet/stats/PerformanceStats.kt | 91-------------------------------------------------------------------------------
Mwallet/src/main/res/navigation/nav_graph.xml | 2+-
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