bookingCreate: improve input phone number ui
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 = "",
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|||||||
Reference in New Issue
Block a user