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.lottie.compose)
implementation(libs.calendar.compose)
implementation(libs.libphonenumber)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.auth.ktx)
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.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -47,7 +46,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.kizitonwose.calendar.compose.HorizontalCalendar
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.CalendarMonth
import com.kizitonwose.calendar.core.DayPosition
@@ -77,7 +75,12 @@ fun BookingCreateScreen(
val checkInNow = remember { mutableStateOf(true) }
val sourceMenuExpanded = remember { mutableStateOf(false) }
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 phoneCountryMenuExpanded = remember { mutableStateOf(false) }
val phoneCountries = remember { phoneCountryOptions() }
val phoneCountrySearch = remember { mutableStateOf("") }
LaunchedEffect(propertyId) {
viewModel.reset()
@@ -171,12 +174,75 @@ fun BookingCreateScreen(
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = state.phoneE164,
onValueChange = viewModel::onPhoneChange,
label = { Text("Guest Phone E164 (optional)") },
modifier = Modifier.fillMaxWidth()
)
Text(text = "Guest Phone (optional)", style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.height(6.dp))
val selectedCountry = findPhoneCountryOption(state.phoneCountryCode)
Row(
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))
ExposedDropdownMenuBox(
expanded = sourceMenuExpanded.value,
@@ -208,12 +274,37 @@ fun BookingCreateScreen(
}
}
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = state.transportMode,
onValueChange = viewModel::onTransportModeChange,
label = { Text("Transport Mode (optional)") },
modifier = Modifier.fillMaxWidth()
)
ExposedDropdownMenuBox(
expanded = transportMenuExpanded.value,
onExpandedChange = { transportMenuExpanded.value = !transportMenuExpanded.value }
) {
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))
OutlinedTextField(
value = state.adultCount,

View File

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

View File

@@ -28,8 +28,18 @@ class BookingCreateViewModel : ViewModel() {
_state.update { it.copy(expectedCheckOutAt = value, error = null) }
}
fun onPhoneChange(value: String) {
_state.update { it.copy(phoneE164 = value, error = null) }
fun onPhoneCountryChange(value: String) {
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) {
@@ -66,7 +76,13 @@ class BookingCreateViewModel : ViewModel() {
_state.update { it.copy(isLoading = true, error = null) }
try {
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 guestResponse = api.searchGuests(propertyId, phone = phone)
if (guestResponse.isSuccessful) {

View File

@@ -20,6 +20,7 @@ vectordrawable = "1.2.0"
coilCompose = "2.7.0"
lottieCompose = "6.7.1"
calendarCompose = "2.6.0"
libphonenumber = "8.13.34"
[libraries]
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" }
lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottieCompose" }
calendar-compose = { group = "com.kizitonwose.calendar", name = "compose", version.ref = "calendarCompose" }
libphonenumber = { group = "com.googlecode.libphonenumber", name = "libphonenumber", version.ref = "libphonenumber" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }