contact details ui
This commit is contained in:
@@ -22,5 +22,21 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"elementType": "File",
|
"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
|
"minSdkVersionForDexing": 26
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.ArrowDownward
|
||||||
import androidx.compose.material.icons.filled.ArrowUpward
|
import androidx.compose.material.icons.filled.ArrowUpward
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.PersonAdd
|
import androidx.compose.material.icons.filled.PersonAdd
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
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.data.db.PaymentAddressType
|
||||||
import com.bitcointxoko.gudariwallet.ui.DetailState
|
import com.bitcointxoko.gudariwallet.ui.DetailState
|
||||||
import com.bitcointxoko.gudariwallet.ui.common.AmountWithFiatColumn
|
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.CopyIconButton
|
||||||
import com.bitcointxoko.gudariwallet.ui.common.DetailRow
|
import com.bitcointxoko.gudariwallet.ui.common.DetailRow
|
||||||
import com.bitcointxoko.gudariwallet.ui.common.DetailSection
|
import com.bitcointxoko.gudariwallet.ui.common.DetailSection
|
||||||
@@ -303,14 +305,11 @@ private fun PaymentDetailContent(
|
|||||||
|
|
||||||
// ── Contact section ────────────────────────────────────────────────────
|
// ── Contact section ────────────────────────────────────────────────────
|
||||||
item {
|
item {
|
||||||
DetailSection(title = "Contact") {
|
|
||||||
if (linkedContacts.isEmpty()) {
|
if (linkedContacts.isEmpty()) {
|
||||||
// No contact linked — show assign button
|
// No contacts — just show an assign button
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = onAssignContact,
|
onClick = onAssignContact,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp)
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.PersonAdd,
|
imageVector = Icons.Default.PersonAdd,
|
||||||
@@ -321,19 +320,58 @@ private fun PaymentDetailContent(
|
|||||||
Text("Assign contact")
|
Text("Assign contact")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show each linked contact with a remove button
|
// Show contacts with avatar + name, plus edit button
|
||||||
linkedContacts.forEach { contact ->
|
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
|
val name = contact.localAlias
|
||||||
?: contact.displayName
|
?: contact.displayName
|
||||||
?: contact.name
|
?: contact.name
|
||||||
?: "Unknown"
|
?: "Unknown"
|
||||||
DetailRow(
|
|
||||||
label = "Contact",
|
Row(
|
||||||
value = name,
|
modifier = Modifier
|
||||||
trailingContent = {
|
.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(
|
IconButton(
|
||||||
onClick = { onUnassignContact(contact.id) },
|
onClick = { onUnassignContact(contact.id) },
|
||||||
modifier = Modifier.size(32.dp)
|
modifier = Modifier.size(28.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Default.Close,
|
||||||
@@ -343,35 +381,19 @@ private fun PaymentDetailContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
if (index < linkedContacts.lastIndex) {
|
||||||
// Allow assigning additional contacts
|
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
thickness = 0.5.dp,
|
||||||
thickness = 0.5.dp
|
color = MaterialTheme.colorScheme.outlineVariant
|
||||||
)
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── Basic info ─────────────────────────────────────────────────────────
|
// ── Basic info ─────────────────────────────────────────────────────────
|
||||||
item {
|
item {
|
||||||
|
|||||||
Reference in New Issue
Block a user