Compare commits

...

3 Commits

Author SHA1 Message Date
ankit c5750e6b5e Merge pull request 'temp' (#1) from temp into dev
Reviewed-on: #1
2025-12-21 09:59:25 +00:00
ankitsaraf 04d06cd16d Fixes 2025-12-21 15:19:24 +05:30
ankitsaraf 4d7e8e3daf temporary changes. Will merge with the other branch. 2025-12-21 14:44:36 +05:30
18 changed files with 2423 additions and 2118 deletions

View File

@ -1 +0,0 @@
LivingAi_Lg

View File

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

View File

@ -22,10 +22,10 @@ import com.example.livingai_lg.ui.models.AnimalType
@Composable @Composable
fun AnimalTypeSelector( fun AnimalTypeSelector(
animalTypes: List<AnimalType>, animalTypes: List<AnimalType>,
selectedAnimalType: MutableState<String?>, selectedAnimalType: String?,
onAnimalTypeSelected: (String) -> Unit onAnimalTypeSelected: (String) -> Unit
) { ) {
val selectedAnimalType: String = selectedAnimalType.value ?: "" val selectedAnimalType: String = selectedAnimalType ?: ""
LazyRow( LazyRow(
modifier = Modifier modifier = Modifier

View File

@ -13,6 +13,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -61,7 +62,7 @@ fun BottomNavigationBar(
@Composable @Composable
fun BottomNavItem( fun BottomNavItem(
label: String, label: String,
iconRes: Int, iconRes: Any,
selected: Boolean, selected: Boolean,
onClick: () -> Unit = {} onClick: () -> Unit = {}
) { ) {
@ -77,12 +78,21 @@ fun BottomNavItem(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
onClick = onClick) onClick = onClick)
) { ) {
Icon( if(iconRes is Int) {
painter = painterResource(iconRes), Icon(
contentDescription = label, painter = painterResource(iconRes),
tint = color, contentDescription = label,
modifier = Modifier.size(24.dp) tint = color,
) modifier = Modifier.size(24.dp)
)
} else if(iconRes is ImageVector) {
Icon(
imageVector = iconRes,
contentDescription = label,
tint = color,
modifier = Modifier.size(24.dp)
)
}
Text( Text(
text = label, text = label,
fontSize = 12.sp, fontSize = 12.sp,

View File

@ -31,6 +31,7 @@ fun DropdownInput(
onExpandedChange: (Boolean) -> Unit, onExpandedChange: (Boolean) -> Unit,
onSelect: (String) -> Unit, onSelect: (String) -> Unit,
placeholder: String = "Select", // NEW - custom placeholder placeholder: String = "Select", // NEW - custom placeholder
textColor: Color = Color.Black,
modifier: Modifier = Modifier // NEW - allows width control modifier: Modifier = Modifier // NEW - allows width control
) { ) {
Column( Column(
@ -38,17 +39,17 @@ fun DropdownInput(
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
// Optional label // Optional label
if (label != null) { // if (label != null) {
Text( Text(
text = label, text = label?:" ",
fontSize = AppTypography.Body, fontSize = AppTypography.Body,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
color = FarmTextDark color = FarmTextDark
) )
} else { // } else {
// Reserve label space so layout doesnt shift // // Reserve label space so layout doesnt shift
Spacer(modifier = Modifier.height(20.dp)) // ← same height as label text line // Spacer(modifier = Modifier.height(20.dp)) // ← same height as label text line
} // }
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = expanded, expanded = expanded,
@ -82,7 +83,7 @@ fun DropdownInput(
Text( Text(
text = selected.ifEmpty { placeholder }, text = selected.ifEmpty { placeholder },
fontSize = AppTypography.Body, fontSize = AppTypography.Body,
color = if (selected.isEmpty()) Color(0xFF99A1AF) else FarmTextDark color = if (selected.isEmpty()) Color(0xFF99A1AF) else textColor
) )
Icon( Icon(

View File

@ -18,13 +18,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.livingai_lg.ui.models.FiltersState
import com.example.livingai_lg.ui.screens.FilterScreen import com.example.livingai_lg.ui.screens.FilterScreen
@Composable @Composable
fun FilterOverlay( fun FilterOverlay(
visible: Boolean, visible: Boolean,
appliedFilters: FiltersState,
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSubmitClick: () -> Unit = {}, onSubmitClick: (filters: FiltersState) -> Unit = {},
) { ) {
BackHandler(enabled = visible) { onDismiss() } BackHandler(enabled = visible) { onDismiss() }
@ -62,10 +64,11 @@ fun FilterOverlay(
.align(Alignment.CenterEnd) .align(Alignment.CenterEnd)
) { ) {
FilterScreen( FilterScreen(
appliedFilters,
onBackClick = onDismiss, onBackClick = onDismiss,
onCancelClick = onDismiss, onCancelClick = onDismiss,
onSubmitClick = { onSubmitClick = { filters ->
onSubmitClick() onSubmitClick(filters)
onDismiss() onDismiss()
} }
) )

View File

@ -27,6 +27,7 @@ fun RangeFilter(
max: Int, max: Int,
valueFrom: Int, valueFrom: Int,
valueTo: Int, valueTo: Int,
modified: Boolean = false,
onValueChange: (from: Int, to: Int) -> Unit, onValueChange: (from: Int, to: Int) -> Unit,
showSlider: Boolean = true, showSlider: Boolean = true,
valueFormatter: (Int) -> String = { it.toString() } valueFormatter: (Int) -> String = { it.toString() }
@ -75,6 +76,7 @@ fun RangeFilter(
RangePill( RangePill(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
value = fromValue, value = fromValue,
modified = modified,
onValueChange = { newFrom -> onValueChange = { newFrom ->
val safeFrom = newFrom.coerceIn(min, toValue) val safeFrom = newFrom.coerceIn(min, toValue)
fromValue = safeFrom fromValue = safeFrom
@ -88,6 +90,7 @@ fun RangeFilter(
RangePill( RangePill(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
value = toValue, value = toValue,
modified = modified,
onValueChange = { newTo -> onValueChange = { newTo ->
val safeTo = newTo.coerceIn(fromValue, max) val safeTo = newTo.coerceIn(fromValue, max)
toValue = safeTo toValue = safeTo
@ -104,6 +107,7 @@ fun RangeFilter(
private fun RangePill( private fun RangePill(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
value: Int, value: Int,
modified: Boolean = false,
onValueChange: (Int) -> Unit, onValueChange: (Int) -> Unit,
formatter: (Int) -> String formatter: (Int) -> String
) { ) {
@ -119,6 +123,7 @@ private fun RangePill(
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
var modified = modified;
BasicTextField( BasicTextField(
value = text, value = text,
onValueChange = { input -> onValueChange = { input ->
@ -129,7 +134,7 @@ private fun RangePill(
singleLine = true, singleLine = true,
textStyle = TextStyle( textStyle = TextStyle(
fontSize = 14.sp, fontSize = 14.sp,
color = Color(0xFF99A1AF) color = if(modified) Color.Black else Color(0xFF99A1AF)
), ),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowForwardIos
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.material.icons.filled.ArrowForwardIos
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -30,42 +31,47 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.components.DropdownInput import com.example.livingai_lg.ui.components.DropdownInput
import com.example.livingai_lg.ui.components.RangeFilter import com.example.livingai_lg.ui.components.RangeFilter
import com.example.livingai_lg.ui.components.WishlistNameOverlay
import com.example.livingai_lg.ui.models.FiltersState
import com.example.livingai_lg.ui.models.RangeFilterState
import com.example.livingai_lg.ui.models.TextFilter
import com.example.livingai_lg.ui.models.WishlistEntry
import com.example.livingai_lg.ui.models.WishlistStore
import com.example.livingai_lg.ui.models.isDefault
import com.example.livingai_lg.ui.theme.AppTypography import com.example.livingai_lg.ui.theme.AppTypography
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun FilterScreen( fun FilterScreen(
appliedFilters: FiltersState,
wishlistEditMode: Boolean = false,
onSubmitClick: (FiltersState) -> Unit,
onBackClick: () -> Unit = {}, onBackClick: () -> Unit = {},
onSubmitClick: () -> Unit = {},
onCancelClick: () -> Unit = {}, onCancelClick: () -> Unit = {},
) { ) {
var selectedAnimal by remember { mutableStateOf("") } var filters by remember {
var selectedBreed by remember { mutableStateOf("") } mutableStateOf(appliedFilters)
var selectedDistance by remember { mutableStateOf("") } }
var selectedGender by remember { mutableStateOf("") } var showWishlistOverlay by remember { mutableStateOf(false) }
var selectedAnimal =filters.animal
var selectedBreed = filters.breed
var selectedDistance = filters.distance
var selectedGender = filters.gender
var animalExpanded by remember { mutableStateOf(false) } var animalExpanded by remember { mutableStateOf(false) }
var breedExpanded by remember { mutableStateOf(false) } var breedExpanded by remember { mutableStateOf(false) }
var distanceExpanded by remember { mutableStateOf(false) } var distanceExpanded by remember { mutableStateOf(false) }
var genderExpanded by remember { mutableStateOf(false) } var genderExpanded by remember { mutableStateOf(false) }
var priceFrom by remember { mutableStateOf("0") } var price =filters.price
var priceTo by remember { mutableStateOf("90,000") } var age = filters.age
var priceSliderValue by remember { mutableFloatStateOf(0f) } var weight = filters.weight
var milkYield = filters.milkYield
var calving = filters.calving
var ageFrom by remember { mutableStateOf("1") } var selectedPregnancyStatus = filters.pregnancyStatuses
var ageTo by remember { mutableStateOf("20") }
var selectedPregnancyStatus by remember { mutableStateOf(setOf<String>()) }
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 calvingFromExpanded by remember { mutableStateOf(false) }
var calvingToExpanded by remember { mutableStateOf(false) } var calvingToExpanded by remember { mutableStateOf(false) }
@ -73,7 +79,7 @@ fun FilterScreen(
val maxCalving = 10 val maxCalving = 10
val calvingFromOptions = (0..maxCalving).map { it.toString() } val calvingFromOptions = (0..maxCalving).map { it.toString() }
val calvingToOptions = (calvingFrom..maxCalving).map { it.toString() } val calvingToOptions = (calving.min..maxCalving).map { it.toString() }
Column( Column(
modifier = Modifier modifier = Modifier
@ -109,6 +115,21 @@ fun FilterScreen(
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = Color.Black color = Color.Black
) )
if(!wishlistEditMode){
IconButton(
onClick = {
if (!filters.isDefault()) {
showWishlistOverlay = true
}
}
) {
Icon(
imageVector = Icons.Default.FavoriteBorder,
contentDescription = "Add to Wishlist"
)
}
}
} }
} }
} }
@ -128,15 +149,21 @@ fun FilterScreen(
) { ) {
DropdownInput( DropdownInput(
label = "Animal", label = "Animal",
selected = selectedAnimal, selected = if (selectedAnimal.filterSet) selectedAnimal.value else "",
options = listOf("Cow", "Buffalo", "Goat", "Sheep"), options = listOf("Cow", "Buffalo", "Goat", "Sheep"),
expanded = animalExpanded, expanded = animalExpanded,
onExpandedChange = { animalExpanded = it }, onExpandedChange = { animalExpanded = it },
onSelect = { item -> onSelect = { item ->
selectedAnimal = item filters = filters.copy(
animal = TextFilter(
value = item,
filterSet = true
)
)
animalExpanded = false animalExpanded = false
}, },
placeholder = "Select Animal", // <--- half width placeholder = "Select Animal", // <--- half width
textColor = if (selectedAnimal.filterSet) Color.Black else Color.Gray
) )
} }
@ -146,15 +173,21 @@ fun FilterScreen(
) { ) {
DropdownInput( DropdownInput(
label = "Breed", label = "Breed",
selected = selectedBreed, selected = if (selectedBreed.filterSet) selectedBreed.value else "",
options = listOf("Holstein", "Jersey", "Gir", "Sahiwal"), options = listOf("Holstein", "Jersey", "Gir", "Sahiwal"),
expanded = breedExpanded, expanded = breedExpanded,
onExpandedChange = { breedExpanded = it }, onExpandedChange = { breedExpanded = it },
onSelect = { item -> onSelect = { item ->
selectedBreed = item filters = filters.copy(
breed = TextFilter(
value = item,
filterSet = true
)
)
breedExpanded = false breedExpanded = false
}, },
placeholder = "Select Breed", // <--- half width placeholder = "Select Breed", // <--- half width
textColor = if (selectedAnimal.filterSet) Color.Black else Color.Gray
) )
} }
@ -166,14 +199,21 @@ fun FilterScreen(
) { ) {
RangeFilter( RangeFilter(
modifier = Modifier.fillMaxWidth(), // 👈 important modifier = Modifier.fillMaxWidth(), // 👈 important
label = "Price", label = "Price",
min = 0, min = 0,
max = 90_000, max = 90_000,
valueFrom = priceFrom.toInt(), valueFrom = price.min,
valueTo = priceTo.replace(",", "").toInt(), valueTo = price.max,
modified = price.filterSet,
onValueChange = { from, to -> onValueChange = { from, to ->
priceFrom = from.toString() filters = filters.copy(
priceTo = to.toString() price = RangeFilterState(
min = from,
max = to,
filterSet = true
)
)
} }
) )
} }
@ -183,18 +223,26 @@ fun FilterScreen(
) { ) {
RangeFilter( RangeFilter(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(), // 👈 important
label = "Age",
label = "Age (years)",
min = 0, min = 0,
max = 20, max = 20,
valueFrom = ageFrom.toInt(), valueFrom = age.min,
valueTo = ageTo.replace(",", "").toInt(), valueTo = age.max,
showSlider = false, showSlider = false,
modified = age.filterSet,
onValueChange = { from, to -> onValueChange = { from, to ->
ageFrom = from.toString() filters = filters.copy(
ageTo = to.toString() age = RangeFilterState(
min = from,
max = to,
filterSet = true
)
)
} }
) )
} }
@ -206,15 +254,21 @@ fun FilterScreen(
) { ) {
DropdownInput( DropdownInput(
label = "Distance", label = "Distance",
selected = selectedDistance, selected = if (selectedDistance.filterSet) selectedDistance.value else "",
options = listOf("0-5 km", "5-10 km", "10-20 km", "20+ km"), options = listOf("0-5 km", "5-10 km", "10-20 km", "20+ km"),
expanded = distanceExpanded, expanded = distanceExpanded,
onExpandedChange = { distanceExpanded = it }, onExpandedChange = { distanceExpanded = it },
onSelect = { item -> onSelect = { item ->
selectedDistance = item filters = filters.copy(
distance = TextFilter(
value = item,
filterSet = true
)
)
distanceExpanded = false distanceExpanded = false
}, },
placeholder = "Choose Distance", // <--- half width placeholder = "Choose Distance", // <--- half width
textColor = if (selectedAnimal.filterSet) Color.Black else Color.Gray
) )
} }
@ -224,15 +278,21 @@ fun FilterScreen(
) { ) {
DropdownInput( DropdownInput(
label = "Gender", label = "Gender",
selected = selectedGender, selected = if (selectedGender.filterSet) selectedGender.value else "",
options = listOf("Male", "Female"), options = listOf("Male", "Female"),
expanded = genderExpanded, expanded = genderExpanded,
onExpandedChange = { genderExpanded = it }, onExpandedChange = { genderExpanded = it },
onSelect = { item -> onSelect = { item ->
selectedGender = item filters = filters.copy(
gender = TextFilter(
value = item,
filterSet = true
)
)
genderExpanded = false genderExpanded = false
}, },
placeholder = "Choose Gender", // <--- half width placeholder = "Choose Gender", // <--- half width
textColor = if (selectedAnimal.filterSet) Color.Black else Color.Gray
) )
} }
@ -259,11 +319,13 @@ fun FilterScreen(
label = status, label = status,
isSelected = selectedPregnancyStatus.contains(status), isSelected = selectedPregnancyStatus.contains(status),
onClick = { onClick = {
selectedPregnancyStatus = if (selectedPregnancyStatus.contains(status)) { filters = filters.copy(
selectedPregnancyStatus - status pregnancyStatuses =
} else { if (filters.pregnancyStatuses.contains(status))
selectedPregnancyStatus + status filters.pregnancyStatuses - status
} else
filters.pregnancyStatuses + status
)
} }
) )
} }
@ -276,14 +338,21 @@ fun FilterScreen(
) { ) {
RangeFilter( RangeFilter(
modifier = Modifier.fillMaxWidth(), // 👈 important modifier = Modifier.fillMaxWidth(), // 👈 important
label = "Weight", label = "Weight",
min = 0, min = 0,
max = 9000, max = 9000,
valueFrom = weightFrom.toInt(), valueFrom = weight.min,
valueTo = weightTo.replace(",", "").toInt(), valueTo = weight.max,
modified = weight.filterSet,
onValueChange = { from, to -> onValueChange = { from, to ->
weightFrom = from.toString() filters = filters.copy(
weightTo = to.toString() weight = RangeFilterState(
min = from,
max = to,
filterSet = true
)
)
} }
) )
} }
@ -292,16 +361,22 @@ fun FilterScreen(
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
RangeFilter( RangeFilter(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(), // 👈 important
label = "Milk Yield", label = "Milk Yield",
min = 0, min = 0,
max = 900, max = 900,
valueFrom = milkYieldFrom.toInt(), valueFrom = milkYield.min,
valueTo = milkYieldTo.replace(",", "").toInt(), valueTo = milkYield.max,
showSlider = true, modified = milkYield.filterSet,
onValueChange = { from, to -> onValueChange = { from, to ->
milkYieldFrom = from.toString() filters = filters.copy(
milkYieldTo = to.toString() milkYield = RangeFilterState(
min = from,
max = to,
filterSet = true
)
)
} }
) )
} }
@ -321,18 +396,21 @@ fun FilterScreen(
// FROM // FROM
DropdownInput( DropdownInput(
label = "Calving Number", label = "Calving Number",
selected = calvingFrom.toString(), selected = calving.min.toString(),
options = calvingFromOptions, options = calvingFromOptions,
expanded = calvingFromExpanded, expanded = calvingFromExpanded,
onExpandedChange = { calvingFromExpanded = it }, onExpandedChange = { calvingFromExpanded = it },
onSelect = { value -> onSelect = { value ->
val newFrom = value.toInt() val newFrom = value.toInt()
calvingFrom = newFrom val newTo = maxOf(calving.max, newFrom)
// 👇 enforce invariant filters = filters.copy(
if (calvingTo < newFrom) { calving = RangeFilterState(
calvingTo = newFrom min = newFrom,
} max = newTo,
filterSet = true
)
)
calvingFromExpanded = false calvingFromExpanded = false
}, },
@ -348,12 +426,21 @@ fun FilterScreen(
// TO // TO
DropdownInput( DropdownInput(
selected = calvingTo.toString(), selected = calving.max.toString(),
options = calvingToOptions, // 👈 constrained options options = calvingToOptions, // 👈 constrained options
expanded = calvingToExpanded, expanded = calvingToExpanded,
onExpandedChange = { calvingToExpanded = it }, onExpandedChange = { calvingToExpanded = it },
onSelect = { value -> onSelect = { value ->
calvingTo = value.toInt() val newTo = value.toInt()
val newFrom = minOf(calving.min, newTo)
filters = filters.copy(
calving = RangeFilterState(
min = newFrom,
max = newTo,
filterSet = true
)
)
calvingToExpanded = false calvingToExpanded = false
}, },
placeholder = "To", placeholder = "To",
@ -372,7 +459,7 @@ fun FilterScreen(
) { ) {
// Submit Button // Submit Button
Button( Button(
onClick = onSubmitClick, onClick = { onSubmitClick(filters) },
modifier = Modifier modifier = Modifier
.width(173.dp) .width(173.dp)
.height(50.dp), .height(50.dp),
@ -414,6 +501,20 @@ fun FilterScreen(
} }
} }
} }
if (showWishlistOverlay) {
WishlistNameOverlay(
onDismiss = { showWishlistOverlay = false },
onSave = { name ->
WishlistStore.add(
WishlistEntry(
name = name,
filters = filters
)
)
showWishlistOverlay = false
}
)
}
} }
@Composable @Composable
@ -438,7 +539,8 @@ fun PregnancyStatusChip(
.clickable( .clickable(
indication = LocalIndication.current, indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
onClick = onClick) onClick = onClick
)
.padding(horizontal = 12.dp), .padding(horizontal = 12.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {

View File

@ -27,6 +27,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.layout.BottomNavScaffold
import com.example.livingai_lg.ui.models.chatBottomNavItems
data class CallRecord( data class CallRecord(
val id: String, val id: String,
@ -101,34 +103,41 @@ fun CallsScreen(
.fillMaxSize() .fillMaxSize()
.background(Color(0xFFFCFBF8)) .background(Color(0xFFFCFBF8))
) { ) {
Column( BottomNavScaffold(
modifier = Modifier.fillMaxSize() items = chatBottomNavItems,
) { currentItem = "Calls",
CallsHeader( onBottomNavItemClick = onTabClick,
onBackClick = onBackClick, ) { paddingValues ->
onCallClick = onCallClick,
onMenuClick = onMenuClick
)
LazyColumn( Column(
modifier = Modifier modifier = Modifier.fillMaxSize().padding(paddingValues)
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
items(callHistory) { call -> CallsHeader(
CallHistoryItem( onBackClick = onBackClick,
call = call, onCallClick = onCallClick,
onClick = { onCallItemClick(call.id) } onMenuClick = onMenuClick
) )
}
}
ContactsBottomNav( LazyColumn(
currentTab = ContactsTab.CALLS, modifier = Modifier
onTabClick = onTabClick .fillMaxWidth()
) .weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(callHistory) { call ->
CallHistoryItem(
call = call,
onClick = { onCallItemClick(call.id) }
)
}
}
// ContactsBottomNav(
// currentTab = ContactsTab.CALLS,
// onTabClick = onTabClick
// )
}
} }
} }
} }

View File

@ -27,6 +27,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.farmmarketplace.ui.screens.ContactsBottomNav import com.example.farmmarketplace.ui.screens.ContactsBottomNav
import com.example.farmmarketplace.ui.screens.ContactsTab import com.example.farmmarketplace.ui.screens.ContactsTab
import com.example.livingai_lg.ui.layout.BottomNavScaffold
import com.example.livingai_lg.ui.models.chatBottomNavItems
data class ChatPreview( data class ChatPreview(
val id: String, val id: String,
@ -108,34 +110,40 @@ fun ChatsScreen(
.fillMaxSize() .fillMaxSize()
.background(Color(0xFFFCFBF8)) .background(Color(0xFFFCFBF8))
) { ) {
Column( BottomNavScaffold(
modifier = Modifier.fillMaxSize() items = chatBottomNavItems,
) { currentItem = "Chats",
ChatsHeader( onBottomNavItemClick = onTabClick,
onBackClick = onBackClick, ) { paddingValues ->
onNewChatClick = onNewChatClick, Column(
onMenuClick = onMenuClick modifier = Modifier.fillMaxSize().padding(paddingValues)
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
items(chatList) { chat -> ChatsHeader(
ChatListItem( onBackClick = onBackClick,
chat = chat, onNewChatClick = onNewChatClick,
onClick = { onChatItemClick(chat.id) } onMenuClick = onMenuClick
) )
}
}
ContactsBottomNav( LazyColumn(
currentTab = ContactsTab.CHATS, modifier = Modifier
onTabClick = onTabClick .fillMaxWidth()
) .weight(1f)
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(chatList) { chat ->
ChatListItem(
chat = chat,
onClick = { onChatItemClick(chat.id) }
)
}
}
// ContactsBottomNav(
// currentTab = ContactsTab.CHATS,
// onTabClick = onTabClick
// )
}
} }
} }
} }

View File

@ -34,6 +34,9 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.livingai_lg.ui.layout.BottomNavScaffold
import com.example.livingai_lg.ui.models.chatBottomNavItems
import com.example.livingai_lg.ui.models.mainBottomNavItems
import com.example.livingai_lg.ui.navigation.AppScreen import com.example.livingai_lg.ui.navigation.AppScreen
data class Contact( data class Contact(
@ -122,34 +125,41 @@ fun ContactsScreen(
.fillMaxSize() .fillMaxSize()
.background(Color(0xFFFCFBF8)) .background(Color(0xFFFCFBF8))
) { ) {
Column( BottomNavScaffold(
modifier = Modifier.fillMaxSize() items = chatBottomNavItems,
) { currentItem = "Contacts",
ContactsHeader( onBottomNavItemClick = onTabClick,
onBackClick = onBackClick, ) { paddingValues ->
onSearchClick = onSearchClick,
onMenuClick = onMenuClick
)
LazyColumn( Column(
modifier = Modifier modifier = Modifier.fillMaxSize().padding(paddingValues)
.fillMaxWidth()
.weight(1f)
) { ) {
items(contacts) { contact -> ContactsHeader(
ContactItem( onBackClick = onBackClick,
contact = contact, onSearchClick = onSearchClick,
onContactClick = { onContactClick(contact.id) }, onMenuClick = onMenuClick
onCallClick = { onCallClick(contact.id) }, )
onMessageClick = { onMessageClick(contact.id) }
)
}
}
ContactsBottomNav( LazyColumn(
currentTab = ContactsTab.CONTACTS, modifier = Modifier
onTabClick = onTabClick .fillMaxWidth()
) .weight(1f)
) {
items(contacts) { contact ->
ContactItem(
contact = contact,
onContactClick = { onContactClick(contact.id) },
onCallClick = { onCallClick(contact.id) },
onMessageClick = { onMessageClick(contact.id) }
)
}
}
// ContactsBottomNav(
// currentTab = ContactsTab.CONTACTS,
// onTabClick = onTabClick
// )
}
} }
} }
} }