diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..2e03d9b
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+LivingAi_Lg
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ba7d270..e190b63 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -72,4 +72,19 @@ dependencies {
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
+
+
+ //UI
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation(platform("androidx.compose:compose-bom:2023.09.00"))
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4")
+ debugImplementation("androidx.compose.ui:ui-tooling")
+ debugImplementation("androidx.compose.ui:ui-test-manifest")
+ implementation("io.coil-kt:coil-compose:2.6.0")
+ implementation("androidx.compose.foundation:foundation")
+ implementation("androidx.compose.material:material-icons-extended")
+ implementation("androidx.media3:media3-exoplayer:1.3.1")
+ implementation("androidx.media3:media3-ui:1.3.1")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3a291db..8456dac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,6 +25,15 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/livingai_lg/MainActivity.kt b/app/src/main/java/com/example/livingai_lg/MainActivity.kt
index 70e935c..6281511 100644
--- a/app/src/main/java/com/example/livingai_lg/MainActivity.kt
+++ b/app/src/main/java/com/example/livingai_lg/MainActivity.kt
@@ -23,30 +23,32 @@ 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.theme.LivingAi_LgTheme
+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()
setContent {
- LivingAi_LgTheme {
+ FarmMarketplaceTheme {
val mainViewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current))
val authState by mainViewModel.authState.collectAsState()
- when (authState) {
- is AuthState.Unknown -> {
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- CircularProgressIndicator()
- }
- }
- is AuthState.Authenticated -> {
- SuccessScreen(mainViewModel)
- }
- is AuthState.Unauthenticated -> {
- AuthNavigation()
- }
- }
+ AppNavigation(authState)
+// when (authState) {
+// is AuthState.Unknown -> {
+// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+// CircularProgressIndicator()
+// }
+// }
+// is AuthState.Authenticated -> {
+// SuccessScreen(mainViewModel)
+// }
+// is AuthState.Unauthenticated -> {
+// AuthNavigation()
+// }
+// }
}
}
}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/AdSpaceBanner.kt b/app/src/main/java/com/example/livingai_lg/ui/components/AdSpaceBanner.kt
new file mode 100644
index 0000000..98a9527
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/AdSpaceBanner.kt
@@ -0,0 +1,42 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.shape.RoundedCornerShape
+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.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun AdSpaceBanner(
+ modifier: Modifier = Modifier,
+ text: String = "AD SPACE"
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(44.dp)
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFFFF0000).copy(alpha = 0.97f),
+ shape = RoundedCornerShape(8.dp)
+ )
+ .background(Color.White, RoundedCornerShape(8.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = text,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/AnimalTypeSelector.kt b/app/src/main/java/com/example/livingai_lg/ui/components/AnimalTypeSelector.kt
new file mode 100644
index 0000000..ad930c7
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/AnimalTypeSelector.kt
@@ -0,0 +1,87 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.ui.models.AnimalType
+
+
+@Composable
+fun AnimalTypeSelector(
+ animalTypes: List,
+ selectedAnimalType: MutableState,
+ onAnimalTypeSelected: (String) -> Unit
+) {
+ val selectedAnimalType: String = selectedAnimalType.value ?: ""
+
+ LazyRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 12.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ items(animalTypes.size) { index ->
+ AnimalTypeButton(
+ animalType = animalTypes[index],
+ isSelected = selectedAnimalType == animalTypes[index].id,
+ onClick = { onAnimalTypeSelected(animalTypes[index].id) }
+ )
+ }
+ }
+}
+
+@Composable
+private fun AnimalTypeButton(
+ animalType: AnimalType,
+ isSelected: Boolean,
+ onClick: () -> Unit
+) {
+ Column(
+ modifier = Modifier
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClick
+ )
+ .padding(4.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .background(
+ if (isSelected) Color(0xFFEDE9FE) else Color.White,
+ RoundedCornerShape(24.dp)
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = animalType.emoji,
+ fontSize = 24.sp
+ )
+ }
+
+ Text(
+ text = animalType.name,
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/BottomNavigationBar.kt b/app/src/main/java/com/example/livingai_lg/ui/components/BottomNavigationBar.kt
new file mode 100644
index 0000000..fbb47bf
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/BottomNavigationBar.kt
@@ -0,0 +1,93 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.ui.models.BottomNavItemData
+
+@Composable
+fun BottomNavigationBar(
+ modifier: Modifier = Modifier,
+ items: List,
+ currentItem: String,
+ onItemClick: (route: String) -> Unit = {}
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .background(Color.White)
+ .border(1.dp, Color(0xFF000000).copy(alpha = 0.1f))
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(66.dp)
+ .padding(horizontal = 8.dp),
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ items.forEach { item ->
+ val isSelected = item.label == currentItem
+ BottomNavItem(
+ label = item.label,
+ iconRes = item.iconRes,
+ selected = isSelected,
+ onClick = {
+ if (!isSelected) {
+ onItemClick(item.route)
+ }
+ }
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun BottomNavItem(
+ label: String,
+ iconRes: Int,
+ selected: Boolean,
+ onClick: () -> Unit = {}
+) {
+ val color = if (selected) Color(0xFF1E88E5) else Color(0xFF0A0A0A)
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ modifier = Modifier
+ .padding(vertical = 4.dp)
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClick)
+ ) {
+ Icon(
+ painter = painterResource(iconRes),
+ contentDescription = label,
+ tint = color,
+ modifier = Modifier.size(24.dp)
+ )
+ Text(
+ text = label,
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Medium,
+ color = color
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt b/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt
new file mode 100644
index 0000000..6d70097
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt
@@ -0,0 +1,225 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.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.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.zIndex
+import com.example.livingai_lg.ui.screens.*
+import com.example.livingai_lg.ui.models.Animal
+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
+
+@Composable
+fun BuyAnimalCard(
+ product: Animal,
+ isSaved: Boolean,
+ onSavedChange: (Boolean) -> Unit,
+ onProductClick: () -> Unit,
+ onSellerClick:(sellerId: String)-> Unit,
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp)
+ .shadow(1.078.dp, RoundedCornerShape(14.dp))
+ .background(Color.White, RoundedCornerShape(14.dp))
+ .border(
+ 1.078.dp,
+ 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
+ )
+
+ // 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 */ }
+ )
+
+
+ }
+ }
+ }
+}
+
+@Composable
+fun ActionButton(icon: Int, label: String) {
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .background(Color(0xFFF5F5F5), RoundedCornerShape(24.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = label,
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier.size(20.dp)
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/DropdownInput.kt b/app/src/main/java/com/example/livingai_lg/ui/components/DropdownInput.kt
new file mode 100644
index 0000000..3620a62
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/DropdownInput.kt
@@ -0,0 +1,113 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.ui.theme.FarmTextDark
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun DropdownInput(
+ label: String? = null, // optional label
+ selected: String,
+ options: List,
+ expanded: Boolean,
+ onExpandedChange: (Boolean) -> Unit,
+ onSelect: (String) -> Unit,
+ placeholder: String = "Select", // NEW - custom placeholder
+ modifier: Modifier = Modifier // NEW - allows width control
+) {
+ Column(
+ modifier = modifier, // <-- now caller can control width
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ // Optional label
+ if (label != null) {
+ Text(
+ text = label,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = FarmTextDark
+ )
+ } else {
+ // Reserve label space so layout doesn’t shift
+ Spacer(modifier = Modifier.height(20.dp)) // ← same height as label text line
+ }
+
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = { onExpandedChange(!expanded) }
+ ) {
+ // Anchor box
+ Box(
+ modifier = Modifier
+ .menuAnchor()
+ .fillMaxWidth()
+ .height(52.dp)
+ .shadow(2.dp, RoundedCornerShape(16.dp))
+ .background(Color.White, RoundedCornerShape(16.dp))
+ .border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp))
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) {
+ onExpandedChange(true)
+ }
+ .padding(horizontal = 16.dp),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ // Custom placeholder support
+ Text(
+ text = selected.ifEmpty { placeholder },
+ fontSize = 16.sp,
+ color = if (selected.isEmpty()) Color(0xFF99A1AF) else FarmTextDark
+ )
+
+ Text(
+ text = "▼",
+ fontSize = 12.sp,
+ color = FarmTextDark
+ )
+ }
+ }
+
+ // Material3 Dropdown
+ ExposedDropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { onExpandedChange(false) },
+ modifier = Modifier.background(Color.White)
+ ) {
+ options.forEach { item ->
+ DropdownMenuItem(
+ text = {
+ Text(item, fontSize = 16.sp, color = FarmTextDark)
+ },
+ onClick = {
+ onSelect(item)
+ onExpandedChange(false)
+ }
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/FarmHeader.kt b/app/src/main/java/com/example/livingai_lg/ui/components/FarmHeader.kt
new file mode 100644
index 0000000..1e51194
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/FarmHeader.kt
@@ -0,0 +1,40 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.ui.theme.FarmTextDark
+import com.example.livingai_lg.ui.theme.FarmTextNormal
+
+@Composable
+fun FarmHeader() {
+ Row {
+ Text(
+ text = "Farm",
+ fontSize = 32.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFFE17100)
+ )
+ Text(
+ text = "Market",
+ fontSize = 32.sp,
+ fontWeight = FontWeight.Medium,
+ color = FarmTextDark
+ )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Find your perfect livestock",
+ fontSize = 16.sp,
+ color = FarmTextNormal
+ )
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/FilterButton.kt b/app/src/main/java/com/example/livingai_lg/ui/components/FilterButton.kt
new file mode 100644
index 0000000..5e07ebe
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/FilterButton.kt
@@ -0,0 +1,58 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.R
+
+@Composable
+fun FilterButton(
+ onFilterClick: () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .height(36.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 = onFilterClick,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_filter),
+ contentDescription = "Filter",
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier.size(16.dp)
+ )
+ Text(
+ text = "Filter",
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/FloatingActionBar.kt b/app/src/main/java/com/example/livingai_lg/ui/components/FloatingActionBar.kt
new file mode 100644
index 0000000..0f094cd
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/FloatingActionBar.kt
@@ -0,0 +1,77 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+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.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.example.livingai_lg.R
+
+@Composable
+fun FloatingActionBar(
+ modifier: Modifier = Modifier,
+ onChatClick: () -> Unit = {},
+ onCallClick: () -> Unit = {},
+ onLocationClick: () -> Unit = {},
+ onBookmarkClick: () -> Unit = {}
+) {
+ 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)
+ ) {
+ 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)
+ }
+ }
+}
+
+
+@Composable
+private fun FloatingActionIcon(
+ iconRes: Int,
+ onClick: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .shadow(
+ elevation = 6.dp,
+ shape = RoundedCornerShape(24.dp),
+ clip = false
+ )
+ .background(Color.White, RoundedCornerShape(24.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(iconRes),
+ contentDescription = null,
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier.size(22.dp)
+ )
+ }
+}
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/IconComponents.kt b/app/src/main/java/com/example/livingai_lg/ui/components/IconComponents.kt
new file mode 100644
index 0000000..80e5cb4
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/IconComponents.kt
@@ -0,0 +1,33 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+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.unit.Dp
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun IconCircle(
+ backgroundColor: Color,
+ size: Dp,
+ content: @Composable () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .size(size)
+ .shadow(
+ elevation = 4.dp,
+ shape = CircleShape
+ )
+ .background(backgroundColor, shape = CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ content()
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/ImageCarousel.kt b/app/src/main/java/com/example/livingai_lg/ui/components/ImageCarousel.kt
new file mode 100644
index 0000000..f0b27d3
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/ImageCarousel.kt
@@ -0,0 +1,146 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+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.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.sp
+
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun ImageCarousel(
+ imageUrls: List,
+ modifier: Modifier = Modifier
+) {
+ when {
+ imageUrls.isEmpty() -> {
+ Box(
+ modifier = modifier
+ .background(Color.LightGray),
+ contentAlignment = Alignment.Center
+ ) {
+ Text("No images", color = Color.White)
+ }
+ }
+
+ imageUrls.size == 1 -> {
+ AsyncImage(
+ model = imageUrls.first(),
+ contentDescription = null,
+ modifier = modifier,
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ else -> {
+ val pagerState = rememberPagerState { imageUrls.size }
+
+ Box(modifier = modifier) {
+ HorizontalPager(
+ state = pagerState,
+ modifier = Modifier.fillMaxSize()
+ ) { page ->
+ AsyncImage(
+ model = imageUrls[page],
+ contentDescription = null,
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ // Page Indicator (inside image)
+ Row(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 8.dp),
+ horizontalArrangement = Arrangement.spacedBy(6.dp)
+ ) {
+ repeat(imageUrls.size) { index ->
+ val isSelected = pagerState.currentPage == index
+ Box(
+ modifier = Modifier
+ .height(6.dp)
+ .width(if (isSelected) 18.dp else 6.dp)
+ .background(
+ Color.White,
+ RoundedCornerShape(50)
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+
+@Composable
+fun PageIndicator(
+ pageCount: Int,
+ currentPage: Int,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .background(
+ color = Color.Black.copy(alpha = 0.3f),
+ shape = RoundedCornerShape(12.dp)
+ )
+ .padding(horizontal = 8.dp, vertical = 6.dp),
+ horizontalArrangement = Arrangement.spacedBy(6.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ repeat(pageCount) { index ->
+ if (index == currentPage) {
+ Box(
+ modifier = Modifier
+ .width(18.dp)
+ .height(6.dp)
+ .background(Color.White, RoundedCornerShape(3.dp))
+ )
+ } else {
+ Box(
+ modifier = Modifier
+ .size(6.dp)
+ .background(Color.White.copy(alpha = 0.6f), CircleShape)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun NoImagePlaceholder(
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .background(Color.LightGray),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "No Images",
+ color = Color.DarkGray,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Medium
+ )
+ }
+}
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/InputField.kt b/app/src/main/java/com/example/livingai_lg/ui/components/InputField.kt
new file mode 100644
index 0000000..02e3738
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/InputField.kt
@@ -0,0 +1,67 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+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.FarmTextDark
+
+@Composable
+fun InputField(
+ label: String,
+ value: String,
+ placeholder: String,
+ onChange: (String) -> Unit
+) {
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+
+ Text(
+ text = label,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = FarmTextDark
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .shadow(2.dp, RoundedCornerShape(16.dp))
+ .background(Color.White)
+ .border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp))
+ .padding(16.dp),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ BasicTextField(
+ value = value,
+ onValueChange = onChange,
+ textStyle = TextStyle(
+ fontSize = 16.sp,
+ color = FarmTextDark
+ ),
+ singleLine = true,
+ decorationBox = { inner ->
+ if (value.isEmpty()) {
+ Text(
+ text = placeholder,
+ fontSize = 16.sp,
+ color = Color(0xFF99A1AF)
+ )
+ }
+ inner()
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/MediaPickerCard.kt b/app/src/main/java/com/example/livingai_lg/ui/components/MediaPickerCard.kt
new file mode 100644
index 0000000..b875b8c
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/MediaPickerCard.kt
@@ -0,0 +1,280 @@
+package com.example.livingai_lg.ui.components
+
+import android.net.Uri
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+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.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.PlayArrow
+import androidx.compose.material.icons.outlined.CameraAlt
+import androidx.compose.material.icons.outlined.Videocam
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+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.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import com.example.livingai_lg.ui.models.MediaType
+import com.example.livingai_lg.ui.models.MediaUpload
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
+import com.example.livingai_lg.ui.utils.createMediaUri
+import com.example.livingai_lg.ui.utils.getVideoThumbnail
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
+@Composable
+fun MediaPickerCard(
+ upload: MediaUpload,
+ modifier: Modifier = Modifier,
+ onUriSelected: (Uri?) -> Unit
+) {
+ val context = LocalContext.current
+ var showSheet by remember { mutableStateOf(false) }
+ var cameraUri by remember { mutableStateOf(null) }
+ var showVideoPlayer by remember { mutableStateOf(false) }
+
+ // Gallery
+ val galleryLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.GetContent()
+ ) { uri ->
+ if (uri != null) onUriSelected(uri)
+ }
+
+ // Camera (photo)
+ val imageCameraLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.TakePicture()
+ ) { success ->
+ if (success) onUriSelected(cameraUri) else cameraUri = null
+ }
+
+ // Camera (video)
+ val videoCameraLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.CaptureVideo()
+ ) { success ->
+ if (success) onUriSelected(cameraUri) else cameraUri = null
+ }
+
+ /* ---------- Picker Sheet (NO delete here anymore) ---------- */
+
+ if (showSheet) {
+ ModalBottomSheet(onDismissRequest = { showSheet = false }) {
+ Column(
+ modifier = Modifier.padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ text = "Select Media",
+ style = MaterialTheme.typography.titleMedium
+ )
+
+ ListItem(
+ headlineContent = { Text("Camera") },
+ leadingContent = { Icon(Icons.Outlined.CameraAlt, null) },
+ modifier = Modifier.clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) {
+ showSheet = false
+ cameraUri = createMediaUri(context, upload.type)
+
+ if (upload.type == MediaType.PHOTO) {
+ imageCameraLauncher.launch(cameraUri!!)
+ } else {
+ videoCameraLauncher.launch(cameraUri!!)
+ }
+ }
+ )
+
+ ListItem(
+ headlineContent = { Text("Gallery") },
+ leadingContent = {
+ Icon(
+ if (upload.type == MediaType.PHOTO)
+ Icons.Outlined.CameraAlt
+ else
+ Icons.Outlined.Videocam,
+ null
+ )
+ },
+ modifier = Modifier.clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) {
+ showSheet = false
+ galleryLauncher.launch(
+ if (upload.type == MediaType.PHOTO) "image/*" else "video/*"
+ )
+ }
+ )
+ }
+ }
+ }
+
+ /* ---------- Card ---------- */
+
+ Box(
+ modifier = modifier
+ .height(204.dp)
+ .shadow(0.5.dp, RoundedCornerShape(8.dp))
+ .background(Color.White, RoundedCornerShape(8.dp))
+ .border(1.dp, Color(0x1A000000), RoundedCornerShape(8.dp))
+ .combinedClickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = {
+ if (upload.uri != null && upload.type == MediaType.VIDEO) {
+ showVideoPlayer = true
+ } else {
+ showSheet = true
+ }
+ },
+ onLongClick = {
+ if (upload.uri != null) {
+ showSheet = true
+ }
+ }
+ )
+ .padding(8.dp),
+ contentAlignment = Alignment.Center
+ ) {
+
+ /* ---------- Media Preview ---------- */
+
+ if (upload.uri != null) {
+
+ Box(modifier = Modifier.fillMaxSize()) {
+
+ if (upload.type == MediaType.VIDEO) {
+
+ val thumbnail = remember(upload.uri) {
+ getVideoThumbnail(context, upload.uri!!)
+ }
+
+ if (thumbnail != null) {
+ Image(
+ bitmap = thumbnail.asImageBitmap(),
+ contentDescription = null,
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ // ▶ Play overlay
+ Box(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(56.dp)
+ .background(
+ Color.Black.copy(alpha = 0.6f),
+ RoundedCornerShape(28.dp)
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.PlayArrow,
+ contentDescription = "Play Video",
+ tint = Color.White,
+ modifier = Modifier.size(32.dp)
+ )
+ }
+
+ } else {
+ // Photo
+ AsyncImage(
+ model = upload.uri,
+ contentDescription = upload.label,
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ /* ---------- ❌ Remove Button ---------- */
+
+ Icon(
+ imageVector = Icons.Default.Close,
+ contentDescription = "Remove media",
+ tint = Color.White,
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .padding(6.dp)
+ .size(24.dp)
+ .background(
+ Color.Black.copy(alpha = 0.6f),
+ RoundedCornerShape(12.dp)
+ )
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) {
+ onUriSelected(null)
+ }
+ )
+ }
+
+ } else {
+ /* ---------- Empty State ---------- */
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Icon(
+ imageVector = if (upload.type == MediaType.PHOTO)
+ Icons.Outlined.CameraAlt
+ else
+ Icons.Outlined.Videocam,
+ contentDescription = null,
+ tint = Color(0xFF717182),
+ modifier = Modifier.size(32.dp)
+ )
+
+ Text(
+ text = if (upload.type == MediaType.PHOTO) "Add Photo" else "Add Video",
+ color = Color(0xFF717182)
+ )
+
+ if (upload.label.isNotEmpty()) {
+ Text(upload.label, color = Color(0xFF717182))
+ }
+ }
+ }
+
+ /* ---------- Video Player ---------- */
+
+ if (showVideoPlayer && upload.uri != null) {
+ VideoPlayerDialog(
+ uri = upload.uri!!,
+ onDismiss = { showVideoPlayer = false }
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/OptionCard.kt b/app/src/main/java/com/example/livingai_lg/ui/components/OptionCard.kt
new file mode 100644
index 0000000..2e5d4d5
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/OptionCard.kt
@@ -0,0 +1,81 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.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.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun OptionCard(
+ label: String,
+ icon: Int,
+ iconBackgroundColor: Color,
+ 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(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Icon container
+ Box(
+ modifier = Modifier
+ .size(56.dp)
+ .background(iconBackgroundColor, RoundedCornerShape(14.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(id = icon),
+ contentDescription = label,
+ tint = Color.Unspecified
+ )
+ }
+
+ // Label
+ Text(
+ text = label,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A),
+ modifier = Modifier.weight(1f)
+ )
+
+ // Dot indicator
+ Box(
+ modifier = Modifier
+ .size(10.dp)
+ .background(Color(0xFF6B7280), CircleShape)
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/PhoneNumberInput.kt b/app/src/main/java/com/example/livingai_lg/ui/components/PhoneNumberInput.kt
new file mode 100644
index 0000000..b67ee9b
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/PhoneNumberInput.kt
@@ -0,0 +1,108 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Phone
+import com.example.livingai_lg.ui.theme.FarmTextDark
+
+@Composable
+fun PhoneNumberInput(
+ phone: String,
+ onChange: (String) -> Unit
+) {
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+
+ Text(
+ text = "Enter Phone Number*",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = FarmTextDark
+ )
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.height(52.dp)
+ ) {
+
+ // Country code box
+ Box(
+ modifier = Modifier
+ .width(65.dp)
+ .height(52.dp)
+ .shadow(2.dp, RoundedCornerShape(16.dp))
+ .background(Color.White)
+ .border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Text("+91", fontSize = 16.sp, color = FarmTextDark)
+ }
+
+ // Phone input field
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .height(52.dp)
+ .shadow(2.dp, RoundedCornerShape(16.dp))
+ .background(Color.White)
+ .border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp))
+ .padding(12.dp),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Row(
+ Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+
+ Icon(
+ imageVector = Icons.Outlined.Phone,
+ contentDescription = "Phone",
+ tint = Color(0xFF99A1AF),
+ modifier = Modifier.size(18.dp)
+ )
+
+ BasicTextField(
+ value = phone,
+ onValueChange = onChange,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number
+ ),
+ textStyle = TextStyle(
+ fontSize = 15.sp,
+ color = FarmTextDark
+ ),
+ singleLine = true,
+ decorationBox = { inner ->
+ if (phone.isEmpty()) {
+ Text(
+ "Enter your Phone Number",
+ fontSize = 15.sp,
+ color = Color(0xFF99A1AF)
+ )
+ }
+ inner()
+ }
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/RangeFilter.kt b/app/src/main/java/com/example/livingai_lg/ui/components/RangeFilter.kt
new file mode 100644
index 0000000..a38cb52
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/RangeFilter.kt
@@ -0,0 +1,137 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import kotlin.math.roundToInt
+
+@Composable
+fun RangeFilter(
+ modifier: Modifier = Modifier,
+
+ label: String,
+ min: Int,
+ max: Int,
+ valueFrom: Int,
+ valueTo: Int,
+ onValueChange: (from: Int, to: Int) -> Unit,
+ showSlider: Boolean = true,
+ valueFormatter: (Int) -> String = { it.toString() }
+) {
+ var fromValue by remember(valueFrom) { mutableStateOf(valueFrom) }
+ var toValue by remember(valueTo) { mutableStateOf(valueTo) }
+
+ Column(modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(12.dp)) {
+
+ // Label
+ Text(
+ text = label,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = Color(0xFF364153)
+ )
+
+ // Slider (optional)
+ if (showSlider) {
+ RangeSlider(
+ value = fromValue.toFloat()..toValue.toFloat(),
+ onValueChange = { range ->
+ fromValue = range.start.roundToInt()
+ .coerceIn(min, toValue)
+ toValue = range.endInclusive.roundToInt()
+ .coerceIn(fromValue, max)
+
+ onValueChange(fromValue, toValue)
+ },
+ valueRange = min.toFloat()..max.toFloat(),
+ colors = SliderDefaults.colors(
+ thumbColor = Color(0xFFD9D9D9),
+ activeTrackColor = Color(0xFFD9D9D9),
+ inactiveTrackColor = Color(0xFFE5E7EB)
+ )
+ )
+ }
+
+ // Pills
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RangePill(
+ modifier = Modifier.weight(1f),
+ value = fromValue,
+ onValueChange = { newFrom ->
+ val safeFrom = newFrom.coerceIn(min, toValue)
+ fromValue = safeFrom
+ onValueChange(safeFrom, toValue)
+ },
+ formatter = valueFormatter
+ )
+
+ Text("to", fontSize = 15.sp)
+
+ RangePill(
+ modifier = Modifier.weight(1f),
+ value = toValue,
+ onValueChange = { newTo ->
+ val safeTo = newTo.coerceIn(fromValue, max)
+ toValue = safeTo
+ onValueChange(fromValue, safeTo)
+ },
+ formatter = valueFormatter
+ )
+ }
+
+ }
+}
+
+@Composable
+private fun RangePill(
+ modifier: Modifier = Modifier,
+ value: Int,
+ onValueChange: (Int) -> Unit,
+ formatter: (Int) -> String
+) {
+ var text by remember(value) {
+ mutableStateOf(formatter(value))
+ }
+
+ Box(
+ modifier = modifier
+ .height(30.dp)
+ .background(Color.White, RoundedCornerShape(16.dp))
+ .border(1.dp, Color(0x12000000), RoundedCornerShape(16.dp))
+ .padding(horizontal = 8.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ BasicTextField(
+ value = text,
+ onValueChange = { input ->
+ val digits = input.filter { it.isDigit() }
+ text = digits
+ digits.toIntOrNull()?.let(onValueChange)
+ },
+ singleLine = true,
+ textStyle = TextStyle(
+ fontSize = 14.sp,
+ color = Color(0xFF99A1AF)
+ ),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/SortButton.kt b/app/src/main/java/com/example/livingai_lg/ui/components/SortButton.kt
new file mode 100644
index 0000000..02a68e9
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/SortButton.kt
@@ -0,0 +1,56 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.R
+
+@Composable
+fun SortButton(onSortClick: () -> Unit) {
+ Row(
+ modifier = Modifier
+ .height(36.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,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_sort),
+ contentDescription = "Sort",
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier.size(16.dp)
+ )
+ Text(
+ text = "Sort by",
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/SortItem.kt b/app/src/main/java/com/example/livingai_lg/ui/components/SortItem.kt
new file mode 100644
index 0000000..27a7391
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/SortItem.kt
@@ -0,0 +1,103 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.filled.ArrowDownward
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.icons.filled.ArrowUpward
+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.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.ui.models.SortDirection
+import com.example.livingai_lg.ui.models.SortField
+
+@Composable
+fun SortItem(
+ field: SortField,
+ onToggle: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val icon = when (field.direction) {
+ SortDirection.ASC -> Icons.Default.ArrowUpward
+ SortDirection.DESC -> Icons.Default.ArrowDownward
+ SortDirection.NONE -> Icons.Default.ArrowDropDown
+ }
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(56.dp)
+ .background(Color.White, RoundedCornerShape(12.dp))
+ .border(1.dp, Color(0x1A000000), RoundedCornerShape(12.dp))
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { onToggle() }
+ .padding(horizontal = 16.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text = field.label,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF364153)
+ )
+
+ // Sort priority indicator
+ field.order?.let {
+ Spacer(modifier = Modifier.width(8.dp))
+ Box(
+ modifier = Modifier
+ .size(18.dp)
+ .background(Color.Black, RoundedCornerShape(9.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = it.toString(),
+ color = Color.White,
+ fontSize = 11.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+
+ Icon(
+ imageVector = icon,
+ contentDescription = null,
+ tint = if (field.direction == SortDirection.NONE)
+ Color(0xFF9CA3AF)
+ else
+ Color.Black
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/UserLocationHeader.kt b/app/src/main/java/com/example/livingai_lg/ui/components/UserLocationHeader.kt
new file mode 100644
index 0000000..d19b3f5
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/UserLocationHeader.kt
@@ -0,0 +1,153 @@
+package com.example.livingai_lg.ui.components
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material3.Divider
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+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.res.painterResource
+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 coil.compose.AsyncImage
+import com.example.livingai_lg.ui.models.UserAddress
+import com.example.livingai_lg.ui.models.UserProfile
+import com.example.livingai_lg.R
+
+@Composable
+fun UserLocationHeader(
+ user: UserProfile,
+ modifier: Modifier = Modifier,
+ onAddressSelected: (UserAddress) -> Unit = {},
+ onAddNewClick: () -> Unit = {} // future navigation hook
+) {
+ 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
+ ) {
+
+ // Profile image
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .clip(CircleShape)
+ .background(Color.Black),
+ contentAlignment = Alignment.Center
+ ) {
+ if (user.profileImageUrl != null) {
+ AsyncImage(
+ model = user.profileImageUrl,
+ contentDescription = null,
+ modifier = Modifier.fillMaxSize()
+ )
+ } else {
+ Icon(
+ painter = painterResource(R.drawable.ic_profile),
+ contentDescription = null,
+ tint = Color.White,
+ modifier = Modifier.size(26.dp)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.width(12.dp))
+
+ // Anchor ONLY the text section
+ Box {
+ Column(
+ modifier = Modifier
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { expanded = true }
+ ) {
+ 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)
+ )
+ }
+
+ Text(
+ text = selectedAddress.address,
+ fontSize = 13.sp,
+ color = Color.Black.copy(alpha = 0.7f),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+
+ // 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()
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/VideoPlayerDialog.kt b/app/src/main/java/com/example/livingai_lg/ui/components/VideoPlayerDialog.kt
new file mode 100644
index 0000000..5cc2261
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/VideoPlayerDialog.kt
@@ -0,0 +1,60 @@
+package com.example.livingai_lg.ui.components
+
+import android.net.Uri
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.compose.ui.window.Dialog
+import androidx.media3.common.MediaItem
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.PlayerView
+
+@Composable
+fun VideoPlayerDialog(
+ uri: Uri,
+ onDismiss: () -> Unit
+) {
+ val context = LocalContext.current
+
+ val player = remember {
+ ExoPlayer.Builder(context).build().apply {
+ setMediaItem(MediaItem.fromUri(uri))
+ prepare()
+ playWhenReady = true
+ }
+ }
+
+ DisposableEffect(Unit) {
+ onDispose {
+ player.release()
+ }
+ }
+
+ Dialog(onDismissRequest = onDismiss) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .aspectRatio(16 / 9f)
+ .background(Color.Black)
+ ) {
+ AndroidView(
+ factory = {
+ PlayerView(it).apply {
+ this.player = player
+ useController = true
+ }
+ },
+ modifier = Modifier.fillMaxSize()
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/backgrounds/DecorativeBackground.kt b/app/src/main/java/com/example/livingai_lg/ui/components/backgrounds/DecorativeBackground.kt
new file mode 100644
index 0000000..7b86ce8
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/backgrounds/DecorativeBackground.kt
@@ -0,0 +1,106 @@
+package com.example.livingai_lg.ui.components.backgrounds
+
+import android.annotation.SuppressLint
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.TextUnit
+import kotlin.math.min
+import com.example.livingai_lg.R
+
+
+@Composable
+fun DecorativeBackground() {
+
+ BoxWithConstraints(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ val screenW = maxWidth.value
+ val screenH = maxHeight.value
+
+ // Original bg image design size (YOUR asset’s intended size)
+ val designW = 393f
+ val designH = 852f
+
+ // Scale factor that preserves aspect ratio
+ val scale = min(screenW / designW, screenH / designH)
+
+ //------------------------------
+ // Helper to scale dp offsets
+ //------------------------------
+ fun s(value: Float) = (value * scale).dp
+
+ //------------------------------
+ // Background Image
+ //------------------------------
+ Image(
+ painter = painterResource(R.drawable.bg),
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxSize(),
+ contentScale = ContentScale.Crop // ensures full-screen coverage
+ )
+
+ //------------------------------
+ // Decorative Elements (scaled)
+ //------------------------------
+
+ // 🐐 Goat
+ ScaledEmoji(
+ emoji = "🐐",
+ baseFontSize = 48.sp,
+ offsetX = 250f,
+ offsetY = 160f,
+ scale = scale,
+ alpha = 0.10f
+ )
+
+ // 🐄 Cow
+ ScaledEmoji(
+ emoji = "🐄",
+ baseFontSize = 60.sp,
+ offsetX = 64f,
+ offsetY = 569f,
+ scale = scale,
+ alpha = 0.12f
+ )
+
+ // 🌾 Wheat
+ ScaledEmoji(
+ emoji = "🌾🌾🌾",
+ baseFontSize = 32.sp,
+ offsetX = 48f,
+ offsetY = 730f,
+ scale = scale,
+ alpha = 0.15f
+ )
+ }
+}
+
+@Composable
+private fun ScaledEmoji(
+ emoji: String,
+ baseFontSize: TextUnit,
+ offsetX: Float,
+ offsetY: Float,
+ scale: Float,
+ alpha: Float
+) {
+ Text(
+ text = emoji,
+ fontSize = (baseFontSize.value * scale).sp,
+ modifier = Modifier
+ .offset(
+ x = (offsetX * scale).dp,
+ y = (offsetY * scale).dp
+ )
+ .alpha(alpha)
+ )
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/backgrounds/StoreBackground.kt b/app/src/main/java/com/example/livingai_lg/ui/components/backgrounds/StoreBackground.kt
new file mode 100644
index 0000000..78146c7
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/components/backgrounds/StoreBackground.kt
@@ -0,0 +1,50 @@
+package com.example.livingai_lg.ui.components.backgrounds
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import kotlin.math.min
+import com.example.livingai_lg.R
+
+@Composable
+fun StoreBackground() {
+
+ BoxWithConstraints(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+ val screenW = maxWidth.value
+ val screenH = maxHeight.value
+
+ // Original bg image design size (YOUR asset’s intended size)
+ val designW = 393f
+ val designH = 852f
+
+ // Scale factor that preserves aspect ratio
+ val scale = min(screenW / designW, screenH / designH)
+
+ //------------------------------
+ // Helper to scale dp offsets
+ //------------------------------
+ fun s(value: Float) = (value * scale).dp
+
+ //------------------------------
+ // Background Image
+ //------------------------------
+ Image(
+ painter = painterResource(R.drawable.bg_shop),
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxSize(),
+ contentScale = ContentScale.Crop // ensures full-screen coverage
+ )
+ }
+}
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/layout/BottomNavScaffold.kt b/app/src/main/java/com/example/livingai_lg/ui/layout/BottomNavScaffold.kt
new file mode 100644
index 0000000..1416918
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/layout/BottomNavScaffold.kt
@@ -0,0 +1,38 @@
+package com.example.livingai_lg.ui.layout
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.zIndex
+import com.example.livingai_lg.ui.components.BottomNavigationBar
+import com.example.livingai_lg.ui.models.BottomNavItemData
+
+@Composable
+fun BottomNavScaffold(
+ modifier: Modifier = Modifier,
+ items: List,
+ currentItem: String,
+ onBottomNavItemClick: (route: String) -> Unit = {},
+ content: @Composable (paddingValues: PaddingValues) -> Unit
+) {
+ Scaffold(
+ modifier = modifier,
+ bottomBar = {
+ BottomNavigationBar(
+ modifier = Modifier
+ .zIndex(1f)
+ .shadow(8.dp),
+ items,
+ currentItem,
+ onItemClick = onBottomNavItemClick
+ )
+ },
+ containerColor = Color.Transparent
+ ) { paddingValues ->
+ content(paddingValues)
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/login/CreateProfileScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/login/CreateProfileScreen.kt
index a33abfe..53e2a83 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/login/CreateProfileScreen.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/login/CreateProfileScreen.kt
@@ -52,11 +52,7 @@ fun CreateProfileScreen(navController: NavController, name: String) {
Box(
modifier = Modifier
.fillMaxSize()
- .background(
- brush = Brush.linearGradient(
- colors = listOf(LightCream, LighterCream, LightestGreen)
- )
- )
+
) {
Column(
modifier = Modifier
@@ -105,7 +101,7 @@ fun ProfileTypeItem(text: String, icon: Int, onClick: () -> Unit) {
@Preview(showBackground = true)
@Composable
fun CreateProfileScreenPreview() {
- LivingAi_LgTheme {
+ FarmMarketplaceTheme {
CreateProfileScreen(rememberNavController(), "John Doe")
}
}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/login/LoginScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/login/LoginScreen.kt
index 99c7fb0..c04ba19 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/login/LoginScreen.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/login/LoginScreen.kt
@@ -12,6 +12,7 @@ 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
import androidx.compose.ui.text.style.TextAlign
@@ -29,11 +30,11 @@ fun LoginScreen(navController: NavController) {
Box(
modifier = Modifier
.fillMaxSize()
- .background(
- brush = Brush.linearGradient(
- colors = listOf(LightCream, LighterCream, LightestGreen)
- )
- )
+// .background(
+// brush = Brush.linearGradient(
+// colors = listOf(LightCream, LighterCream, LightestGreen)
+// )
+// )
) {
// Decorative elements...
@@ -55,7 +56,7 @@ fun LoginScreen(navController: NavController) {
Box(
modifier = Modifier
.size(56.dp)
- .background(Gold, RoundedCornerShape(28.dp)),
+ .background(Color.Yellow, RoundedCornerShape(28.dp)),
contentAlignment = Alignment.Center
) {
Text(text = "🌾", fontSize = 24.sp)
@@ -64,7 +65,7 @@ fun LoginScreen(navController: NavController) {
Box(
modifier = Modifier
.size(72.dp)
- .background(LightOrange.copy(alpha = 0.72f), RoundedCornerShape(36.dp)),
+ .background(Color.Yellow.copy(alpha = 0.72f), RoundedCornerShape(36.dp)),
contentAlignment = Alignment.Center
) {
Text(text = "🌱", fontSize = 32.sp)
@@ -73,7 +74,7 @@ fun LoginScreen(navController: NavController) {
Box(
modifier = Modifier
.size(56.dp)
- .background(DarkOrange.copy(alpha = 0.67f), RoundedCornerShape(28.dp)),
+ .background(Color.Yellow.copy(alpha = 0.67f), RoundedCornerShape(28.dp)),
contentAlignment = Alignment.Center
) {
Text(text = "☀️", fontSize = 24.sp)
@@ -86,13 +87,13 @@ fun LoginScreen(navController: NavController) {
text = "Welcome!",
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
- color = DarkBrown
+ color = Color.Yellow
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Join the farm marketplace community",
fontSize = 16.sp,
- color = MidBrown,
+ color = Color.Yellow,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(48.dp))
@@ -101,12 +102,12 @@ fun LoginScreen(navController: NavController) {
Button(
onClick = { navController.navigate("signup") },
shape = RoundedCornerShape(16.dp),
- colors = ButtonDefaults.buttonColors(containerColor = LightOrange),
+ colors = ButtonDefaults.buttonColors(containerColor = Color.Yellow),
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
) {
- Text(text = "New user? Sign up", color = DarkerBrown, fontSize = 16.sp, fontWeight = FontWeight.Medium)
+ Text(text = "New user? Sign up", color = Color.Yellow, fontSize = 16.sp, fontWeight = FontWeight.Medium)
}
Spacer(modifier = Modifier.height(16.dp))
@@ -115,12 +116,12 @@ fun LoginScreen(navController: NavController) {
Button(
onClick = { navController.navigate("signin") },
shape = RoundedCornerShape(16.dp),
- colors = ButtonDefaults.buttonColors(containerColor = TerraCotta),
+ colors = ButtonDefaults.buttonColors(containerColor = Color.Yellow),
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
) {
- Text(text = "Already a user? Sign in", color = DarkerBrown, fontSize = 16.sp, fontWeight = FontWeight.Medium)
+ Text(text = "Already a user? Sign in", color = Color.Yellow, fontSize = 16.sp, fontWeight = FontWeight.Medium)
}
Spacer(modifier = Modifier.height(24.dp))
@@ -128,7 +129,7 @@ fun LoginScreen(navController: NavController) {
// Guest Button
Text(
text = "Continue as Guest",
- color = MidBrown,
+ color = Color.Yellow,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.clickable { Toast.makeText(context, "Guest mode is not yet available", Toast.LENGTH_SHORT).show() }
@@ -142,7 +143,7 @@ fun LoginScreen(navController: NavController) {
@Preview(showBackground = true)
@Composable
fun LoginScreenPreview() {
- LivingAi_LgTheme {
+ FarmMarketplaceTheme() {
LoginScreen(rememberNavController())
}
}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/login/OtpScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/login/OtpScreen.kt
index 0a4f3e6..16e3154 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/login/OtpScreen.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/login/OtpScreen.kt
@@ -40,11 +40,11 @@ fun OtpScreen(navController: NavController, phoneNumber: String, name: String) {
Box(
modifier = Modifier
.fillMaxSize()
- .background(
- brush = Brush.linearGradient(
- colors = listOf(LightCream, LighterCream, LightestGreen)
- )
- )
+// .background(
+// brush = Brush.linearGradient(
+// colors = listOf(LightCream, LighterCream, LightestGreen)
+// )
+// )
) {
Column(
modifier = Modifier
@@ -60,7 +60,7 @@ fun OtpScreen(navController: NavController, phoneNumber: String, name: String) {
TextField(
value = otp.value,
- onValueChange = { if (it.length <= 6) otp.value = it },
+ onValueChange = { if (it.length <= 6) otp.value = it },
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
@@ -117,7 +117,7 @@ fun OtpScreen(navController: NavController, phoneNumber: String, name: String) {
@Preview(showBackground = true)
@Composable
fun OtpScreenPreview() {
- LivingAi_LgTheme {
+ FarmMarketplaceTheme() {
OtpScreen(rememberNavController(), "+919876543210", "John Doe")
}
}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/login/SignInScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/login/SignInScreen.kt
index 1e468df..32d0cbb 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/login/SignInScreen.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/login/SignInScreen.kt
@@ -42,11 +42,11 @@ fun SignInScreen(navController: NavController) {
Box(
modifier = Modifier
.fillMaxSize()
- .background(
- brush = Brush.linearGradient(
- colors = listOf(LightCream, LighterCream, LightestGreen)
- )
- )
+// .background(
+// brush = Brush.linearGradient(
+// colors = listOf(LightCream, LighterCream, LightestGreen)
+// )
+// )
) {
Column(
modifier = Modifier
@@ -161,7 +161,7 @@ fun SignInScreen(navController: NavController) {
@Preview(showBackground = true)
@Composable
fun SignInScreenPreview() {
- LivingAi_LgTheme {
+ FarmMarketplaceTheme() {
SignInScreen(rememberNavController())
}
}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/login/SignUpScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/login/SignUpScreen.kt
index 9e8672c..da8f0e6 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/login/SignUpScreen.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/login/SignUpScreen.kt
@@ -45,11 +45,11 @@ fun SignUpScreen(navController: NavController) {
Box(
modifier = Modifier
.fillMaxSize()
- .background(
- brush = Brush.linearGradient(
- colors = listOf(LightCream, LighterCream, LightestGreen)
- )
- )
+// .background(
+// brush = Brush.linearGradient(
+// colors = listOf(LightCream, LighterCream, LightestGreen)
+// )
+// )
) {
Column(
modifier = Modifier
@@ -190,7 +190,7 @@ fun SignUpScreen(navController: NavController) {
@Preview(showBackground = true)
@Composable
fun SignUpScreenPreview() {
- LivingAi_LgTheme {
+ FarmMarketplaceTheme {
SignUpScreen(rememberNavController())
}
}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/login/SuccessScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/login/SuccessScreen.kt
index be96d44..a5e23b2 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/login/SuccessScreen.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/login/SuccessScreen.kt
@@ -14,9 +14,6 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.livingai_lg.ui.MainViewModel
import com.example.livingai_lg.ui.UserState
-import com.example.livingai_lg.ui.theme.LightCream
-import com.example.livingai_lg.ui.theme.LighterCream
-import com.example.livingai_lg.ui.theme.LightestGreen
import com.example.livingai_lg.ui.MainViewModelFactory // <-- This was the missing import
@Composable
@@ -28,11 +25,12 @@ fun SuccessScreen(mainViewModel: MainViewModel = viewModel(factory = MainViewMod
Box(
modifier = Modifier
.fillMaxSize()
- .background(
- brush = Brush.linearGradient(
- colors = listOf(LightCream, LighterCream, LightestGreen)
- )
- ),
+// .background(
+// brush = Brush.linearGradient(
+// colors = listOf(LightCream, LighterCream, LightestGreen)
+// )
+// )
+ ,
contentAlignment = Alignment.Center
) {
when (val state = userState) {
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt b/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt
new file mode 100644
index 0000000..1861b93
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt
@@ -0,0 +1,74 @@
+package com.example.livingai_lg.ui.models
+
+data class Animal(
+ val id: String,
+ val name: String? = null,
+ val age: Int? = null,
+ val breed: String? = null,
+ val price: Long? = null,
+ val isFairPrice: Boolean? = null,
+ val imageUrl: List? = null,
+ val location: String? = null,
+ val displayLocation: String? = null,
+ val distance: Long? = null,
+ val views: Long? = null,
+ val sellerId: String? = null,
+ val sellerName: String? = null,
+ val sellerType: String? = null,
+ val aiScore: Float? = null,
+ val rating: Float? = null,
+ val ratingCount: Int? = null,
+ val description: String? = null,
+ val milkCapacity: Float? = null,
+)
+
+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
+ sellerId = "1",
+ sellerName = "Seller 1",
+ sellerType = "Wholeseller",
+ rating = 4.5f,
+ ratingCount = 2076,
+ description = "Friendly and energetic companion looking for an active family.",
+ ),
+ 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"),
+ distance = 0L,
+ views = 100000,
+ sellerId = "1",
+ sellerName = "Seller ???",
+ sellerType = "Wholeseller",
+ rating = 5f,
+ ratingCount = 2076,
+ description = "Friendly and energetic companion looking for an active family.",
+ ),
+ Animal(
+ id = "3",
+ name = "Bessie",
+ age = 48,
+ breed = "Holstein Friesian",
+ location = "Punjab",
+ distance = 12000,
+ imageUrl = listOf("https://api.builder.io/api/v1/image/assets/TEMP/885e24e34ede6a39f708df13dabc4c1683c3e976?width=786"),
+ views = 94,
+ aiScore = 0.80f,
+ price = 80000,
+ isFairPrice = true,
+ rating = 4.5f,
+ ratingCount = 2076,
+ sellerId = "1",
+ sellerName = "Seller 1",
+ description = "Premium Holstein Friesian dairy cow in excellent health condition. Bessie is a 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 = 2.3f
+ )
+)
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/AnimalType.kt b/app/src/main/java/com/example/livingai_lg/ui/models/AnimalType.kt
new file mode 100644
index 0000000..79209c2
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/AnimalType.kt
@@ -0,0 +1,18 @@
+package com.example.livingai_lg.ui.models
+
+data class AnimalType(
+ val id: String,
+ val name: String,
+ val emoji: String
+)
+
+val animalTypes = listOf(
+ AnimalType("cows", "Cows", "🐄"),
+ AnimalType("buffalo", "Buffalo", "🐃"),
+ AnimalType("goat", "Goat", "🐐"),
+ AnimalType("bull", "Bull", "🐂"),
+ AnimalType("baby_cow", "Baby Cow", "🐮"),
+ AnimalType("dog", "Dog", "🐕"),
+ AnimalType("cat", "Cat", "🐱"),
+ AnimalType("others", "Others", "🦜")
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/BottomNavItemData.kt b/app/src/main/java/com/example/livingai_lg/ui/models/BottomNavItemData.kt
new file mode 100644
index 0000000..d6501d0
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/BottomNavItemData.kt
@@ -0,0 +1,27 @@
+package com.example.livingai_lg.ui.models
+
+import com.example.livingai_lg.R
+import com.example.livingai_lg.ui.navigation.AppScreen
+
+data class BottomNavItemData(
+ val label: String,
+ val iconRes: Int,
+ val route: String,
+)
+
+val mainBottomNavItems = listOf(
+ BottomNavItemData("Home", R.drawable.ic_home , AppScreen.BUY_ANIMALS),
+ BottomNavItemData("Sell", R.drawable.ic_tag, AppScreen.CREATE_ANIMAL_LISTING),
+ // TODO:
+ BottomNavItemData("Chats", R.drawable.ic_chat, AppScreen.CREATE_PROFILE),
+ 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("Chats", R.drawable.ic_chat, "chats"),
+ BottomNavItemData("Services", R.drawable.ic_config, "services"),
+ BottomNavItemData("Mandi", R.drawable.ic_market, "mandi")
+)
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/NewAnimalForm.kt b/app/src/main/java/com/example/livingai_lg/ui/models/NewAnimalForm.kt
new file mode 100644
index 0000000..07b09cd
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/NewAnimalForm.kt
@@ -0,0 +1,28 @@
+package com.example.livingai_lg.ui.models
+
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+
+class NewListingFormState {
+
+ val name = mutableStateOf("")
+ val animal = mutableStateOf("")
+ val breed = mutableStateOf("")
+ val age = mutableStateOf("")
+ val milkYield = mutableStateOf("")
+ val calvingNumber = mutableStateOf("")
+ val reproductiveStatus = mutableStateOf("")
+ val description = mutableStateOf("")
+
+ val animalExpanded = mutableStateOf(false)
+ val breedExpanded = mutableStateOf(false)
+
+ val mediaUploads = mutableStateListOf(
+ MediaUpload("left-view", "Left View", MediaType.PHOTO),
+ MediaUpload("right-view", "Right View", MediaType.PHOTO),
+ MediaUpload("left-angle", "Left Angle View", MediaType.PHOTO),
+ MediaUpload("right-angle", "Right Angle View", MediaType.PHOTO),
+ MediaUpload("angle", "Angle View", MediaType.PHOTO),
+ MediaUpload("video", "", MediaType.VIDEO)
+ )
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/ProfileType.kt b/app/src/main/java/com/example/livingai_lg/ui/models/ProfileType.kt
new file mode 100644
index 0000000..309245f
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/ProfileType.kt
@@ -0,0 +1,38 @@
+package com.example.livingai_lg.ui.models
+
+import androidx.compose.ui.graphics.Color
+import com.example.livingai_lg.R
+
+data class ProfileType(
+ val id: String,
+ val title: String,
+ val icon: Int,
+ val backgroundColor: Color
+)
+
+val profileTypes = listOf(
+ ProfileType(
+ id = "buyer_seller",
+ title = "I'm a Buyer/Seller",
+ icon = R.drawable.ic_shop,
+ backgroundColor = Color(0xFF9D4EDD)
+ ),
+ ProfileType(
+ id = "wholesale_trader",
+ title = "I'm a Wholesale Trader",
+ icon = R.drawable.ic_bag,
+ backgroundColor = Color(0xFF3A86FF)
+ ),
+ ProfileType(
+ id = "service_provider",
+ title = "I'm a Service Provider",
+ icon = R.drawable.ic_spanner,
+ backgroundColor = Color(0xFFFF5722)
+ ),
+ ProfileType(
+ id = "mandi_host",
+ title = "I'm a Mandi Host",
+ icon = R.drawable.ic_shop2,
+ backgroundColor = Color(0xFF4CAF50)
+ )
+)
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/Seller.kt b/app/src/main/java/com/example/livingai_lg/ui/models/Seller.kt
new file mode 100644
index 0000000..cdb0495
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/Seller.kt
@@ -0,0 +1,4 @@
+package com.example.livingai_lg.ui.models
+
+class Seller {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/Sort.kt b/app/src/main/java/com/example/livingai_lg/ui/models/Sort.kt
new file mode 100644
index 0000000..825aac1
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/Sort.kt
@@ -0,0 +1,14 @@
+package com.example.livingai_lg.ui.models
+
+enum class SortDirection {
+ NONE,
+ ASC,
+ DESC
+}
+
+data class SortField(
+ val key: String,
+ val label: String,
+ val direction: SortDirection = SortDirection.NONE,
+ val order: Int? = null
+)
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/User.kt b/app/src/main/java/com/example/livingai_lg/ui/models/User.kt
new file mode 100644
index 0000000..4af6432
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/User.kt
@@ -0,0 +1,30 @@
+package com.example.livingai_lg.ui.models
+
+data class UserAddress(
+ val name: String,
+ val address: String,
+ val isPrimary: Boolean = false
+)
+
+data class UserProfile(
+ val name: String,
+ val profileImageUrl: String? = null,
+ val addresses: List
+)
+
+val userProfile =
+ UserProfile(
+ name = "John Doe",
+ 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(
+ name = "Home",
+ address = "205 1st floor 7th cross 27th main, PW",
+ isPrimary = true
+ ),
+ UserAddress(
+ name = "Farm",
+ address = "2nd block, MG Farms"
+ )
+ )
+ )
diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/mediaType.kt b/app/src/main/java/com/example/livingai_lg/ui/models/mediaType.kt
new file mode 100644
index 0000000..7719bd8
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/models/mediaType.kt
@@ -0,0 +1,19 @@
+package com.example.livingai_lg.ui.models
+
+import android.net.Uri
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+
+class MediaUpload(
+ val id: String,
+ val label: String,
+ val type: MediaType,
+ initialUri: Uri? = null
+) {
+ var uri by mutableStateOf(initialUri)
+}
+
+enum class MediaType {
+ PHOTO, VIDEO
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt
new file mode 100644
index 0000000..140f78d
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt
@@ -0,0 +1,338 @@
+package com.example.livingai_lg.ui.navigation
+
+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.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavController
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.example.livingai_lg.ui.AuthState
+import com.example.livingai_lg.ui.screens.AnimalProfileScreen
+import com.example.livingai_lg.ui.screens.BuyScreen
+import com.example.livingai_lg.ui.screens.ChooseServiceScreen
+import com.example.livingai_lg.ui.screens.CreateProfileScreen
+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.SellerProfileScreen
+import com.example.livingai_lg.ui.screens.SortScreen
+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
+
+
+object AppScreen {
+ const val LANDING = "landing"
+ const val SIGN_IN = "sign_in"
+ const val SIGN_UP = "sign_up"
+
+ const val OTP = "otp"
+
+ const val CHOOSE_SERVICE = "choose_service"
+
+ const val CREATE_PROFILE = "create_profile"
+
+ const val BUY_ANIMALS = "buy_animals"
+ const val ANIMAL_PROFILE = "animal_profile"
+
+ const val CREATE_ANIMAL_LISTING = "create_animal_listing"
+
+ const val BUY_ANIMALS_FILTERS = "buy_animals_filters"
+ const val BUY_ANIMALS_SORT = "buy_animals_sort"
+
+ const val SELLER_PROFILE = "seller_profile"
+
+ const val SALE_ARCHIVE = "sale_archive"
+
+ const val POST_SALE_SURVEY = "post_sale_survey"
+
+ fun otp(phone: String, name: String) =
+ "$OTP/$phone/$name"
+
+ fun createProfile(name: String) =
+ "$CREATE_PROFILE/$name"
+
+ fun chooseService(profileId: String) =
+ "$CHOOSE_SERVICE/$profileId"
+ fun postSaleSurvey(animalId: String) =
+ "$POST_SALE_SURVEY/$animalId"
+
+ fun animalProfile(animalId: String) =
+ "$ANIMAL_PROFILE/$animalId"
+
+ fun sellerProfile(sellerId: String) =
+ "$SELLER_PROFILE/$sellerId"
+
+ fun saleArchive(saleId: String) =
+ "$SALE_ARCHIVE/$saleId"
+}
+
+@Composable
+fun AppNavigation(
+ authState: AuthState
+) {
+ 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 navController = rememberNavController()
+
+// val onNavClick: (String) -> Unit = { route ->
+// val currentRoute =
+// navController.currentBackStackEntry?.destination?.route
+//
+// if (currentRoute != route) {
+// navController.navigate(route) {
+// launchSingleTop = true
+// restoreState = true
+// popUpTo(navController.graph.startDestinationId) {
+// saveState = true
+// }
+// }
+// }
+// }
+
+
+
+// NavHost(
+// navController = navController,
+// 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) }
+// )
+// }
+//
+// composable(AppScreen.SIGN_IN) {
+// SignInScreen(
+// onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) },
+// onSignInClick = {
+// navController.navigate(AppScreen.OTP)
+// }
+// )
+// }
+//
+// composable(AppScreen.SIGN_UP) {
+// SignUpScreen(
+// onSignUpClick = {
+// navController.navigate(AppScreen.OTP)
+// },
+// onSignInClick = {
+// navController.navigate(AppScreen.SIGN_IN) {
+// popUpTo(AppScreen.SIGN_UP) { inclusive = true }
+// }
+// }
+// )
+// }
+//
+// composable(AppScreen.OTP) {
+// OtpScreen(
+// onContinue = { navController.navigate(AppScreen.CREATE_PROFILE) }
+// )
+// }
+
+// composable(AppScreen.CREATE_PROFILE) {
+// CreateProfileScreen (
+// onProfileSelected = { profileId ->
+// if (profileId == "buyer_seller") {
+// navController.navigate(AppScreen.BUY_ANIMALS)
+// } else {
+// navController.navigate(AppScreen.CHOOSE_SERVICE)
+// } },
+// )
+// }
+//
+// composable(AppScreen.CHOOSE_SERVICE) {
+// ChooseServiceScreen (
+// onServiceSelected = { navController.navigate(AppScreen.LANDING) },
+// )
+// }
+//
+// composable(AppScreen.BUY_ANIMALS) {
+// BuyScreen(
+// onBackClick = {
+// navController.popBackStack()
+// },
+// onProductClick = { animalId ->
+// navController.navigate(
+// AppScreen.animalProfile(animalId)
+// )
+// },
+// onNavClick = onNavClick,
+// onFilterClick = {navController.navigate(AppScreen.BUY_ANIMALS_FILTERS)},
+// onSortClick = {navController.navigate(AppScreen.BUY_ANIMALS_SORT)},
+// onSellerClick = { sellerId ->
+// navController.navigate(
+// AppScreen.sellerProfile(sellerId)
+// )
+// },
+// )
+// }
+//
+// composable(AppScreen.BUY_ANIMALS_FILTERS) {
+// FilterScreen(
+// onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
+// onBackClick = {
+// navController.popBackStack()
+// },
+// onSkipClick = {
+// navController.popBackStack()
+// },
+// onCancelClick = {
+// navController.popBackStack()
+// },
+//
+// )
+// }
+//
+// composable(AppScreen.BUY_ANIMALS_SORT) {
+// SortScreen(
+// onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
+// onBackClick = {
+// navController.popBackStack()
+// },
+// onSkipClick = {
+// navController.popBackStack()
+// },
+// onCancelClick = {
+// navController.popBackStack()
+// },
+//
+// )
+// }
+//
+// composable(AppScreen.CREATE_ANIMAL_LISTING) {
+// NewListingScreen (
+// onSaveClick = {navController.navigate(
+// AppScreen.postSaleSurvey("2")
+// )},
+// onBackClick = {
+// navController.popBackStack()
+// }
+// )
+// }
+//
+// composable(
+// route = "${AppScreen.SALE_ARCHIVE}/{saleId}",
+// arguments = listOf(
+// navArgument("saleId") { type = NavType.StringType }
+// )
+// ) { backStackEntry ->
+//
+// val saleId = backStackEntry
+// .arguments
+// ?.getString("saleId")
+// ?: return@composable
+//
+// SaleArchiveScreen(
+// saleId = saleId,
+// onBackClick = {
+// navController.popBackStack()
+// },
+// onSaleSurveyClick = { saleId ->
+// navController.navigate(
+// AppScreen.sellerProfile(saleId)
+// )
+// },
+// )
+// }
+//
+// composable(
+// route = "${AppScreen.POST_SALE_SURVEY}/{animalId}",
+// arguments = listOf(
+// navArgument("animalId") { type = NavType.StringType }
+// )
+// ) { backStackEntry ->
+//
+// val animalId = backStackEntry
+// .arguments
+// ?.getString("animalId")
+// ?: return@composable
+//
+// PostSaleSurveyScreen (
+// animalId = animalId,
+// onBackClick = {
+// navController.popBackStack()
+// },
+// onSubmit = {navController.navigate(
+// AppScreen.saleArchive("2")
+// )}
+// )
+// }
+//
+// composable(
+// route = "${AppScreen.ANIMAL_PROFILE}/{animalId}",
+// arguments = listOf(
+// navArgument("animalId") { type = NavType.StringType }
+// )
+// ) { backStackEntry ->
+//
+// val animalId = backStackEntry
+// .arguments
+// ?.getString("animalId")
+// ?: return@composable
+//
+// AnimalProfileScreen(
+// animalId = animalId,
+// onBackClick = {
+// navController.popBackStack()
+// },
+// onSellerClick = { sellerId ->
+// navController.navigate(
+// AppScreen.sellerProfile(sellerId)
+// )
+// },
+// )
+// }
+//
+// composable(
+// route = "${AppScreen.SELLER_PROFILE}/{sellerId}",
+// arguments = listOf(
+// navArgument("sellerId") { type = NavType.StringType }
+// )
+// ) { backStackEntry ->
+//
+// val sellerId = backStackEntry
+// .arguments
+// ?.getString("sellerId")
+// ?: return@composable
+//
+// SellerProfileScreen(
+// sellerId = sellerId,
+// onBackClick = {
+// navController.popBackStack()
+// }
+// )
+// }
+// composable(AppScreen.SELLER_PROFILE) {
+// SellerProfileScreen (
+// onBackClick = {
+// navController.popBackStack()
+// }
+// )
+// }
+
+
+// }
+}
+
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt
new file mode 100644
index 0000000..092b125
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt
@@ -0,0 +1,70 @@
+package com.example.livingai_lg.ui.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavHostController
+import androidx.navigation.NavType
+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.screens.SaleArchiveScreen
+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
+
+@Composable
+fun AuthNavGraph(
+ navController: NavHostController = rememberNavController()
+) {
+ NavHost(
+ navController = navController,
+ 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) }
+ )
+ }
+
+ composable(AppScreen.SIGN_IN) {
+ SignInScreen(
+ onSignUpClick = { navController.navigate(AppScreen.SIGN_UP){
+ popUpTo(AppScreen.SIGN_IN) { inclusive = true }
+ } },
+ onSignInClick = { phone, name ->
+ navController.navigate(AppScreen.otp(phone,name))
+ }
+ )
+ }
+
+ composable(AppScreen.SIGN_UP) {
+ SignUpScreen(
+ onSignUpClick = { phone, name ->
+ navController.navigate(AppScreen.otp(phone,name))
+ },
+ onSignInClick = {
+ navController.navigate(AppScreen.SIGN_IN) {
+ popUpTo(AppScreen.SIGN_UP) { inclusive = true }
+ }
+ }
+ )
+ }
+
+ composable(
+ "${AppScreen.OTP}/{phoneNumber}/{name}",
+ arguments = listOf(
+ navArgument("phoneNumber") { type = NavType.StringType },
+ navArgument("name") { type = NavType.StringType }
+ )
+ ) { backStackEntry ->
+ OtpScreen(
+ phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "",
+ name = backStackEntry.arguments?.getString("name") ?: "",
+ onSuccess = { navController.navigate(AppScreen.chooseService("1"))}
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt
new file mode 100644
index 0000000..d5dcfb6
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt
@@ -0,0 +1,230 @@
+package com.example.livingai_lg.ui.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavHostController
+import androidx.navigation.NavType
+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.login.SuccessScreen
+import com.example.livingai_lg.ui.screens.AnimalProfileScreen
+import com.example.livingai_lg.ui.screens.BuyScreen
+import com.example.livingai_lg.ui.screens.ChooseServiceScreen
+import com.example.livingai_lg.ui.screens.CreateProfileScreen
+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.SellerProfileScreen
+import com.example.livingai_lg.ui.screens.SortScreen
+
+@Composable
+fun MainNavGraph(
+ navController: NavHostController = rememberNavController()
+) {
+ val onNavClick: (String) -> Unit = { route ->
+ val currentRoute =
+ navController.currentBackStackEntry?.destination?.route
+
+ if (currentRoute != route) {
+ navController.navigate(route) {
+ launchSingleTop = true
+ restoreState = true
+ popUpTo(navController.graph.startDestinationId) {
+ saveState = true
+ }
+ }
+ }
+ }
+
+ NavHost(
+ navController = navController,
+ startDestination = AppScreen.BUY_ANIMALS
+ ) {
+ composable(
+ "${AppScreen.CREATE_PROFILE}/{name}",
+ arguments = listOf(navArgument("name") { type = NavType.StringType })
+ ) { backStackEntry ->
+ CreateProfileScreen(
+ name = backStackEntry.arguments?.getString("name") ?: "",
+ onProfileSelected = { profileId ->
+ navController.navigate(AppScreen.chooseService(profileId))
+
+ }
+ )
+ }
+
+
+ composable(AppScreen.CHOOSE_SERVICE) {
+ ChooseServiceScreen (
+ onServiceSelected = { navController.navigate(AppScreen.LANDING) },
+ )
+ }
+ composable(
+ "${AppScreen.CHOOSE_SERVICE}/{profileId}",
+ arguments = listOf(navArgument("profileId") { type = NavType.StringType })
+ ) { backStackEntry ->
+ ChooseServiceScreen (
+ profileId = backStackEntry.arguments?.getString("profileId") ?: "",
+ onServiceSelected = { navController.navigate(AppScreen.BUY_ANIMALS) },
+ )
+ }
+
+ composable(AppScreen.BUY_ANIMALS) {
+ BuyScreen(
+ onBackClick = {
+ navController.popBackStack()
+ },
+ onProductClick = { animalId ->
+ navController.navigate(
+ AppScreen.animalProfile(animalId)
+ )
+ },
+ onNavClick = onNavClick,
+ onFilterClick = {navController.navigate(AppScreen.BUY_ANIMALS_FILTERS)},
+ onSortClick = {navController.navigate(AppScreen.BUY_ANIMALS_SORT)},
+ onSellerClick = { sellerId ->
+ navController.navigate(
+ AppScreen.sellerProfile(sellerId)
+ )
+ },
+ )
+ }
+
+ composable(AppScreen.BUY_ANIMALS_FILTERS) {
+ FilterScreen(
+ onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
+ onBackClick = {
+ navController.popBackStack()
+ },
+ onSkipClick = {
+ navController.popBackStack()
+ },
+ onCancelClick = {
+ navController.popBackStack()
+ },
+
+ )
+ }
+
+ composable(AppScreen.BUY_ANIMALS_SORT) {
+ SortScreen(
+ onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
+ onBackClick = {
+ navController.popBackStack()
+ },
+ onSkipClick = {
+ navController.popBackStack()
+ },
+ onCancelClick = {
+ navController.popBackStack()
+ },
+
+ )
+ }
+
+ composable(AppScreen.CREATE_ANIMAL_LISTING) {
+ NewListingScreen (
+ onSaveClick = {navController.navigate(
+ AppScreen.postSaleSurvey("2")
+ )},
+ onBackClick = {
+ navController.popBackStack()
+ }
+ )
+ }
+
+ composable(
+ route = "${AppScreen.SALE_ARCHIVE}/{saleId}",
+ arguments = listOf(
+ navArgument("saleId") { type = NavType.StringType }
+ )
+ ) { backStackEntry ->
+
+ val saleId = backStackEntry
+ .arguments
+ ?.getString("saleId")
+ ?: return@composable
+
+ SaleArchiveScreen(
+ saleId = saleId,
+ onBackClick = {
+ navController.popBackStack()
+ },
+ onSaleSurveyClick = { saleId ->
+ navController.navigate(
+ AppScreen.sellerProfile(saleId)
+ )
+ },
+ )
+ }
+
+ composable(
+ route = "${AppScreen.POST_SALE_SURVEY}/{animalId}",
+ arguments = listOf(
+ navArgument("animalId") { type = NavType.StringType }
+ )
+ ) { backStackEntry ->
+
+ val animalId = backStackEntry
+ .arguments
+ ?.getString("animalId")
+ ?: return@composable
+
+ PostSaleSurveyScreen (
+ animalId = animalId,
+ onBackClick = {
+ navController.popBackStack()
+ },
+ onSubmit = {navController.navigate(
+ AppScreen.saleArchive("2")
+ )}
+ )
+ }
+
+ composable(
+ route = "${AppScreen.ANIMAL_PROFILE}/{animalId}",
+ arguments = listOf(
+ navArgument("animalId") { type = NavType.StringType }
+ )
+ ) { backStackEntry ->
+
+ val animalId = backStackEntry
+ .arguments
+ ?.getString("animalId")
+ ?: return@composable
+
+ AnimalProfileScreen(
+ animalId = animalId,
+ onBackClick = {
+ navController.popBackStack()
+ },
+ onSellerClick = { sellerId ->
+ navController.navigate(
+ AppScreen.sellerProfile(sellerId)
+ )
+ },
+ )
+ }
+
+ composable(
+ route = "${AppScreen.SELLER_PROFILE}/{sellerId}",
+ arguments = listOf(
+ navArgument("sellerId") { type = NavType.StringType }
+ )
+ ) { backStackEntry ->
+
+ val sellerId = backStackEntry
+ .arguments
+ ?.getString("sellerId")
+ ?: return@composable
+
+ SellerProfileScreen(
+ sellerId = sellerId,
+ onBackClick = {
+ navController.popBackStack()
+ }
+ )
+ }}
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/AnimalProfileScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/AnimalProfileScreen.kt
new file mode 100644
index 0000000..13b84a7
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/AnimalProfileScreen.kt
@@ -0,0 +1,371 @@
+package com.example.livingai_lg.ui.screens
+
+import com.example.livingai_lg.ui.components.ImageCarousel
+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.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.verticalScroll
+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.draw.shadow
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+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.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.zIndex
+import com.example.livingai_lg.ui.models.Animal
+import com.example.livingai_lg.ui.utils.formatPrice
+import com.example.livingai_lg.ui.utils.formatViews
+import com.example.livingai_lg.ui.components.AdSpaceBanner
+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
+
+
+@Composable
+fun AnimalProfileScreen(
+ animalId: String,
+ onBackClick: () -> Unit = {},
+ onSellerClick: (sellerId: String) -> Unit = {},
+) {
+ val animal = sampleAnimals.find { animal -> animal.id == animalId } ?: Animal(id = "null")
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ .verticalScroll(rememberScrollState())
+ ) {
+ // Photo section with overlays
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(500.dp)
+ .shadow(4.dp)
+ ) {
+ // Main image
+ val product = null
+ ImageCarousel(
+ imageUrls = animal.imageUrl ?: emptyList(),
+ modifier = Modifier.fillMaxSize()
+ )
+
+ // Gradient overlay at bottom
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ .align(Alignment.BottomCenter)
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ Color.Transparent,
+ Color.Black.copy(alpha = 0.6f)
+ )
+ )
+ )
+ )
+
+ // Views indicator (top left)
+ Row(
+ modifier = Modifier
+ .align(Alignment.TopStart)
+ .padding(start = 16.dp, top = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_view),
+ contentDescription = "Views",
+ tint = Color.White,
+ modifier = Modifier.size(24.dp)
+ )
+ Text(
+ text = formatViews(animal.views),
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.White
+ )
+ }
+
+ // Animal info (centered bottom)
+ Column(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 68.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text(
+ text = "${animal.name}, ${formatAge(animal.age)}",
+ fontSize = 30.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.White
+ )
+ Text(
+ text = "${animal.breed} • ${animal.location}, ${animal.distance}".uppercase(),
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.White.copy(alpha = 0.7f),
+ letterSpacing = 1.2.sp
+ )
+ }
+
+ // AI Score badge (bottom left)
+ Row(
+ modifier = Modifier
+ .align(Alignment.BottomStart)
+ .padding(start = 0.dp, bottom = 5.dp)
+ .height(48.dp)
+ .border(2.dp, Color(0xFF717182), CircleShape)
+ .padding(horizontal = 12.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ AIScoreCircle(score = animal.aiScore ?: 0f)
+ Text(
+ text = "AI Score",
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.White
+ )
+ }
+
+ // Display location (bottom right)
+ Text(
+ text = buildString {
+ append("At Display: ")
+ append(animal.displayLocation)
+ },
+ fontSize = 8.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.White,
+ lineHeight = 13.sp,
+ textDecoration = TextDecoration.Underline,
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(end = 16.dp, bottom = 8.dp)
+ .width(98.dp)
+ )
+ }
+
+ // Info card section
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp)
+ ) {
+ Spacer(modifier = Modifier.height(20.dp))
+
+ // Price and seller info
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.Top
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Text(
+ text = formatPrice(animal.price),
+ fontSize = 30.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A)
+ )
+
+ if (animal.isFairPrice ?: false) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(start = 0.dp)
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_thumbs_up),
+ contentDescription = "Fair Price",
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier.size(15.dp)
+ )
+ Text(
+ text = "Fair Price",
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF00C950)
+ )
+ }
+ }
+ }
+
+ Column(
+ horizontalAlignment = Alignment.End,
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier.clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) {
+ onSellerClick(animal.sellerId?:"")
+ })
+ {
+ Text(
+ text = "Sold By: ${animal.sellerName}",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+
+ // Star rating
+ Row(
+ 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)
+ )
+ }
+ Text(
+ text = "${animal.rating} (${animal.ratingCount} Ratings)",
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // About section
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text(
+ text = "About",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF09090B).copy(alpha = 0.5f)
+ )
+ Text(
+ text = animal.description ?: "",
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF09090B),
+ lineHeight = 24.sp
+ )
+ }
+
+// Spacer(modifier = Modifier.height(24.dp))
+//
+//
+// FloatingActionBar(
+// modifier = Modifier,
+// // .offset(y = (-40).dp), // 👈 hover effect
+// onChatClick = { /* TODO */ },
+// onCallClick = { /* TODO */ },
+// onLocationClick = { /* TODO */ },
+// onBookmarkClick = { /* TODO */ }
+// )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // Ad space banner
+ AdSpaceBanner()
+
+ Spacer(modifier = Modifier.height(32.dp))
+ }
+ }
+ FloatingActionBar(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 24.dp)
+ .offset(y = (-20).dp)
+ .zIndex(10f), // 👈 ensure it floats above everything
+ onChatClick = { /* TODO */ },
+ onCallClick = { /* TODO */ },
+ onLocationClick = { /* TODO */ },
+ onBookmarkClick = { /* TODO */ }
+ )
+
+ }
+}
+
+@Composable
+fun AIScoreCircle(score: Float) {
+ Box(
+ modifier = Modifier
+ .size(40.dp)
+ .drawBehind {
+ // Background circle
+ drawCircle(
+ color = Color(0xFFDD88CF),
+ radius = size.minDimension / 2,
+ alpha = 0.3f,
+ style = Stroke(width = 4.dp.toPx())
+ )
+
+ // Progress arc
+ val sweepAngle = 360f * score
+ drawArc(
+ color = Color(0xFF9AFF9A),
+ startAngle = -90f,
+ sweepAngle = sweepAngle,
+ useCenter = false,
+ style = Stroke(width = 4.dp.toPx(), cap = StrokeCap.Round),
+ size = Size(size.width, size.height),
+ topLeft = Offset.Zero
+ )
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "${(score * 100).toInt()}%",
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.White
+ )
+ }
+}
+
+@Composable
+fun ActionButtonLarge(icon: Int, label: String) {
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .shadow(8.dp, CircleShape)
+ .background(Color.White, CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = label,
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier.size(20.dp)
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt
new file mode 100644
index 0000000..d66fff0
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt
@@ -0,0 +1,170 @@
+package com.example.livingai_lg.ui.screens
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.example.livingai_lg.ui.components.AdSpaceBanner
+import com.example.livingai_lg.ui.components.AnimalTypeSelector
+import com.example.livingai_lg.ui.components.BuyAnimalCard
+import com.example.livingai_lg.ui.models.animalTypes
+
+import com.example.livingai_lg.ui.components.FilterButton
+import com.example.livingai_lg.ui.components.SortButton
+import com.example.livingai_lg.ui.components.UserLocationHeader
+import com.example.livingai_lg.ui.layout.BottomNavScaffold
+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
+
+@Composable
+fun BuyScreen(
+ onProductClick: (productId: String) -> Unit = {},
+ onBackClick: () -> Unit = {},
+ onNavClick: (route: String) -> Unit = {},
+ onFilterClick: () -> Unit = {},
+ onSortClick: () -> Unit = {},
+ onSellerClick: (sellerId: String) -> Unit = {},
+) {
+ val selectedAnimalType = remember { mutableStateOf(null) }
+ val isSaved = remember { mutableStateOf(false) }
+
+
+
+
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+
+ BottomNavScaffold(
+ items = mainBottomNavItems,
+ currentItem = "Home",
+ onBottomNavItemClick = onNavClick,
+ ) { paddingValues ->
+
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ .padding(paddingValues)
+ ) {
+ item {
+ // Header with profile and notification
+ // Top header strip
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(Color(0xFFF7F4EE))
+ .padding(horizontal = 16.dp, vertical = 12.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ UserLocationHeader(
+ user = userProfile,
+ onAddressSelected = {
+ // optional: reload listings, persist selection, etc.
+ },
+ onAddNewClick = {}
+ )
+
+ // Right-side actions (notifications, etc.)
+ Icon(
+ painter = painterResource(R.drawable.ic_notification_unread),
+ contentDescription = "Notifications",
+ tint = Color.Black,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+// Row(
+// modifier = Modifier
+// .fillMaxWidth()
+// .padding(horizontal = 16.dp, vertical = 12.dp),
+// horizontalArrangement = Arrangement.SpaceBetween,
+// verticalAlignment = Alignment.CenterVertically
+// ) {
+// UserLocationHeader(
+// user = userProfile,
+// modifier = Modifier.padding(horizontal = 16.dp)
+// )
+//
+// Icon(
+// painter = painterResource(R.drawable.ic_notification_unread),
+// contentDescription = "Notifications",
+// tint = Color.Black,
+// modifier = Modifier.size(24.dp)
+// )
+// }
+
+ // Animal type filter buttons
+ AnimalTypeSelector(
+ animalTypes = animalTypes,
+ selectedAnimalType = selectedAnimalType
+ ) { }
+
+ // Ad space banner
+ AdSpaceBanner(
+ modifier = Modifier.padding(horizontal = 22.dp)
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+
+ // Sort and Filter buttons
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 22.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ SortButton(onSortClick)
+ FilterButton(onFilterClick)
+ }
+
+ sampleAnimals.forEach { animal ->
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Product card
+ BuyAnimalCard(
+ product = animal,
+ isSaved = isSaved.value,
+ onSavedChange = { isSaved.value = it },
+ onProductClick = { onProductClick(animal.id)},
+ onSellerClick = onSellerClick
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Ad space banner at bottom
+ AdSpaceBanner(
+ modifier = Modifier.padding(horizontal = 22.dp)
+ )
+ }
+
+ Spacer(modifier = Modifier.height(80.dp))
+
+
+ }
+ }
+ }
+
+
+ }
+}
+
+
+
+
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/ChooseServiceScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/ChooseServiceScreen.kt
new file mode 100644
index 0000000..62cb862
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/ChooseServiceScreen.kt
@@ -0,0 +1,227 @@
+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.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Icon
+import androidx.compose.material3.RadioButton
+import androidx.compose.material3.RadioButtonDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+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.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.components.OptionCard
+import com.example.livingai_lg.ui.components.backgrounds.StoreBackground
+import com.example.livingai_lg.R
+data class ServiceType(
+ val id: String,
+ val title: String,
+ val icon: Int,
+ val backgroundColor: Color
+)
+
+@Composable
+fun ChooseServiceScreen(
+ profileId: String? = null,
+ onServiceSelected: (serviceType: String) -> Unit = {}
+) {
+ val selectedService = remember { mutableStateOf(null) }
+
+ val serviceTypes = listOf(
+ ServiceType(
+ id = "transport",
+ title = "Transport",
+ icon = R.drawable.ic_shop,
+ backgroundColor = Color(0xFF9D4EDD)
+ ),
+ ServiceType(
+ id = "vet",
+ title = "Vet",
+ icon = R.drawable.ic_bag,
+ backgroundColor = Color(0xFF3A86FF)
+ ),
+ ServiceType(
+ id = "feed_supplier",
+ title = "Feed Supplier",
+ icon = R.drawable.ic_spanner,
+ backgroundColor = Color(0xFFFF5722)
+ ),
+ ServiceType(
+ id = "medicine_supplier",
+ title = "Medicine Supplier",
+ icon = R.drawable.ic_shop2,
+ backgroundColor = Color(0xFF4CAF50)
+ ),
+ ServiceType(
+ id = "other",
+ title = "Other",
+ icon = R.drawable.ic_other,
+ backgroundColor = Color(0xFFD4A942)
+ )
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF5F1E8))
+ ) {
+ // Decorative background
+ StoreBackground()
+
+ // Main content
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 24.dp, vertical = 32.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.height(32.dp))
+
+ // Header
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "Choose Service",
+ fontSize = 36.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Choose Service**",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A).copy(alpha = 0.8f)
+ )
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+
+ // Service selection cards
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ serviceTypes.forEach { service ->
+ OptionCard(
+ label = service.title,
+ icon = service.icon,
+ iconBackgroundColor = service.backgroundColor,
+ onClick = {
+ selectedService.value = service.id
+ onServiceSelected(service.id)
+ }
+ )
+ }
+
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+ }
+ }
+}
+
+@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)
+ )
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/CreateProfileScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/CreateProfileScreen.kt
new file mode 100644
index 0000000..25511bc
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/CreateProfileScreen.kt
@@ -0,0 +1,117 @@
+package com.example.livingai_lg.ui.screens
+
+import android.widget.Toast
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.api.AuthApiClient
+import com.example.livingai_lg.api.AuthManager
+import com.example.livingai_lg.api.TokenManager
+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 kotlinx.coroutines.launch
+
+
+@Composable
+fun CreateProfileScreen(
+ name: String,
+ onProfileSelected: (profileId: String) -> Unit = {}
+) {
+ val selectedProfile = remember { mutableStateOf(profileTypes[0].id) }
+ val context = LocalContext.current.applicationContext
+ val scope = rememberCoroutineScope()
+ val authManager = remember { AuthManager(context, AuthApiClient(context), TokenManager(context)) }
+
+ fun updateProfile(profileId: String) {
+ scope.launch {
+ authManager.updateProfile(name, profileId)
+ .onSuccess {
+ onProfileSelected(profileId)
+ }
+ .onFailure {
+ Toast.makeText(context, "Failed to update profile: ${it.message}", Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF5F1E8))
+ ) {
+ // Decorative background
+ StoreBackground()
+
+ // Main content
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 24.dp, vertical = 32.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.height(32.dp))
+
+ // Header
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "Create Profile",
+ fontSize = 36.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Choose Profile Type",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF0A0A0A).copy(alpha = 0.8f)
+ )
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+
+ // Profile selection cards
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ profileTypes.forEach { profile ->
+ OptionCard(
+ label = profile.title,
+ icon = profile.icon,
+ iconBackgroundColor = profile.backgroundColor,
+ onClick = {
+ updateProfile(profile.id)
+ }
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/FilterScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/FilterScreen.kt
new file mode 100644
index 0000000..5f4c9d6
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/FilterScreen.kt
@@ -0,0 +1,552 @@
+package com.example.livingai_lg.ui.screens
+
+import androidx.compose.foundation.BorderStroke
+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.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+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
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun FilterScreen(
+ onBackClick: () -> Unit = {},
+ onSkipClick: () -> Unit = {},
+ onSubmitClick: () -> Unit = {},
+ onCancelClick: () -> Unit = {}
+) {
+ var selectedAnimal by remember { mutableStateOf("") }
+ var selectedBreed by remember { mutableStateOf("") }
+ var selectedDistance by remember { mutableStateOf("") }
+ var selectedGender by remember { mutableStateOf("") }
+
+ var animalExpanded by remember { mutableStateOf(false) }
+ var breedExpanded by remember { mutableStateOf(false) }
+ var distanceExpanded by remember { mutableStateOf(false) }
+ var genderExpanded by remember { mutableStateOf(false) }
+
+ var priceFrom by remember { mutableStateOf("0") }
+ var priceTo by remember { mutableStateOf("90,000") }
+ var priceSliderValue by remember { mutableFloatStateOf(0f) }
+
+ var ageFrom by remember { mutableStateOf("1") }
+ var ageTo by remember { mutableStateOf("20") }
+
+ var selectedPregnancyStatus by remember { mutableStateOf(setOf()) }
+
+ var weightFrom by remember { mutableStateOf("0") }
+ var weightTo by remember { mutableStateOf("9000") }
+
+ var milkYieldFrom by remember { mutableStateOf("0") }
+ var milkYieldTo by remember { mutableStateOf("900") }
+
+ var calvingFrom by remember { mutableStateOf(0) }
+ var calvingTo by remember { mutableStateOf(10) }
+
+ var calvingFromExpanded by remember { mutableStateOf(false) }
+ var calvingToExpanded by remember { mutableStateOf(false) }
+
+ val maxCalving = 10
+
+ val calvingFromOptions = (0..maxCalving).map { it.toString() }
+ val calvingToOptions = (calvingFrom..maxCalving).map { it.toString() }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+ // Header
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 16.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ IconButton(onClick = onBackClick) {
+ Icon(
+ imageVector = Icons.Default.ArrowBack,
+ contentDescription = "Back",
+ tint = Color(0xFF0A0A0A)
+ )
+ }
+
+ Text(
+ text = "Filters",
+ fontSize = 32.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.Black
+ )
+ }
+
+ TextButton(onClick = onSkipClick) {
+ Text(
+ text = "Skip",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF4A5565),
+ textDecoration = TextDecoration.Underline
+ )
+ }
+ }
+ }
+
+ // Scrollable Content
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 16.dp)
+ .padding(bottom = 24.dp),
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ // Animal Section
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text(
+ text = "Animal",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.Black
+ )
+
+ ExposedDropdownMenuBox(
+ 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
+ }
+ )
+ }
+ }
+ }
+ }
+
+ // Breed Section
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text(
+ text = "Breed",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color.Black
+ )
+
+ ExposedDropdownMenuBox(
+ 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
+ }
+ )
+ }
+ }
+ }
+ }
+
+ // Price and Age Row
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp) // 👈 reduce spacing
+ ) {
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ RangeFilter(
+ modifier = Modifier.fillMaxWidth(), // 👈 important
+ label = "Price",
+ min = 0,
+ max = 90_000,
+ valueFrom = priceFrom.toInt(),
+ valueTo = priceTo.replace(",", "").toInt(),
+ onValueChange = { from, to ->
+ priceFrom = from.toString()
+ priceTo = to.toString()
+ }
+ )
+ }
+
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ RangeFilter(
+ modifier = Modifier.fillMaxWidth(),
+ label = "Age",
+ min = 0,
+ max = 20,
+ valueFrom = ageFrom.toInt(),
+ valueTo = ageTo.replace(",", "").toInt(),
+ showSlider = false,
+ onValueChange = { from, to ->
+ ageFrom = from.toString()
+ ageTo = to.toString()
+ }
+ )
+ }
+ }
+
+ // 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)
+ ) {
+ DropdownInput(
+ label = "Distance",
+ selected = selectedDistance,
+ options = listOf("0-5 km", "5-10 km", "10-20 km", "20+ km"),
+ expanded = distanceExpanded,
+ onExpandedChange = { distanceExpanded = it },
+ onSelect = { item ->
+ selectedDistance = item
+ distanceExpanded = false
+ },
+ placeholder = "Choose Distance", // <--- half width
+ )
+ }
+
+ // Gender Section
+ Column(
+ modifier = Modifier.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(3.dp)
+ ) {
+ DropdownInput(
+ label = "Gender",
+ selected = selectedGender,
+ options = listOf("Male", "Female"),
+ expanded = genderExpanded,
+ onExpandedChange = { genderExpanded = it },
+ onSelect = { item ->
+ selectedGender = item
+ genderExpanded = false
+ },
+ placeholder = "Choose Gender", // <--- half width
+ )
+ }
+
+
+ }
+
+ // Pregnancy Status Section
+ Column(
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ text = "Pregnancy Status",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = Color(0xFF364153)
+ )
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(20.dp)
+ ) {
+ listOf("Pregnant", "Calved", "Option").forEach { status ->
+ PregnancyStatusChip(
+ label = status,
+ isSelected = selectedPregnancyStatus.contains(status),
+ onClick = {
+ selectedPregnancyStatus = if (selectedPregnancyStatus.contains(status)) {
+ selectedPregnancyStatus - status
+ } else {
+ selectedPregnancyStatus + status
+ }
+ }
+ )
+ }
+ }
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp) // 👈 reduce spacing
+ ) {
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ RangeFilter(
+ modifier = Modifier.fillMaxWidth(), // 👈 important
+ label = "Weight",
+ min = 0,
+ max = 9000,
+ valueFrom = weightFrom.toInt(),
+ valueTo = weightTo.replace(",", "").toInt(),
+ onValueChange = { from, to ->
+ weightFrom = from.toString()
+ weightTo = to.toString()
+ }
+ )
+ }
+
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ RangeFilter(
+ modifier = Modifier.fillMaxWidth(),
+ label = "Milk Yield",
+ min = 0,
+ max = 900,
+ valueFrom = milkYieldFrom.toInt(),
+ valueTo = milkYieldTo.replace(",", "").toInt(),
+ showSlider = true,
+ onValueChange = { from, to ->
+ milkYieldFrom = from.toString()
+ milkYieldTo = to.toString()
+ }
+ )
+ }
+ }
+
+ // Calving Number Section
+ Column(
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ // FROM
+ DropdownInput(
+ label = "Calving Number",
+ selected = calvingFrom.toString(),
+ options = calvingFromOptions,
+ expanded = calvingFromExpanded,
+ onExpandedChange = { calvingFromExpanded = it },
+ onSelect = { value ->
+ val newFrom = value.toInt()
+ calvingFrom = newFrom
+
+ // 👇 enforce invariant
+ if (calvingTo < newFrom) {
+ calvingTo = newFrom
+ }
+
+ calvingFromExpanded = false
+ },
+ placeholder = "From",
+ modifier = Modifier.weight(1f)
+ )
+
+ Text(
+ text = "to",
+ fontSize = 20.sp,
+ color = Color.Black
+ )
+
+ // TO
+ DropdownInput(
+ selected = calvingTo.toString(),
+ options = calvingToOptions, // 👈 constrained options
+ expanded = calvingToExpanded,
+ onExpandedChange = { calvingToExpanded = it },
+ onSelect = { value ->
+ calvingTo = value.toInt()
+ calvingToExpanded = false
+ },
+ placeholder = "To",
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // Action Buttons
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Submit Button
+ Button(
+ onClick = onSubmitClick,
+ modifier = Modifier
+ .width(173.dp)
+ .height(50.dp),
+ shape = RoundedCornerShape(25.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Black,
+ contentColor = Color.White
+ )
+ ) {
+ Text(
+ text = "Submit",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal
+ )
+ }
+
+ // Cancel Button
+ OutlinedButton(
+ onClick = onCancelClick,
+ modifier = Modifier
+ .width(173.dp)
+ .height(50.dp),
+ shape = RoundedCornerShape(25.dp),
+ border = BorderStroke(
+ 1.dp,
+ Color(0x1A000000)
+ ),
+ colors = ButtonDefaults.outlinedButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = Color(0xFF0A0A0A)
+ )
+ ) {
+ Text(
+ text = "Cancel",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun PregnancyStatusChip(
+ label: String,
+ isSelected: Boolean,
+ onClick: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .height(29.dp)
+ .widthIn(min = 76.dp)
+ .background(
+ if (isSelected) Color(0xFF007BFF) else Color.Transparent,
+ RoundedCornerShape(24.dp)
+ )
+ .border(
+ 1.dp,
+ Color(0xFFE5E7EB),
+ RoundedCornerShape(24.dp)
+ )
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClick)
+ .padding(horizontal = 12.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = label,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Normal,
+ color = if (isSelected) Color(0xFFF4F4F4) else Color.Black,
+ textDecoration = TextDecoration.Underline
+ )
+
+ if (isSelected) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Selected",
+ tint = Color(0xFFE3E3E3),
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/NewListingScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/NewListingScreen.kt
new file mode 100644
index 0000000..4792fd5
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/NewListingScreen.kt
@@ -0,0 +1,473 @@
+package com.example.livingai_lg.ui.screens
+
+
+import androidx.compose.foundation.BorderStroke
+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.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.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.components.MediaPickerCard
+import com.example.livingai_lg.ui.models.NewListingFormState
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun NewListingScreen(
+ onBackClick: () -> Unit = {},
+ onSaveClick: () -> Unit = {},
+ formState: NewListingFormState = remember { NewListingFormState() }
+) {
+ var name by formState.name
+ var animal by formState.animal
+ var breed by formState.breed
+ var age by formState.age
+ var milkYield by formState.milkYield
+ var calvingNumber by formState.calvingNumber
+ var reproductiveStatus by formState.reproductiveStatus
+ var description by formState.description
+
+ var animalExpanded by formState.animalExpanded
+ var breedExpanded by formState.breedExpanded
+
+ val mediaUploads = formState.mediaUploads
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+ // Header
+ TopAppBar(
+ title = {
+ Text(
+ text = "New Listing",
+ fontSize = 24.sp,
+ 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)
+ )
+ )
+
+ // Form Content
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 16.dp)
+ .padding(bottom = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Name Input
+ OutlinedTextField(
+ value = name,
+ onValueChange = { name = it },
+ placeholder = {
+ Text(
+ "Name",
+ color = Color(0xFF717182),
+ fontSize = 16.sp
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp),
+ shape = RoundedCornerShape(8.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White,
+ unfocusedBorderColor = Color(0x1A000000),
+ focusedBorderColor = Color(0x1A000000)
+ ),
+ singleLine = true
+ )
+
+ // Animal and Breed Row
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ // Animal Dropdown
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .height(50.dp)
+ ) {
+ ExposedDropdownMenuBox(
+ expanded = animalExpanded,
+ onExpandedChange = { animalExpanded = it }
+ ) {
+ OutlinedTextField(
+ value = animal,
+ onValueChange = {},
+ readOnly = true,
+ placeholder = {
+ Text(
+ "Animal",
+ color = Color(0xFF717182),
+ fontSize = 16.sp
+ )
+ },
+ trailingIcon = {
+ Icon(
+ imageVector = Icons.Default.ArrowDropDown,
+ contentDescription = "Dropdown",
+ tint = Color(0xFFA5A5A5)
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .menuAnchor(),
+ shape = RoundedCornerShape(8.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White,
+ unfocusedBorderColor = Color(0x1A000000),
+ focusedBorderColor = Color(0x1A000000)
+ ),
+ singleLine = true
+ )
+ ExposedDropdownMenu(
+ expanded = animalExpanded,
+ onDismissRequest = { animalExpanded = false }
+ ) {
+ listOf("Cow", "Buffalo", "Goat", "Sheep").forEach { option ->
+ DropdownMenuItem(
+ text = { Text(option) },
+ onClick = {
+ animal = option
+ animalExpanded = false
+ }
+ )
+ }
+ }
+ }
+ }
+
+ // Breed Dropdown
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .height(50.dp)
+ ) {
+ ExposedDropdownMenuBox(
+ expanded = breedExpanded,
+ onExpandedChange = { breedExpanded = it }
+ ) {
+ OutlinedTextField(
+ value = breed,
+ onValueChange = {},
+ readOnly = true,
+ placeholder = {
+ Text(
+ "Breed",
+ color = Color(0xFF717182),
+ fontSize = 16.sp
+ )
+ },
+ trailingIcon = {
+ Icon(
+ imageVector = Icons.Default.ArrowDropDown,
+ contentDescription = "Dropdown",
+ tint = Color(0xFFA5A5A5)
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .menuAnchor(),
+ shape = RoundedCornerShape(8.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White,
+ unfocusedBorderColor = Color(0x1A000000),
+ focusedBorderColor = Color(0x1A000000)
+ ),
+ singleLine = true
+ )
+ ExposedDropdownMenu(
+ expanded = breedExpanded,
+ onDismissRequest = { breedExpanded = false }
+ ) {
+ listOf("Holstein", "Jersey", "Gir", "Sahiwal").forEach { option ->
+ DropdownMenuItem(
+ text = { Text(option) },
+ onClick = {
+ breed = option
+ breedExpanded = false
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+
+ // Age and Milk Yield Row
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ OutlinedTextField(
+ value = age,
+ onValueChange = { age = it },
+ placeholder = {
+ Text(
+ "Age (Years)",
+ color = Color(0xFF717182),
+ fontSize = 16.sp
+ )
+ },
+ trailingIcon = {
+ Icon(
+ imageVector = Icons.Filled.ArrowDropDown,
+ contentDescription = "Sort",
+ tint = Color(0xFF353535)
+ )
+ },
+ modifier = Modifier
+ .weight(1f)
+ .height(50.dp),
+ shape = RoundedCornerShape(8.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White,
+ unfocusedBorderColor = Color(0x1A000000),
+ focusedBorderColor = Color(0x1A000000)
+ ),
+ singleLine = true
+ )
+
+ OutlinedTextField(
+ value = milkYield,
+ onValueChange = { milkYield = it },
+ placeholder = {
+ Text(
+ "Average Milk Yield (L)",
+ color = Color(0xFF717182),
+ fontSize = 16.sp
+ )
+ },
+ modifier = Modifier
+ .weight(1f)
+ .height(50.dp),
+ shape = RoundedCornerShape(8.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White,
+ unfocusedBorderColor = Color(0x1A000000),
+ focusedBorderColor = Color(0x1A000000)
+ ),
+ singleLine = true
+ )
+ }
+
+ // Calving Number
+ OutlinedTextField(
+ value = calvingNumber,
+ onValueChange = { calvingNumber = it },
+ placeholder = {
+ Text(
+ "Calving Number",
+ color = Color(0xFF717182),
+ fontSize = 16.sp
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp),
+ shape = RoundedCornerShape(8.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White,
+ unfocusedBorderColor = Color(0x1A000000),
+ focusedBorderColor = Color(0x1A000000)
+ ),
+ singleLine = true
+ )
+
+ // Reproductive Status
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text(
+ text = "Reproductive Status",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ listOf("Pregnant", "Calved", "None").forEach { status ->
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier.clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ){
+ reproductiveStatus = status
+ }
+ ) {
+ RadioButton(
+ selected = reproductiveStatus == status,
+ onClick = { reproductiveStatus = status },
+ colors = RadioButtonDefaults.colors(
+ selectedColor = Color(0xFF717182),
+ unselectedColor = Color(0xFF717182)
+ )
+ )
+ Text(
+ text = status,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+ }
+ }
+ }
+
+ // Description
+ OutlinedTextField(
+ value = description,
+ onValueChange = { description = it },
+ placeholder = {
+ Text(
+ "Description",
+ color = Color(0xFF717182),
+ fontSize = 16.sp
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(74.dp),
+ shape = RoundedCornerShape(8.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White,
+ unfocusedBorderColor = Color(0x1A000000),
+ focusedBorderColor = Color(0x1A000000)
+ ),
+ maxLines = 3
+ )
+
+ // Upload Media Section
+ Text(
+ text = "Upload Media",
+ fontSize = 20.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = Color(0xFF000000),
+ modifier = Modifier.padding(top = 8.dp)
+ )
+
+ // Photo Uploads Grid
+ Column(
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ mediaUploads.chunked(2).forEach { rowItems ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ rowItems.forEach { upload ->
+ MediaPickerCard(
+ upload = upload,
+ modifier = Modifier.weight(1f),
+ onUriSelected = { uri ->
+ upload.uri = uri
+ }
+ )
+ }
+ // Fill empty space if odd number of items
+ if (rowItems.size == 1) {
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+ }
+ }
+
+ // Action Buttons
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ // Cancel Button
+ OutlinedButton(
+ onClick = onBackClick,
+ modifier = Modifier
+ .weight(1f)
+ .height(50.dp),
+ shape = RoundedCornerShape(25.dp),
+ border = BorderStroke(
+ 1.dp,
+ Color(0x1A000000)
+ ),
+ colors = ButtonDefaults.outlinedButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = Color(0xFF0A0A0A)
+ )
+ ) {
+ Text(
+ text = "Cancel",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal
+ )
+ }
+
+ // Save Profile Button
+ Button(
+ onClick = onSaveClick,
+ modifier = Modifier
+ .weight(1f)
+ .height(50.dp),
+ shape = RoundedCornerShape(25.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Black,
+ contentColor = Color.White
+ )
+ ) {
+ Text(
+ text = "Save Profile",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/PostSaleSurvey.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/PostSaleSurvey.kt
new file mode 100644
index 0000000..ee28d9e
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/PostSaleSurvey.kt
@@ -0,0 +1,523 @@
+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.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.icons.filled.CalendarToday
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+data class SaleSurveyData(
+ val buyerName: String = "",
+ val salePrice: String = "",
+ val saleDate: String = "",
+ val saleLocation: String = "",
+ val notes: String = "",
+ val attachments: List = emptyList()
+)
+
+data class SurveyAttachment(
+ val id: String,
+ val name: String,
+ val type: SurveyAttachmentType
+)
+
+enum class SurveyAttachmentType {
+ DOCUMENT,
+ PHOTO,
+ ADD_NEW
+}
+
+@Composable
+fun PostSaleSurveyScreen(
+ animalId: String? = null,
+ onBackClick: () -> Unit = {},
+ onSkipClick: () -> Unit = {},
+ onSubmit: (SaleSurveyData) -> Unit = {},
+ onCancel: () -> Unit = {}
+) {
+ var surveyData by remember { mutableStateOf(SaleSurveyData()) }
+ var buyerNameExpanded by remember { mutableStateOf(false) }
+
+ val defaultAttachments = listOf(
+ SurveyAttachment("1", "Sale Papers 1", SurveyAttachmentType.DOCUMENT),
+ SurveyAttachment("2", "Photo 2", SurveyAttachmentType.PHOTO),
+ SurveyAttachment("3", "Photo 1", SurveyAttachmentType.PHOTO),
+ SurveyAttachment("4", "Add new", SurveyAttachmentType.ADD_NEW)
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 16.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Default.ArrowBack,
+ contentDescription = "Back",
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier
+ .size(36.dp)
+ .clickable(
+ indication = LocalIndication.current,
+ 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))
+
+ FormField(
+ label = "Sold to*",
+ isRequired = true
+ ) {
+ DropdownField(
+ placeholder = "Buyer Name",
+ value = surveyData.buyerName,
+ expanded = buyerNameExpanded,
+ onExpandedChange = { buyerNameExpanded = it },
+ onValueChange = { surveyData = surveyData.copy(buyerName = it) }
+ )
+ }
+
+ Spacer(modifier = Modifier.height(29.dp))
+
+ FormField(
+ label = "Sale Price*",
+ isRequired = true
+ ) {
+ TextInputField(
+ placeholder = "Price",
+ value = surveyData.salePrice,
+ onValueChange = { surveyData = surveyData.copy(salePrice = it) }
+ )
+ }
+
+ Spacer(modifier = Modifier.height(21.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ FormField(
+ label = "Sale Date*",
+ isRequired = true,
+ modifier = Modifier.weight(1f)
+ ) {
+ DatePickerField(
+ placeholder = "Date",
+ value = surveyData.saleDate,
+ onValueChange = { surveyData = surveyData.copy(saleDate = it) }
+ )
+ }
+
+ FormField(
+ label = "Sale Location",
+ isRequired = false,
+ modifier = Modifier.weight(1f)
+ ) {
+ TextInputField(
+ placeholder = "Location",
+ value = surveyData.saleLocation,
+ onValueChange = { surveyData = surveyData.copy(saleLocation = it) }
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(34.dp))
+
+ MultilineTextInputField(
+ placeholder = "Notes",
+ value = surveyData.notes,
+ onValueChange = { surveyData = surveyData.copy(notes = it) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(104.dp)
+ )
+
+ Spacer(modifier = Modifier.height(31.dp))
+
+ Text(
+ text = "Attachments",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF000000),
+ lineHeight = 29.sp,
+ modifier = Modifier.padding(start = 11.dp)
+ )
+
+ Spacer(modifier = Modifier.height(31.dp))
+
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(19.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ AttachmentChip(
+ name = defaultAttachments[0].name,
+ type = defaultAttachments[0].type,
+ modifier = Modifier.weight(1f)
+ )
+ AttachmentChip(
+ name = defaultAttachments[1].name,
+ type = defaultAttachments[1].type,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ AttachmentChip(
+ name = defaultAttachments[2].name,
+ type = defaultAttachments[2].type,
+ modifier = Modifier.weight(1f)
+ )
+ AttachmentChip(
+ name = defaultAttachments[3].name,
+ type = defaultAttachments[3].type,
+ isAddNew = true,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(53.dp))
+
+ PrimaryButton(
+ text = "Submit",
+ onClick = { onSubmit(surveyData) },
+ modifier = Modifier.align(Alignment.CenterHorizontally)
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ SecondaryButton(
+ text = "Cancel",
+ onClick = onCancel,
+ modifier = Modifier.align(Alignment.CenterHorizontally)
+ )
+
+ Spacer(modifier = Modifier.height(32.dp))
+ }
+ }
+}
+
+@Composable
+fun FormField(
+ label: String,
+ isRequired: Boolean,
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(3.dp)
+ ) {
+ Text(
+ text = label,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF000000),
+ lineHeight = 29.sp
+ )
+ content()
+ }
+}
+
+@Composable
+fun TextInputField(
+ placeholder: String,
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFF000000).copy(alpha = 0.1f),
+ shape = RoundedCornerShape(8.dp)
+ )
+ .background(Color.Transparent, RoundedCornerShape(8.dp))
+ .padding(horizontal = 12.dp),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ if (value.isEmpty()) {
+ Text(
+ text = placeholder,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF717182),
+ letterSpacing = (-0.312).sp
+ )
+ }
+ }
+}
+
+@Composable
+fun MultilineTextInputField(
+ placeholder: String,
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFF000000).copy(alpha = 0.1f),
+ shape = RoundedCornerShape(8.dp)
+ )
+ .background(Color.Transparent, RoundedCornerShape(8.dp))
+ .padding(12.dp),
+ contentAlignment = Alignment.TopStart
+ ) {
+ if (value.isEmpty()) {
+ Text(
+ text = placeholder,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF717182),
+ lineHeight = 24.sp,
+ letterSpacing = (-0.312).sp
+ )
+ }
+ }
+}
+
+@Composable
+fun DropdownField(
+ placeholder: String,
+ value: String,
+ expanded: Boolean,
+ onExpandedChange: (Boolean) -> Unit,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFF000000).copy(alpha = 0.1f),
+ shape = RoundedCornerShape(8.dp)
+ )
+ .background(Color.Transparent, RoundedCornerShape(8.dp))
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { onExpandedChange(!expanded) }
+ .padding(horizontal = 12.dp)
+ ) {
+ Text(
+ text = value.ifEmpty { placeholder },
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF717182),
+ letterSpacing = (-0.312).sp,
+ modifier = Modifier.align(Alignment.CenterStart)
+ )
+ Icon(
+ imageVector = Icons.Default.ArrowDropDown,
+ contentDescription = "Dropdown",
+ tint = Color(0xFFA5A5A5),
+ modifier = Modifier
+ .size(15.dp, 10.dp)
+ .align(Alignment.CenterEnd)
+ )
+ }
+}
+
+@Composable
+fun DatePickerField(
+ placeholder: String,
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFF000000).copy(alpha = 0.1f),
+ shape = RoundedCornerShape(8.dp)
+ )
+ .background(Color.Transparent, RoundedCornerShape(8.dp))
+ .padding(horizontal = 12.dp)
+ ) {
+ Text(
+ text = if (value.isEmpty()) placeholder else value,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF717182),
+ letterSpacing = (-0.312).sp,
+ modifier = Modifier.align(Alignment.CenterStart)
+ )
+ Icon(
+ imageVector = Icons.Default.CalendarToday,
+ contentDescription = "Calendar",
+ tint = Color(0xFF000000),
+ modifier = Modifier
+ .size(24.dp)
+ .align(Alignment.CenterEnd)
+ )
+ }
+}
+
+@Composable
+fun AttachmentChip(
+ name: String,
+ type: SurveyAttachmentType,
+ modifier: Modifier = Modifier,
+ isAddNew: Boolean = false,
+ onClick: () -> Unit = {}
+) {
+ Row(
+ modifier = modifier
+ .height(43.dp)
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFFE5E7EB),
+ shape = RoundedCornerShape(24.dp)
+ )
+ .background(Color.Transparent, RoundedCornerShape(24.dp))
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { onClick() }
+ .padding(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = name,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF4A5565),
+ lineHeight = 24.sp,
+ textDecoration = TextDecoration.Underline
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Icon(
+ imageVector = if (isAddNew) Icons.Default.Add else Icons.Default.ArrowDropDown,
+ contentDescription = if (isAddNew) "Add" else "Attachment",
+ tint = if (isAddNew) Color(0xFF777777) else Color(0xFFADADAD),
+ modifier = Modifier.size(24.dp)
+ )
+ }
+}
+
+@Composable
+fun PrimaryButton(
+ text: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .width(173.dp)
+ .height(50.dp)
+ .background(Color(0xFF0A0A0A), RoundedCornerShape(50.dp))
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { onClick() },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = text,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFFFFFFFF),
+ lineHeight = 24.sp,
+ letterSpacing = (-0.312).sp,
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
+@Composable
+fun SecondaryButton(
+ text: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .width(173.dp)
+ .height(50.dp)
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFF000000).copy(alpha = 0.1f),
+ shape = RoundedCornerShape(50.dp)
+ )
+ .background(Color.Transparent, RoundedCornerShape(50.dp))
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { onClick() },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = text,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A),
+ lineHeight = 24.sp,
+ letterSpacing = (-0.312).sp,
+ textAlign = TextAlign.Center
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/SaleArchiveScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/SaleArchiveScreen.kt
new file mode 100644
index 0000000..4cd3517
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/SaleArchiveScreen.kt
@@ -0,0 +1,355 @@
+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.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.AttachFile
+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.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.compose.AsyncImage
+
+data class SaleArchive(
+ val id: String,
+ val name: String,
+ val breed: String,
+ val soldTo: String,
+ val saleDate: String,
+ val salePrice: String,
+ val location: String,
+ val notes: String,
+ val imageUrl: String,
+ val attachments: List
+)
+
+data class Attachment(
+ val id: String,
+ val name: String,
+ val type: AttachmentType
+)
+
+enum class AttachmentType {
+ SALE_PAPERS,
+ PHOTO
+}
+
+@Composable
+fun SaleArchiveScreen(
+ saleId: String,
+ onBackClick: () -> Unit = {},
+ onSaleSurveyClick:(saleId: String) -> Unit = {},
+) {
+ val sale = SaleArchive(
+ id = "1",
+ name = "Gauri",
+ breed = "Gir",
+ soldTo = "Buyer 1",
+ saleDate = "07.12.2025",
+ salePrice = "₹30,000",
+ location = "Ravi Nagar, Nagpur",
+ notes = "Buyer requested a check up after 15 days of sale. Additional support for 20 days.",
+ imageUrl = "https://api.builder.io/api/v1/image/assets/TEMP/9389ccf57f6d262b500be45f6d9cbfe929302413?width=784",
+ attachments = listOf(
+ Attachment("1", "Sale Papers 1", AttachmentType.SALE_PAPERS),
+ Attachment("2", "Photo 1", AttachmentType.PHOTO),
+ Attachment("3", "Sale Papers 1", AttachmentType.SALE_PAPERS),
+ Attachment("4", "Photo 2", AttachmentType.PHOTO)
+ )
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(64.dp)
+ .padding(horizontal = 11.dp, vertical = 15.dp)
+ ) {
+ Icon(
+ Icons.Default.ArrowBack,
+ contentDescription = "Back",
+ tint = Color(0xFF0A0A0A),
+ modifier = Modifier
+ .size(36.dp)
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onBackClick
+ )
+ )
+ }
+
+ AsyncImage(
+ model = sale.imageUrl,
+ contentDescription = "${sale.name} image",
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(273.dp),
+ contentScale = ContentScale.Crop
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .shadow(
+ elevation = 25.dp,
+ shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
+ spotColor = Color.Black.copy(alpha = 0.25f)
+ )
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFFE5E7EB).copy(alpha = 0.5f),
+ shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)
+ )
+ .background(
+ color = Color(0xFFFCFBF8),
+ shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)
+ )
+ .padding(horizontal = 21.dp, vertical = 17.dp)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(1.987.dp)
+ ) {
+ Text(
+ text = "Name, Breed",
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF6A7282),
+ lineHeight = 16.sp
+ )
+ Text(
+ text = "${sale.name}, (${sale.breed})",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF101828),
+ lineHeight = 22.sp
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(11.99.dp)
+ ) {
+ InfoField(
+ label = "Sold To",
+ value = sale.soldTo,
+ modifier = Modifier.weight(1f)
+ )
+ InfoField(
+ label = "Sale Date",
+ value = sale.saleDate,
+ modifier = Modifier.weight(1f)
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 17.dp),
+ horizontalArrangement = Arrangement.spacedBy(11.99.dp)
+ ) {
+ InfoField(
+ label = "Sale Price",
+ value = sale.salePrice,
+ modifier = Modifier.weight(1f)
+ )
+ InfoField(
+ label = "Location",
+ value = sale.location,
+ modifier = Modifier.weight(1f)
+ )
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.078.dp)
+ .background(Color(0xFFD1D5DC))
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(5.995.dp)
+ ) {
+ Text(
+ text = "Notes",
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF6A7282),
+ lineHeight = 16.sp
+ )
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = Color.Transparent,
+ shape = RoundedCornerShape(12.dp)
+ )
+ ) {
+ Text(
+ text = sale.notes,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF364153),
+ lineHeight = 20.sp
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ text = "Attachments",
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF000000),
+ lineHeight = 24.sp
+ )
+
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(11.dp)
+ ) {
+ AttachmentButton(
+ name = sale.attachments[0].name,
+ modifier = Modifier.weight(1f)
+ )
+ AttachmentButton(
+ name = sale.attachments[1].name,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(11.dp)
+ ) {
+ AttachmentButton(
+ name = sale.attachments[2].name,
+ modifier = Modifier.weight(1f)
+ )
+ AttachmentButton(
+ name = sale.attachments[3].name,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+ }
+ }
+ }
+}
+
+@Composable
+fun InfoField(
+ label: String,
+ value: String,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(1.987.dp)
+ ) {
+ Text(
+ text = label,
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color(0xFF6A7282),
+ lineHeight = 16.sp
+ )
+ Text(
+ text = value,
+ fontSize = 15.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF101828),
+ lineHeight = 20.sp
+ )
+ }
+}
+
+@Composable
+fun AttachmentButton(
+ name: String,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {}
+) {
+ Row(
+ modifier = modifier
+ .height(38.dp)
+ .border(
+ width = 1.078.dp,
+ color = Color(0xFFE5E7EB),
+ shape = RoundedCornerShape(20.dp)
+ )
+ .background(
+ color = Color.Transparent,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClick,
+ )
+ .padding(horizontal = 12.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = name,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF4A5565),
+ lineHeight = 20.sp,
+ textDecoration = TextDecoration.Underline
+ )
+ Spacer(modifier = Modifier.width(5.995.dp))
+ Icon(
+ imageVector = Icons.Default.AttachFile,
+ contentDescription = "Attachment",
+ tint = Color(0xFFADADAD),
+ modifier = Modifier.size(19.99.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/SellerProfileScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/SellerProfileScreen.kt
new file mode 100644
index 0000000..d7d0484
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/SellerProfileScreen.kt
@@ -0,0 +1,560 @@
+package com.example.livingai_lg.ui.screens
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+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.filled.ArrowBack
+import androidx.compose.material.icons.filled.Call
+import androidx.compose.material.icons.filled.LocationOn
+import androidx.compose.material.icons.filled.Message
+import androidx.compose.material.icons.filled.Shield
+import androidx.compose.material.icons.filled.Star
+import androidx.compose.material3.*
+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.RectangleShape
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+data class SellerProfile(
+ val name: String,
+ val initials: String,
+ val location: String,
+ val rating: Double,
+ val reviewCount: Int,
+ val trustScore: Int,
+ val totalSales: Int,
+ val yearsActive: String,
+ val responseRate: String,
+ val about: String
+)
+
+data class PastSale(
+ val id: String,
+ val name: String,
+ val emoji: String,
+ val age: String,
+ val breed: String,
+ val price: String,
+ val rating: Double
+)
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SellerProfileScreen(
+ sellerId: String = "1",
+ onBackClick: () -> Unit = {},
+ onCallClick: () -> Unit = {},
+ onMessageClick: () -> Unit = {},
+ onViewAllSalesClick: () -> Unit = {}
+) {
+ // Sample data - in real app, fetch based on sellerId
+ val seller = SellerProfile(
+ name = "Ramesh Singh",
+ initials = "RS",
+ location = "Nagpur, Maharashtra",
+ rating = 4.8,
+ reviewCount = 156,
+ trustScore = 95,
+ totalSales = 47,
+ yearsActive = "5+",
+ responseRate = "98%",
+ about = "Experienced cattle farmer and trader with over 5 years in the business. Specializing in dairy cattle breeds including Gir, Holstein, and Jersey. All animals are well-maintained with complete vaccination records and health certificates."
+ )
+
+ val pastSales = listOf(
+ PastSale("1", "Gir Cow", "🐄", "Age: 3 yrs", "Holstein", "₹45,000", 5.0),
+ PastSale("2", "Buffalo", "🐃", "Age: 4 yrs", "Murrah", "₹55,000", 4.9),
+ PastSale("3", "Goat", "🐐", "Age: 2 yrs", "Sirohi", "₹12,000", 4.7)
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+ // Header
+ TopAppBar(
+ title = {
+ Text(
+ text = "Seller Profile",
+ fontSize = 24.sp,
+ 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)
+ ),
+ modifier = Modifier.border(
+ width = 1.dp,
+ color = Color(0x1A000000),
+ shape = RectangleShape
+ )
+ )
+
+ // Scrollable Content
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 16.dp)
+ .padding(top = 16.dp, bottom = 24.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Profile Card
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(8.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White),
+ border = BorderStroke(1.dp, Color(0x1A000000))
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Profile Info
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Avatar
+ Box(
+ modifier = Modifier
+ .size(80.dp)
+ .background(Color(0xFFE5E7EB), CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = seller.initials,
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+
+ // Info
+ Column(
+ modifier = Modifier.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Text(
+ text = seller.name,
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Default.LocationOn,
+ contentDescription = "Location",
+ tint = Color(0xFF717182),
+ modifier = Modifier.size(12.dp)
+ )
+ Text(
+ text = seller.location,
+ fontSize = 14.sp,
+ color = Color(0xFF717182)
+ )
+ }
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Star,
+ contentDescription = "Rating",
+ tint = Color(0xFFFDC700),
+ modifier = Modifier.size(16.dp)
+ )
+ Text(
+ text = seller.rating.toString(),
+ fontSize = 14.sp,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+
+ Text(
+ text = "•",
+ fontSize = 14.sp,
+ color = Color(0xFF717182)
+ )
+
+ Text(
+ text = "${seller.reviewCount} reviews",
+ fontSize = 14.sp,
+ color = Color(0xFF717182)
+ )
+ }
+ }
+ }
+
+ // AI Trust Score
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ Color(0x4D60A5FA),
+ RoundedCornerShape(8.dp)
+ )
+ .border(
+ 1.dp,
+ Color(0x4D60A5FA),
+ RoundedCornerShape(8.dp)
+ )
+ .padding(12.dp)
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Shield,
+ contentDescription = "Trust",
+ tint = Color(0xFF155DFC),
+ modifier = Modifier.size(20.dp)
+ )
+ Text(
+ text = "AI Trust Score",
+ fontSize = 14.sp,
+ color = Color(0xFF0A0A0A)
+ )
+ }
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Star,
+ contentDescription = "Score",
+ tint = Color(0xFF155DFC),
+ modifier = Modifier.size(16.dp)
+ )
+ Text(
+ text = "${seller.trustScore}/100",
+ fontSize = 16.sp,
+ color = Color(0xFF155DFC)
+ )
+ }
+ }
+
+ Text(
+ text = "Verified seller with excellent transaction history",
+ fontSize = 12.sp,
+ color = Color(0xFF717182)
+ )
+ }
+ }
+
+ // Action Buttons
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Button(
+ onClick = onCallClick,
+ modifier = Modifier
+ .weight(1f)
+ .height(46.dp),
+ shape = RoundedCornerShape(23.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Black,
+ contentColor = Color.White
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Default.Call,
+ contentDescription = "Call",
+ modifier = Modifier.size(16.dp)
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "Call",
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal
+ )
+ }
+
+ OutlinedButton(
+ onClick = onMessageClick,
+ modifier = Modifier
+ .weight(1f)
+ .height(46.dp),
+ shape = RoundedCornerShape(23.dp),
+ border = BorderStroke(
+ 1.dp,
+ Color(0x1A000000)
+ ),
+ colors = ButtonDefaults.outlinedButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = Color(0xFF0A0A0A)
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Default.Message,
+ contentDescription = "Message",
+ modifier = Modifier.size(16.dp)
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "Message",
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal
+ )
+ }
+ }
+ }
+ }
+
+ // Stats Row
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ StatCard(
+ value = seller.totalSales.toString(),
+ label = "Total Sales",
+ modifier = Modifier.weight(1f)
+ )
+ StatCard(
+ value = seller.yearsActive,
+ label = "Years Active",
+ modifier = Modifier.weight(1f)
+ )
+ StatCard(
+ value = seller.responseRate,
+ label = "Response\nRate",
+ modifier = Modifier.weight(1f)
+ )
+ }
+
+ // Past Sales Section
+ Column(
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "Past Sales",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+
+ TextButton(onClick = onViewAllSalesClick) {
+ Text(
+ text = "View All",
+ fontSize = 14.sp,
+ color = Color(0xFF717182),
+ textDecoration = TextDecoration.Underline
+ )
+ }
+ }
+
+ pastSales.forEach { sale ->
+ PastSaleCard(sale)
+ }
+ }
+
+ // About Seller Section
+ Column(
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ text = "About Seller",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(8.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White),
+ border = BorderStroke(1.dp, Color(0x1A000000))
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = seller.about,
+ fontSize = 14.sp,
+ lineHeight = 22.75.sp,
+ color = Color(0xFF717182)
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun StatCard(
+ value: String,
+ label: String,
+ modifier: Modifier = Modifier
+) {
+ Card(
+ modifier = modifier.height(94.dp),
+ shape = RoundedCornerShape(8.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White),
+ border = BorderStroke(1.dp, Color(0x1A000000))
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = value,
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = label,
+ fontSize = 12.sp,
+ color = Color(0xFF717182),
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+}
+
+@Composable
+fun PastSaleCard(sale: PastSale) {
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(8.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White),
+ border = BorderStroke(1.dp, Color(0x1A000000))
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(12.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // Animal Icon
+ Box(
+ modifier = Modifier
+ .size(64.dp)
+ .background(Color(0xFFF7F4EE), RoundedCornerShape(8.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = sale.emoji,
+ fontSize = 24.sp
+ )
+ }
+
+ // Details
+ Column(
+ modifier = Modifier.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Text(
+ text = sale.name,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Text(
+ text = sale.age,
+ fontSize = 12.sp,
+ color = Color(0xFF717182)
+ )
+ Text(
+ text = sale.breed,
+ fontSize = 12.sp,
+ color = Color(0xFF717182)
+ )
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = sale.price,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF0A0A0A)
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Star,
+ contentDescription = "Rating",
+ tint = Color(0xFFFDC700),
+ modifier = Modifier.size(12.dp)
+ )
+ Text(
+ text = sale.rating.toString(),
+ fontSize = 12.sp,
+ color = Color(0xFF717182)
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/SortScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/SortScreen.kt
new file mode 100644
index 0000000..57043e2
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/SortScreen.kt
@@ -0,0 +1,168 @@
+package com.example.livingai_lg.ui.screens
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+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.width
+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.filled.ArrowBack
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import com.example.livingai_lg.ui.models.SortDirection
+import com.example.livingai_lg.ui.models.SortField
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextDecoration
+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) -> Unit = {},
+ onCancelClick: () -> Unit = {}
+) {
+ var fields by remember {
+ mutableStateOf(
+ listOf(
+ SortField("distance", "Distance"),
+ SortField("price", "Price"),
+ SortField("milk", "Milk Capacity"),
+ SortField("ai", "AI Score"),
+ SortField("age", "Age")
+ )
+ )
+ }
+
+ fun toggleField(index: Int) {
+ val current = fields[index]
+
+ val nextDirection = when (current.direction) {
+ SortDirection.NONE -> SortDirection.ASC
+ SortDirection.ASC -> SortDirection.DESC
+ SortDirection.DESC -> SortDirection.NONE
+ }
+
+ val updated = fields.toMutableList()
+
+ updated[index] = current.copy(
+ direction = nextDirection,
+ order = if (nextDirection == SortDirection.NONE) null else current.order
+ )
+
+ // Recalculate sort order
+ val active = updated
+ .filter { it.direction != SortDirection.NONE }
+ .sortedBy { it.order ?: Int.MAX_VALUE }
+ .mapIndexed { i, item -> item.copy(order = i + 1) }
+
+ fields = updated.map { field ->
+ active.find { it.key == field.key } ?: field.copy(order = null)
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF7F4EE))
+ ) {
+
+ // Header
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ IconButton(onClick = onBackClick) {
+ Icon(Icons.Default.ArrowBack, null)
+ }
+ Text("Sort By", fontSize = 32.sp)
+ }
+
+ TextButton(onClick = onSkipClick) {
+ Text(
+ "Skip",
+ textDecoration = TextDecoration.Underline,
+ fontSize = 20.sp
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ fields.forEachIndexed { index, field ->
+ SortItem(
+ field = field,
+ onToggle = { toggleField(index) }
+ )
+ }
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ Button(
+ onClick = { onApplyClick(fields.filter { it.direction != SortDirection.NONE }) },
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .width(173.dp)
+ .height(50.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Black,
+ contentColor = Color.White
+ ),
+ shape = RoundedCornerShape(25.dp)
+ ) {
+ Text("Apply")
+ }
+
+ OutlinedButton(
+ onClick = onCancelClick,
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .width(173.dp)
+ .height(50.dp),
+ colors = ButtonDefaults.outlinedButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = Color(0xFF0A0A0A)
+ ),
+ shape = RoundedCornerShape(25.dp)
+ ) {
+ Text("Cancel")
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/LandingScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/LandingScreen.kt
new file mode 100644
index 0000000..37a8103
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/LandingScreen.kt
@@ -0,0 +1,203 @@
+package com.example.livingai_lg.ui.screens.auth
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.ui.components.backgrounds.DecorativeBackground
+import com.example.livingai_lg.ui.components.IconCircle
+import com.example.livingai_lg.ui.theme.FarmButtonPrimary
+import com.example.livingai_lg.ui.theme.FarmButtonSecondary
+import com.example.livingai_lg.ui.theme.FarmButtonText
+
+import com.example.livingai_lg.ui.theme.FarmSproutBg
+import com.example.livingai_lg.ui.theme.FarmSproutIcon
+import com.example.livingai_lg.ui.theme.FarmSunBg
+import com.example.livingai_lg.ui.theme.FarmSunIcon
+import com.example.livingai_lg.ui.theme.FarmTextDark
+import com.example.livingai_lg.ui.theme.FarmTextLight
+import com.example.livingai_lg.ui.theme.FarmTextNormal
+import com.example.livingai_lg.ui.theme.FarmWheatBg
+import com.example.livingai_lg.ui.theme.FarmWheatIcon
+import com.example.livingai_lg.R
+
+@Composable
+fun LandingScreen(
+ onSignUpClick: () -> Unit = {},
+ onSignInClick: () -> Unit = {},
+ onGuestClick: () -> Unit = {}
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(FarmTextLight)
+ ) {
+ // Decorative background elements
+ DecorativeBackground()
+
+ // Main content
+ BoxWithConstraints(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(vertical = 32.dp)
+ ) {
+ val horizontalPadding = maxWidth * 0.06f // proportional padding
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = horizontalPadding)
+ .padding( top = 56.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ //Spacer(modifier = Modifier.height(24.dp))
+
+ // Icon row
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.Bottom
+ ) {
+ IconCircle(
+ backgroundColor = FarmWheatBg,
+ size = 56.dp
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_wheat),
+ contentDescription = "Wheat Icon",
+ tint = FarmWheatIcon // keeps original vector colors
+ )
+ }
+
+ Spacer(modifier = Modifier.width(12.dp))
+
+ IconCircle(
+ backgroundColor = FarmSproutBg,
+ size = 64.dp
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_sprout),
+ contentDescription = "Wheat Icon",
+ tint = FarmSproutIcon // keeps original vector colors
+ )
+ }
+
+ Spacer(modifier = Modifier.width(12.dp))
+
+ IconCircle(
+ backgroundColor = FarmSunBg,
+ size = 56.dp
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_sun),
+ contentDescription = "Wheat Icon",
+ tint = FarmSunIcon // keeps original vector colors
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+
+ // Welcome text
+ Text(
+ text = "Welcome!",
+ fontSize = 32.sp,
+ fontWeight = FontWeight.Medium,
+ color = FarmTextDark
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Join the farm marketplace community",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ color = FarmTextNormal
+ )
+
+ Spacer(modifier = Modifier.height(48.dp))
+
+ // Sign up button
+ Button(
+ onClick = onSignUpClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp),
+ shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = FarmButtonPrimary,
+ contentColor = FarmButtonText
+ ),
+ elevation = ButtonDefaults.buttonElevation(
+ defaultElevation = 8.dp
+ )
+ ) {
+ Text(
+ text = "New user? Sign up",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Sign in button
+ Button(
+ onClick = onSignInClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp),
+ shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = FarmButtonSecondary,
+ contentColor = FarmButtonText
+ ),
+ elevation = ButtonDefaults.buttonElevation(
+ defaultElevation = 8.dp
+ )
+ ) {
+ Text(
+ text = "Already a user? Sign in",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium
+ )
+ }
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ // Continue as guest
+ TextButton(
+ onClick = onGuestClick
+ ) {
+ Text(
+ text = "Continue as Guest",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold,
+ color = FarmTextNormal,
+ textDecoration = TextDecoration.Underline
+ )
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt
new file mode 100644
index 0000000..436024e
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt
@@ -0,0 +1,287 @@
+package com.example.livingai_lg.ui.screens.auth
+
+import android.widget.Toast
+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.foundation.text.BasicTextField
+import androidx.compose.material3.Text
+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.text.font.FontWeight
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.api.AuthApiClient
+import com.example.livingai_lg.api.AuthManager
+import com.example.livingai_lg.api.TokenManager
+import com.example.livingai_lg.ui.components.backgrounds.DecorativeBackground
+import kotlin.math.min
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.platform.LocalFocusManager
+import kotlinx.coroutines.launch
+
+
+@Composable
+fun OtpScreen(
+ phoneNumber: String,
+ name: String,
+ onSuccess: () -> Unit = {},
+ onCreateProfile: (name: String) -> Unit = {},
+) {
+ val otp = remember { mutableStateOf("") }
+ val context = LocalContext.current.applicationContext
+ val scope = rememberCoroutineScope()
+ val authManager = remember { AuthManager(context, AuthApiClient(context), TokenManager(context)) }
+
+ // Flag to determine if this is a sign-in flow for an existing user.
+ val isSignInFlow = name == "existing_user"
+
+ BoxWithConstraints(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ Brush.linearGradient(
+ listOf(
+ Color(0xFFFFFBEA),
+ Color(0xFFFDFBE8),
+ Color(0xFFF7FEE7)
+ )
+ )
+ )
+ ) {
+ DecorativeBackground()
+
+ val screenW = maxWidth.value
+ val screenH = maxHeight.value
+
+ // Figma design reference size from Flutter widget
+ val designW = 393.39f
+ val designH = 852.53f
+
+ val scale = min(screenW / designW, screenH / designH)
+
+ fun s(v: Float) = (v * scale).dp // dp scaling
+ fun fs(v: Float) = (v * scale).sp // font scaling
+
+ // ---------------------------
+ // "Enter OTP" Title
+ // ---------------------------
+ Text(
+ text = "Enter OTP",
+ color = Color(0xFF927B5E),
+ fontSize = fs(20f),
+ fontWeight = FontWeight.Medium,
+ modifier = Modifier.offset(x = s(139f), y = s(279f)),
+ style = LocalTextStyle.current.copy(
+ shadow = Shadow(
+ color = Color.Black.copy(alpha = 0.25f),
+ offset = Offset(0f, s(4f).value),
+ blurRadius = s(4f).value
+ )
+ )
+ )
+
+ // ---------------------------
+ // OTP 4-Box Input Row
+ // ---------------------------
+// Row(
+// Modifier.offset(x = s(38f), y = s(319f)),
+// horizontalArrangement = Arrangement.spacedBy(s(17f))
+// ) {
+// repeat(4) { index ->
+// OtpBox(
+// index = index,
+// otp = otp.value,
+// onChange = { if (it.length <= 6) otp.value = it },
+// scale = scale
+// )
+// }
+// }
+ OtpInputRow(
+ otpLength = 6,
+ scale = scale,
+ otp = otp.value,
+ onOtpChange = { if (it.length <= 6) otp.value = it }
+ )
+
+ // ---------------------------
+ // Continue Button
+ // ---------------------------
+ Box(
+ modifier = Modifier
+ .offset(x = s(57f), y = s(411f))
+ .size(s(279.25f), s(55.99f))
+ .shadow(
+ elevation = s(6f),
+ ambientColor = Color.Black.copy(alpha = 0.10f),
+ shape = RoundedCornerShape(s(16f)),
+ )
+ .shadow(
+ elevation = s(15f),
+ ambientColor = Color.Black.copy(alpha = 0.10f),
+ shape = RoundedCornerShape(s(16f)),
+ )
+ .background(
+ Brush.horizontalGradient(
+ listOf(Color(0xFFFD9900), Color(0xFFE17100))
+ ),
+ shape = RoundedCornerShape(s(16f))
+ )
+ .clickable(
+ indication = LocalIndication.current,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = {
+ scope.launch {
+ authManager.login(phoneNumber, otp.value)
+ .onSuccess { response ->
+ if (isSignInFlow) {
+ // For existing users, always go to the success screen.
+ onSuccess()
+ } else {
+ // For new users, check if a profile needs to be created.
+ if (response.needsProfile) {
+ onCreateProfile(name)
+ } else {
+ onSuccess()
+ }
+ }
+ }
+ .onFailure {
+ Toast.makeText(context, "Invalid or expired OTP", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ "Continue",
+ color = Color.White,
+ fontSize = fs(16f),
+ fontWeight = FontWeight.Medium
+ )
+ }
+ }
+}
+
+@Composable
+fun OtpInputRow(
+ otpLength: Int = 4,
+ scale: Float,
+ otp: String,
+ onOtpChange: (String) -> Unit
+) {
+ val focusRequesters = remember {
+ List(otpLength) { FocusRequester() }
+ }
+
+ Row(horizontalArrangement = Arrangement.spacedBy((12 * scale).dp)) {
+ repeat(otpLength) { index ->
+ OtpBox(
+ index = index,
+ otp = otp,
+ scale = scale,
+ focusRequester = focusRequesters[index],
+ onRequestFocus = {
+ val firstEmpty = otp.length.coerceAtMost(otpLength - 1)
+ focusRequesters[firstEmpty].requestFocus()
+ },
+ onNextFocus = {
+ if (index + 1 < otpLength) {
+ focusRequesters[index + 1].requestFocus()
+ }
+ },
+ onPrevFocus = {
+ if (index - 1 >= 0) {
+ focusRequesters[index - 1].requestFocus()
+ }
+ },
+ onChange = onOtpChange
+ )
+ }
+ }
+}
+
+@Composable
+private fun OtpBox(
+ index: Int,
+ otp: String,
+ scale: Float,
+ focusRequester: FocusRequester,
+ onRequestFocus: () -> Unit,
+ onNextFocus: () -> Unit,
+ onPrevFocus: () -> Unit,
+ onChange: (String) -> Unit
+) {
+ val boxW = 66f * scale
+ val boxH = 52f * scale
+ val radius = 16f * scale
+
+ val char = otp.getOrNull(index)?.toString() ?: ""
+
+ Box(
+ modifier = Modifier
+ .size(boxW.dp, boxH.dp)
+ .shadow((4f * scale).dp, RoundedCornerShape(radius.dp))
+ .background(Color.White, RoundedCornerShape(radius.dp))
+ .clickable { onRequestFocus() },
+ contentAlignment = Alignment.Center
+ ) {
+ BasicTextField(
+ value = char,
+ onValueChange = { new ->
+ when {
+ // DIGIT ENTERED
+ new.matches(Regex("\\d")) -> {
+ val updated = otp.padEnd(index, ' ').toMutableList()
+ if (updated.size > index) updated[index] = new.first()
+ else updated.add(new.first())
+ onChange(updated.joinToString("").trim())
+ onNextFocus()
+ }
+
+ // BACKSPACE
+ new.isEmpty() -> {
+ if (char.isNotEmpty()) {
+ val updated = otp.toMutableList()
+ updated.removeAt(index)
+ onChange(updated.joinToString(""))
+ } else {
+ onPrevFocus()
+ }
+ }
+ }
+ },
+ textStyle = LocalTextStyle.current.copy(
+ fontSize = (24f * scale).sp,
+ fontWeight = FontWeight.Medium,
+ textAlign = TextAlign.Center
+ ),
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.NumberPassword
+ ),
+ singleLine = true,
+ modifier = Modifier
+ .focusRequester(focusRequester)
+ .align(Alignment.Center)
+ )
+ }
+}
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignInScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignInScreen.kt
new file mode 100644
index 0000000..bfa0e4d
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignInScreen.kt
@@ -0,0 +1,136 @@
+package com.example.livingai_lg.ui.screens.auth
+
+import android.widget.Toast
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.api.AuthApiClient
+import com.example.livingai_lg.api.AuthManager
+import com.example.livingai_lg.api.TokenManager
+import com.example.livingai_lg.ui.components.backgrounds.DecorativeBackground
+import com.example.livingai_lg.ui.components.PhoneNumberInput
+
+import com.example.livingai_lg.ui.theme.FarmTextLight
+import com.example.livingai_lg.ui.theme.FarmTextLink
+import com.example.livingai_lg.ui.theme.FarmTextNormal
+
+import com.example.livingai_lg.ui.components.FarmHeader
+import com.example.livingai_lg.ui.utils.isKeyboardOpen
+import kotlinx.coroutines.launch
+
+@Composable
+fun SignInScreen(
+ onSignUpClick: () -> Unit = {},
+ onSignInClick: (phone: String, name: String) -> Unit = {},
+) {
+ val phoneNumber = remember { mutableStateOf("") }
+ val isValid = phoneNumber.value.length == 10
+ val context = LocalContext.current.applicationContext
+ val scope = rememberCoroutineScope()
+ val authManager = remember { AuthManager(context, AuthApiClient(context), TokenManager(context)) }
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(FarmTextLight)
+ ) {
+ DecorativeBackground()
+
+ BoxWithConstraints(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(vertical = 32.dp)
+ ) {
+ val horizontalPadding = maxWidth * 0.06f // proportional padding
+ val keyboardOpen = isKeyboardOpen()
+ val topPadding by animateDpAsState(
+ targetValue = if (keyboardOpen) 40.dp else 140.dp,
+ animationSpec = tween(280, easing = FastOutSlowInEasing)
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = horizontalPadding)
+ .padding( top = 40.dp),//topPadding),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = if (keyboardOpen) Arrangement.Top else Arrangement.Center
+ ) {
+ //Spacer(Modifier.height(if (keyboardOpen) 32.dp else 56.dp))
+ // Header
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+
+ FarmHeader()
+ }
+
+
+ Spacer(modifier = Modifier.height(48.dp))
+
+ PhoneNumberInput(
+ phone = phoneNumber.value,
+ onChange = { phoneNumber.value = it }
+ )
+
+ Spacer(modifier = Modifier.height(48.dp))
+
+ Button(
+ onClick = {
+ val fullPhoneNumber = "+91${phoneNumber.value}"
+ scope.launch {
+ authManager.requestOtp(fullPhoneNumber)
+ .onSuccess {
+ onSignInClick(fullPhoneNumber,"existing_user")
+ }
+ .onFailure {
+ Toast.makeText(context, "Failed to send OTP: ${it.message}", Toast.LENGTH_LONG).show()
+ }
+ }
+ },
+ enabled = isValid,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp),
+ shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = if (isValid) Color(0xFFE17100) else Color(0xFFE17100).copy(
+ alpha = 0.4f
+ ),
+ contentColor = Color.White
+ )
+ ) {
+ Text("Sign In", fontSize = 16.sp, fontWeight = FontWeight.Medium)
+ }
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text("Don't have an account? ", fontSize = 16.sp, color = FarmTextNormal)
+ TextButton(onClick = onSignUpClick) {
+ Text("Sign up", fontSize = 16.sp, color = FarmTextLink)
+ }
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignUpScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignUpScreen.kt
new file mode 100644
index 0000000..e45d56a
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignUpScreen.kt
@@ -0,0 +1,203 @@
+package com.example.livingai_lg.ui.screens.auth
+
+import android.widget.Toast
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+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.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.livingai_lg.api.AuthApiClient
+import com.example.livingai_lg.api.AuthManager
+import com.example.livingai_lg.api.TokenManager
+import com.example.livingai_lg.ui.components.backgrounds.DecorativeBackground
+import com.example.livingai_lg.ui.components.DropdownInput
+import com.example.livingai_lg.ui.components.InputField
+import com.example.livingai_lg.ui.components.PhoneNumberInput
+import com.example.livingai_lg.ui.theme.FarmTextLight
+import com.example.livingai_lg.ui.theme.FarmTextNormal
+import com.example.livingai_lg.ui.components.FarmHeader
+import com.example.livingai_lg.ui.utils.isKeyboardOpen
+import kotlinx.coroutines.launch
+
+data class SignUpFormData(
+ val name: String = "",
+ val state: String = "",
+ val district: String = "",
+ val village: String = "",
+ val phoneNumber: String = ""
+){
+ val isValid: Boolean
+ get() =
+ name.isNotBlank() &&
+ state.isNotBlank() &&
+ district.isNotBlank() &&
+ village.isNotBlank() &&
+ phoneNumber.isValidPhoneNumber()
+}
+
+private fun String.isValidPhoneNumber(): Boolean {
+ return length == 10 && all { it.isDigit() }
+}
+
+
+@Composable
+fun SignUpScreen(
+ onSignInClick: () -> Unit = {},
+ onSignUpClick: (phone: String, name: String) -> Unit = {}
+) {
+ var formData by remember { mutableStateOf(SignUpFormData()) }
+
+ var showStateDropdown by remember { mutableStateOf(false) }
+ var showDistrictDropdown by remember { mutableStateOf(false) }
+ var showVillageDropdown by remember { mutableStateOf(false) }
+
+ val states = listOf("Maharashtra", "Gujarat", "Tamil Nadu", "Karnataka", "Rajasthan")
+ val districts = listOf("Mumbai", "Pune", "Nagpur", "Aurangabad", "Nashik")
+ val villages = listOf("Village 1", "Village 2", "Village 3", "Village 4", "Village 5")
+
+ val context = LocalContext.current.applicationContext
+ val scope = rememberCoroutineScope()
+ // Use 10.0.2.2 to connect to host machine's localhost from emulator
+ val authManager = remember { AuthManager(context, AuthApiClient(context), TokenManager(context)) }
+
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(FarmTextLight)
+ ) {
+ DecorativeBackground()
+
+ BoxWithConstraints(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(vertical = 32.dp)
+ ) {
+ val horizontalPadding = maxWidth * 0.06f // proportional padding
+ val keyboardOpen = isKeyboardOpen()
+// val topPadding by animateDpAsState(
+// targetValue = if (keyboardOpen) 40.dp else 140.dp,
+// animationSpec = tween(280, easing = FastOutSlowInEasing)
+// )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = horizontalPadding)
+ .padding( top = 40.dp),//topPadding),
+
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = if (keyboardOpen) Arrangement.Top else Arrangement.Center
+ ) {
+ // Spacer(Modifier.height(if (keyboardOpen) 32.dp else 56.dp))
+ // Title
+ FarmHeader()
+ Spacer(modifier = Modifier.height(32.dp))
+
+ // Name Input
+ InputField(
+ label = "Enter Name*",
+ value = formData.name,
+ placeholder = "Enter your name",
+ onChange = { formData = formData.copy(name = it) }
+ )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // State + District Row
+ Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
+
+ DropdownInput(
+ label = "Enter Location",
+ selected = formData.state,
+ options = states,
+ expanded = showStateDropdown,
+ onExpandedChange = { showStateDropdown = it },
+ onSelect = { formData = formData.copy(state = it) },
+ placeholder = "Select State",
+ modifier = Modifier.weight(0.4f) // <--- half width
+ )
+
+ DropdownInput(
+ selected = formData.district,
+ options = districts,
+ expanded = showDistrictDropdown,
+ onExpandedChange = { showDistrictDropdown = it },
+ onSelect = { formData = formData.copy(district = it) },
+ placeholder = "Select District",
+ modifier = Modifier.weight(0.6f) // <--- half width
+ )
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // Village Dropdown
+ DropdownInput(
+ label = "Enter Village/Place",
+ selected = formData.village,
+ options = villages,
+ expanded = showVillageDropdown,
+ onExpandedChange = { showVillageDropdown = it },
+ onSelect = {
+ formData = formData.copy(village = it); showVillageDropdown = false
+ }
+ )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // Phone Number
+ PhoneNumberInput(
+ phone = formData.phoneNumber,
+ onChange = { formData = formData.copy(phoneNumber = it) }
+ )
+
+ Spacer(modifier = Modifier.height(40.dp))
+
+ // Sign Up button
+ Button(
+ onClick = {
+ val fullPhoneNumber = "+91${formData.phoneNumber}"
+ scope.launch {
+ authManager.requestOtp(fullPhoneNumber)
+ .onSuccess {
+ onSignUpClick(fullPhoneNumber,formData.name)
+ }
+ .onFailure {
+ Toast.makeText(context, "Failed to send OTP: ${it.message}", Toast.LENGTH_LONG).show()
+ }
+ }
+ },
+ modifier = Modifier.fillMaxWidth().height(56.dp),
+ enabled = formData.isValid,
+ shape = RoundedCornerShape(16.dp),
+
+ colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE17100))
+ ) {
+ Text("Sign Up", fontSize = 16.sp)
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // Already have account?
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text("Already have an account? ", color = FarmTextNormal)
+ TextButton(onClick = onSignInClick) {
+ Text("Sign In", color = Color(0xFFE17100))
+ }
+ }
+
+ Spacer(modifier = Modifier.height(40.dp))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/theme/Color.kt b/app/src/main/java/com/example/livingai_lg/ui/theme/Color.kt
index 33f0480..a7ec9c6 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/theme/Color.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/theme/Color.kt
@@ -2,25 +2,57 @@ package com.example.livingai_lg.ui.theme
import androidx.compose.ui.graphics.Color
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFFEFB8C8)
+// Farm Marketplace Color Palette
+val FarmTextLight = Color(0xFFF5F1E8)
+val FarmTextDark = Color(0xFF5D4E37) //Title
+val FarmTextNormal = Color(0xFF8B7355)
+val FarmTextLink = Color(0xFFE17100)
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
+val FarmSproutBg = Color(0xFFFFCB79)
+val FarmSproutIcon = Color(0xFF8B6F47)
-// Custom Colors from Figma
-val LightCream = Color(0xFFFFFBEB)
-val LighterCream = Color(0xFFFEFCE8)
-val LightestGreen = Color(0xFFF7FEE7)
-val Maize = Color(0xFFF7C35F)
-val TerraCotta = Color(0xFFD97D5D)
-val DarkBrown = Color(0xFF5D4E37)
-val MidBrown = Color(0xFF8B7355)
-val DarkerBrown = Color(0xFF322410)
-val OrangeBrown = Color(0xFFD4A574)
-val DarkOrange = Color(0xFFE07A5F)
-val Gold = Color(0xFFFFDD88)
-val LightOrange = Color(0xFFFFB84D)
-val DarkerOrange = Color(0xFFD96A4F)
+val FarmWheatBg = Color(0xFFFFDD88)
+val FarmWheatIcon = Color(0xFFD4A574)
+
+val FarmSunBg = Color(0xFFFFB179)
+val FarmSunIcon = Color(0xFFE07A5F)
+
+val FarmGold = Color(0xFFFFB84D)
+val FarmPink = Color(0xFFEDD5C8)
+val FarmFence = Color(0xFFD4B5A0)
+val FarmBox = Color(0xFFE8D9CC)
+val FarmButtonPrimary = Color(0xFFFFB84D)
+val FarmButtonSecondary = Color(0xFFE07A5F)
+val FarmButtonText = Color(0xFF322410)
+
+// Material Design 3 Color Scheme
+val md_theme_light_primary = Color(0xFF5D4E37)
+val md_theme_light_onPrimary = Color(0xFFFFFFFF)
+val md_theme_light_primaryContainer = Color(0xFFF5F1E8)
+val md_theme_light_onPrimaryContainer = Color(0xFF3E3220)
+val md_theme_light_secondary = Color(0xFF8B7355)
+val md_theme_light_onSecondary = Color(0xFFFFFFFF)
+val md_theme_light_secondaryContainer = Color(0xFFFFDDBD)
+val md_theme_light_onSecondaryContainer = Color(0xFF2B2415)
+val md_theme_light_tertiary = Color(0xFFE07A5F)
+val md_theme_light_onTertiary = Color(0xFFFFFFFF)
+val md_theme_light_tertiaryContainer = Color(0xFFFFDDBD)
+val md_theme_light_onTertiaryContainer = Color(0xFF3E2415)
+val md_theme_light_error = Color(0xFFB3261E)
+val md_theme_light_onError = Color(0xFFFFFFFF)
+val md_theme_light_errorContainer = Color(0xFFF9DEDC)
+val md_theme_light_onErrorContainer = Color(0xFF410E0B)
+val md_theme_light_background = Color(0xFFFBF8F3)
+val md_theme_light_onBackground = Color(0xFF1C1B1F)
+val md_theme_light_surface = Color(0xFFFBF8F3)
+val md_theme_light_onSurface = Color(0xFF1C1B1F)
+val md_theme_light_surfaceVariant = Color(0xFFEFE0D6)
+val md_theme_light_onSurfaceVariant = Color(0xFF49454E)
+val md_theme_light_outline = Color(0xFF79747E)
+val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4)
+val md_theme_light_inverseSurface = Color(0xFF313033)
+val md_theme_light_inversePrimary = Color(0xFFFFDDBD)
+val md_theme_light_shadow = Color(0xFF000000)
+val md_theme_light_surfaceTint = Color(0xFF5D4E37)
+val md_theme_light_outlineVariant = Color(0xFFD3C4BC)
+val md_theme_light_scrim = Color(0xFF000000)
diff --git a/app/src/main/java/com/example/livingai_lg/ui/theme/Theme.kt b/app/src/main/java/com/example/livingai_lg/ui/theme/Theme.kt
index 9a570d3..3863d35 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/theme/Theme.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/theme/Theme.kt
@@ -3,56 +3,111 @@ package com.example.livingai_lg.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
-private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
+private val lightScheme = lightColorScheme(
+ primary = md_theme_light_primary,
+ onPrimary = md_theme_light_onPrimary,
+ primaryContainer = md_theme_light_primaryContainer,
+ onPrimaryContainer = md_theme_light_onPrimaryContainer,
+ secondary = md_theme_light_secondary,
+ onSecondary = md_theme_light_onSecondary,
+ secondaryContainer = md_theme_light_secondaryContainer,
+ onSecondaryContainer = md_theme_light_onSecondaryContainer,
+ tertiary = md_theme_light_tertiary,
+ onTertiary = md_theme_light_onTertiary,
+ tertiaryContainer = md_theme_light_tertiaryContainer,
+ onTertiaryContainer = md_theme_light_onTertiaryContainer,
+ error = md_theme_light_error,
+ onError = md_theme_light_onError,
+ errorContainer = md_theme_light_errorContainer,
+ onErrorContainer = md_theme_light_onErrorContainer,
+ background = md_theme_light_background,
+ onBackground = md_theme_light_onBackground,
+ surface = md_theme_light_surface,
+ onSurface = md_theme_light_onSurface,
+ surfaceVariant = md_theme_light_surfaceVariant,
+ onSurfaceVariant = md_theme_light_onSurfaceVariant,
+ outline = md_theme_light_outline,
+ inverseOnSurface = md_theme_light_inverseOnSurface,
+ inverseSurface = md_theme_light_inverseSurface,
+ inversePrimary = md_theme_light_inversePrimary,
+ surfaceTint = md_theme_light_surfaceTint,
+ outlineVariant = md_theme_light_outlineVariant,
+ scrim = md_theme_light_scrim,
)
-private val LightColorScheme = lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40
-
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
- onPrimary = Color.White,
- onSecondary = Color.White,
- onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
+private val darkScheme = darkColorScheme(
+ primary = Color(0xFFFFDDBD),
+ onPrimary = Color(0xFF3E3220),
+ primaryContainer = Color(0xFF57472D),
+ onPrimaryContainer = Color(0xFFFFDDBD),
+ secondary = Color(0xFFFFDDBD),
+ onSecondary = Color(0xFF2B2415),
+ secondaryContainer = Color(0xFF6B5344),
+ onSecondaryContainer = Color(0xFFFFDDBD),
+ tertiary = Color(0xFFFFB893),
+ onTertiary = Color(0xFF3E2415),
+ tertiaryContainer = Color(0xFF8B5A3C),
+ onTertiaryContainer = Color(0xFFFFDDBD),
+ error = Color(0xFFFFB4AB),
+ onError = Color(0xFF690005),
+ errorContainer = Color(0xFF93000A),
+ onErrorContainer = Color(0xFFFFDAD6),
+ background = Color(0xFF1C1B1F),
+ onBackground = Color(0xFFE7E0E8),
+ surface = Color(0xFF1C1B1F),
+ onSurface = Color(0xFFE7E0E8),
+ surfaceVariant = Color(0xFF49454E),
+ onSurfaceVariant = Color(0xFFCAC7D0),
+ outline = Color(0xFF94919A),
+ inverseOnSurface = Color(0xFF1C1B1F),
+ inverseSurface = Color(0xFFE7E0E8),
+ inversePrimary = Color(0xFF5D4E37),
+ surfaceTint = Color(0xFFFFDDBD),
+ outlineVariant = Color(0xFF49454E),
+ scrim = Color(0xFF000000),
)
@Composable
-fun LivingAi_LgTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
- // Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
+fun FarmMarketplaceTheme(
+ useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ if (useDarkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
+ useDarkTheme -> darkScheme
+ else -> lightScheme
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view)?.isAppearanceLightStatusBars =
+ !useDarkTheme
+ }
}
MaterialTheme(
colorScheme = colorScheme,
- typography = Typography,
+ typography = typography,
content = content
)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/theme/Type.kt b/app/src/main/java/com/example/livingai_lg/ui/theme/Type.kt
index 85ff3ea..1711f3f 100644
--- a/app/src/main/java/com/example/livingai_lg/ui/theme/Type.kt
+++ b/app/src/main/java/com/example/livingai_lg/ui/theme/Type.kt
@@ -6,29 +6,110 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
-// Set of Material typography styles to start with
-val Typography = Typography(
+val typography = Typography(
+ displayLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 57.sp,
+ lineHeight = 64.sp,
+ letterSpacing = (-0.25).sp,
+ ),
+ displayMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 45.sp,
+ lineHeight = 52.sp,
+ letterSpacing = 0.sp,
+ ),
+ displaySmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 36.sp,
+ lineHeight = 44.sp,
+ letterSpacing = 0.sp,
+ ),
+ headlineLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 32.sp,
+ lineHeight = 40.sp,
+ letterSpacing = 0.sp,
+ ),
+ headlineMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 28.sp,
+ lineHeight = 36.sp,
+ letterSpacing = 0.sp,
+ ),
+ headlineSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ letterSpacing = 0.sp,
+ ),
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp,
+ ),
+ titleMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.15.sp,
+ ),
+ titleSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp,
+ ),
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
- letterSpacing = 0.5.sp
- )
- /* Other default text styles to override
- titleLarge = TextStyle(
+ letterSpacing = 0.15.sp,
+ ),
+ bodyMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
- fontSize = 22.sp,
- lineHeight = 28.sp,
- letterSpacing = 0.sp
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.25.sp,
+ ),
+ bodySmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.4.sp,
+ ),
+ labelLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp,
+ ),
+ labelMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp,
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
- letterSpacing = 0.5.sp
- )
- */
-)
\ No newline at end of file
+ letterSpacing = 0.5.sp,
+ ),
+)
diff --git a/app/src/main/java/com/example/livingai_lg/ui/utils/FormatUtils.kt b/app/src/main/java/com/example/livingai_lg/ui/utils/FormatUtils.kt
new file mode 100644
index 0000000..dabec56
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/utils/FormatUtils.kt
@@ -0,0 +1,61 @@
+package com.example.livingai_lg.ui.utils
+
+import java.text.DecimalFormat
+import kotlin.math.roundToInt
+
+import java.text.NumberFormat
+import java.util.Locale
+
+class FormatUtils {
+}
+fun formatter(): NumberFormat{
+ return NumberFormat.getInstance(Locale.forLanguageTag("en-IN"))
+}
+
+
+fun formatViews(views: Long?): String {
+ val count = views ?: 0
+
+ val formattedNumber = formatter().format(count)
+
+ return when (count) {
+ 1L -> "$formattedNumber View"
+ else -> "$formattedNumber Views"
+ }
+}
+
+fun formatPrice(price: Long?): String {
+ val value = price ?: 0
+
+ val formattedNumber = formatter().format(value)
+
+ return "₹$formattedNumber"
+}
+
+fun formatDistance(distanceMeters: Long?): String {
+ val meters = distanceMeters ?: 0L
+
+ return if (meters < 1_000) {
+ val unit = if (meters == 1L) "mt" else "mts"
+ "$meters $unit away"
+ } else {
+ val km = meters / 1_000.0
+ val formatter = DecimalFormat("#.#") // 1 decimal if needed
+ val formattedKm = formatter.format(km)
+
+ val unit = if (formattedKm == "1") "km" else "kms"
+ "$formattedKm $unit away"
+ }
+}
+
+fun formatAge(months: Int?): String {
+ val value = months ?: 0
+
+ val years = value / 12f
+ val roundedYears = (years * 10).roundToInt() / 10f
+
+ val unit = if (roundedYears == 1f) "Year" else "Years"
+
+ return "$roundedYears $unit"
+}
+
diff --git a/app/src/main/java/com/example/livingai_lg/ui/utils/KeyboardUtils.kt b/app/src/main/java/com/example/livingai_lg/ui/utils/KeyboardUtils.kt
new file mode 100644
index 0000000..d100f78
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/utils/KeyboardUtils.kt
@@ -0,0 +1,12 @@
+package com.example.livingai_lg.ui.utils
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.ime
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+
+@Composable
+fun isKeyboardOpen(): Boolean {
+ val density = LocalDensity.current
+ return WindowInsets.ime.getBottom(density) > 0
+}
diff --git a/app/src/main/java/com/example/livingai_lg/ui/utils/MediaUtils.kt b/app/src/main/java/com/example/livingai_lg/ui/utils/MediaUtils.kt
new file mode 100644
index 0000000..9db4857
--- /dev/null
+++ b/app/src/main/java/com/example/livingai_lg/ui/utils/MediaUtils.kt
@@ -0,0 +1,51 @@
+package com.example.livingai_lg.ui.utils
+
+import android.content.Context
+import android.net.Uri
+import androidx.core.content.FileProvider
+import java.io.File
+import java.util.UUID
+import android.graphics.Bitmap
+import android.media.MediaMetadataRetriever
+import com.example.livingai_lg.ui.models.MediaType
+
+fun createMediaUri(
+ context: Context,
+ type: MediaType
+): Uri {
+ val directory = when (type) {
+ MediaType.PHOTO -> File(context.cacheDir, "images")
+ MediaType.VIDEO -> File(context.cacheDir, "videos")
+ }
+
+ if (!directory.exists()) {
+ directory.mkdirs()
+ }
+
+ val fileName = when (type) {
+ MediaType.PHOTO -> "${UUID.randomUUID()}.jpg"
+ MediaType.VIDEO -> "${UUID.randomUUID()}.mp4"
+ }
+
+ val file = File(directory, fileName)
+
+ return FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.fileprovider",
+ file
+ )
+}
+
+
+fun getVideoThumbnail(
+ context: Context,
+ uri: Uri
+): Bitmap? {
+ return try {
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(context, uri)
+ retriever.getFrameAtTime(0)
+ } catch (e: Exception) {
+ null
+ }
+}
diff --git a/app/src/main/res/drawable/bg.xml b/app/src/main/res/drawable/bg.xml
new file mode 100644
index 0000000..70fa575
--- /dev/null
+++ b/app/src/main/res/drawable/bg.xml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_shop.xml b/app/src/main/res/drawable/bg_shop.xml
new file mode 100644
index 0000000..14a9666
--- /dev/null
+++ b/app/src/main/res/drawable/bg_shop.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_bag.xml b/app/src/main/res/drawable/ic_bag.xml
new file mode 100644
index 0000000..8af69b2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bag.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_bookmark.xml b/app/src/main/res/drawable/ic_bookmark.xml
new file mode 100644
index 0000000..92f4ab2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bookmark.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_bookmark_plus.xml b/app/src/main/res/drawable/ic_bookmark_plus.xml
new file mode 100644
index 0000000..b2bd7b6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bookmark_plus.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_chat.xml b/app/src/main/res/drawable/ic_chat.xml
new file mode 100644
index 0000000..945046d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chat.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_config.xml b/app/src/main/res/drawable/ic_config.xml
new file mode 100644
index 0000000..b940b1a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_config.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml
new file mode 100644
index 0000000..bef4199
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml
new file mode 100644
index 0000000..3dd01b4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_home.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml
new file mode 100644
index 0000000..22b1b35
--- /dev/null
+++ b/app/src/main/res/drawable/ic_location.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_market.xml b/app/src/main/res/drawable/ic_market.xml
new file mode 100644
index 0000000..be0835f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_market.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notification_unread.xml b/app/src/main/res/drawable/ic_notification_unread.xml
new file mode 100644
index 0000000..a88e046
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notification_unread.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_other.xml b/app/src/main/res/drawable/ic_other.xml
new file mode 100644
index 0000000..fceb745
--- /dev/null
+++ b/app/src/main/res/drawable/ic_other.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml
new file mode 100644
index 0000000..aeaa31c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_phone.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_profile.xml b/app/src/main/res/drawable/ic_profile.xml
new file mode 100644
index 0000000..01b28e8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_profile.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_shop.xml b/app/src/main/res/drawable/ic_shop.xml
new file mode 100644
index 0000000..0ebe824
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shop.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_shop2.xml b/app/src/main/res/drawable/ic_shop2.xml
new file mode 100644
index 0000000..fc2a5e8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shop2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml
new file mode 100644
index 0000000..6d38123
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sort.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_spanner.xml b/app/src/main/res/drawable/ic_spanner.xml
new file mode 100644
index 0000000..0cef492
--- /dev/null
+++ b/app/src/main/res/drawable/ic_spanner.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_sprout.xml b/app/src/main/res/drawable/ic_sprout.xml
new file mode 100644
index 0000000..896841a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sprout.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_star.xml b/app/src/main/res/drawable/ic_star.xml
new file mode 100644
index 0000000..9c66783
--- /dev/null
+++ b/app/src/main/res/drawable/ic_star.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_star_half.xml b/app/src/main/res/drawable/ic_star_half.xml
new file mode 100644
index 0000000..db0b78c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_star_half.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_sun.xml b/app/src/main/res/drawable/ic_sun.xml
new file mode 100644
index 0000000..31f3ba1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sun.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_supplier.xml b/app/src/main/res/drawable/ic_supplier.xml
new file mode 100644
index 0000000..0cef492
--- /dev/null
+++ b/app/src/main/res/drawable/ic_supplier.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_tag.xml b/app/src/main/res/drawable/ic_tag.xml
new file mode 100644
index 0000000..e74d670
--- /dev/null
+++ b/app/src/main/res/drawable/ic_tag.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_thumbs_up.xml b/app/src/main/res/drawable/ic_thumbs_up.xml
new file mode 100644
index 0000000..98238b3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_thumbs_up.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_view.xml b/app/src/main/res/drawable/ic_view.xml
new file mode 100644
index 0000000..1192780
--- /dev/null
+++ b/app/src/main/res/drawable/ic_view.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_wheat.xml b/app/src/main/res/drawable/ic_wheat.xml
new file mode 100644
index 0000000..9a04a62
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wheat.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..0e295bb
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,7 @@
+
+
+
+
+