New screens and improvements

This commit is contained in:
ankitsaraf 2025-12-19 18:59:01 +05:30
parent 4f8ae88820
commit 6b634f40b7
45 changed files with 3277 additions and 649 deletions

View File

@ -4,14 +4,9 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavType
@ -19,17 +14,16 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.livingai_lg.ui.AuthState
import com.example.livingai_lg.ui.MainViewModel
import com.example.livingai_lg.ui.MainViewModelFactory
import com.example.livingai_lg.ui.login.*
import com.example.livingai_lg.ui.login_legacy.*
import com.example.livingai_lg.ui.navigation.AppNavigation
import com.example.livingai_lg.ui.theme.FarmMarketplaceTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
//enableEdgeToEdge()
setContent {
FarmMarketplaceTheme {
val mainViewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current))
@ -54,6 +48,8 @@ class MainActivity : ComponentActivity() {
}
}
// TODO: remove the old code after testing new stuff
@Composable
fun AuthNavigation() {
val navController = rememberNavController()

View File

@ -0,0 +1,109 @@
package com.example.livingai_lg.ui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
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.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
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.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
@Composable
fun ActionPopup(
visible: Boolean,
text: String,
icon: ImageVector,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Black,
contentColor: Color = Color.White,
autoDismissMillis: Long = 5000L,
onClick: (() -> Unit)? = null,
onDismiss: () -> Unit
) {
// Auto dismiss
LaunchedEffect(visible) {
if (visible) {
delay(autoDismissMillis)
onDismiss()
}
}
AnimatedVisibility(
visible = visible,
enter = fadeIn() + slideInVertically { it / 2 },
exit = fadeOut() + slideOutVertically { it / 2 },
modifier = modifier
) {
Row(
modifier = Modifier
.shadow(12.dp, RoundedCornerShape(50))
.background(backgroundColor, RoundedCornerShape(50))
.clickable(
enabled = onClick != null,
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) {
onClick?.invoke()
}
.padding(horizontal = 16.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = contentColor,
modifier = Modifier.size(18.dp)
)
Text(
text = text,
color = contentColor,
fontSize = 14.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.width(4.dp))
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
tint = contentColor.copy(alpha = 0.8f),
modifier = Modifier
.size(16.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onDismiss()
}
)
}
}
}

View File

@ -0,0 +1,229 @@
package com.example.livingai_lg.ui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
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.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.layout.width
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.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.LibraryAdd
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.MyLocation
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.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.models.UserAddress
@Composable
fun AddressSelectorOverlay(
visible: Boolean,
addresses: List<UserAddress>,
selectedAddressId: String?,
onSelect: (String) -> Unit,
onClose: () -> Unit
) {
AnimatedVisibility(
visible = visible,
enter = slideInVertically(
initialOffsetY = { -it }
) + fadeIn(),
exit = slideOutVertically(
targetOffsetY = { -it }
) + fadeOut()
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF4F4F4))
) {
Column {
AddressSelectorHeader(onClose)
AddressSearchBar()
AddressActionsRow()
SavedAddressesList(
addresses = addresses,
selectedAddressId = selectedAddressId,
onSelect = onSelect
)
}
}
}
}
@Composable
fun AddressSelectorHeader(onClose: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = onClose) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
Text(
text = "Select Your Location",
fontSize = 20.sp,
fontWeight = FontWeight.Medium
)
}
}
@Composable
fun AddressSearchBar() {
OutlinedTextField(
value = "",
onValueChange = {},
placeholder = { Text("Search an area or address") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
trailingIcon = {
Icon(Icons.Default.Search, contentDescription = null)
},
enabled = false // 👈 explicitly disabled for now
)
}
@Composable
fun AddressActionsRow() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
AddressActionCard(Icons.Default.MyLocation,"Use Current Location")
AddressActionCard(Icons.Default.LibraryAdd,"Add New Address")
}
}
@Composable
fun SavedAddressesList(
addresses: List<UserAddress>,
selectedAddressId: String?,
onSelect: (String) -> Unit
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "SAVED ADDRESSES",
fontSize = 12.sp,
color = Color.Gray
)
Spacer(Modifier.height(8.dp))
addresses.forEach { address ->
AddressItem(
address = address,
selected = address.id == selectedAddressId,
onClick = { onSelect(address.id) }
)
Spacer(Modifier.height(8.dp))
}
}
}
@Composable
fun AddressItem(
address: UserAddress,
selected: Boolean,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(12.dp))
.clickable(onClick = onClick)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Home, contentDescription = null)
Spacer(Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(address.name, fontWeight = FontWeight.Medium)
if (selected) {
Spacer(Modifier.width(8.dp))
Text(
"SELECTED",
fontSize = 10.sp,
color = Color.Green
)
}
}
Text(address.address, fontSize = 12.sp, color = Color.Gray)
}
Icon(Icons.Default.MoreVert, contentDescription = null)
}
}
@Composable
fun AddressActionCard(
icon: ImageVector,
label: String,
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
Column(
modifier = modifier
.size(110.dp)
.background(Color.White, RoundedCornerShape(16.dp))
.border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp))
.clickable { onClick() }
.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = icon,
contentDescription = label,
tint = Color.Black,
modifier = Modifier.size(28.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = label,
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center
)
}
}

View File

@ -6,15 +6,22 @@ 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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.LocationOn
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
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.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@ -26,6 +33,7 @@ import com.example.livingai_lg.ui.utils.formatDistance
import com.example.livingai_lg.ui.utils.formatPrice
import com.example.livingai_lg.ui.utils.formatViews
import com.example.livingai_lg.R
import com.example.livingai_lg.ui.theme.AppTypography
@Composable
fun BuyAnimalCard(
@ -34,6 +42,7 @@ fun BuyAnimalCard(
onSavedChange: (Boolean) -> Unit,
onProductClick: () -> Unit,
onSellerClick:(sellerId: String)-> Unit,
onBookmarkClick: () -> Unit
) {
Box(
modifier = Modifier
@ -46,163 +55,204 @@ fun BuyAnimalCard(
Color(0xFF000000).copy(alpha = 0.1f),
RoundedCornerShape(14.dp)
)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onProductClick
)
) {
Column {
// Image
Box(
modifier = Modifier
.fillMaxWidth()
.height(257.dp)
) {
ImageCarousel(
imageUrls = product.imageUrl ?: emptyList(),
modifier = Modifier.fillMaxSize()
)
// Views
Row(
modifier = Modifier
.align(Alignment.TopStart)
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(R.drawable.ic_view),
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(16.dp)
)
Spacer(Modifier.width(4.dp))
Text(formatViews(product.views), fontSize = 10.sp, color = Color.White)
}
// Distance
Row(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(R.drawable.ic_location),
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(12.dp)
)
Spacer(Modifier.width(4.dp))
Text(formatDistance( product.distance), fontSize = 16.sp, color = Color.White)
}
}
// Content
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(
product.name?: "",
fontSize = 16.sp,
fontWeight = FontWeight.Medium
)
Text(
formatPrice(product.price),
fontSize = 16.sp,
fontWeight = FontWeight.Medium
)
}
Column(horizontalAlignment = Alignment.End,modifier = Modifier.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) {
onSellerClick(product.sellerId?:"")
}) {
Text("Sold By: ${product.sellerName}", fontSize = 13.sp)
Text(product.sellerType?: "???", fontSize = 13.sp)
}
}
// Rating
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(R.drawable.ic_star),
contentDescription = null,
tint = Color(0xFFDE9A07),
modifier = Modifier.size(8.dp)
)
Spacer(Modifier.width(4.dp))
Text(
"${product.rating} (${product.ratingCount} Ratings)",
fontSize = 8.sp
)
}
// Badges
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Box(
modifier = Modifier
.background(Color(0xFFF5F5F5), RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp, vertical = 6.dp)
) {
val scoreString = "AI Score: ${product.aiScore?: 0}"
Text(scoreString, fontSize = 12.sp)
}
Box(
modifier = Modifier
.background(Color(0xFFF5F5F5), RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp, vertical = 6.dp)
) {
Text("placeholder", fontSize = 12.sp)
}
if(product.milkCapacity != null) {
Box(
modifier = Modifier
.background(Color(0xFFF5F5F5), RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp, vertical = 6.dp)
) {
Text("Milk Capacity: ${product.milkCapacity}L", fontSize = 12.sp)
}
}
}
// Description
Text(
product.description?: "",
fontSize = 14.sp,
color = Color(0xFF717182),
lineHeight = 20.sp
modifier = Modifier.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onProductClick
)
) {
// Actions
// Image
Box(
modifier = Modifier
.fillMaxWidth()
.height(257.dp)
) {
ImageCarousel(
imageUrls = product.imageUrl ?: emptyList(),
modifier = Modifier.fillMaxSize()
)
// Views
Row(
modifier = Modifier
.align(Alignment.TopStart)
.padding(6.dp)
.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(50),
ambientColor = Color.Black.copy(alpha = 0.4f),
spotColor = Color.Black.copy(alpha = 0.4f)
)
.background(
color = Color.Black.copy(alpha = 0.35f), // 👈 light but effective
shape = RoundedCornerShape(50)
)
.padding(horizontal = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Outlined.Visibility,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(16.dp).shadow(
elevation = 6.dp,
shape = CircleShape,
clip = false
)
)
Spacer(Modifier.width(4.dp))
Text(formatViews(product.views), fontSize = AppTypography.Caption, color = Color.White,style = LocalTextStyle.current.copy(
))
}
// Distance
Row(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(6.dp)
.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(50),
ambientColor = Color.Black.copy(alpha = 0.4f),
spotColor = Color.Black.copy(alpha = 0.4f)
)
.background(
color = Color.Black.copy(alpha = 0.35f), // 👈 light but effective
shape = RoundedCornerShape(50)
)
.padding(horizontal = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Outlined.LocationOn,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(12.dp)
)
Spacer(Modifier.width(4.dp))
Text(
formatDistance(product.distance),
fontSize = AppTypography.Body,
color = Color.White,
)
}
}
// Content
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(
product.name ?: "",
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium
)
Text(
formatPrice(product.price),
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium
)
}
Column(
horizontalAlignment = Alignment.End, modifier = Modifier.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) {
onSellerClick(product.sellerId ?: "")
}) {
Text("Sold By: ${product.sellerName}", fontSize = AppTypography.BodySmall)
Text(product.sellerType ?: "???", fontSize = AppTypography.BodySmall)
}
}
// Rating
Row(verticalAlignment = Alignment.CenterVertically) {
RatingStars(product.rating?:0f, starSize = 12.dp)
Spacer(Modifier.width(4.dp))
Text(
"${product.rating} (${product.ratingCount} Ratings)",
fontSize = AppTypography.Caption
)
}
// Badges
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Box(
modifier = Modifier
.background(Color(0xFFF5F5F5), RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp, vertical = 6.dp)
) {
Row {
Text("AI Score: ", fontSize = AppTypography.Caption)
val scoreString = "${product.aiScore ?: 0}"
Text(scoreString, fontSize = AppTypography.Caption, fontWeight = FontWeight.Bold)
}
}
if (product.milkCapacity != null) {
Box(
modifier = Modifier
.background(Color(0xFFF5F5F5), RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp, vertical = 6.dp)
) {
Row {
Text(
"Milk Capacity:",
fontSize = AppTypography.Caption
)
Text(
"${product.milkCapacity}L",
fontSize = AppTypography.Caption,
fontWeight = FontWeight.Bold
)
}
}
}
}
// Description
Text(
product.description ?: "",
fontSize = 14.sp,
color = Color(0xFF717182),
lineHeight = 20.sp
)
// Actions
// Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
// ActionButton(R.drawable.ic_chat, "Chat")
// ActionButton(R.drawable.ic_phone, "Call")
// ActionButton(R.drawable.ic_location, "Location")
// ActionButton(R.drawable.ic_bookmark_plus, "Bookmark")
// }
FloatingActionBar(
modifier = Modifier
.padding(bottom = 12.dp)
.zIndex(10f), // 👈 ensure it floats above everything
onChatClick = { /* TODO */ },
onCallClick = { /* TODO */ },
onLocationClick = { /* TODO */ },
onBookmarkClick = { /* TODO */ }
)
}
}
FloatingActionBar(
modifier = Modifier
.padding(bottom = 12.dp)
.zIndex(10f), // 👈 ensure it floats above everything
showContainer = false,
onChatClick = { /* TODO */ },
onCallClick = { /* TODO */ },
onLocationClick = { /* TODO */ },
onBookmarkClick = onBookmarkClick
)
}
}
}

View File

@ -7,6 +7,8 @@ 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
@ -16,6 +18,7 @@ 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)
@ -38,7 +41,7 @@ fun DropdownInput(
if (label != null) {
Text(
text = label,
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium,
color = FarmTextDark
)
@ -78,15 +81,16 @@ fun DropdownInput(
// Custom placeholder support
Text(
text = selected.ifEmpty { placeholder },
fontSize = 16.sp,
fontSize = AppTypography.Body,
color = if (selected.isEmpty()) Color(0xFF99A1AF) else FarmTextDark
)
Text(
text = "",
fontSize = 12.sp,
color = FarmTextDark
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = "Dropdown",
tint = FarmTextDark
)
}
}
@ -99,7 +103,7 @@ fun DropdownInput(
options.forEach { item ->
DropdownMenuItem(
text = {
Text(item, fontSize = 16.sp, color = FarmTextDark)
Text(item, fontSize = AppTypography.Body, color = FarmTextDark)
},
onClick = {
onSelect(item)

View File

@ -26,7 +26,7 @@ import com.example.livingai_lg.R
@Composable
fun FilterButton(
onFilterClick: () -> Unit
onClick: () -> Unit
) {
Row(
modifier = Modifier
@ -37,7 +37,7 @@ fun FilterButton(
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onFilterClick,
onClick = onClick,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)

View File

@ -0,0 +1,60 @@
package com.example.livingai_lg.ui.screens
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
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
@Composable
fun FilterOverlay(
visible: Boolean,
onDismiss: () -> Unit,
onSubmitClick: () -> Unit = {},
) {
if (!visible) return
BackHandler { onDismiss() }
Box(
modifier = Modifier
.fillMaxSize()
) {
// Dimmed background
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.35f))
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onDismiss() }
)
AnimatedVisibility(
visible = visible,
enter = slideInHorizontally { fullWidth -> fullWidth },
exit = slideOutHorizontally { fullWidth -> fullWidth },
modifier = Modifier.fillMaxHeight()
) {
FilterScreen(
onBackClick = onDismiss,
onCancelClick = onDismiss,
onSubmitClick = {
onSubmitClick()
onDismiss()
}
)
}
}
}

View File

@ -1,14 +1,26 @@
package com.example.livingai_lg.ui.components
import android.media.Image
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.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Chat
import androidx.compose.material.icons.outlined.BookmarkAdd
import androidx.compose.material.icons.outlined.Chat
import androidx.compose.material.icons.outlined.LocationOn
import androidx.compose.material.icons.outlined.Phone
import androidx.compose.material3.Icon
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.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.example.livingai_lg.R
@ -16,35 +28,44 @@ import com.example.livingai_lg.R
@Composable
fun FloatingActionBar(
modifier: Modifier = Modifier,
showContainer: Boolean = true,
onChatClick: () -> Unit = {},
onCallClick: () -> Unit = {},
onLocationClick: () -> Unit = {},
onBookmarkClick: () -> Unit = {}
) {
val containerModifier =
if (showContainer) {
Modifier
.shadow(
elevation = 12.dp,
shape = RoundedCornerShape(50),
clip = false
)
.background(
color = Color.White,
shape = RoundedCornerShape(50)
)
.padding(horizontal = 12.dp, vertical = 12.dp)
} else {
Modifier // no background, no shadow
}
Box(
modifier = modifier
.fillMaxWidth()
//.padding(horizontal = 24.dp)
.shadow(
elevation = 12.dp,
shape = RoundedCornerShape(50), // pill shape
clip = false
)
.background(
color = Color.White,
shape = RoundedCornerShape(50)
)
.padding(vertical = 12.dp)
.then(containerModifier)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
FloatingActionIcon(R.drawable.ic_chat, onChatClick)
FloatingActionIcon(R.drawable.ic_phone, onCallClick)
FloatingActionIcon(R.drawable.ic_location, onLocationClick)
FloatingActionIcon(R.drawable.ic_bookmark_plus, onBookmarkClick)
FloatingActionIcon(Icons.AutoMirrored.Outlined.Chat, onChatClick)
FloatingActionIcon(Icons.Outlined.Phone, onCallClick)
FloatingActionIcon(Icons.Outlined.LocationOn, onLocationClick)
FloatingActionIcon(Icons.Outlined.BookmarkAdd, onBookmarkClick)
}
}
}
@ -52,7 +73,7 @@ fun FloatingActionBar(
@Composable
private fun FloatingActionIcon(
iconRes: Int,
icon: ImageVector,
onClick: () -> Unit
) {
Box(
@ -63,11 +84,16 @@ private fun FloatingActionIcon(
shape = RoundedCornerShape(24.dp),
clip = false
)
.background(Color.White, RoundedCornerShape(24.dp)),
.background(Color.White, RoundedCornerShape(24.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick
),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(iconRes),
imageVector = icon,
contentDescription = null,
tint = Color(0xFF0A0A0A),
modifier = Modifier.size(22.dp)

View File

@ -0,0 +1,30 @@
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.runtime.Composable
import com.example.livingai_lg.ui.models.AppNotification
import com.example.livingai_lg.ui.screens.NotificationsScreen
@Composable
fun NotificationsOverlay(
visible: Boolean,
notifications: List<AppNotification>,
onClose: () -> Unit,
onDismiss: (String) -> Unit,
onNotificationClick: (String) -> Unit = {}
) {
AnimatedVisibility(
visible = visible,
enter = slideInVertically { -it },
exit = slideOutVertically { -it }
) {
NotificationsScreen(
notifications = notifications,
onBackClick = onClose,
onDismiss = onDismiss,
onNotificationClick = onNotificationClick
)
}
}

View File

@ -16,16 +16,19 @@ 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.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.theme.AppTypography
@Composable
fun OptionCard(
label: String,
icon: Int,
icon: Any,
iconBackgroundColor: Color,
iconTint: Color = Color.Unspecified,
onClick: () -> Unit
) {
Box(
@ -54,17 +57,28 @@ fun OptionCard(
.background(iconBackgroundColor, RoundedCornerShape(14.dp)),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = icon),
contentDescription = label,
tint = Color.Unspecified
)
when (icon) {
is ImageVector -> {
Icon(
imageVector = icon,
contentDescription = label,
tint = iconTint
)
}
is Int -> {
Icon(
painter = painterResource(icon),
contentDescription = label,
tint = Color.Unspecified
)
}
}
}
// Label
Text(
text = label,
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Normal,
color = Color(0xFF0A0A0A),
modifier = Modifier.weight(1f)

View File

@ -0,0 +1,57 @@
package com.example.livingai_lg.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.StarHalf
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun RatingStars(
rating: Float,
modifier: Modifier = Modifier,
starSize: Dp = 14.dp,
color: Color = Color(0xFFDE9A07)
) {
val fullStars = rating.toInt()
val remainder = rating - fullStars
val showHalfStar = remainder in 0.25f..0.74f
val extraFullStar = remainder >= 0.75f
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Full stars
repeat(
if (extraFullStar) fullStars + 1 else fullStars
) {
Icon(
imageVector = Icons.Default.Star,
contentDescription = null,
tint = color,
modifier = Modifier.size(starSize)
)
}
// Half star (only if not rounded up)
if (showHalfStar) {
Icon(
imageVector = Icons.AutoMirrored.Filled.StarHalf,
contentDescription = null,
tint = color,
modifier = Modifier.size(starSize)
)
}
}
}

View File

@ -25,17 +25,23 @@ import androidx.compose.ui.unit.sp
import com.example.livingai_lg.R
@Composable
fun SortButton(onSortClick: () -> Unit) {
fun SortButton(
onClick: () -> Unit
) {
Row(
modifier = Modifier
.height(36.dp)
.border(1.078.dp, Color(0xFF000000).copy(alpha = 0.1f), RoundedCornerShape(8.dp))
.border(
1.078.dp,
Color(0xFF000000).copy(alpha = 0.1f),
RoundedCornerShape(8.dp)
)
.background(Color.White, RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onSortClick,
onClick = onClick
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
@ -53,4 +59,4 @@ fun SortButton(onSortClick: () -> Unit) {
color = Color(0xFF0A0A0A)
)
}
}
}

View File

@ -0,0 +1,74 @@
package com.example.livingai_lg.ui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.example.livingai_lg.ui.models.SortField
import com.example.livingai_lg.ui.screens.SortScreen
@Composable
fun SortOverlay(
visible: Boolean,
onApplyClick: (selected: List<SortField>) -> Unit,
onDismiss: () -> Unit,
) {
if (!visible) return
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.35f))
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onDismiss() } // tap outside closes
) {
AnimatedVisibility(
visible = visible,
enter = slideInHorizontally(
initialOffsetX = { -it }
),
exit = slideOutHorizontally(
targetOffsetX = { -it }
)
) {
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.85f) // 👈 DOES NOT cover full screen
.background(Color(0xFFF7F4EE))
) {
// Prevent click-through
Box(
modifier = Modifier
.fillMaxSize()
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {}
) {
SortScreen(
onApplyClick = { selected ->
onApplyClick(selected)
onDismiss()
// TODO: apply sort
},
onCancelClick = onDismiss,
onBackClick = onDismiss
)
}
}
}
}
}

View File

@ -31,23 +31,15 @@ import com.example.livingai_lg.R
@Composable
fun UserLocationHeader(
user: UserProfile,
selectedAddressId: String,
modifier: Modifier = Modifier,
onAddressSelected: (UserAddress) -> Unit = {},
onAddNewClick: () -> Unit = {} // future navigation hook
onOpenAddressOverlay: () -> Unit
) {
var expanded by remember { mutableStateOf(false) }
var selectedAddress by remember {
mutableStateOf(
user.addresses.firstOrNull { it.isPrimary }
?: user.addresses.first()
)
}
Row(
modifier = modifier.wrapContentWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val selectedAddress = user.addresses.find { it.id == selectedAddressId } ?: user.addresses.first()
// Profile image
Box(
@ -75,79 +67,38 @@ fun UserLocationHeader(
Spacer(modifier = Modifier.width(12.dp))
// Anchor ONLY the text section
Box {
Column(
modifier = Modifier
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { expanded = true }
// Address + arrow (click opens overlay)
Column(
modifier = Modifier.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = selectedAddress.name,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color.Black
)
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(18.dp)
)
}
onOpenAddressOverlay()
}
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = selectedAddress.address,
fontSize = 13.sp,
color = Color.Black.copy(alpha = 0.7f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
text = selectedAddress.name,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color.Black
)
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(18.dp)
)
}
// Dropdown appears BELOW name/address
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
user.addresses.forEach { address ->
DropdownMenuItem(
text = {
Text(
text = address.name,
fontWeight = if (address == selectedAddress)
FontWeight.SemiBold
else FontWeight.Normal
)
},
onClick = {
selectedAddress = address
expanded = false
onAddressSelected(address)
}
)
}
Divider()
// Add New option
DropdownMenuItem(
text = {
Text(
text = "Add New +",
fontWeight = FontWeight.Medium,
color = Color(0xFF007BFF)
)
},
onClick = {
expanded = false
onAddNewClick()
}
)
}
Text(
text = selectedAddress.address,
fontSize = 13.sp,
color = Color.Black.copy(alpha = 0.7f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}

View File

@ -1,6 +1,5 @@
package com.example.livingai_lg.ui.login
package com.example.livingai_lg.ui.login_legacy
import android.app.Application
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -14,7 +13,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource

View File

@ -1,4 +1,4 @@
package com.example.livingai_lg.ui.login
package com.example.livingai_lg.ui.login_legacy
import android.widget.Toast
import androidx.compose.foundation.background
@ -11,7 +11,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight

View File

@ -1,8 +1,7 @@
package com.example.livingai_lg.ui.login
package com.example.livingai_lg.ui.login_legacy
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
@ -10,7 +9,6 @@ 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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight

View File

@ -1,4 +1,4 @@
package com.example.livingai_lg.ui.login
package com.example.livingai_lg.ui.login_legacy
import android.widget.Toast
import androidx.compose.foundation.background
@ -14,7 +14,6 @@ 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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight

View File

@ -1,4 +1,4 @@
package com.example.livingai_lg.ui.login
package com.example.livingai_lg.ui.login_legacy
import android.widget.Toast
import androidx.compose.foundation.background
@ -15,7 +15,6 @@ 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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight

View File

@ -1,12 +1,10 @@
package com.example.livingai_lg.ui.login
package com.example.livingai_lg.ui.login_legacy
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp

View File

@ -25,31 +25,42 @@ data class Animal(
val sampleAnimals = listOf(
Animal(
id = "1",
name = "Golden Retriever",
price = 80000,
imageUrl = listOf("https://api.builder.io/api/v1/image/assets/TEMP/8b3d82a183db9ded7741b4b8bf0cd81fb2733b89?width=686"),
distance = 2500L,// miles away
views = 94,//Views
name = "Rita",
age = 45,
breed = "Gir",
location = "Punjab",
distance = 12000,
imageUrl = listOf("https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2F4.bp.blogspot.com%2F_tecSnxaePMo%2FTLLVknW8dOI%2FAAAAAAAAACo%2F_kd1ZNBXU1o%2Fs1600%2FGIR%2CGujrat.jpg&f=1&nofb=1&ipt=da6ba1d040c396b64d3f08cc99998f66200dcd6c001e4a56def143ab3d1a87ea","https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcpimg.tistatic.com%2F4478702%2Fb%2F4%2Fgir-cow.jpg&f=1&nofb=1&ipt=19bf391461480585c786d01433d863a383c60048ac2ce063ce91f173e215205d"),
views = 9001,
aiScore = 0.80f,
price = 120000,
isFairPrice = true,
rating = 4.7f,
ratingCount = 2076,
sellerId = "1",
sellerName = "Seller 1",
sellerType = "Wholeseller",
rating = 4.5f,
ratingCount = 2076,
description = "Friendly and energetic companion looking for an active family.",
description = "Premium Gir dairy cow in excellent health condition. High-yielding milk producer with consistent output and gentle temperament, making her ideal for commercial dairy operations or family farms.",
displayLocation = "Mediatek Pashu Mela, Marathalli, Bangalore (13 km)",
milkCapacity = 3.2f
),
Animal(
id = "2",
name = "Mudkip",
price = 999999999,
imageUrl = listOf("https://img.pokemondb.net/artwork/large/mudkip.jpg", "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdna.artstation.com%2Fp%2Fassets%2Fimages%2Fimages%2F012%2F524%2F022%2Flarge%2Fanna-ben-david-poster-size-mudkip.jpg%3F1535218851&f=1&nofb=1&ipt=aac92abbad440468461cbec5d9208d4258e5f4ed72e18a9ae17cfd6e0da1ce9f"),
name = "Sahi",
price = 95000,
age = 35,
breed = "Sahiwal",
location = "Punjab",
isFairPrice = true,
imageUrl = listOf("https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdnbbsr.s3waas.gov.in%2Fs3a5a61717dddc3501cfdf7a4e22d7dbaa%2Fuploads%2F2020%2F09%2F2020091812-1024x680.jpg&f=1&nofb=1&ipt=bb426406b3747e54151e4812472e203f33922fa3b4e11c4feef9aa59a5733146"),
distance = 0L,
views = 100000,
views = 100,
sellerId = "1",
sellerName = "Seller ???",
sellerType = "Wholeseller",
rating = 5f,
ratingCount = 2076,
description = "Friendly and energetic companion looking for an active family.",
milkCapacity = 2.5f
),
Animal(
id = "3",

View File

@ -13,15 +13,13 @@ 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.CREATE_PROFILE),
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("Home", R.drawable.ic_home ,"home"),
BottomNavItemData("Sell", R.drawable.ic_tag, "sell"),
BottomNavItemData("Contacts", R.drawable.ic_home ,"home"),
BottomNavItemData("Calls", R.drawable.ic_tag, "sell"),
BottomNavItemData("Chats", R.drawable.ic_chat, "chats"),
BottomNavItemData("Services", R.drawable.ic_config, "services"),
BottomNavItemData("Mandi", R.drawable.ic_market, "mandi")
)
)

View File

@ -0,0 +1,37 @@
package com.example.livingai_lg.ui.models
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Chat
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.Star
data class AppNotification(
val id: String,
val title: String,
val icon: ImageVector
)
val sampleNotifications = listOf(
AppNotification(
id = "1",
title = "Animal saved successfully",
icon = Icons.Default.Bookmark
),
AppNotification(
id = "2",
title = "New message from Seller",
icon = Icons.Default.Chat
),
AppNotification(
id = "3",
title = "Price dropped on an animal you viewed",
icon = Icons.Default.Notifications
),
AppNotification(
id = "4",
title = "You received a new rating",
icon = Icons.Default.Star
)
)

View File

@ -1,13 +1,16 @@
package com.example.livingai_lg.ui.models
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import com.example.livingai_lg.R
data class ProfileType(
val id: String,
val title: String,
val icon: Int,
val backgroundColor: Color
val icon: Any,
val iconTint: Color = Color.Unspecified,
val backgroundColor: Color,
)
val profileTypes = listOf(
@ -15,24 +18,28 @@ val profileTypes = listOf(
id = "buyer_seller",
title = "I'm a Buyer/Seller",
icon = R.drawable.ic_shop,
iconTint = Color.White,
backgroundColor = Color(0xFF9D4EDD)
),
ProfileType(
id = "wholesale_trader",
title = "I'm a Wholesale Trader",
icon = R.drawable.ic_bag,
iconTint = Color.White,
backgroundColor = Color(0xFF3A86FF)
),
ProfileType(
id = "service_provider",
title = "I'm a Service Provider",
icon = R.drawable.ic_spanner,
iconTint = Color.White,
backgroundColor = Color(0xFFFF5722)
),
ProfileType(
id = "mandi_host",
title = "I'm a Mandi Host",
icon = R.drawable.ic_shop2,
iconTint = Color.White,
backgroundColor = Color(0xFF4CAF50)
)
)

View File

@ -1,6 +1,7 @@
package com.example.livingai_lg.ui.models
data class UserAddress(
val id: String,
val name: String,
val address: String,
val isPrimary: Boolean = false
@ -18,11 +19,13 @@ val userProfile =
profileImageUrl = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic.vecteezy.com%2Fsystem%2Fresources%2Fpreviews%2F014%2F016%2F808%2Fnon_2x%2Findian-farmer-face-vector.jpg&f=1&nofb=1&ipt=c352fec591428aebefe6cd263d2958765e85d4da69cce3c46b725ba2ff7d3448",
addresses = listOf(
UserAddress(
id = "1",
name = "Home",
address = "205 1st floor 7th cross 27th main, PW",
isPrimary = true
),
UserAddress(
id = "2",
name = "Farm",
address = "2nd block, MG Farms"
)

View File

@ -4,6 +4,9 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
@ -28,7 +31,8 @@ import com.example.livingai_lg.ui.screens.auth.LandingScreen
import com.example.livingai_lg.ui.screens.auth.OtpScreen
import com.example.livingai_lg.ui.screens.auth.SignInScreen
import com.example.livingai_lg.ui.screens.auth.SignUpScreen
import com.example.livingai_lg.ui.navigation.authNavGraph
import com.example.livingai_lg.ui.navigation.mainNavGraph
object AppScreen {
const val LANDING = "landing"
@ -55,6 +59,19 @@ object AppScreen {
const val POST_SALE_SURVEY = "post_sale_survey"
const val SAVED_LISTINGS = "saved_listings"
const val CONTACTS = "contacts"
const val CALLS = "calls"
const val CHAT = "chat"
const val CHATS = "chats"
fun chats(contact: String) =
"$CHAT/$contact"
fun otp(phone: String, name: String) =
"$OTP/$phone/$name"
@ -74,23 +91,55 @@ object AppScreen {
fun saleArchive(saleId: String) =
"$SALE_ARCHIVE/$saleId"
}
object Graph {
const val AUTH = "auth"
const val MAIN = "auth"
fun auth(route: String)=
"$AUTH/$route"
fun main(route: String)=
"$MAIN/$route"
}
@Composable
fun AppNavigation(
authState: AuthState
) {
val navController = rememberNavController()
var isLoggedIn = false;
when (authState) {
is AuthState.Unauthenticated -> {AuthNavGraph()}
is AuthState.Authenticated -> {MainNavGraph()}
is AuthState.Unauthenticated -> {isLoggedIn = false; }
is AuthState.Authenticated -> {isLoggedIn = true;}
is AuthState.Unknown -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
// AuthState.Loading -> SplashScreen()
}
// val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = if (isLoggedIn) Graph.MAIN else Graph.AUTH
) {
authNavGraph(navController)
mainNavGraph(navController)
}
// MainNavGraph(navController)
// AuthNavGraph(navController)
// when (authState) {
// is AuthState.Unauthenticated -> {AuthNavGraph()}
// is AuthState.Authenticated -> {MainNavGraph()}
// is AuthState.Unknown -> {
// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
// CircularProgressIndicator()
// }
// }
// AuthState.Loading -> SplashScreen()
// }
// val onNavClick: (String) -> Unit = { route ->
// val currentRoute =

View File

@ -1,10 +1,13 @@
package com.example.livingai_lg.ui.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.livingai_lg.ui.screens.SaleArchiveScreen
@ -13,19 +16,16 @@ import com.example.livingai_lg.ui.screens.auth.OtpScreen
import com.example.livingai_lg.ui.screens.auth.SignInScreen
import com.example.livingai_lg.ui.screens.auth.SignUpScreen
@Composable
fun AuthNavGraph(
navController: NavHostController = rememberNavController()
) {
NavHost(
navController = navController,
fun NavGraphBuilder.authNavGraph(navController: NavController) {
navigation(
route = Graph.AUTH,
startDestination = AppScreen.LANDING
) {
composable(AppScreen.LANDING) {
LandingScreen(
onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) },
onSignInClick = { navController.navigate(AppScreen.SIGN_IN) },
onGuestClick = { navController.navigate(AppScreen.CREATE_PROFILE) }
onGuestClick = { navController.navigate(Graph.main(AppScreen.createProfile("guest"))) }
)
}
@ -63,7 +63,8 @@ fun AuthNavGraph(
OtpScreen(
phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "",
name = backStackEntry.arguments?.getString("name") ?: "",
onSuccess = { navController.navigate(AppScreen.chooseService("1"))}
onCreateProfile = {name -> navController.navigate(Graph.main(AppScreen.createProfile(name)))},
onSuccess = { navController.navigate(Graph.auth(AppScreen.chooseService("1")))}
)
}
}

View File

@ -1,13 +1,16 @@
package com.example.livingai_lg.ui.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import android.util.Log
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.compose.navigation
import androidx.navigation.navArgument
import com.example.livingai_lg.ui.login.SuccessScreen
import com.example.farmmarketplace.ui.screens.CallsScreen
import com.example.farmmarketplace.ui.screens.ContactsScreen
import com.example.livingai_lg.ui.models.profileTypes
import com.example.livingai_lg.ui.screens.AnimalProfileScreen
import com.example.livingai_lg.ui.screens.BuyScreen
import com.example.livingai_lg.ui.screens.ChooseServiceScreen
@ -16,32 +19,39 @@ import com.example.livingai_lg.ui.screens.FilterScreen
import com.example.livingai_lg.ui.screens.NewListingScreen
import com.example.livingai_lg.ui.screens.PostSaleSurveyScreen
import com.example.livingai_lg.ui.screens.SaleArchiveScreen
import com.example.livingai_lg.ui.screens.SavedListingsScreen
import com.example.livingai_lg.ui.screens.SellerProfileScreen
import com.example.livingai_lg.ui.screens.SortScreen
import com.example.livingai_lg.ui.screens.chat.ChatScreen
import com.example.livingai_lg.ui.screens.chat.ChatsScreen
@Composable
fun MainNavGraph(
navController: NavHostController = rememberNavController()
) {
fun NavGraphBuilder.mainNavGraph(navController: NavController) {
val onNavClick: (String) -> Unit = { route ->
val currentRoute =
navController.currentBackStackEntry?.destination?.route
Log.d("Current Route:"," $currentRoute $route")
if (currentRoute != route) {
navController.navigate(route) {
launchSingleTop = true
restoreState = true
popUpTo(navController.graph.startDestinationId) {
saveState = true
}
//restoreState = true
// popUpTo(navController.graph.startDestinationId) {
// saveState = true
// }
}
}
}
NavHost(
navController = navController,
startDestination = AppScreen.BUY_ANIMALS
) {
navigation(
route = Graph.MAIN,
startDestination = AppScreen.createProfile("guest")
){
// NavHost(
// navController = navController,
// startDestination = AppScreen.createProfile("guest")
// ) {
composable(
"${AppScreen.CREATE_PROFILE}/{name}",
arguments = listOf(navArgument("name") { type = NavType.StringType })
@ -55,12 +65,6 @@ fun MainNavGraph(
)
}
composable(AppScreen.CHOOSE_SERVICE) {
ChooseServiceScreen (
onServiceSelected = { navController.navigate(AppScreen.LANDING) },
)
}
composable(
"${AppScreen.CHOOSE_SERVICE}/{profileId}",
arguments = listOf(navArgument("profileId") { type = NavType.StringType })
@ -82,8 +86,6 @@ fun MainNavGraph(
)
},
onNavClick = onNavClick,
onFilterClick = {navController.navigate(AppScreen.BUY_ANIMALS_FILTERS)},
onSortClick = {navController.navigate(AppScreen.BUY_ANIMALS_SORT)},
onSellerClick = { sellerId ->
navController.navigate(
AppScreen.sellerProfile(sellerId)
@ -92,20 +94,17 @@ fun MainNavGraph(
)
}
composable(AppScreen.BUY_ANIMALS_FILTERS) {
FilterScreen(
onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
onBackClick = {
navController.popBackStack()
},
onSkipClick = {
navController.popBackStack()
},
onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
onCancelClick = {
navController.popBackStack()
},
)
)
}
composable(AppScreen.BUY_ANIMALS_SORT) {
@ -114,9 +113,6 @@ fun MainNavGraph(
onBackClick = {
navController.popBackStack()
},
onSkipClick = {
navController.popBackStack()
},
onCancelClick = {
navController.popBackStack()
},
@ -124,13 +120,25 @@ fun MainNavGraph(
)
}
composable(AppScreen.SAVED_LISTINGS) {
SavedListingsScreen(
onNavClick = onNavClick,
onBackClick = { navController.popBackStack() })
}
composable(AppScreen.CREATE_ANIMAL_LISTING) {
NewListingScreen (
onSaveClick = {navController.navigate(
AppScreen.postSaleSurvey("2")
)},
onBackClick = {
navController.popBackStack()
navController.navigate(AppScreen.BUY_ANIMALS){
popUpTo(AppScreen.BUY_ANIMALS){
inclusive = true
}
}
}
)
}
@ -175,7 +183,7 @@ fun MainNavGraph(
PostSaleSurveyScreen (
animalId = animalId,
onBackClick = {
navController.popBackStack()
navController.navigate(AppScreen.CREATE_ANIMAL_LISTING)
},
onSubmit = {navController.navigate(
AppScreen.saleArchive("2")
@ -200,6 +208,7 @@ fun MainNavGraph(
onBackClick = {
navController.popBackStack()
},
onNavClick = onNavClick,
onSellerClick = { sellerId ->
navController.navigate(
AppScreen.sellerProfile(sellerId)
@ -226,5 +235,51 @@ fun MainNavGraph(
navController.popBackStack()
}
)
}}
}
composable(AppScreen.CONTACTS) {
ContactsScreen(
onBackClick = {navController.navigate(AppScreen.BUY_ANIMALS)},//{navController.popBackStack()},
onTabClick = onNavClick,
)
}
composable(AppScreen.CALLS) {
CallsScreen(
onBackClick = {navController.navigate(AppScreen.BUY_ANIMALS)},//{navController.popBackStack()},
onTabClick = onNavClick,
)
}
composable(AppScreen.CHATS) {
ChatsScreen(
onBackClick = {navController.navigate(AppScreen.BUY_ANIMALS)},//{navController.popBackStack()},
onTabClick = onNavClick,
onChatItemClick = {navController.navigate(AppScreen.chats("2"))}
)
}
composable(
route = "${AppScreen.CHAT}/{contact}",
arguments = listOf(
navArgument("contact") { type = NavType.StringType }
)
) { backStackEntry ->
val sellerId = backStackEntry
.arguments
?.getString("contact")
?: return@composable
ChatScreen(
sellerId,
onBackClick = {
navController.navigate(AppScreen.CHATS)
//navController.popBackStack()
}
)
}
}
}

View File

@ -1,5 +1,6 @@
package com.example.livingai_lg.ui.screens
import androidx.compose.foundation.Image
import com.example.livingai_lg.ui.components.ImageCarousel
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
@ -9,11 +10,21 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.StarHalf
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.StarHalf
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
@ -26,9 +37,12 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.zIndex
import com.example.livingai_lg.ui.models.Animal
import com.example.livingai_lg.ui.utils.formatPrice
@ -38,6 +52,11 @@ import com.example.livingai_lg.ui.components.FloatingActionBar
import com.example.livingai_lg.ui.models.sampleAnimals
import com.example.livingai_lg.ui.utils.formatAge
import com.example.livingai_lg.R
import com.example.livingai_lg.ui.components.ActionPopup
import com.example.livingai_lg.ui.components.RatingStars
import com.example.livingai_lg.ui.navigation.AppScreen
import com.example.livingai_lg.ui.theme.AppTypography
import com.example.livingai_lg.ui.utils.formatDistance
@Composable
@ -45,12 +64,15 @@ fun AnimalProfileScreen(
animalId: String,
onBackClick: () -> Unit = {},
onSellerClick: (sellerId: String) -> Unit = {},
onNavClick: (route: String) -> Unit = {}
) {
var showSavedPopup by remember { mutableStateOf(false) }
val animal = sampleAnimals.find { animal -> animal.id == animalId } ?: Animal(id = "null")
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F4EE))
.padding(12.dp)
) {
Column(
modifier = Modifier
@ -62,7 +84,7 @@ fun AnimalProfileScreen(
Box(
modifier = Modifier
.fillMaxWidth()
.height(500.dp)
.height(350.dp)
.shadow(4.dp)
) {
// Main image
@ -92,19 +114,31 @@ fun AnimalProfileScreen(
Row(
modifier = Modifier
.align(Alignment.TopStart)
.padding(start = 16.dp, top = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
.padding(start = 5.dp, top = 5.dp)
.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(50),
ambientColor = Color.Black.copy(alpha = 0.4f),
spotColor = Color.Black.copy(alpha = 0.4f)
)
.background(
color = Color.Black.copy(alpha = 0.35f), // 👈 light but effective
shape = RoundedCornerShape(50)
)
.padding(horizontal = 6.dp, vertical = 3.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(R.drawable.ic_view),
contentDescription = "Views",
tint = Color.White,
modifier = Modifier.size(24.dp)
modifier = Modifier.size(16.dp)
)
Text(
text = formatViews(animal.views),
fontSize = 12.sp,
fontSize = AppTypography.BodySmall,
fontWeight = FontWeight.Normal,
color = Color.White
)
@ -120,25 +154,41 @@ fun AnimalProfileScreen(
) {
Text(
text = "${animal.name}, ${formatAge(animal.age)}",
fontSize = 30.sp,
fontSize = AppTypography.Title,
fontWeight = FontWeight.Normal,
color = Color.White
color = Color.White,
style = LocalTextStyle.current.copy(
shadow = Shadow(
color = Color.Black.copy(alpha = 0.75f),
offset = Offset(0f, 2f),
blurRadius = 6f
)
)
)
Text(
text = "${animal.breed}${animal.location}, ${animal.distance}".uppercase(),
fontSize = 12.sp,
text = "${animal.breed}${animal.location}, ${formatDistance(animal.distance)}".uppercase(),
fontSize = AppTypography.Body,
fontWeight = FontWeight.Normal,
color = Color.White.copy(alpha = 0.7f),
letterSpacing = 1.2.sp
letterSpacing = 1.2.sp,
style = LocalTextStyle.current.copy(
shadow = Shadow(
color = Color.Black.copy(alpha = 0.6f),
offset = Offset(0f, 1.5f),
blurRadius = 4f
)
)
)
Spacer(modifier = Modifier.height(4.dp))
}
// AI Score badge (bottom left)
Row(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(start = 0.dp, bottom = 5.dp)
.height(48.dp)
.padding(start = 5.dp, bottom = 5.dp)
.height(60.dp)
.border(2.dp, Color(0xFF717182), CircleShape)
.padding(horizontal = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
@ -147,7 +197,7 @@ fun AnimalProfileScreen(
AIScoreCircle(score = animal.aiScore ?: 0f)
Text(
text = "AI Score",
fontSize = 18.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Normal,
color = Color.White
)
@ -159,11 +209,12 @@ fun AnimalProfileScreen(
append("At Display: ")
append(animal.displayLocation)
},
fontSize = 8.sp,
fontSize = AppTypography.Caption,
fontWeight = FontWeight.Normal,
color = Color.White,
color = Color.White.copy(alpha = 0.7f),
lineHeight = 13.sp,
textDecoration = TextDecoration.Underline,
textAlign = TextAlign.Right,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 16.dp, bottom = 8.dp)
@ -190,8 +241,8 @@ fun AnimalProfileScreen(
) {
Text(
text = formatPrice(animal.price),
fontSize = 30.sp,
fontWeight = FontWeight.Medium,
fontSize = AppTypography.Title,
fontWeight = FontWeight.Bold,
color = Color(0xFF0A0A0A)
)
@ -204,12 +255,12 @@ fun AnimalProfileScreen(
Icon(
painter = painterResource(R.drawable.ic_thumbs_up),
contentDescription = "Fair Price",
tint = Color(0xFF0A0A0A),
tint = Color(0xFF00C950),
modifier = Modifier.size(15.dp)
)
Text(
text = "Fair Price",
fontSize = 12.sp,
fontSize = AppTypography.Caption,
fontWeight = FontWeight.Normal,
color = Color(0xFF00C950)
)
@ -219,7 +270,7 @@ fun AnimalProfileScreen(
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
@ -229,7 +280,7 @@ fun AnimalProfileScreen(
{
Text(
text = "Sold By: ${animal.sellerName}",
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Normal,
color = Color(0xFF0A0A0A)
)
@ -239,19 +290,13 @@ fun AnimalProfileScreen(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
repeat(5) { index ->
Icon(
painter = painterResource(R.drawable.ic_star),
contentDescription = null,
tint = if (index < (animal.rating ?: 0).toInt()) Color(
0xFFDE9A07
) else Color(0xFFDE9A07),
modifier = Modifier.size(12.dp)
)
}
RatingStars(
rating = animal.rating ?: 0f,
starSize = 12.dp
)
Text(
text = "${animal.rating} (${animal.ratingCount} Ratings)",
fontSize = 10.sp,
fontSize = AppTypography.Caption,
fontWeight = FontWeight.Normal,
color = Color(0xFF0A0A0A)
)
@ -267,13 +312,13 @@ fun AnimalProfileScreen(
) {
Text(
text = "About",
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium,
color = Color(0xFF09090B).copy(alpha = 0.5f)
)
Text(
text = animal.description ?: "",
fontSize = 14.sp,
fontSize = AppTypography.BodySmall,
fontWeight = FontWeight.Normal,
color = Color(0xFF09090B),
lineHeight = 24.sp
@ -297,24 +342,43 @@ fun AnimalProfileScreen(
// Ad space banner
AdSpaceBanner()
Spacer(modifier = Modifier.height(32.dp))
Spacer(modifier = Modifier.height(64.dp))
}
}
FloatingActionBar(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 24.dp)
.offset(y = (-20).dp)
.padding(bottom = 10.dp)
.offset(y = (-10).dp)
.zIndex(10f), // 👈 ensure it floats above everything
onChatClick = { /* TODO */ },
onCallClick = { /* TODO */ },
onLocationClick = { /* TODO */ },
onBookmarkClick = { /* TODO */ }
onBookmarkClick = { showSavedPopup = true }
)
ActionPopup(
visible = showSavedPopup,
text = "Saved",
icon = Icons.Default.Bookmark,
backgroundColor = Color.Black,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 16.dp, bottom = 96.dp),
onClick = {
onNavClick(AppScreen.SAVED_LISTINGS)
// Navigate to saved items
},
onDismiss = {
showSavedPopup = false
}
)
}
}
fun Modifier.Companion.align(bottomEnd: Alignment) {}
@Composable
fun AIScoreCircle(score: Float) {
Box(

View File

@ -1,12 +1,20 @@
package com.example.livingai_lg.ui.screens
import androidx.compose.foundation.Indication
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.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material3.Icon
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -25,6 +33,12 @@ import com.example.livingai_lg.ui.models.mainBottomNavItems
import com.example.livingai_lg.ui.models.sampleAnimals
import com.example.livingai_lg.ui.models.userProfile
import com.example.livingai_lg.R
import com.example.livingai_lg.ui.components.ActionPopup
import com.example.livingai_lg.ui.components.AddressSelectorOverlay
import com.example.livingai_lg.ui.components.NotificationsOverlay
import com.example.livingai_lg.ui.components.SortOverlay
import com.example.livingai_lg.ui.models.sampleNotifications
import com.example.livingai_lg.ui.navigation.AppScreen
@Composable
fun BuyScreen(
@ -37,10 +51,13 @@ fun BuyScreen(
) {
val selectedAnimalType = remember { mutableStateOf<String?>(null) }
val isSaved = remember { mutableStateOf(false) }
var showAddressSelector by remember { mutableStateOf(false) }
var selectedAddressId by remember { mutableStateOf<String?>(userProfile.addresses.find { address -> address.isPrimary }?.id) }
val showFilterOverlay = remember { mutableStateOf(false) }
val showSortOverlay = remember { mutableStateOf(false) }
var showSavedPopup by remember { mutableStateOf(false) }
var showNotifications by remember { mutableStateOf(false) }
val sampleNotifications = sampleNotifications
Box(
modifier = Modifier
@ -74,10 +91,8 @@ fun BuyScreen(
UserLocationHeader(
user = userProfile,
onAddressSelected = {
// optional: reload listings, persist selection, etc.
},
onAddNewClick = {}
onOpenAddressOverlay = { showAddressSelector = true },
selectedAddressId = selectedAddressId?:"1",
)
// Right-side actions (notifications, etc.)
@ -86,6 +101,10 @@ fun BuyScreen(
contentDescription = "Notifications",
tint = Color.Black,
modifier = Modifier.size(24.dp)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
){ showNotifications = true }
)
}
// Row(
@ -129,8 +148,10 @@ fun BuyScreen(
.padding(horizontal = 22.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
SortButton(onSortClick)
FilterButton(onFilterClick)
SortButton(
onClick = { showSortOverlay.value = true }
)
FilterButton(onClick = { showFilterOverlay.value = true })
}
sampleAnimals.forEach { animal ->
@ -142,7 +163,8 @@ fun BuyScreen(
isSaved = isSaved.value,
onSavedChange = { isSaved.value = it },
onProductClick = { onProductClick(animal.id)},
onSellerClick = onSellerClick
onSellerClick = onSellerClick,
onBookmarkClick = { showSavedPopup = true }
)
Spacer(modifier = Modifier.height(16.dp))
@ -160,6 +182,62 @@ fun BuyScreen(
}
}
AddressSelectorOverlay(
visible = showAddressSelector,
addresses = userProfile.addresses,
selectedAddressId = selectedAddressId,
onSelect = { addressId ->
selectedAddressId = addressId
showAddressSelector = false
},
onClose = { showAddressSelector = false }
)
SortOverlay(
visible = showSortOverlay.value,
onApplyClick = { selected ->
// TODO: apply sort
},
onDismiss = { showSortOverlay.value = false }
)
FilterOverlay(
visible = showFilterOverlay.value,
onDismiss = { showFilterOverlay.value = false },
onSubmitClick = {
// apply filters
}
)
ActionPopup(
visible = showSavedPopup,
text = "Saved",
icon = Icons.Default.Bookmark,
backgroundColor = Color.Black,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 16.dp, bottom = 96.dp),
onClick = {
onNavClick(AppScreen.SAVED_LISTINGS)
// Navigate to saved items
},
onDismiss = {
showSavedPopup = false
}
)
NotificationsOverlay(
visible = showNotifications,
notifications = sampleNotifications,
onClose = { showNotifications = false },
onDismiss = { id ->
// remove notification from list
},
onNotificationClick = { id ->
// optional navigation
}
)
}
}

View File

@ -27,10 +27,13 @@ import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.components.OptionCard
import com.example.livingai_lg.ui.components.backgrounds.StoreBackground
import com.example.livingai_lg.R
import com.example.livingai_lg.ui.theme.AppTypography
data class ServiceType(
val id: String,
val title: String,
val icon: Int,
val icon: Any,
val iconTint: Color,
val backgroundColor: Color
)
@ -46,30 +49,35 @@ fun ChooseServiceScreen(
id = "transport",
title = "Transport",
icon = R.drawable.ic_shop,
iconTint = Color.White,
backgroundColor = Color(0xFF9D4EDD)
),
ServiceType(
id = "vet",
title = "Vet",
icon = R.drawable.ic_bag,
iconTint = Color.White,
backgroundColor = Color(0xFF3A86FF)
),
ServiceType(
id = "feed_supplier",
title = "Feed Supplier",
icon = R.drawable.ic_spanner,
iconTint = Color.White,
backgroundColor = Color(0xFFFF5722)
),
ServiceType(
id = "medicine_supplier",
title = "Medicine Supplier",
icon = R.drawable.ic_shop2,
iconTint = Color.White,
backgroundColor = Color(0xFF4CAF50)
),
ServiceType(
id = "other",
title = "Other",
icon = R.drawable.ic_other,
iconTint = Color.White,
backgroundColor = Color(0xFFD4A942)
)
)
@ -101,7 +109,7 @@ fun ChooseServiceScreen(
) {
Text(
text = "Choose Service",
fontSize = 36.sp,
fontSize = AppTypography.Display,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A)
)
@ -109,8 +117,8 @@ fun ChooseServiceScreen(
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Choose Service**",
fontSize = 16.sp,
text = "Choose a Service",
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A).copy(alpha = 0.8f)
)
@ -129,6 +137,7 @@ fun ChooseServiceScreen(
OptionCard(
label = service.title,
icon = service.icon,
iconTint = service.iconTint,
iconBackgroundColor = service.backgroundColor,
onClick = {
selectedService.value = service.id
@ -144,84 +153,3 @@ fun ChooseServiceScreen(
}
}
@Composable
fun ServiceTypeCard(
service: ServiceType,
isSelected: Boolean,
onClick: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(90.dp)
.shadow(2.dp, RoundedCornerShape(16.dp))
.background(Color.White, RoundedCornerShape(16.dp))
.border(1.dp, Color(0xFFF3F4F6), RoundedCornerShape(16.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick
)
.padding(12.dp)
) {
Row(
modifier = Modifier
.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Icon container
Box(
modifier = Modifier
.size(56.dp)
.background(service.backgroundColor, RoundedCornerShape(14.dp)),
contentAlignment = Alignment.Center
) {
when (service.id) {
"transport" ->
Icon(
painter = painterResource(id = R.drawable.ic_shop),
contentDescription = "Transport",
)
"vet" -> Icon(
painter = painterResource(id = R.drawable.ic_bag),
contentDescription = "Vet",
)
"feed_supplier" -> Icon(
painter = painterResource(id = R.drawable.ic_spanner),
contentDescription = "Feed Supplier",
)
"medicine_supplier" -> Icon(
painter = painterResource(id = R.drawable.ic_shop2),
contentDescription = "Medicine Supplier",
)
"other" -> Icon(
painter = painterResource(id = R.drawable.ic_other),
contentDescription = "Other",
)
}
}
// Title
Text(
text = service.title,
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF0A0A0A),
modifier = Modifier.weight(1f)
)
// Radio button
RadioButton(
selected = isSelected,
onClick = onClick,
colors = RadioButtonDefaults.colors(
selectedColor = Color(0xFF6B7280),
unselectedColor = Color(0xFF6B7280)
)
)
}
}
}

View File

@ -5,6 +5,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
@ -24,6 +26,7 @@ import com.example.livingai_lg.ui.components.OptionCard
import com.example.livingai_lg.ui.components.backgrounds.StoreBackground
import com.example.livingai_lg.ui.models.profileTypes
import com.example.livingai_lg.ui.theme.AppTypography
import kotlinx.coroutines.launch
@ -75,7 +78,7 @@ fun CreateProfileScreen(
) {
Text(
text = "Create Profile",
fontSize = 36.sp,
fontSize = AppTypography.Display,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A)
)
@ -84,7 +87,7 @@ fun CreateProfileScreen(
Text(
text = "Choose Profile Type",
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A).copy(alpha = 0.8f)
)
@ -105,7 +108,8 @@ fun CreateProfileScreen(
icon = profile.icon,
iconBackgroundColor = profile.backgroundColor,
onClick = {
updateProfile(profile.id)
onProfileSelected(profile.id)
//updateProfile(profile.id)
}
)
}

View File

@ -1,5 +1,9 @@
package com.example.livingai_lg.ui.screens
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
@ -24,14 +28,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.components.DropdownInput
import com.example.livingai_lg.ui.components.RangeFilter
import com.example.livingai_lg.ui.theme.AppTypography
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilterScreen(
onBackClick: () -> Unit = {},
onSkipClick: () -> Unit = {},
onSubmitClick: () -> Unit = {},
onCancelClick: () -> Unit = {}
onCancelClick: () -> Unit = {},
) {
var selectedAnimal by remember { mutableStateOf("") }
var selectedBreed by remember { mutableStateOf("") }
@ -104,16 +108,6 @@ fun FilterScreen(
color = Color.Black
)
}
TextButton(onClick = onSkipClick) {
Text(
text = "Skip",
fontSize = 24.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF4A5565),
textDecoration = TextDecoration.Underline
)
}
}
}
@ -122,7 +116,7 @@ fun FilterScreen(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp)
.padding(horizontal = 32.dp)
.padding(bottom = 24.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
@ -130,117 +124,43 @@ fun FilterScreen(
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Animal",
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = Color.Black
)
ExposedDropdownMenuBox(
DropdownInput(
label = "Animal",
selected = selectedAnimal,
options = listOf("Cow", "Buffalo", "Goat", "Sheep"),
expanded = animalExpanded,
onExpandedChange = { animalExpanded = it }
) {
OutlinedTextField(
value = selectedAnimal,
onValueChange = {},
readOnly = true,
placeholder = {
Text(
"Select Animal",
color = Color(0xFF717182),
fontSize = 16.sp
)
},
modifier = Modifier
.fillMaxWidth()
.menuAnchor(),
shape = RoundedCornerShape(8.dp),
colors = OutlinedTextFieldDefaults.colors(
unfocusedContainerColor = Color.White,
focusedContainerColor = Color.White,
unfocusedBorderColor = Color(0x1A000000),
focusedBorderColor = Color(0x1A000000)
)
)
ExposedDropdownMenu(
expanded = animalExpanded,
onDismissRequest = { animalExpanded = false }
) {
listOf("Cow", "Buffalo", "Goat", "Sheep").forEach { animal ->
DropdownMenuItem(
text = { Text(animal) },
onClick = {
selectedAnimal = animal
animalExpanded = false
}
)
}
}
}
onExpandedChange = { animalExpanded = it },
onSelect = { item ->
selectedAnimal = item
animalExpanded = false
},
placeholder = "Select Animal", // <--- half width
)
}
// Breed Section
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Breed",
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = Color.Black
)
ExposedDropdownMenuBox(
DropdownInput(
label = "Breed",
selected = selectedBreed,
options = listOf("Holstein", "Jersey", "Gir", "Sahiwal"),
expanded = breedExpanded,
onExpandedChange = { breedExpanded = it }
) {
OutlinedTextField(
value = selectedBreed,
onValueChange = {},
readOnly = true,
placeholder = {
Text(
"Select Breed",
color = Color(0xFF717182),
fontSize = 16.sp
)
},
modifier = Modifier
.fillMaxWidth()
.menuAnchor(),
shape = RoundedCornerShape(8.dp),
colors = OutlinedTextFieldDefaults.colors(
unfocusedContainerColor = Color.White,
focusedContainerColor = Color.White,
unfocusedBorderColor = Color(0x1A000000),
focusedBorderColor = Color(0x1A000000)
)
)
ExposedDropdownMenu(
expanded = breedExpanded,
onDismissRequest = { breedExpanded = false }
) {
listOf("Holstein", "Jersey", "Gir", "Sahiwal").forEach { breed ->
DropdownMenuItem(
text = { Text(breed) },
onClick = {
selectedBreed = breed
breedExpanded = false
}
)
}
}
}
onExpandedChange = { breedExpanded = it },
onSelect = { item ->
selectedBreed = item
breedExpanded = false
},
placeholder = "Select Breed", // <--- half width
)
}
// Price and Age Row
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp) // 👈 reduce spacing
) {
Column(
modifier = Modifier.weight(1f)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
RangeFilter(
modifier = Modifier.fillMaxWidth(), // 👈 important
@ -257,7 +177,8 @@ fun FilterScreen(
}
Column(
modifier = Modifier.weight(1f)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
RangeFilter(
modifier = Modifier.fillMaxWidth(),
@ -273,17 +194,13 @@ fun FilterScreen(
}
)
}
}
// Distance and Gender Row
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(64.dp)
) {
// Distance Section
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(3.dp)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
DropdownInput(
label = "Distance",
@ -301,8 +218,7 @@ fun FilterScreen(
// Gender Section
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(3.dp)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
DropdownInput(
label = "Gender",
@ -319,7 +235,7 @@ fun FilterScreen(
}
}
// Pregnancy Status Section
Column(
@ -327,7 +243,7 @@ fun FilterScreen(
) {
Text(
text = "Pregnancy Status",
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF364153)
)
@ -352,12 +268,9 @@ fun FilterScreen(
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp) // 👈 reduce spacing
) {
Column(
modifier = Modifier.weight(1f)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
RangeFilter(
modifier = Modifier.fillMaxWidth(), // 👈 important
@ -374,7 +287,7 @@ fun FilterScreen(
}
Column(
modifier = Modifier.weight(1f)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
RangeFilter(
modifier = Modifier.fillMaxWidth(),
@ -390,7 +303,7 @@ fun FilterScreen(
}
)
}
}
// Calving Number Section
Column(
@ -469,7 +382,7 @@ fun FilterScreen(
) {
Text(
text = "Submit",
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Normal
)
}
@ -492,7 +405,7 @@ fun FilterScreen(
) {
Text(
text = "Cancel",
fontSize = 16.sp,
fontSize = AppTypography.Body,
fontWeight = FontWeight.Normal
)
}

View File

@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.components.MediaPickerCard
import com.example.livingai_lg.ui.models.NewListingFormState
import com.example.livingai_lg.ui.theme.AppTypography
@OptIn(ExperimentalMaterial3Api::class)
@ -56,7 +57,7 @@ fun NewListingScreen(
title = {
Text(
text = "New Listing",
fontSize = 24.sp,
fontSize = AppTypography.Display,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A)
)

View File

@ -0,0 +1,165 @@
package com.example.livingai_lg.ui.screens
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
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.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.layout.width
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.Close
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.models.AppNotification
@Composable
fun NotificationsScreen(
notifications: List<AppNotification>,
onBackClick: () -> Unit,
onDismiss: (String) -> Unit,
onNotificationClick: (String) -> Unit = {},
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxSize()
.background(Color(0xFFF7F4EE))
) {
// Header
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
tint = Color.Black
)
}
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Notifications",
fontSize = 24.sp,
fontWeight = FontWeight.Normal,
color = Color.Black
)
}
Divider(color = Color(0xFFE5E7EB))
if (notifications.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "No notifications",
fontSize = 14.sp,
color = Color(0xFF6B7280)
)
}
} else {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(
items = notifications,
key = { it.id }
) { notification ->
NotificationItem(
notification = notification,
onDismiss = onDismiss,
onClick = onNotificationClick
)
}
}
}
}
}
@Composable
fun NotificationItem(
notification: AppNotification,
onDismiss: (String) -> Unit,
onClick: (String) -> Unit = {}
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(14.dp))
.border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(14.dp))
.clickable { onClick(notification.id) }
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = notification.icon,
contentDescription = null,
tint = Color(0xFF0A0A0A),
modifier = Modifier.size(22.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = notification.title,
fontSize = 14.sp,
color = Color(0xFF1F2937),
modifier = Modifier.weight(1f)
)
IconButton(
onClick = { onDismiss(notification.id) },
modifier = Modifier.size(20.dp)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Dismiss",
tint = Color(0xFF6B7280),
modifier = Modifier.size(16.dp)
)
}
}
}

View File

@ -94,18 +94,6 @@ fun PostSaleSurveyScreen(
interactionSource = remember { MutableInteractionSource() }
) { onBackClick() }
)
Text(
text = "Skip",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF4A5565),
textDecoration = TextDecoration.Underline,
modifier = Modifier.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) { onSkipClick() }
)
}
Spacer(modifier = Modifier.height(26.dp))

View File

@ -0,0 +1,266 @@
package com.example.livingai_lg.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.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
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.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.layout.ContentScale
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 coil.compose.AsyncImage
import com.example.livingai_lg.ui.components.AdSpaceBanner
import com.example.livingai_lg.ui.layout.BottomNavScaffold
import com.example.livingai_lg.ui.models.mainBottomNavItems
import com.example.livingai_lg.ui.theme.AppTypography
data class SavedListing(
val id: String,
val name: String,
val age: String,
val breed: String,
val price: String,
val views: String,
val distance: String,
val imageUrl: String
)
enum class BottomNavTab {
HOME,
SELL,
CHATS,
SERVICES,
MANDI
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SavedListingsScreen(
onListingClick: (String) -> Unit = {},
onNavClick: (route: String) -> Unit = {},
onBackClick: () -> Unit = {}
) {
val savedListings = listOf(
SavedListing(
id = "1",
name = "Eauri",
age = "Age: 3 yrs",
breed = "Holstein",
price = "₹45,000",
views = "124 views",
distance = "Distance: 12 km",
imageUrl = "https://api.builder.io/api/v1/image/assets/TEMP/db216f496f6d5fd681db9afc8006eb8da3164c17?width=192"
),
SavedListing(
id = "2",
name = "Fauri",
age = "Age: 2 yrs",
breed = "Jersey",
price = "₹38,000",
views = "89 views",
distance = "Distance: 17 km",
imageUrl = "https://api.builder.io/api/v1/image/assets/TEMP/a49aa5540b0cb6bf76ba3aa99cb8d4f94835e8ee?width=192"
),
SavedListing(
id = "3",
name = "Gauri",
age = "Age: 4 yrs",
breed = "Gir",
price = "₹52,000",
views = "156 views",
distance = "Distance: 20 km",
imageUrl = "https://api.builder.io/api/v1/image/assets/TEMP/26fd38c965180ef066862fb1d0aa6df040a0d389?width=192"
),
SavedListing(
id = "4",
name = "Hauri",
age = "Age: 1.5 yrs",
breed = "Sahiwal",
price = "₹28,000",
views = "67 views",
distance = "Distance: 6 km",
imageUrl = "https://api.builder.io/api/v1/image/assets/TEMP/b8edfd48d908a8e0eeeee55745ab409e454be25f?width=192"
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F4EE))
) {
BottomNavScaffold(
items = mainBottomNavItems,
currentItem = "Home",
onBottomNavItemClick = onNavClick,
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 72.dp)
) {
TopAppBar(
title = {
Text(
text = "Saved Listings",
fontSize = AppTypography.Display,
fontWeight = FontWeight.Medium,
color = Color(0xFF0A0A0A)
)
},
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A)
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color(0xFFF7F4EE)
)
)
LazyColumn(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 15.dp),
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
item {
AdSpaceBanner()
}
items(savedListings) { listing ->
ListingCard(
listing = listing,
onClick = { onListingClick(listing.id) }
)
}
}
}
}
}
}
@Composable
fun ListingCard(
listing: SavedListing,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(138.dp)
.border(
width = 1.078.dp,
color = Color(0xFFF3F4F6).copy(alpha = 0.5f),
shape = RoundedCornerShape(24.dp)
)
.background(Color(0xFFFCFBF8), RoundedCornerShape(24.dp))
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick
)
.padding(horizontal = 16.dp, vertical = 21.dp),
horizontalArrangement = Arrangement.spacedBy(20.dp),
verticalAlignment = Alignment.CenterVertically
) {
AsyncImage(
model = listing.imageUrl,
contentDescription = listing.name,
modifier = Modifier
.size(96.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = listing.name,
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF1E2939),
lineHeight = 24.sp
)
Text(
text = listing.age,
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF4A5565),
lineHeight = 20.sp
)
Text(
text = listing.breed,
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF4A5565),
lineHeight = 20.sp
)
}
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = listing.price,
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF1E2939),
lineHeight = 24.sp,
textAlign = TextAlign.End
)
Text(
text = listing.views,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF6A7282),
lineHeight = 16.sp,
textAlign = TextAlign.End
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = listing.distance,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF4A5565),
lineHeight = 20.sp
)
}
}
}

View File

@ -1,19 +1,29 @@
package com.example.livingai_lg.ui.screens
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@ -39,12 +49,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.components.SortItem
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SortScreen(
onBackClick: () -> Unit = {},
onSkipClick: () -> Unit = {},
onApplyClick: (List<SortField>) -> Unit = {},
onCancelClick: () -> Unit = {}
) {
@ -103,18 +111,14 @@ fun SortScreen(
) {
Row(verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = onBackClick) {
Icon(Icons.Default.ArrowBack, null)
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A)
)
}
Text("Sort By", fontSize = 32.sp)
}
TextButton(onClick = onSkipClick) {
Text(
"Skip",
textDecoration = TextDecoration.Underline,
fontSize = 20.sp
)
}
}
Column(

View File

@ -39,7 +39,7 @@ import kotlinx.coroutines.launch
@Composable
fun SignInScreen(
onSignUpClick: () -> Unit = {},
onSignInClick: (phone: String, name: String) -> Unit = {},
onSignInClick: (phone: String, name: String) -> Unit = {_,_->},
) {
val phoneNumber = remember { mutableStateOf("") }
val isValid = phoneNumber.value.length == 10

View File

@ -52,7 +52,7 @@ private fun String.isValidPhoneNumber(): Boolean {
@Composable
fun SignUpScreen(
onSignInClick: () -> Unit = {},
onSignUpClick: (phone: String, name: String) -> Unit = {}
onSignUpClick: (phone: String, name: String) -> Unit = {_,_->}
) {
var formData by remember { mutableStateOf(SignUpFormData()) }

View File

@ -0,0 +1,327 @@
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)
)
}
}
}

View File

@ -0,0 +1,365 @@
package com.example.livingai_lg.ui.screens.chat
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.Send
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Attachment
import androidx.compose.material.icons.filled.KeyboardVoice
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Phone
import androidx.compose.material.icons.filled.Send
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.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
data class ChatMessage(
val text: String,
val timestamp: String,
val isSentByCurrentUser: Boolean
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChatScreen(
sellerName: String = "Seller 1",
sellerStatus: String = "Online",
onBackClick: () -> Unit = {},
onPhoneClick: () -> Unit = {},
onMenuClick: () -> Unit = {}
) {
var messageText by remember { mutableStateOf("") }
val messages = remember {
listOf(
ChatMessage(
text = "Hello! Yes, the cow is still available. Would you like to visit?",
timestamp = "10:30 AM",
isSentByCurrentUser = false
),
ChatMessage(
text = "Hey is she still available",
timestamp = "10:28 AM",
isSentByCurrentUser = true
),
ChatMessage(
text = "She's a healthy Gir cow, 2 years old, great milk yield.",
timestamp = "10:31 AM",
isSentByCurrentUser = false
),
ChatMessage(
text = "What's the price?",
timestamp = "10:32 AM",
isSentByCurrentUser = true
),
ChatMessage(
text = "₹45,000. Negotiable if you visit today.",
timestamp = "10:33 AM",
isSentByCurrentUser = false
)
)
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF8F8F8))
) {
Column(
modifier = Modifier.fillMaxSize()
) {
ChatHeader(
sellerName = sellerName,
sellerStatus = sellerStatus,
onBackClick = onBackClick,
onPhoneClick = onPhoneClick,
onMenuClick = onMenuClick
)
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(vertical = 16.dp)
) {
items(messages) { message ->
MessageBubble(message = message)
}
}
MessageInputBar(
messageText = messageText,
onMessageChange = { messageText = it },
onSendClick = {
messageText = ""
}
)
}
}
}
@Composable
fun ChatHeader(
sellerName: String,
sellerStatus: String,
onBackClick: () -> Unit,
onPhoneClick: () -> Unit,
onMenuClick: () -> Unit
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.height(65.dp),
color = Color.White,
shadowElevation = 1.dp
) {
Row(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF0A0A0A),
modifier = Modifier.size(20.dp)
)
}
Box(
modifier = Modifier
.size(40.dp)
.background(Color(0xFFF0F0F0), shape = CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = "S1",
fontSize = 14.sp,
color = Color(0xFF0A0A0A)
)
}
Spacer(modifier = Modifier.width(12.dp))
Column {
Text(
text = sellerName,
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF1E2939)
)
Text(
text = sellerStatus,
fontSize = 12.sp,
color = Color(0xFF4A5565)
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
IconButton(onClick = onPhoneClick) {
Icon(
imageVector = Icons.Default.Phone,
contentDescription = "Phone",
tint = Color(0xFF0A0A0A),
modifier = Modifier.size(20.dp)
)
}
IconButton(onClick = onMenuClick) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color(0xFF0A0A0A),
modifier = Modifier.size(20.dp)
)
}
}
}
}
}
@Composable
fun MessageBubble(message: ChatMessage) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = if (message.isSentByCurrentUser) Alignment.End else Alignment.Start
) {
Surface(
shape = if (message.isSentByCurrentUser) {
RoundedCornerShape(topStart = 16.dp, topEnd = 4.dp, bottomStart = 16.dp, bottomEnd = 16.dp)
} else {
RoundedCornerShape(topStart = 4.dp, topEnd = 16.dp, bottomStart = 16.dp, bottomEnd = 16.dp)
},
color = if (message.isSentByCurrentUser) Color(0xFF0A0A0A) else Color.White,
modifier = Modifier
.widthIn(max = 271.dp)
.then(
if (!message.isSentByCurrentUser) {
Modifier.border(
width = 1.dp,
color = Color(0x1A000000),
shape = RoundedCornerShape(topStart = 4.dp, topEnd = 16.dp, bottomStart = 16.dp, bottomEnd = 16.dp)
)
} else Modifier
)
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = message.text,
fontSize = 14.sp,
lineHeight = 20.sp,
color = if (message.isSentByCurrentUser) Color.White else Color(0xFF0A0A0A)
)
Text(
text = message.timestamp,
fontSize = 12.sp,
lineHeight = 16.sp,
color = if (message.isSentByCurrentUser) Color(0x99FFFFFF) else Color(0xFF717182)
)
}
}
}
}
@Composable
fun MessageInputBar(
messageText: String,
onMessageChange: (String) -> Unit,
onSendClick: () -> Unit
) {
val isMultiline = messageText.contains("\n") || messageText.length > 40
Surface(
modifier = Modifier.fillMaxWidth().imePadding(),
color = Color.White,
shadowElevation = 1.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// TEXT INPUT
OutlinedTextField(
value = messageText,
onValueChange = onMessageChange,
modifier = Modifier
.weight(1f)
.heightIn(
min = if (isMultiline) 44.dp else 36.dp,
max = 120.dp
),
placeholder = {
Text(
text = "Type a message",
fontSize = 14.sp,
color = Color(0xFF717182)
)
},
textStyle = LocalTextStyle.current.copy(
fontSize = 14.sp,
color = Color.Black // ✅ typed text black
),
colors = OutlinedTextFieldDefaults.colors(
unfocusedBorderColor = Color(0x1A000000),
focusedBorderColor = Color(0x33000000),
unfocusedContainerColor = Color.White,
focusedContainerColor = Color.White,
cursorColor = Color.Black
),
shape = RoundedCornerShape(20.dp),
singleLine = false,
maxLines = 4
)
// ATTACHMENT ICON
IconButton(
onClick = { },
modifier = Modifier.size(36.dp)
) {
Icon(
imageVector = Icons.Default.Attachment,
contentDescription = "Attachment",
tint = Color(0xFF717182),
modifier = Modifier.size(20.dp)
)
}
// VOICE BUTTON
Box(
modifier = Modifier
.size(40.dp)
.background(Color(0xFF0A0A0A), CircleShape),
contentAlignment = Alignment.Center
) {
IconButton(
onClick = { },
modifier = Modifier.size(20.dp)
) {
Icon(
imageVector = Icons.Default.KeyboardVoice,
contentDescription = "Voice",
tint = Color.White,
modifier = Modifier.size(18.dp)
)
}
}
// SEND BUTTON
Box(
modifier = Modifier
.size(40.dp)
.background(Color(0xFF0A0A0A), CircleShape),
contentAlignment = Alignment.Center
) {
IconButton(
onClick = onSendClick,
modifier = Modifier.size(20.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.Send,
contentDescription = "Send",
tint = Color.White,
modifier = Modifier.size(18.dp)
)
}
}
}
}
}

View File

@ -0,0 +1,333 @@
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
)
}
}
}
}
}

View File

@ -0,0 +1,422 @@
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
)
}
}

View File

@ -0,0 +1,12 @@
package com.example.livingai_lg.ui.theme
import androidx.compose.ui.unit.sp
object AppTypography {
val Display = 32.sp
val Title = 20.sp
val Body = 16.sp
val BodySmall = 14.sp
val Caption = 12.sp
val Micro = 10.sp
}