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 }