Coverage Summary for Class: SurferMemberRowKt (com.georgeci.moneysurfer.uikit.components.workspace)

Class Class, % Method, % Branch, % Line, % Instruction, %
SurferMemberRowKt 0% (0/1) 0% (0/7) 0% (0/18) 0% (0/58) 0% (0/682)


 package com.georgeci.moneysurfer.uikit.components.workspace
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.georgeci.moneysurfer.uikit.atom.SurferCard
 import com.georgeci.moneysurfer.uikit.icons.SurferIcons
 import com.georgeci.moneysurfer.uikit.preview.SurferComponentPreview
 import com.georgeci.moneysurfer.uikit.semantics.SurferSemantics
 import com.georgeci.moneysurfer.uikit.theme.AppTheme
 
 /**
  * A single member row for the Members list: colored initial avatar, name/role pair, and an
  * outlined role chip at the trailing edge. Tint of the avatar is a stable hash of the initial.
  *
  * When [isYou] is true a small "you" tag sits next to the name; when [secondaryLine] is given
  * (typically the email) it replaces the role-label subtitle while the chip on the trailing edge
  * still surfaces the role.
  */
 @Composable
 fun SurferMemberRow(
     name: String,
     initial: String,
     roleLabel: String,
     modifier: Modifier = Modifier,
     onClick: (() -> Unit)? = null,
     isYou: Boolean = false,
     secondaryLine: String? = null,
 ) {
     SurferCard(
         modifier = modifier.fillMaxWidth(),
         onClick = onClick,
     ) {
         Row(
             verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.spacedBy(AppTheme.spacing.medium),
         ) {
             Box(
                 modifier = Modifier
                     .size(40.dp)
                     .clip(CircleShape)
                     .background(memberTint(initial)),
                 contentAlignment = Alignment.Center,
             ) {
                 Text(
                     text = initial,
                     style = AppTheme.typography.titleSmall,
                     color = Color.White,
                 )
             }
             Column(Modifier.weight(1f)) {
                 Row(
                     verticalAlignment = Alignment.CenterVertically,
                     horizontalArrangement = Arrangement.spacedBy(6.dp),
                 ) {
                     Text(
                         text = name,
                         style = AppTheme.typography.bodyLarge,
                         color = AppTheme.materialColors.onSurface,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis,
                         modifier = Modifier.weight(1f, fill = false),
                     )
                     if (isYou) {
                         YouTag()
                     }
                 }
                 Text(
                     text = secondaryLine ?: roleLabel,
                     style = AppTheme.typography.bodySmall,
                     color = AppTheme.materialColors.onSurfaceVariant,
                     maxLines = 1,
                     overflow = TextOverflow.Ellipsis,
                     modifier = Modifier.padding(top = 2.dp),
                 )
             }
             RoleChip(label = roleLabel)
         }
     }
 }
 
 @Composable
 private fun YouTag() {
     Box(
         modifier = Modifier
             .clip(RoundedCornerShape(6.dp))
             .background(AppTheme.materialColors.surfaceContainerHigh)
             .padding(horizontal = 6.dp, vertical = 1.dp),
     ) {
         Text(
             text = "you",
             style = AppTheme.typography.labelSmall,
             color = AppTheme.materialColors.onSurfaceVariant,
         )
     }
 }
 
 @Composable
 private fun RoleChip(label: String) {
     val outlineVariant = AppTheme.materialColors.outlineVariant
     Row(
         modifier = Modifier
             .height(30.dp)
             .clip(RoundedCornerShape(15.dp))
             .drawBehind {
                 drawRoundRect(
                     color = outlineVariant,
                     style = Stroke(width = 1.dp.toPx()),
                     cornerRadius = CornerRadius(15.dp.toPx(), 15.dp.toPx()),
                 )
             }
             .padding(horizontal = 10.dp),
         verticalAlignment = Alignment.CenterVertically,
         horizontalArrangement = Arrangement.spacedBy(4.dp),
     ) {
         Text(
             text = label,
             style = AppTheme.typography.labelMedium,
             color = AppTheme.materialColors.onSurfaceVariant,
         )
         Icon(
             imageVector = SurferIcons.DropDown,
             contentDescription = SurferSemantics.Decorative,
             tint = AppTheme.materialColors.onSurfaceVariant,
             modifier = Modifier.size(14.dp),
         )
     }
 }
 
 private val MemberHues = listOf(
     Color(0xFF9B76B5),
     Color(0xFF5F7DC9),
     Color(0xFF4D9075),
     Color(0xFFC07856),
     Color(0xFFC5708F),
     Color(0xFF7B9556),
 )
 
 private fun memberTint(initial: String): Color {
     if (initial.isEmpty()) return MemberHues[0]
     val hash = initial.fold(0) { acc, c -> acc * 31 + c.code }
     val idx = ((hash % MemberHues.size) + MemberHues.size) % MemberHues.size
     return MemberHues[idx]
 }
 
 @Preview
 @Composable
 private fun SurferMemberRowPreview() {
     SurferComponentPreview {
         Column(
             modifier = Modifier.padding(16.dp),
             verticalArrangement = Arrangement.spacedBy(12.dp),
         ) {
             SurferMemberRow(name = "Kasia M.", initial = "K", roleLabel = "Owner", isYou = true)
             SurferMemberRow(name = "Julian P.", initial = "J", roleLabel = "Editor")
             SurferMemberRow(name = "Asia W.", initial = "A", roleLabel = "Editor")
             SurferMemberRow(name = "Tom N.", initial = "T", roleLabel = "Viewer")
         }
     }
 }