bookingCreate: improve input phone number ui

This commit is contained in:
androidlover5842
2026-01-29 09:01:29 +05:30
parent 8bd2c2eeae
commit 3a7667c609
5 changed files with 130 additions and 19 deletions

View File

@@ -58,6 +58,7 @@ dependencies {
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.lottie.compose) implementation(libs.lottie.compose)
implementation(libs.calendar.compose) implementation(libs.calendar.compose)
implementation(libs.libphonenumber)
implementation(platform(libs.firebase.bom)) implementation(platform(libs.firebase.bom))
implementation(libs.firebase.auth.ktx) implementation(libs.firebase.auth.ktx)
implementation(libs.kotlinx.coroutines.play.services) implementation(libs.kotlinx.coroutines.play.services)

View File

@@ -3,7 +3,6 @@ package com.android.trisolarispms.ui.booking
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -47,7 +46,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.kizitonwose.calendar.compose.HorizontalCalendar import com.kizitonwose.calendar.compose.HorizontalCalendar
import com.kizitonwose.calendar.compose.rememberCalendarState import com.kizitonwose.calendar.compose.rememberCalendarState
import com.android.trisolarispms.data.api.model.GuestDto
import com.kizitonwose.calendar.core.CalendarDay import com.kizitonwose.calendar.core.CalendarDay
import com.kizitonwose.calendar.core.CalendarMonth import com.kizitonwose.calendar.core.CalendarMonth
import com.kizitonwose.calendar.core.DayPosition import com.kizitonwose.calendar.core.DayPosition
@@ -77,7 +75,12 @@ fun BookingCreateScreen(
val checkInNow = remember { mutableStateOf(true) } val checkInNow = remember { mutableStateOf(true) }
val sourceMenuExpanded = remember { mutableStateOf(false) } val sourceMenuExpanded = remember { mutableStateOf(false) }
val sourceOptions = listOf("WALKIN", "OTA", "AGENT") val sourceOptions = listOf("WALKIN", "OTA", "AGENT")
val transportMenuExpanded = remember { mutableStateOf(false) }
val transportOptions = listOf("CAR", "BIKE", "TRAIN", "PLANE", "BUS", "FOOT", "CYCLE", "OTHER")
val displayFormatter = remember { DateTimeFormatter.ofPattern("dd-MM-yy HH:mm") } val displayFormatter = remember { DateTimeFormatter.ofPattern("dd-MM-yy HH:mm") }
val phoneCountryMenuExpanded = remember { mutableStateOf(false) }
val phoneCountries = remember { phoneCountryOptions() }
val phoneCountrySearch = remember { mutableStateOf("") }
LaunchedEffect(propertyId) { LaunchedEffect(propertyId) {
viewModel.reset() viewModel.reset()
@@ -171,12 +174,75 @@ fun BookingCreateScreen(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField( Text(text = "Guest Phone (optional)", style = MaterialTheme.typography.titleSmall)
value = state.phoneE164, Spacer(modifier = Modifier.height(6.dp))
onValueChange = viewModel::onPhoneChange, val selectedCountry = findPhoneCountryOption(state.phoneCountryCode)
label = { Text("Guest Phone E164 (optional)") }, Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth(),
) horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.Top
) {
ExposedDropdownMenuBox(
expanded = phoneCountryMenuExpanded.value,
onExpandedChange = { phoneCountryMenuExpanded.value = !phoneCountryMenuExpanded.value },
modifier = Modifier.weight(0.3f)
) {
OutlinedTextField(
value = "${selectedCountry.code} +${selectedCountry.dialCode}",
onValueChange = {},
readOnly = true,
label = { Text("Country") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = phoneCountryMenuExpanded.value)
},
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
)
ExposedDropdownMenu(
expanded = phoneCountryMenuExpanded.value,
onDismissRequest = { phoneCountryMenuExpanded.value = false }
) {
OutlinedTextField(
value = phoneCountrySearch.value,
onValueChange = { phoneCountrySearch.value = it },
label = { Text("Search") },
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
val filteredCountries = phoneCountries.filter { option ->
val query = phoneCountrySearch.value.trim()
if (query.isBlank()) {
true
} else {
option.name.contains(query, ignoreCase = true) ||
option.code.contains(query, ignoreCase = true) ||
option.dialCode.contains(query)
}
}
filteredCountries.forEach { option ->
DropdownMenuItem(
text = { Text("${option.name} (+${option.dialCode})") },
onClick = {
phoneCountryMenuExpanded.value = false
phoneCountrySearch.value = ""
viewModel.onPhoneCountryChange(option.code)
}
)
}
}
}
OutlinedTextField(
value = state.phoneNationalNumber,
onValueChange = viewModel::onPhoneNationalNumberChange,
label = { Text("Number") },
prefix = { Text("+${selectedCountry.dialCode}") },
supportingText = { Text("Max ${selectedCountry.maxLength} digits") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
modifier = Modifier.weight(0.7f)
)
}
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = sourceMenuExpanded.value, expanded = sourceMenuExpanded.value,
@@ -208,12 +274,37 @@ fun BookingCreateScreen(
} }
} }
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField( ExposedDropdownMenuBox(
value = state.transportMode, expanded = transportMenuExpanded.value,
onValueChange = viewModel::onTransportModeChange, onExpandedChange = { transportMenuExpanded.value = !transportMenuExpanded.value }
label = { Text("Transport Mode (optional)") }, ) {
modifier = Modifier.fillMaxWidth() OutlinedTextField(
) value = state.transportMode,
onValueChange = {},
readOnly = true,
label = { Text("Transport Mode") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = transportMenuExpanded.value)
},
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
)
ExposedDropdownMenu(
expanded = transportMenuExpanded.value,
onDismissRequest = { transportMenuExpanded.value = false }
) {
transportOptions.forEach { option ->
DropdownMenuItem(
text = { Text(option) },
onClick = {
transportMenuExpanded.value = false
viewModel.onTransportModeChange(option)
}
)
}
}
}
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField( OutlinedTextField(
value = state.adultCount, value = state.adultCount,

View File

@@ -1,11 +1,12 @@
package com.android.trisolarispms.ui.booking package com.android.trisolarispms.ui.booking
data class BookingCreateState( data class BookingCreateState(
val phoneE164: String = "", val phoneCountryCode: String = "IN",
val phoneNationalNumber: String = "",
val expectedCheckInAt: String = "", val expectedCheckInAt: String = "",
val expectedCheckOutAt: String = "", val expectedCheckOutAt: String = "",
val source: String = "WALKIN", val source: String = "WALKIN",
val transportMode: String = "", val transportMode: String = "CAR",
val adultCount: String = "", val adultCount: String = "",
val totalGuestCount: String = "", val totalGuestCount: String = "",
val notes: String = "", val notes: String = "",

View File

@@ -28,8 +28,18 @@ class BookingCreateViewModel : ViewModel() {
_state.update { it.copy(expectedCheckOutAt = value, error = null) } _state.update { it.copy(expectedCheckOutAt = value, error = null) }
} }
fun onPhoneChange(value: String) { fun onPhoneCountryChange(value: String) {
_state.update { it.copy(phoneE164 = value, error = null) } val option = findPhoneCountryOption(value)
_state.update { current ->
val trimmed = current.phoneNationalNumber.filter { it.isDigit() }.take(option.maxLength)
current.copy(phoneCountryCode = value, phoneNationalNumber = trimmed, error = null)
}
}
fun onPhoneNationalNumberChange(value: String) {
val option = findPhoneCountryOption(_state.value.phoneCountryCode)
val trimmed = value.filter { it.isDigit() }.take(option.maxLength)
_state.update { it.copy(phoneNationalNumber = trimmed, error = null) }
} }
fun onSourceChange(value: String) { fun onSourceChange(value: String) {
@@ -66,7 +76,13 @@ class BookingCreateViewModel : ViewModel() {
_state.update { it.copy(isLoading = true, error = null) } _state.update { it.copy(isLoading = true, error = null) }
try { try {
val api = ApiClient.create() val api = ApiClient.create()
val phone = current.phoneE164.trim().ifBlank { null } val phoneCountry = findPhoneCountryOption(current.phoneCountryCode)
val phoneDigits = current.phoneNationalNumber.trim()
val phone = if (phoneDigits.isNotBlank()) {
"+${phoneCountry.dialCode}$phoneDigits"
} else {
null
}
val existingGuest = if (!phone.isNullOrBlank()) { val existingGuest = if (!phone.isNullOrBlank()) {
val guestResponse = api.searchGuests(propertyId, phone = phone) val guestResponse = api.searchGuests(propertyId, phone = phone)
if (guestResponse.isSuccessful) { if (guestResponse.isSuccessful) {

View File

@@ -20,6 +20,7 @@ vectordrawable = "1.2.0"
coilCompose = "2.7.0" coilCompose = "2.7.0"
lottieCompose = "6.7.1" lottieCompose = "6.7.1"
calendarCompose = "2.6.0" calendarCompose = "2.6.0"
libphonenumber = "8.13.34"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -50,6 +51,7 @@ androidx-vectordrawable-animated = { group = "androidx.vectordrawable", name = "
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coilCompose" } coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coilCompose" }
lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottieCompose" } lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottieCompose" }
calendar-compose = { group = "com.kizitonwose.calendar", name = "compose", version.ref = "calendarCompose" } calendar-compose = { group = "com.kizitonwose.calendar", name = "compose", version.ref = "calendarCompose" }
libphonenumber = { group = "com.googlecode.libphonenumber", name = "libphonenumber", version.ref = "libphonenumber" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }