diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 5681485..1afcdaa 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -22,5 +22,21 @@ } ], "elementType": "File", + "baselineProfiles": [ + { + "minApi": 28, + "maxApi": 30, + "baselineProfiles": [ + "baselineProfiles/1/app-arm64-v8a-release.dm" + ] + }, + { + "minApi": 31, + "maxApi": 2147483647, + "baselineProfiles": [ + "baselineProfiles/0/app-arm64-v8a-release.dm" + ] + } + ], "minSdkVersionForDexing": 26 } \ No newline at end of file diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/common/ContactAvatar.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/common/ContactAvatar.kt new file mode 100644 index 0000000..eee9bda --- /dev/null +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/common/ContactAvatar.kt @@ -0,0 +1,57 @@ +package com.bitcointxoko.gudariwallet.ui.common + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.abs + +// ── Contact avatar ─────────────────────────────────────────── + +private fun String.toHslColor( + saturation: Float = 0.5f, + lightness: Float = 0.4f +): Color { + val hue = fold(0) { acc, char -> char.code + acc * 37 } % 360 + return Color.hsl(abs(hue).toFloat(), saturation, lightness) +} + +@Composable +fun ContactAvatar( + name: String, + modifier: Modifier = Modifier, + size: Dp = 40.dp +) { + val bgColor = remember(name) { + name.toHslColor() + } + val initial = name.firstOrNull()?.uppercaseChar()?.toString() ?: "?" + Box( + modifier = modifier + .size(size) + .clip(CircleShape), + contentAlignment = Alignment.Center + ) { + Canvas(modifier = Modifier.fillMaxSize()) { + drawCircle(SolidColor(bgColor)) + } + Text( + text = initial, + color = Color.White, + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold) + ) + } +} diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailScreen.kt index 696a8ae..d4353cd 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailScreen.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowUpward import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.PersonAdd import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator @@ -55,6 +56,7 @@ import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.data.db.PaymentAddressType import com.bitcointxoko.gudariwallet.ui.DetailState import com.bitcointxoko.gudariwallet.ui.common.AmountWithFiatColumn +import com.bitcointxoko.gudariwallet.ui.common.ContactAvatar import com.bitcointxoko.gudariwallet.ui.common.CopyIconButton import com.bitcointxoko.gudariwallet.ui.common.DetailRow import com.bitcointxoko.gudariwallet.ui.common.DetailSection @@ -303,37 +305,73 @@ private fun PaymentDetailContent( // ── Contact section ──────────────────────────────────────────────────── item { - DetailSection(title = "Contact") { - if (linkedContacts.isEmpty()) { - // No contact linked — show assign button - OutlinedButton( - onClick = onAssignContact, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - Icon( - imageVector = Icons.Default.PersonAdd, - contentDescription = null, - modifier = Modifier.size(18.dp) - ) - Spacer(Modifier.width(6.dp)) - Text("Assign contact") - } - } else { - // Show each linked contact with a remove button - linkedContacts.forEach { contact -> - val name = contact.localAlias - ?: contact.displayName - ?: contact.name - ?: "Unknown" - DetailRow( - label = "Contact", - value = name, - trailingContent = { + if (linkedContacts.isEmpty()) { + // No contacts — just show an assign button + OutlinedButton( + onClick = onAssignContact, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + imageVector = Icons.Default.PersonAdd, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Spacer(Modifier.width(6.dp)) + Text("Assign contact") + } + } else { + // Show contacts with avatar + name, plus edit button + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + // Header row: title + edit button + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Contacts", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + IconButton( + onClick = onAssignContact, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = "Edit contacts", + modifier = Modifier.size(18.dp), + tint = MaterialTheme.colorScheme.primary + ) + } + } + + Spacer(Modifier.height(8.dp)) + + // Each linked contact: avatar + name + remove + linkedContacts.forEachIndexed { index, contact -> + val name = contact.localAlias + ?: contact.displayName + ?: contact.name + ?: "Unknown" + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + ContactAvatar(name = name) + Spacer(Modifier.width(12.dp)) + Text( + text = name, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.weight(1f) + ) IconButton( onClick = { onUnassignContact(contact.id) }, - modifier = Modifier.size(32.dp) + modifier = Modifier.size(28.dp) ) { Icon( imageVector = Icons.Default.Close, @@ -343,36 +381,20 @@ private fun PaymentDetailContent( ) } } - ) - } - // Allow assigning additional contacts - HorizontalDivider( - modifier = Modifier.padding(horizontal = 16.dp), - thickness = 0.5.dp - ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 4.dp), - horizontalArrangement = Arrangement.End - ) { - androidx.compose.material3.TextButton(onClick = onAssignContact) { - Icon( - imageVector = Icons.Default.PersonAdd, - contentDescription = null, - modifier = Modifier.size(16.dp) - ) - Spacer(Modifier.width(4.dp)) - Text( - text = "Add another", - style = MaterialTheme.typography.labelMedium - ) + + if (index < linkedContacts.lastIndex) { + HorizontalDivider( + thickness = 0.5.dp, + color = MaterialTheme.colorScheme.outlineVariant + ) + } } } } } } + // ── Basic info ───────────────────────────────────────────────────────── item { DetailSection(title = strings.history.detailSectionDetails) {