taler-android

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

PerformanceStatsScreen.kt (8188B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2026 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.wallet.settings
     18 
     19 import androidx.compose.foundation.layout.Arrangement
     20 import androidx.compose.foundation.layout.Box
     21 import androidx.compose.foundation.layout.Row
     22 import androidx.compose.foundation.layout.Spacer
     23 import androidx.compose.foundation.layout.fillMaxWidth
     24 import androidx.compose.foundation.layout.padding
     25 import androidx.compose.foundation.layout.fillMaxSize
     26 import androidx.compose.foundation.layout.size
     27 import androidx.compose.foundation.lazy.LazyColumn
     28 import androidx.compose.foundation.lazy.itemsIndexed
     29 import androidx.compose.material.icons.Icons
     30 import androidx.compose.material.icons.filled.Refresh
     31 import androidx.compose.material3.Badge
     32 import androidx.compose.material3.Button
     33 import androidx.compose.material3.ButtonDefaults
     34 import androidx.compose.material3.Icon
     35 import androidx.compose.material3.ListItem
     36 import androidx.compose.material3.MaterialTheme
     37 import androidx.compose.material3.Text
     38 import androidx.compose.runtime.Composable
     39 import androidx.compose.runtime.LaunchedEffect
     40 import androidx.compose.runtime.getValue
     41 import androidx.compose.runtime.remember
     42 import androidx.compose.ui.Alignment
     43 import androidx.compose.ui.Modifier
     44 import androidx.compose.ui.res.stringResource
     45 import androidx.compose.ui.text.font.FontFamily
     46 import androidx.compose.ui.text.font.FontWeight
     47 import androidx.compose.ui.unit.dp
     48 import kotlinx.serialization.json.Json
     49 import net.taler.wallet.BottomInsetsSpacer
     50 import net.taler.wallet.R
     51 import net.taler.wallet.balances.SectionHeader
     52 import net.taler.wallet.compose.EmptyComposable
     53 import net.taler.wallet.compose.GlobalScaffold
     54 import net.taler.wallet.compose.ShareButton
     55 import net.taler.wallet.compose.TalerSurface
     56 import net.taler.wallet.compose.collectAsStateLifecycleAware
     57 import net.taler.wallet.main.MainViewModel
     58 
     59 @Composable
     60 fun PerformanceStatsScreen(
     61     model: MainViewModel,
     62     onNavigateBack: () -> Unit,
     63 ) {
     64     val settingsManager = model.settingsManager
     65     val stats by settingsManager.performanceTable.collectAsStateLifecycleAware()
     66 
     67     LaunchedEffect(Unit) {
     68         settingsManager.loadPerformanceStats()
     69     }
     70 
     71     TalerSurface {
     72         GlobalScaffold(
     73             model = model,
     74             modifier = Modifier.fillMaxSize(),
     75             title = { Text(stringResource(R.string.performance_stats_title)) },
     76             onNavigateBack = onNavigateBack,
     77         ) { paddingValues ->
     78             val it = stats
     79             if (it == null) {
     80                 EmptyComposable(Modifier.padding(paddingValues))
     81             } else {
     82                 PerformanceTableComposable(
     83                     modifier = Modifier.padding(paddingValues),
     84                     stats = it,
     85                     onReload = { settingsManager.loadPerformanceStats() },
     86                 )
     87             }
     88         }
     89     }
     90 }
     91 
     92 @Composable
     93 fun PerformanceTableComposable(
     94     stats: PerformanceTable,
     95     onReload: () -> Unit,
     96     modifier: Modifier = Modifier,
     97 ) {
     98     val json = remember { Json {
     99         prettyPrint = true
    100         ignoreUnknownKeys = true
    101         coerceInputValues = true
    102     } }
    103 
    104     LazyColumn(modifier = modifier) {
    105         item {
    106             Row(
    107                 modifier = Modifier
    108                     .fillMaxWidth()
    109                     .padding(16.dp),
    110                 verticalAlignment = Alignment.CenterVertically,
    111                 horizontalArrangement = Arrangement.SpaceAround,
    112             ) {
    113                 ShareButton(json.encodeToString(stats))
    114 
    115                 Button(onClick = onReload) {
    116                     Icon(
    117                         Icons.Default.Refresh,
    118                         contentDescription = null,
    119                         modifier = Modifier.size(ButtonDefaults.IconSize),
    120                     )
    121                     Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    122                     Text(stringResource(R.string.reload))
    123                 }
    124             }
    125         }
    126 
    127         if (stats.httpFetch.isNotEmpty()) {
    128             stickyHeader {
    129                 SectionHeader { Text(stringResource(R.string.performance_stats_http_fetch)) }
    130             }
    131 
    132             itemsIndexed(stats.httpFetch) { i, stat ->
    133                 PerformanceStatItem(i + 1, stat)
    134             }
    135         }
    136 
    137         if (stats.dbQuery.isNotEmpty()) {
    138             stickyHeader {
    139                 SectionHeader { Text(stringResource(R.string.performance_stats_db_query)) }
    140             }
    141 
    142             itemsIndexed(stats.dbQuery) { i, stat ->
    143                 PerformanceStatItem(i + 1, stat)
    144             }
    145         }
    146 
    147 
    148         if (stats.crypto.isNotEmpty()) {
    149             stickyHeader {
    150                 SectionHeader { Text(stringResource(R.string.performance_stats_crypto)) }
    151             }
    152 
    153             itemsIndexed(stats.crypto) { i, stat ->
    154                 PerformanceStatItem(i + 1, stat)
    155             }
    156         }
    157 
    158         if (stats.walletRequest.isNotEmpty()) {
    159             stickyHeader {
    160                 SectionHeader { Text(stringResource(R.string.performance_stats_wallet_request)) }
    161             }
    162 
    163             itemsIndexed(stats.walletRequest) { i, stat ->
    164                 PerformanceStatItem(i + 1, stat)
    165             }
    166         }
    167 
    168         if (stats.walletTask.isNotEmpty()) {
    169             stickyHeader {
    170                 SectionHeader { Text(stringResource(R.string.performance_stats_wallet_task)) }
    171             }
    172 
    173             itemsIndexed(stats.walletTask) { i, stat ->
    174                 PerformanceStatItem(i + 1, stat)
    175             }
    176         }
    177 
    178         item {
    179             BottomInsetsSpacer()
    180         }
    181     }
    182 }
    183 
    184 @Composable
    185 fun PerformanceStatItem(
    186     ranking: Int,
    187     stat: PerformanceStat,
    188 ) {
    189     ListItem(
    190         leadingContent = {
    191             Text(
    192                 text = stringResource(R.string.ranking, ranking),
    193                 fontWeight = FontWeight.ExtraBold,
    194                 style = MaterialTheme.typography.titleLarge,
    195             )
    196         },
    197 
    198         headlineContent = {
    199             Row(
    200                 modifier = Modifier.fillMaxWidth(),
    201                 verticalAlignment = Alignment.CenterVertically,
    202 
    203                 ) {
    204                 Box(Modifier.weight(1f, fill = false)) {
    205                     when (stat) {
    206                         is PerformanceStat.HttpFetch -> Text(
    207                             text = stat.url,
    208                             style = MaterialTheme.typography.bodySmall,
    209                         )
    210 
    211                         is PerformanceStat.DbQuery -> Text(stat.name)
    212                         is PerformanceStat.Crypto -> Text(stat.operation)
    213                         is PerformanceStat.WalletRequest -> Text(stat.operation)
    214                         is PerformanceStat.WalletTask -> Text(
    215                             text = stat.taskId,
    216                             style = MaterialTheme.typography.bodySmall,
    217                             fontFamily = FontFamily.Monospace,
    218                         )
    219                     }
    220                 }
    221 
    222                 Badge(Modifier.padding(start = 10.dp)) {
    223                     Text(stat.count.toString())
    224                 }
    225             }
    226         },
    227 
    228         supportingContent = {
    229             when(stat) {
    230                 is PerformanceStat.DbQuery -> Text(
    231                     text = stat.location,
    232                     style = MaterialTheme.typography.bodySmall,
    233                     fontFamily = FontFamily.Monospace,
    234                 )
    235                 else -> {}
    236             }
    237         },
    238 
    239         trailingContent = {
    240             Text(stringResource(R.string.millisecond, stat.maxDurationMs))
    241         },
    242     )
    243 }