taler-android

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

PerformanceFragment.kt (8224B)


      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 android.os.Bundle
     20 import android.view.LayoutInflater
     21 import android.view.View
     22 import android.view.ViewGroup
     23 import androidx.compose.foundation.layout.Arrangement
     24 import androidx.compose.foundation.layout.Box
     25 import androidx.compose.foundation.layout.Row
     26 import androidx.compose.foundation.layout.Spacer
     27 import androidx.compose.foundation.layout.fillMaxWidth
     28 import androidx.compose.foundation.layout.padding
     29 import androidx.compose.foundation.layout.size
     30 import androidx.compose.foundation.lazy.LazyColumn
     31 import androidx.compose.foundation.lazy.itemsIndexed
     32 import androidx.compose.material.icons.Icons
     33 import androidx.compose.material.icons.filled.Refresh
     34 import androidx.compose.material3.Badge
     35 import androidx.compose.material3.Button
     36 import androidx.compose.material3.ButtonDefaults
     37 import androidx.compose.material3.Icon
     38 import androidx.compose.material3.ListItem
     39 import androidx.compose.material3.MaterialTheme
     40 import androidx.compose.material3.Text
     41 import androidx.compose.runtime.Composable
     42 import androidx.compose.runtime.getValue
     43 import androidx.compose.runtime.remember
     44 import androidx.compose.ui.Alignment
     45 import androidx.compose.ui.Modifier
     46 import androidx.compose.ui.platform.ComposeView
     47 import androidx.compose.ui.res.stringResource
     48 import androidx.compose.ui.text.font.FontFamily
     49 import androidx.compose.ui.text.font.FontWeight
     50 import androidx.compose.ui.unit.dp
     51 import androidx.fragment.app.Fragment
     52 import androidx.fragment.app.activityViewModels
     53 import kotlinx.serialization.json.Json
     54 import net.taler.wallet.BottomInsetsSpacer
     55 import net.taler.wallet.R
     56 import net.taler.wallet.balances.SectionHeader
     57 import net.taler.wallet.compose.EmptyComposable
     58 import net.taler.wallet.compose.ShareButton
     59 import net.taler.wallet.compose.TalerSurface
     60 import net.taler.wallet.compose.collectAsStateLifecycleAware
     61 import net.taler.wallet.main.MainViewModel
     62 
     63 class PerformanceFragment: Fragment() {
     64     private val model: MainViewModel by activityViewModels()
     65 
     66     override fun onCreateView(
     67         inflater: LayoutInflater,
     68         container: ViewGroup?,
     69         savedInstanceState: Bundle?
     70     ) = ComposeView(requireContext()).apply {
     71         setContent {
     72             TalerSurface {
     73                 val stats by model.settingsManager.performanceTable.collectAsStateLifecycleAware()
     74 
     75                 if (stats == null) {
     76                     EmptyComposable()
     77                     return@TalerSurface
     78                 }
     79 
     80                 stats?.let {
     81                     PerformanceTableComposable(it,
     82                         onReload = { model.settingsManager.loadPerformanceStats() },
     83                     )
     84                 }
     85             }
     86         }
     87     }
     88 
     89     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     90         super.onViewCreated(view, savedInstanceState)
     91         model.settingsManager.loadPerformanceStats()
     92     }
     93 }
     94 
     95 @Composable
     96 fun PerformanceTableComposable(
     97     stats: PerformanceTable,
     98     onReload: () -> Unit,
     99 ) {
    100     val json = remember { Json {
    101         prettyPrint = true
    102         ignoreUnknownKeys = true
    103         coerceInputValues = true
    104     } }
    105 
    106     LazyColumn {
    107         item {
    108             Row(
    109                 modifier = Modifier
    110                     .fillMaxWidth()
    111                     .padding(16.dp),
    112                 verticalAlignment = Alignment.CenterVertically,
    113                 horizontalArrangement = Arrangement.SpaceAround,
    114             ) {
    115                 ShareButton(json.encodeToString(stats))
    116 
    117                 Button(onClick = onReload) {
    118                     Icon(
    119                         Icons.Default.Refresh,
    120                         contentDescription = null,
    121                         modifier = Modifier.size(ButtonDefaults.IconSize),
    122                     )
    123                     Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    124                     Text(stringResource(R.string.reload))
    125                 }
    126             }
    127         }
    128 
    129         if (stats.httpFetch.isNotEmpty()) {
    130             stickyHeader {
    131                 SectionHeader { Text(stringResource(R.string.performance_stats_http_fetch)) }
    132             }
    133 
    134             itemsIndexed(stats.httpFetch) { i, stat ->
    135                 PerformanceStatItem(i + 1, stat)
    136             }
    137         }
    138 
    139         if (stats.dbQuery.isNotEmpty()) {
    140             stickyHeader {
    141                 SectionHeader { Text(stringResource(R.string.performance_stats_db_query)) }
    142             }
    143 
    144             itemsIndexed(stats.dbQuery) { i, stat ->
    145                 PerformanceStatItem(i + 1, stat)
    146             }
    147         }
    148 
    149 
    150         if (stats.crypto.isNotEmpty()) {
    151             stickyHeader {
    152                 SectionHeader { Text(stringResource(R.string.performance_stats_crypto)) }
    153             }
    154 
    155             itemsIndexed(stats.crypto) { i, stat ->
    156                 PerformanceStatItem(i + 1, stat)
    157             }
    158         }
    159 
    160         if (stats.walletRequest.isNotEmpty()) {
    161             stickyHeader {
    162                 SectionHeader { Text(stringResource(R.string.performance_stats_wallet_request)) }
    163             }
    164 
    165             itemsIndexed(stats.walletRequest) { i, stat ->
    166                 PerformanceStatItem(i + 1, stat)
    167             }
    168         }
    169 
    170         if (stats.walletTask.isNotEmpty()) {
    171             stickyHeader {
    172                 SectionHeader { Text(stringResource(R.string.performance_stats_wallet_task)) }
    173             }
    174 
    175             itemsIndexed(stats.walletTask) { i, stat ->
    176                 PerformanceStatItem(i + 1, stat)
    177             }
    178         }
    179 
    180         item {
    181             BottomInsetsSpacer()
    182         }
    183     }
    184 }
    185 
    186 @Composable
    187 fun PerformanceStatItem(
    188     ranking: Int,
    189     stat: PerformanceStat,
    190 ) {
    191     ListItem(
    192         leadingContent = {
    193             Text(
    194                 text = stringResource(R.string.ranking, ranking),
    195                 fontWeight = FontWeight.ExtraBold,
    196                 style = MaterialTheme.typography.titleLarge,
    197             )
    198         },
    199 
    200         headlineContent = {
    201             Row(
    202                 modifier = Modifier.fillMaxWidth(),
    203                 verticalAlignment = Alignment.CenterVertically,
    204 
    205             ) {
    206                 Box(Modifier.weight(1f, fill = false)) {
    207                     when (stat) {
    208                         is PerformanceStat.HttpFetch -> Text(
    209                             text = stat.url,
    210                             style = MaterialTheme.typography.bodySmall,
    211                         )
    212 
    213                         is PerformanceStat.DbQuery -> Text(stat.name)
    214                         is PerformanceStat.Crypto -> Text(stat.operation)
    215                         is PerformanceStat.WalletRequest -> Text(stat.operation)
    216                         is PerformanceStat.WalletTask -> Text(
    217                             text = stat.taskId,
    218                             style = MaterialTheme.typography.bodySmall,
    219                             fontFamily = FontFamily.Monospace,
    220                         )
    221                     }
    222                 }
    223 
    224                 Badge(Modifier.padding(start = 10.dp)) {
    225                     Text(stat.count.toString())
    226                 }
    227             }
    228         },
    229 
    230         supportingContent = {
    231             when(stat) {
    232                 is PerformanceStat.DbQuery -> Text(
    233                     text = stat.location,
    234                     style = MaterialTheme.typography.bodySmall,
    235                     fontFamily = FontFamily.Monospace,
    236                 )
    237                 else -> {}
    238             }
    239         },
    240 
    241         trailingContent = {
    242             Text(stringResource(R.string.millisecond, stat.maxDurationMs))
    243         },
    244     )
    245 }