temp #1

Merged
ankit merged 2 commits from temp into dev 2025-12-21 09:59:25 +00:00
18 changed files with 2423 additions and 2118 deletions

View File

@ -1 +0,0 @@
LivingAi_Lg

View File

@ -27,6 +27,7 @@ class UserNotFoundException(
class AuthApiClient(private val context: Context) {
private val route = "http://10.0.2.2:3000/"
private val tokenManager = TokenManager(context)
val client = HttpClient(CIO) {
@ -64,7 +65,7 @@ class AuthApiClient(private val context: Context) {
android.util.Log.d("AuthApiClient", "refreshTokens: Calling /auth/refresh endpoint")
try {
val response: RefreshResponse = client.post("http://10.0.2.2:3000/auth/refresh") {
val response: RefreshResponse = client.post("${route}auth/refresh") {
markAsRefreshTokenRequest()
contentType(ContentType.Application.Json)
setBody(RefreshRequest(refreshToken))
@ -84,7 +85,7 @@ class AuthApiClient(private val context: Context) {
}
defaultRequest {
url("http://10.0.2.2:3000/")
url(route)
}
}

View File

@ -1,87 +1,87 @@
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.models.AnimalType
@Composable
fun AnimalTypeSelector(
animalTypes: List<AnimalType>,
selectedAnimalType: MutableState<String?>,
onAnimalTypeSelected: (String) -> Unit
) {
val selectedAnimalType: String = selectedAnimalType.value ?: ""
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(animalTypes.size) { index ->
AnimalTypeButton(
animalType = animalTypes[index],
isSelected = selectedAnimalType == animalTypes[index].id,
onClick = { onAnimalTypeSelected(animalTypes[index].id) }
)
}
}
}
@Composable
private fun AnimalTypeButton(
animalType: AnimalType,
isSelected: Boolean,
onClick: () -> Unit
) {
Column(
modifier = Modifier
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick
)
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Box(
modifier = Modifier
.size(48.dp)
.background(
if (isSelected) Color(0xFFEDE9FE) else Color.White,
RoundedCornerShape(24.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = animalType.emoji,
fontSize = 24.sp
)
}
Text(
text = animalType.name,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.models.AnimalType
@Composable
fun AnimalTypeSelector(
animalTypes: List<AnimalType>,
selectedAnimalType: String?,
onAnimalTypeSelected: (String) -> Unit
) {
val selectedAnimalType: String = selectedAnimalType ?: ""
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(animalTypes.size) { index ->
AnimalTypeButton(
animalType = animalTypes[index],
isSelected = selectedAnimalType == animalTypes[index].id,
onClick = { onAnimalTypeSelected(animalTypes[index].id) }
)
}
}
}
@Composable
private fun AnimalTypeButton(
animalType: AnimalType,
isSelected: Boolean,
onClick: () -> Unit
) {
Column(
modifier = Modifier
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick
)
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Box(
modifier = Modifier
.size(48.dp)
.background(
if (isSelected) Color(0xFFEDE9FE) else Color.White,
RoundedCornerShape(24.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = animalType.emoji,
fontSize = 24.sp
)
}
Text(
text = animalType.name,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}

View File

@ -1,93 +1,103 @@
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
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.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.models.BottomNavItemData
@Composable
fun BottomNavigationBar(
modifier: Modifier = Modifier,
items: List<BottomNavItemData>,
currentItem: String,
onItemClick: (route: String) -> Unit = {}
) {
Column(
modifier = modifier
.fillMaxWidth()
.background(Color.White)
.border(1.dp, Color(0xFF000000).copy(alpha = 0.1f))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(66.dp)
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
items.forEach { item ->
val isSelected = item.label == currentItem
BottomNavItem(
label = item.label,
iconRes = item.iconRes,
selected = isSelected,
onClick = {
if (!isSelected) {
onItemClick(item.route)
}
}
)
}
}
}
}
@Composable
fun BottomNavItem(
label: String,
iconRes: Int,
selected: Boolean,
onClick: () -> Unit = {}
) {
val color = if (selected) Color(0xFF1E88E5) else Color(0xFF0A0A0A)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.padding(vertical = 4.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick)
) {
Icon(
painter = painterResource(iconRes),
contentDescription = label,
tint = color,
modifier = Modifier.size(24.dp)
)
Text(
text = label,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = color
)
}
}
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.models.BottomNavItemData
@Composable
fun BottomNavigationBar(
modifier: Modifier = Modifier,
items: List<BottomNavItemData>,
currentItem: String,
onItemClick: (route: String) -> Unit = {}
) {
Column(
modifier = modifier
.fillMaxWidth()
.background(Color.White)
.border(1.dp, Color(0xFF000000).copy(alpha = 0.1f))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(66.dp)
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
items.forEach { item ->
val isSelected = item.label == currentItem
BottomNavItem(
label = item.label,
iconRes = item.iconRes,
selected = isSelected,
onClick = {
if (!isSelected) {
onItemClick(item.route)
}
}
)
}
}
}
}
@Composable
fun BottomNavItem(
label: String,
iconRes: Any,
selected: Boolean,
onClick: () -> Unit = {}
) {
val color = if (selected) Color(0xFF1E88E5) else Color(0xFF0A0A0A)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.padding(vertical = 4.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick)
) {
if(iconRes is Int) {
Icon(
painter = painterResource(iconRes),
contentDescription = label,
tint = color,
modifier = Modifier.size(24.dp)
)
} else if(iconRes is ImageVector) {
Icon(
imageVector = iconRes,
contentDescription = label,
tint = color,
modifier = Modifier.size(24.dp)
)
}
Text(
text = label,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = color
)
}
}

View File

@ -1,117 +1,118 @@
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.theme.AppTypography
import com.example.livingai_lg.ui.theme.FarmTextDark
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DropdownInput(
label: String? = null, // optional label
selected: String,
options: List<String>,
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
onSelect: (String) -> Unit,
placeholder: String = "Select", // NEW - custom placeholder
modifier: Modifier = Modifier // NEW - allows width control
) {
Column(
modifier = modifier, // <-- now caller can control width
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Optional label
if (label != null) {
Text(
text = label,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium,
color = FarmTextDark
)
} else {
// Reserve label space so layout doesnt shift
Spacer(modifier = Modifier.height(20.dp)) // ← same height as label text line
}
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { onExpandedChange(!expanded) }
) {
// Anchor box
Box(
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
.height(52.dp)
.shadow(2.dp, RoundedCornerShape(16.dp))
.background(Color.White, RoundedCornerShape(16.dp))
.border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) {
onExpandedChange(true)
}
.padding(horizontal = 16.dp),
contentAlignment = Alignment.CenterStart
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Custom placeholder support
Text(
text = selected.ifEmpty { placeholder },
fontSize = AppTypography.Body,
color = if (selected.isEmpty()) Color(0xFF99A1AF) else FarmTextDark
)
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = "Dropdown",
tint = FarmTextDark
)
}
}
// Material3 Dropdown
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { onExpandedChange(false) },
modifier = Modifier.background(Color.White)
) {
options.forEach { item ->
DropdownMenuItem(
text = {
Text(item, fontSize = AppTypography.Body, color = FarmTextDark)
},
onClick = {
onSelect(item)
onExpandedChange(false)
}
)
}
}
}
}
}
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.theme.AppTypography
import com.example.livingai_lg.ui.theme.FarmTextDark
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DropdownInput(
label: String? = null, // optional label
selected: String,
options: List<String>,
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
onSelect: (String) -> Unit,
placeholder: String = "Select", // NEW - custom placeholder
textColor: Color = Color.Black,
modifier: Modifier = Modifier // NEW - allows width control
) {
Column(
modifier = modifier, // <-- now caller can control width
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Optional label
// if (label != null) {
Text(
text = label?:" ",
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium,
color = FarmTextDark
)
// } else {
// // Reserve label space so layout doesnt shift
// Spacer(modifier = Modifier.height(20.dp)) // ← same height as label text line
// }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { onExpandedChange(!expanded) }
) {
// Anchor box
Box(
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
.height(52.dp)
.shadow(2.dp, RoundedCornerShape(16.dp))
.background(Color.White, RoundedCornerShape(16.dp))
.border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) {
onExpandedChange(true)
}
.padding(horizontal = 16.dp),
contentAlignment = Alignment.CenterStart
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Custom placeholder support
Text(
text = selected.ifEmpty { placeholder },
fontSize = AppTypography.Body,
color = if (selected.isEmpty()) Color(0xFF99A1AF) else textColor
)
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = "Dropdown",
tint = FarmTextDark
)
}
}
// Material3 Dropdown
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { onExpandedChange(false) },
modifier = Modifier.background(Color.White)
) {
options.forEach { item ->
DropdownMenuItem(
text = {
Text(item, fontSize = AppTypography.Body, color = FarmTextDark)
},
onClick = {
onSelect(item)
onExpandedChange(false)
}
)
}
}
}
}
}

View File

@ -1,74 +1,77 @@
package com.example.livingai_lg.ui.components
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.livingai_lg.ui.screens.FilterScreen
@Composable
fun FilterOverlay(
visible: Boolean,
onDismiss: () -> Unit,
onSubmitClick: () -> Unit = {},
) {
BackHandler(enabled = visible) { onDismiss() }
Box(
modifier = Modifier.fillMaxSize()
) {
// Dimmed background
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.35f))
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onDismiss() }
)
}
// Sliding panel
AnimatedVisibility(
visible = visible,
enter = slideInHorizontally(
initialOffsetX = { it } // from right
),
exit = slideOutHorizontally(
targetOffsetX = { it } // to right
),
modifier = Modifier
.fillMaxHeight()
.align(Alignment.CenterEnd)
) {
FilterScreen(
onBackClick = onDismiss,
onCancelClick = onDismiss,
onSubmitClick = {
onSubmitClick()
onDismiss()
}
)
}
}
}
package com.example.livingai_lg.ui.components
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.livingai_lg.ui.models.FiltersState
import com.example.livingai_lg.ui.screens.FilterScreen
@Composable
fun FilterOverlay(
visible: Boolean,
appliedFilters: FiltersState,
onDismiss: () -> Unit,
onSubmitClick: (filters: FiltersState) -> Unit = {},
) {
BackHandler(enabled = visible) { onDismiss() }
Box(
modifier = Modifier.fillMaxSize()
) {
// Dimmed background
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.35f))
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onDismiss() }
)
}
// Sliding panel
AnimatedVisibility(
visible = visible,
enter = slideInHorizontally(
initialOffsetX = { it } // from right
),
exit = slideOutHorizontally(
targetOffsetX = { it } // to right
),
modifier = Modifier
.fillMaxHeight()
.align(Alignment.CenterEnd)
) {
FilterScreen(
appliedFilters,
onBackClick = onDismiss,
onCancelClick = onDismiss,
onSubmitClick = { filters ->
onSubmitClick(filters)
onDismiss()
}
)
}
}
}

View File

@ -1,137 +1,142 @@
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.roundToInt
@Composable
fun RangeFilter(
modifier: Modifier = Modifier,
label: String,
min: Int,
max: Int,
valueFrom: Int,
valueTo: Int,
onValueChange: (from: Int, to: Int) -> Unit,
showSlider: Boolean = true,
valueFormatter: (Int) -> String = { it.toString() }
) {
var fromValue by remember(valueFrom) { mutableStateOf(valueFrom) }
var toValue by remember(valueTo) { mutableStateOf(valueTo) }
Column(modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp)) {
// Label
Text(
text = label,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF364153)
)
// Slider (optional)
if (showSlider) {
RangeSlider(
value = fromValue.toFloat()..toValue.toFloat(),
onValueChange = { range ->
fromValue = range.start.roundToInt()
.coerceIn(min, toValue)
toValue = range.endInclusive.roundToInt()
.coerceIn(fromValue, max)
onValueChange(fromValue, toValue)
},
valueRange = min.toFloat()..max.toFloat(),
colors = SliderDefaults.colors(
thumbColor = Color(0xFFD9D9D9),
activeTrackColor = Color(0xFFD9D9D9),
inactiveTrackColor = Color(0xFFE5E7EB)
)
)
}
// Pills
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
RangePill(
modifier = Modifier.weight(1f),
value = fromValue,
onValueChange = { newFrom ->
val safeFrom = newFrom.coerceIn(min, toValue)
fromValue = safeFrom
onValueChange(safeFrom, toValue)
},
formatter = valueFormatter
)
Text("to", fontSize = 15.sp)
RangePill(
modifier = Modifier.weight(1f),
value = toValue,
onValueChange = { newTo ->
val safeTo = newTo.coerceIn(fromValue, max)
toValue = safeTo
onValueChange(fromValue, safeTo)
},
formatter = valueFormatter
)
}
}
}
@Composable
private fun RangePill(
modifier: Modifier = Modifier,
value: Int,
onValueChange: (Int) -> Unit,
formatter: (Int) -> String
) {
var text by remember(value) {
mutableStateOf(formatter(value))
}
Box(
modifier = modifier
.height(30.dp)
.background(Color.White, RoundedCornerShape(16.dp))
.border(1.dp, Color(0x12000000), RoundedCornerShape(16.dp))
.padding(horizontal = 8.dp),
contentAlignment = Alignment.Center
) {
BasicTextField(
value = text,
onValueChange = { input ->
val digits = input.filter { it.isDigit() }
text = digits
digits.toIntOrNull()?.let(onValueChange)
},
singleLine = true,
textStyle = TextStyle(
fontSize = 14.sp,
color = Color(0xFF99A1AF)
),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
}
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.roundToInt
@Composable
fun RangeFilter(
modifier: Modifier = Modifier,
label: String,
min: Int,
max: Int,
valueFrom: Int,
valueTo: Int,
modified: Boolean = false,
onValueChange: (from: Int, to: Int) -> Unit,
showSlider: Boolean = true,
valueFormatter: (Int) -> String = { it.toString() }
) {
var fromValue by remember(valueFrom) { mutableStateOf(valueFrom) }
var toValue by remember(valueTo) { mutableStateOf(valueTo) }
Column(modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp)) {
// Label
Text(
text = label,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF364153)
)
// Slider (optional)
if (showSlider) {
RangeSlider(
value = fromValue.toFloat()..toValue.toFloat(),
onValueChange = { range ->
fromValue = range.start.roundToInt()
.coerceIn(min, toValue)
toValue = range.endInclusive.roundToInt()
.coerceIn(fromValue, max)
onValueChange(fromValue, toValue)
},
valueRange = min.toFloat()..max.toFloat(),
colors = SliderDefaults.colors(
thumbColor = Color(0xFFD9D9D9),
activeTrackColor = Color(0xFFD9D9D9),
inactiveTrackColor = Color(0xFFE5E7EB)
)
)
}
// Pills
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
RangePill(
modifier = Modifier.weight(1f),
value = fromValue,
modified = modified,
onValueChange = { newFrom ->
val safeFrom = newFrom.coerceIn(min, toValue)
fromValue = safeFrom
onValueChange(safeFrom, toValue)
},
formatter = valueFormatter
)
Text("to", fontSize = 15.sp)
RangePill(
modifier = Modifier.weight(1f),
value = toValue,
modified = modified,
onValueChange = { newTo ->
val safeTo = newTo.coerceIn(fromValue, max)
toValue = safeTo
onValueChange(fromValue, safeTo)
},
formatter = valueFormatter
)
}
}
}
@Composable
private fun RangePill(
modifier: Modifier = Modifier,
value: Int,
modified: Boolean = false,
onValueChange: (Int) -> Unit,
formatter: (Int) -> String
) {
var text by remember(value) {
mutableStateOf(formatter(value))
}
Box(
modifier = modifier
.height(30.dp)
.background(Color.White, RoundedCornerShape(16.dp))
.border(1.dp, Color(0x12000000), RoundedCornerShape(16.dp))
.padding(horizontal = 8.dp),
contentAlignment = Alignment.Center
) {
var modified = modified;
BasicTextField(
value = text,
onValueChange = { input ->
val digits = input.filter { it.isDigit() }
text = digits
digits.toIntOrNull()?.let(onValueChange)
},
singleLine = true,
textStyle = TextStyle(
fontSize = 14.sp,
color = if(modified) Color.Black else Color(0xFF99A1AF)
),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
}

View File

@ -0,0 +1,80 @@
package com.example.livingai_lg.ui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
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.padding
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun WishlistNameOverlay(
onSave: (String) -> Unit,
onDismiss: () -> Unit
) {
var name by remember { mutableStateOf("") }
AnimatedVisibility(
visible = true,
enter = slideInVertically { -it },
exit = slideOutVertically { -it }
) {
Box(
Modifier
.fillMaxWidth()
.background(Color.White)
.padding(16.dp)
) {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
text = "Save Filters",
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold
)
OutlinedTextField(
value = name,
onValueChange = { name = it },
placeholder = { Text("Wishlist name") },
singleLine = true
)
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
) {
TextButton(onClick = onDismiss) {
Text("Cancel")
}
Button(
onClick = {
if (name.isNotBlank()) {
onSave(name)
}
}
) {
Text("Save")
}
}
}
}
}
}

View File

@ -1,25 +1,28 @@
package com.example.livingai_lg.ui.models
import com.example.livingai_lg.R
import com.example.livingai_lg.ui.navigation.AppScreen
data class BottomNavItemData(
val label: String,
val iconRes: Int,
val route: String,
)
val mainBottomNavItems = listOf(
BottomNavItemData("Home", R.drawable.ic_home , AppScreen.BUY_ANIMALS),
BottomNavItemData("Sell", R.drawable.ic_tag, AppScreen.CREATE_ANIMAL_LISTING),
// TODO:
BottomNavItemData("Chats", R.drawable.ic_chat, AppScreen.CHATS),
BottomNavItemData("Services", R.drawable.ic_config, AppScreen.CREATE_PROFILE),
BottomNavItemData("Mandi", R.drawable.ic_market, AppScreen.CREATE_PROFILE)
)
val chatBottomNavItems = listOf(
BottomNavItemData("Contacts", R.drawable.ic_home ,"home"),
BottomNavItemData("Calls", R.drawable.ic_tag, "sell"),
BottomNavItemData("Chats", R.drawable.ic_chat, "chats"),
)
package com.example.livingai_lg.ui.models
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Build
import androidx.compose.material.icons.outlined.Home
import com.example.livingai_lg.R
import com.example.livingai_lg.ui.navigation.AppScreen
data class BottomNavItemData(
val label: String,
val iconRes: Any,
val route: String,
)
val mainBottomNavItems = listOf(
BottomNavItemData("Home", R.drawable.ic_home , AppScreen.BUY_ANIMALS),
BottomNavItemData("Sell", R.drawable.ic_tag, AppScreen.CREATE_ANIMAL_LISTING),
// TODO:
BottomNavItemData("Chats", R.drawable.ic_chat, AppScreen.CHATS),
BottomNavItemData("Services", R.drawable.ic_config, AppScreen.CREATE_PROFILE),
BottomNavItemData("Mandi", R.drawable.ic_shop2, AppScreen.CREATE_PROFILE)
)
val chatBottomNavItems = listOf(
BottomNavItemData("Contacts", R.drawable.ic_home ,AppScreen.CONTACTS),
BottomNavItemData("Calls", R.drawable.ic_tag, AppScreen.CALLS),
BottomNavItemData("Chats", R.drawable.ic_chat, AppScreen.CHATS),
)

View File

@ -0,0 +1,42 @@
package com.example.livingai_lg.ui.models
data class TextFilter(
val value: String = "",
val filterSet: Boolean = false
)
data class RangeFilterState(
val min: Int,
val max: Int,
val filterSet: Boolean = false
)
data class FiltersState(
val animal: TextFilter = TextFilter(),
val breed: TextFilter = TextFilter(),
val distance: TextFilter = TextFilter(),
val gender: TextFilter = TextFilter(),
val price: RangeFilterState = RangeFilterState(0, 90_000),
val age: RangeFilterState = RangeFilterState(0, 20),
val weight: RangeFilterState = RangeFilterState(0, 9_000),
val milkYield: RangeFilterState = RangeFilterState(0, 900),
val pregnancyStatuses: Set<String> = emptySet(),
val calving: RangeFilterState = RangeFilterState(0, 10)
)
fun FiltersState.isDefault(): Boolean {
return animal.filterSet.not() &&
breed.filterSet.not() &&
distance.filterSet.not() &&
gender.filterSet.not() &&
price.filterSet.not() &&
age.filterSet.not() &&
weight.filterSet.not() &&
milkYield.filterSet.not() &&
pregnancyStatuses.isEmpty() &&
calving.filterSet.not()
}

View File

@ -0,0 +1,32 @@
package com.example.livingai_lg.ui.models
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
data class WishlistEntry(
val id: String = UUID.randomUUID().toString(),
val name: String,
val filters: FiltersState,
val createdAt: Long = System.currentTimeMillis()
)
object WishlistStore {
private val _wishlist =
MutableStateFlow<List<WishlistEntry>>(emptyList())
val wishlist: StateFlow<List<WishlistEntry>> = _wishlist
fun add(entry: WishlistEntry) {
_wishlist.value = _wishlist.value + entry
}
fun remove(id: String) {
_wishlist.value = _wishlist.value.filterNot { it.id == id }
}
fun clear() {
_wishlist.value = emptyList()
}
}

View File

@ -281,30 +281,30 @@ fun AppNavigation(
)
}
composable(AppScreen.BUY_ANIMALS_FILTERS) {
FilterScreen(
onBackClick = {
navController.popBackStack()
},
onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
onCancelClick = {
navController.popBackStack()
},
)
}
// composable(AppScreen.BUY_ANIMALS_FILTERS) {
// FilterScreen(
// onBackClick = {
// navController.popBackStack()
// },
// onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
// onCancelClick = {
// navController.popBackStack()
// },
// )
// }
composable(AppScreen.BUY_ANIMALS_SORT) {
SortScreen(
onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
onBackClick = {
navController.popBackStack()
},
onCancelClick = {
navController.popBackStack()
},
)
}
// composable(AppScreen.BUY_ANIMALS_SORT) {
// SortScreen(
// onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
// onBackClick = {
// navController.popBackStack()
// },
// onCancelClick = {
// navController.popBackStack()
// },
//
// )
// }
composable(AppScreen.SAVED_LISTINGS) {
SavedListingsScreen(

View File

@ -95,17 +95,17 @@ fun NavGraphBuilder.mainNavGraph(navController: NavController) {
}
composable(AppScreen.BUY_ANIMALS_FILTERS) {
FilterScreen(
onBackClick = {
navController.popBackStack()
},
onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
onCancelClick = {
navController.popBackStack()
},
)
}
// composable(AppScreen.BUY_ANIMALS_FILTERS) {
// FilterScreen(
// onBackClick = {
// navController.popBackStack()
// },
// onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
// onCancelClick = {
// navController.popBackStack()
// },
// )
// }
composable(AppScreen.BUY_ANIMALS_SORT) {
SortScreen(

View File

@ -138,7 +138,7 @@ fun BuyScreen(
// Animal type filter buttons
AnimalTypeSelector(
animalTypes = animalTypes,
selectedAnimalType = selectedAnimalType
selectedAnimalType =
) { }
// Ad space banner

View File

@ -1,327 +1,336 @@
package com.example.farmmarketplace.ui.screens
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.CallMade
import androidx.compose.material.icons.automirrored.filled.CallMissed
import androidx.compose.material.icons.automirrored.filled.CallReceived
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.Icon
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.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
data class CallRecord(
val id: String,
val name: String,
val initials: String,
val callType: CallType,
val duration: String,
val timestamp: String,
val isVideoCall: Boolean = false
)
enum class CallType {
INCOMING,
OUTGOING,
MISSED
}
@Composable
fun CallsScreen(
onBackClick: () -> Unit = {},
onCallClick: () -> Unit = {},
onMenuClick: () -> Unit = {},
onCallItemClick: (String) -> Unit = {},
onTabClick: (route: String) -> Unit = {}
) {
val callHistory = listOf(
CallRecord(
id = "1",
name = "Farmer Kumar",
initials = "FK",
callType = CallType.INCOMING,
duration = "5m 23s",
timestamp = "Today, 2:30 PM"
),
CallRecord(
id = "2",
name = "Seller Raj",
initials = "SR",
callType = CallType.OUTGOING,
duration = "2m 10s",
timestamp = "Today, 11:45 AM"
),
CallRecord(
id = "3",
name = "Buyer Priya",
initials = "BP",
callType = CallType.MISSED,
duration = "",
timestamp = "Yesterday, 8:15 PM"
),
CallRecord(
id = "4",
name = "Seller 1",
initials = "S1",
callType = CallType.OUTGOING,
duration = "8m 45s",
timestamp = "Yesterday, 5:30 PM",
isVideoCall = true
),
CallRecord(
id = "5",
name = "Veterinarian",
initials = "V",
callType = CallType.INCOMING,
duration = "12m 30s",
timestamp = "2 days ago"
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFFCFBF8))
) {
Column(
modifier = Modifier.fillMaxSize()
) {
CallsHeader(
onBackClick = onBackClick,
onCallClick = onCallClick,
onMenuClick = onMenuClick
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(callHistory) { call ->
CallHistoryItem(
call = call,
onClick = { onCallItemClick(call.id) }
)
}
}
ContactsBottomNav(
currentTab = ContactsTab.CALLS,
onTabClick = onTabClick
)
}
}
}
@Composable
fun CallsHeader(
onBackClick: () -> Unit,
onCallClick: () -> Unit,
onMenuClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(26.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onBackClick() }
)
Text(
text = "Calls",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 42.sp
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Phone,
contentDescription = "New Call",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onCallClick() }
)
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMenuClick() }
)
}
}
}
}
@Composable
fun CallHistoryItem(
call: CallRecord,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(76.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f),
shape = RoundedCornerShape(12.dp)
)
.background(Color(0xFFFCFBF8), RoundedCornerShape(12.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
.padding(horizontal = 16.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Box(
modifier = Modifier
.size(48.dp)
.background(Color(0xFFE5E7EB), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = call.initials,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 21.sp
)
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = call.name,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF1E2939),
lineHeight = 20.sp,
letterSpacing = (-0.312).sp
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = when (call.callType) {
CallType.INCOMING -> Icons.AutoMirrored.Filled.CallReceived
CallType.OUTGOING -> Icons.AutoMirrored.Filled.CallMade
CallType.MISSED -> Icons.AutoMirrored.Filled.CallMissed
},
contentDescription = call.callType.name,
tint = when (call.callType) {
CallType.INCOMING -> Color(0xFF00A63E)
CallType.OUTGOING -> Color(0xFF155DFC)
CallType.MISSED -> Color(0xFFE7000B)
},
modifier = Modifier.size(20.dp)
)
Text(
text = when (call.callType) {
CallType.INCOMING -> "Incoming • ${call.duration}"
CallType.OUTGOING -> "Outgoing • ${call.duration}"
CallType.MISSED -> "Missed"
},
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
}
}
}
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = call.timestamp,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
Icon(
imageVector = if (call.isVideoCall) Icons.Default.Videocam else Icons.Default.Phone,
contentDescription = if (call.isVideoCall) "Video Call" else "Voice Call",
tint = Color(0xFF030213),
modifier = Modifier.size(24.dp)
)
}
}
}
package com.example.farmmarketplace.ui.screens
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.CallMade
import androidx.compose.material.icons.automirrored.filled.CallMissed
import androidx.compose.material.icons.automirrored.filled.CallReceived
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.Icon
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.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.layout.BottomNavScaffold
import com.example.livingai_lg.ui.models.chatBottomNavItems
data class CallRecord(
val id: String,
val name: String,
val initials: String,
val callType: CallType,
val duration: String,
val timestamp: String,
val isVideoCall: Boolean = false
)
enum class CallType {
INCOMING,
OUTGOING,
MISSED
}
@Composable
fun CallsScreen(
onBackClick: () -> Unit = {},
onCallClick: () -> Unit = {},
onMenuClick: () -> Unit = {},
onCallItemClick: (String) -> Unit = {},
onTabClick: (route: String) -> Unit = {}
) {
val callHistory = listOf(
CallRecord(
id = "1",
name = "Farmer Kumar",
initials = "FK",
callType = CallType.INCOMING,
duration = "5m 23s",
timestamp = "Today, 2:30 PM"
),
CallRecord(
id = "2",
name = "Seller Raj",
initials = "SR",
callType = CallType.OUTGOING,
duration = "2m 10s",
timestamp = "Today, 11:45 AM"
),
CallRecord(
id = "3",
name = "Buyer Priya",
initials = "BP",
callType = CallType.MISSED,
duration = "",
timestamp = "Yesterday, 8:15 PM"
),
CallRecord(
id = "4",
name = "Seller 1",
initials = "S1",
callType = CallType.OUTGOING,
duration = "8m 45s",
timestamp = "Yesterday, 5:30 PM",
isVideoCall = true
),
CallRecord(
id = "5",
name = "Veterinarian",
initials = "V",
callType = CallType.INCOMING,
duration = "12m 30s",
timestamp = "2 days ago"
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFFCFBF8))
) {
BottomNavScaffold(
items = chatBottomNavItems,
currentItem = "Calls",
onBottomNavItemClick = onTabClick,
) { paddingValues ->
Column(
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
CallsHeader(
onBackClick = onBackClick,
onCallClick = onCallClick,
onMenuClick = onMenuClick
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(callHistory) { call ->
CallHistoryItem(
call = call,
onClick = { onCallItemClick(call.id) }
)
}
}
// ContactsBottomNav(
// currentTab = ContactsTab.CALLS,
// onTabClick = onTabClick
// )
}
}
}
}
@Composable
fun CallsHeader(
onBackClick: () -> Unit,
onCallClick: () -> Unit,
onMenuClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(26.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onBackClick() }
)
Text(
text = "Calls",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 42.sp
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Phone,
contentDescription = "New Call",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onCallClick() }
)
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMenuClick() }
)
}
}
}
}
@Composable
fun CallHistoryItem(
call: CallRecord,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(76.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f),
shape = RoundedCornerShape(12.dp)
)
.background(Color(0xFFFCFBF8), RoundedCornerShape(12.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
.padding(horizontal = 16.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Box(
modifier = Modifier
.size(48.dp)
.background(Color(0xFFE5E7EB), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = call.initials,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 21.sp
)
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = call.name,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF1E2939),
lineHeight = 20.sp,
letterSpacing = (-0.312).sp
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = when (call.callType) {
CallType.INCOMING -> Icons.AutoMirrored.Filled.CallReceived
CallType.OUTGOING -> Icons.AutoMirrored.Filled.CallMade
CallType.MISSED -> Icons.AutoMirrored.Filled.CallMissed
},
contentDescription = call.callType.name,
tint = when (call.callType) {
CallType.INCOMING -> Color(0xFF00A63E)
CallType.OUTGOING -> Color(0xFF155DFC)
CallType.MISSED -> Color(0xFFE7000B)
},
modifier = Modifier.size(20.dp)
)
Text(
text = when (call.callType) {
CallType.INCOMING -> "Incoming • ${call.duration}"
CallType.OUTGOING -> "Outgoing • ${call.duration}"
CallType.MISSED -> "Missed"
},
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
}
}
}
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = call.timestamp,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
Icon(
imageVector = if (call.isVideoCall) Icons.Default.Videocam else Icons.Default.Phone,
contentDescription = if (call.isVideoCall) "Video Call" else "Voice Call",
tint = Color(0xFF030213),
modifier = Modifier.size(24.dp)
)
}
}
}

View File

@ -1,333 +1,341 @@
package com.example.livingai_lg.ui.screens.chat
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.farmmarketplace.ui.screens.ContactsBottomNav
import com.example.farmmarketplace.ui.screens.ContactsTab
data class ChatPreview(
val id: String,
val name: String,
val initials: String,
val lastMessage: String,
val timestamp: String,
val isOnline: Boolean = false,
val unreadCount: Int = 0
)
@Composable
fun ChatsScreen(
onBackClick: () -> Unit = {},
onNewChatClick: () -> Unit = {},
onMenuClick: () -> Unit = {},
onChatItemClick: (String) -> Unit = {},
onTabClick: (route: String) -> Unit = {}
) {
val chatList = listOf(
ChatPreview(
id = "1",
name = "Farmer Kumar",
initials = "FK",
lastMessage = "The cows are healthy and ready for viewing",
timestamp = "Today, 2:30 PM",
isOnline = true,
unreadCount = 2
),
ChatPreview(
id = "2",
name = "Seller Raj",
initials = "SR",
lastMessage = "You: Can you send more photos?",
timestamp = "Today, 11:45 AM",
isOnline = true,
unreadCount = 0
),
ChatPreview(
id = "3",
name = "Buyer Priya",
initials = "BP",
lastMessage = "What's the best time to visit?",
timestamp = "Yesterday, 8:15 PM",
isOnline = false,
unreadCount = 1
),
ChatPreview(
id = "4",
name = "Seller 1",
initials = "S1",
lastMessage = "You: Thanks for the information",
timestamp = "Yesterday, 5:30 PM",
isOnline = true,
unreadCount = 0
),
ChatPreview(
id = "5",
name = "Veterinarian",
initials = "V",
lastMessage = "The animal health check is complete",
timestamp = "2 days ago",
isOnline = false,
unreadCount = 0
),
ChatPreview(
id = "6",
name = "Market Vendor",
initials = "MV",
lastMessage = "You: Available this weekend?",
timestamp = "3 days ago",
isOnline = false,
unreadCount = 0
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFFCFBF8))
) {
Column(
modifier = Modifier.fillMaxSize()
) {
ChatsHeader(
onBackClick = onBackClick,
onNewChatClick = onNewChatClick,
onMenuClick = onMenuClick
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(chatList) { chat ->
ChatListItem(
chat = chat,
onClick = { onChatItemClick(chat.id) }
)
}
}
ContactsBottomNav(
currentTab = ContactsTab.CHATS,
onTabClick = onTabClick
)
}
}
}
@Composable
fun ChatsHeader(
onBackClick: () -> Unit,
onNewChatClick: () -> Unit,
onMenuClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(26.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onBackClick() }
)
Text(
text = "Chats",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 42.sp
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "New Chat",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onNewChatClick() }
)
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMenuClick() }
)
}
}
}
}
@Composable
fun ChatListItem(
chat: ChatPreview,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(76.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f),
shape = RoundedCornerShape(12.dp)
)
.background(Color(0xFFFCFBF8), RoundedCornerShape(12.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
.padding(horizontal = 16.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Box(
modifier = Modifier
.size(48.dp)
.background(Color(0xFFE5E7EB), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = chat.initials,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 21.sp
)
if (chat.isOnline) {
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.size(12.dp)
.background(Color(0xFF00A63E), CircleShape)
.border(2.dp, Color.White, CircleShape)
)
}
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f)
) {
Text(
text = chat.name,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF1E2939),
lineHeight = 20.sp,
letterSpacing = (-0.312).sp
)
Text(
text = chat.lastMessage,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = chat.timestamp,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
if (chat.unreadCount > 0) {
Box(
modifier = Modifier
.size(24.dp)
.background(Color(0xFF155DFC), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = chat.unreadCount.toString(),
fontSize = 10.sp,
fontWeight = FontWeight.Medium,
color = Color.White,
lineHeight = 14.sp
)
}
}
}
}
package com.example.livingai_lg.ui.screens.chat
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.farmmarketplace.ui.screens.ContactsBottomNav
import com.example.farmmarketplace.ui.screens.ContactsTab
import com.example.livingai_lg.ui.layout.BottomNavScaffold
import com.example.livingai_lg.ui.models.chatBottomNavItems
data class ChatPreview(
val id: String,
val name: String,
val initials: String,
val lastMessage: String,
val timestamp: String,
val isOnline: Boolean = false,
val unreadCount: Int = 0
)
@Composable
fun ChatsScreen(
onBackClick: () -> Unit = {},
onNewChatClick: () -> Unit = {},
onMenuClick: () -> Unit = {},
onChatItemClick: (String) -> Unit = {},
onTabClick: (route: String) -> Unit = {}
) {
val chatList = listOf(
ChatPreview(
id = "1",
name = "Farmer Kumar",
initials = "FK",
lastMessage = "The cows are healthy and ready for viewing",
timestamp = "Today, 2:30 PM",
isOnline = true,
unreadCount = 2
),
ChatPreview(
id = "2",
name = "Seller Raj",
initials = "SR",
lastMessage = "You: Can you send more photos?",
timestamp = "Today, 11:45 AM",
isOnline = true,
unreadCount = 0
),
ChatPreview(
id = "3",
name = "Buyer Priya",
initials = "BP",
lastMessage = "What's the best time to visit?",
timestamp = "Yesterday, 8:15 PM",
isOnline = false,
unreadCount = 1
),
ChatPreview(
id = "4",
name = "Seller 1",
initials = "S1",
lastMessage = "You: Thanks for the information",
timestamp = "Yesterday, 5:30 PM",
isOnline = true,
unreadCount = 0
),
ChatPreview(
id = "5",
name = "Veterinarian",
initials = "V",
lastMessage = "The animal health check is complete",
timestamp = "2 days ago",
isOnline = false,
unreadCount = 0
),
ChatPreview(
id = "6",
name = "Market Vendor",
initials = "MV",
lastMessage = "You: Available this weekend?",
timestamp = "3 days ago",
isOnline = false,
unreadCount = 0
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFFCFBF8))
) {
BottomNavScaffold(
items = chatBottomNavItems,
currentItem = "Chats",
onBottomNavItemClick = onTabClick,
) { paddingValues ->
Column(
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
ChatsHeader(
onBackClick = onBackClick,
onNewChatClick = onNewChatClick,
onMenuClick = onMenuClick
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(chatList) { chat ->
ChatListItem(
chat = chat,
onClick = { onChatItemClick(chat.id) }
)
}
}
// ContactsBottomNav(
// currentTab = ContactsTab.CHATS,
// onTabClick = onTabClick
// )
}
}
}
}
@Composable
fun ChatsHeader(
onBackClick: () -> Unit,
onNewChatClick: () -> Unit,
onMenuClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(26.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onBackClick() }
)
Text(
text = "Chats",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 42.sp
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "New Chat",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onNewChatClick() }
)
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMenuClick() }
)
}
}
}
}
@Composable
fun ChatListItem(
chat: ChatPreview,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(76.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f),
shape = RoundedCornerShape(12.dp)
)
.background(Color(0xFFFCFBF8), RoundedCornerShape(12.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
.padding(horizontal = 16.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Box(
modifier = Modifier
.size(48.dp)
.background(Color(0xFFE5E7EB), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = chat.initials,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 21.sp
)
if (chat.isOnline) {
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.size(12.dp)
.background(Color(0xFF00A63E), CircleShape)
.border(2.dp, Color.White, CircleShape)
)
}
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f)
) {
Text(
text = chat.name,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF1E2939),
lineHeight = 20.sp,
letterSpacing = (-0.312).sp
)
Text(
text = chat.lastMessage,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = chat.timestamp,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
if (chat.unreadCount > 0) {
Box(
modifier = Modifier
.size(24.dp)
.background(Color(0xFF155DFC), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = chat.unreadCount.toString(),
fontSize = 10.sp,
fontWeight = FontWeight.Medium,
color = Color.White,
lineHeight = 14.sp
)
}
}
}
}
}

View File

@ -1,422 +1,432 @@
package com.example.farmmarketplace.ui.screens
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Chat
import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.automirrored.outlined.Message
import androidx.compose.material.icons.filled.Chat
import androidx.compose.material.icons.filled.Contacts
import androidx.compose.material.icons.filled.Message
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Phone
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Phone
import androidx.compose.material3.Icon
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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.navigation.AppScreen
data class Contact(
val id: String,
val name: String,
val initials: String,
val status: String,
val isOnline: Boolean = false,
val phoneNumber: String? = null
)
enum class ContactsTab {
CONTACTS,
CALLS,
CHATS
}
@Composable
fun ContactsScreen(
onBackClick: () -> Unit = {},
onSearchClick: () -> Unit = {},
onMenuClick: () -> Unit = {},
onContactClick: (String) -> Unit = {},
onCallClick: (String) -> Unit = {},
onMessageClick: (String) -> Unit = {},
onTabClick: (route: String) -> Unit = {}
) {
val contacts = listOf(
Contact(
id = "1",
name = "Farmer Kumar",
initials = "FK",
status = "Online",
isOnline = true
),
Contact(
id = "2",
name = "Seller Raj",
initials = "SR",
status = "+91 98765 43211",
isOnline = false,
phoneNumber = "+91 98765 43211"
),
Contact(
id = "3",
name = "Buyer Priya",
initials = "BP",
status = "Online",
isOnline = true
),
Contact(
id = "4",
name = "Seller 1",
initials = "S1",
status = "+91 98765 43213",
isOnline = false,
phoneNumber = "+91 98765 43213"
),
Contact(
id = "5",
name = "Veterinarian",
initials = "V",
status = "Online",
isOnline = true
),
Contact(
id = "6",
name = "Farm Supply",
initials = "FS",
status = "+91 98765 43215",
isOnline = false,
phoneNumber = "+91 98765 43215"
),
Contact(
id = "7",
name = "Transport Co.",
initials = "TC",
status = "+91 98765 43216",
isOnline = false,
phoneNumber = "+91 98765 43216"
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFFCFBF8))
) {
Column(
modifier = Modifier.fillMaxSize()
) {
ContactsHeader(
onBackClick = onBackClick,
onSearchClick = onSearchClick,
onMenuClick = onMenuClick
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
items(contacts) { contact ->
ContactItem(
contact = contact,
onContactClick = { onContactClick(contact.id) },
onCallClick = { onCallClick(contact.id) },
onMessageClick = { onMessageClick(contact.id) }
)
}
}
ContactsBottomNav(
currentTab = ContactsTab.CONTACTS,
onTabClick = onTabClick
)
}
}
}
@Composable
fun ContactsHeader(
onBackClick: () -> Unit,
onSearchClick: () -> Unit,
onMenuClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(26.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onBackClick() }
)
Text(
text = "Contacts",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 42.sp
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onSearchClick() }
)
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMenuClick() }
)
}
}
}
}
@Composable
fun ContactItem(
contact: Contact,
onContactClick: () -> Unit,
onCallClick: () -> Unit,
onMessageClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(73.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.05f)
)
.background(Color(0xFFFCFBF8))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onContactClick() }
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(48.dp)
.background(Color(0xFFE5E7EB), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = contact.initials,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 21.sp
)
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = contact.name,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF1E2939),
lineHeight = 20.sp,
letterSpacing = (-0.312).sp
)
Text(
text = contact.status,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(36.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onCallClick() },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Outlined.Phone,
contentDescription = "Call",
tint = Color(0xFF030213),
modifier = Modifier.size(20.dp)
)
}
Box(
modifier = Modifier
.size(36.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMessageClick() },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.Message,
contentDescription = "Message",
tint = Color(0xFF030213),
modifier = Modifier.size(20.dp)
)
}
}
}
}
@Composable
fun ContactsBottomNav(
currentTab: ContactsTab,
onTabClick: (route: String) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
.padding(horizontal = 32.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ContactsTabItem(
icon = Icons.AutoMirrored.Filled.Chat,
label = "Chats",
isSelected = currentTab == ContactsTab.CHATS,
onClick = { onTabClick(AppScreen.CHATS) }
)
ContactsTabItem(
icon = Icons.Default.Phone,
label = "Calls",
isSelected = currentTab == ContactsTab.CALLS,
onClick = { onTabClick(AppScreen.CALLS) }
)
ContactsTabItem(
icon = Icons.Default.Contacts,
label = "Contacts",
isSelected = currentTab == ContactsTab.CONTACTS,
onClick = { onTabClick(AppScreen.CONTACTS) }
)
}
}
@Composable
fun ContactsTabItem(
icon: ImageVector,
label: String,
isSelected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
imageVector = icon,
contentDescription = label,
tint = Color(0xFF0A0A0A),
modifier = Modifier.size(24.dp)
)
Text(
text = label,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 16.sp,
textAlign = TextAlign.Center
)
}
package com.example.farmmarketplace.ui.screens
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Chat
import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.automirrored.outlined.Message
import androidx.compose.material.icons.filled.Chat
import androidx.compose.material.icons.filled.Contacts
import androidx.compose.material.icons.filled.Message
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Phone
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Phone
import androidx.compose.material3.Icon
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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.layout.BottomNavScaffold
import com.example.livingai_lg.ui.models.chatBottomNavItems
import com.example.livingai_lg.ui.models.mainBottomNavItems
import com.example.livingai_lg.ui.navigation.AppScreen
data class Contact(
val id: String,
val name: String,
val initials: String,
val status: String,
val isOnline: Boolean = false,
val phoneNumber: String? = null
)
enum class ContactsTab {
CONTACTS,
CALLS,
CHATS
}
@Composable
fun ContactsScreen(
onBackClick: () -> Unit = {},
onSearchClick: () -> Unit = {},
onMenuClick: () -> Unit = {},
onContactClick: (String) -> Unit = {},
onCallClick: (String) -> Unit = {},
onMessageClick: (String) -> Unit = {},
onTabClick: (route: String) -> Unit = {}
) {
val contacts = listOf(
Contact(
id = "1",
name = "Farmer Kumar",
initials = "FK",
status = "Online",
isOnline = true
),
Contact(
id = "2",
name = "Seller Raj",
initials = "SR",
status = "+91 98765 43211",
isOnline = false,
phoneNumber = "+91 98765 43211"
),
Contact(
id = "3",
name = "Buyer Priya",
initials = "BP",
status = "Online",
isOnline = true
),
Contact(
id = "4",
name = "Seller 1",
initials = "S1",
status = "+91 98765 43213",
isOnline = false,
phoneNumber = "+91 98765 43213"
),
Contact(
id = "5",
name = "Veterinarian",
initials = "V",
status = "Online",
isOnline = true
),
Contact(
id = "6",
name = "Farm Supply",
initials = "FS",
status = "+91 98765 43215",
isOnline = false,
phoneNumber = "+91 98765 43215"
),
Contact(
id = "7",
name = "Transport Co.",
initials = "TC",
status = "+91 98765 43216",
isOnline = false,
phoneNumber = "+91 98765 43216"
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFFCFBF8))
) {
BottomNavScaffold(
items = chatBottomNavItems,
currentItem = "Contacts",
onBottomNavItemClick = onTabClick,
) { paddingValues ->
Column(
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
ContactsHeader(
onBackClick = onBackClick,
onSearchClick = onSearchClick,
onMenuClick = onMenuClick
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
items(contacts) { contact ->
ContactItem(
contact = contact,
onContactClick = { onContactClick(contact.id) },
onCallClick = { onCallClick(contact.id) },
onMessageClick = { onMessageClick(contact.id) }
)
}
}
// ContactsBottomNav(
// currentTab = ContactsTab.CONTACTS,
// onTabClick = onTabClick
// )
}
}
}
}
@Composable
fun ContactsHeader(
onBackClick: () -> Unit,
onSearchClick: () -> Unit,
onMenuClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(26.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onBackClick() }
)
Text(
text = "Contacts",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 42.sp
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onSearchClick() }
)
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color(0xFF0A0A0A),
modifier = Modifier
.size(20.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMenuClick() }
)
}
}
}
}
@Composable
fun ContactItem(
contact: Contact,
onContactClick: () -> Unit,
onCallClick: () -> Unit,
onMessageClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(73.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.05f)
)
.background(Color(0xFFFCFBF8))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onContactClick() }
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(48.dp)
.background(Color(0xFFE5E7EB), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = contact.initials,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 21.sp
)
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = contact.name,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF1E2939),
lineHeight = 20.sp,
letterSpacing = (-0.312).sp
)
Text(
text = contact.status,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF717182),
lineHeight = 16.sp
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(36.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onCallClick() },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Outlined.Phone,
contentDescription = "Call",
tint = Color(0xFF030213),
modifier = Modifier.size(20.dp)
)
}
Box(
modifier = Modifier
.size(36.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onMessageClick() },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.Message,
contentDescription = "Message",
tint = Color(0xFF030213),
modifier = Modifier.size(20.dp)
)
}
}
}
}
@Composable
fun ContactsBottomNav(
currentTab: ContactsTab,
onTabClick: (route: String) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(65.dp)
.border(
width = 1.078.dp,
color = Color(0xFF000000).copy(alpha = 0.1f)
)
.background(Color(0xFFFCFBF8))
.padding(horizontal = 32.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ContactsTabItem(
icon = Icons.AutoMirrored.Filled.Chat,
label = "Chats",
isSelected = currentTab == ContactsTab.CHATS,
onClick = { onTabClick(AppScreen.CHATS) }
)
ContactsTabItem(
icon = Icons.Default.Phone,
label = "Calls",
isSelected = currentTab == ContactsTab.CALLS,
onClick = { onTabClick(AppScreen.CALLS) }
)
ContactsTabItem(
icon = Icons.Default.Contacts,
label = "Contacts",
isSelected = currentTab == ContactsTab.CONTACTS,
onClick = { onTabClick(AppScreen.CONTACTS) }
)
}
}
@Composable
fun ContactsTabItem(
icon: ImageVector,
label: String,
isSelected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
imageVector = icon,
contentDescription = label,
tint = Color(0xFF0A0A0A),
modifier = Modifier.size(24.dp)
)
Text(
text = label,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A),
lineHeight = 16.sp,
textAlign = TextAlign.Center
)
}
}