extend checkout time improve ui

This commit is contained in:
androidlover5842
2026-02-04 13:41:06 +05:30
parent b0c28d0aa4
commit eab5517f9b
7 changed files with 249 additions and 193 deletions

View File

@@ -1,35 +0,0 @@
kotlin version: 2.3.0
error message: Incremental compilation failed: /home/androidlover5842/AndroidStudioProjects/TrisolarisPMS/app/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin (No such file or directory)
java.io.FileNotFoundException: /home/androidlover5842/AndroidStudioProjects/TrisolarisPMS/app/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(Unknown Source)
at java.base/java.io.FileInputStream.<init>(Unknown Source)
at org.jetbrains.kotlin.incremental.storage.ExternalizersKt.loadFromFile(externalizers.kt:184)
at org.jetbrains.kotlin.incremental.snapshots.LazyClasspathSnapshot.getSavedShrunkClasspathAgainstPreviousLookups(LazyClasspathSnapshot.kt:86)
at org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinkerKt.shrinkAndSaveClasspathSnapshot(ClasspathSnapshotShrinker.kt:267)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.performWorkAfterCompilation(IncrementalJvmCompilerRunner.kt:76)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.performWorkAfterCompilation(IncrementalJvmCompilerRunner.kt:23)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:420)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally$lambda$0$compile(IncrementalCompilerRunner.kt:249)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:267)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:119)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:684)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:94)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1810)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

View File

@@ -1,5 +1,9 @@
# TrisolarisPMS API Usage
## API Docs Path
- `/home/androidlover5842/IdeaProjects/TrisolarisServer/docs`
## 1) Booking
### Create booking

View File

@@ -1,7 +1,5 @@
package com.android.trisolarispms.ui.booking
import android.app.TimePickerDialog
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -15,7 +13,6 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExposedDropdownMenuBox
@@ -36,7 +33,6 @@ 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.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -192,7 +188,7 @@ fun BookingCreateScreen(
}
if (state.billingMode == BookingBillingMode.CUSTOM_WINDOW) {
Spacer(modifier = Modifier.height(12.dp))
TimePickerTextField(
BookingTimePickerTextField(
value = state.billingCheckoutTime,
label = { Text("Billing check-out (HH:mm)") },
onTimeSelected = viewModel::onBillingCheckoutTimeChange,
@@ -511,55 +507,3 @@ fun BookingCreateScreen(
)
}
}
@Composable
private fun TimePickerTextField(
value: String,
label: @Composable () -> Unit,
onTimeSelected: (String) -> Unit,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val parsed = runCatching {
val parts = value.split(":")
val hour = parts.getOrNull(0)?.toIntOrNull() ?: 12
val minute = parts.getOrNull(1)?.toIntOrNull() ?: 0
(hour.coerceIn(0, 23)) to (minute.coerceIn(0, 59))
}.getOrDefault(12 to 0)
OutlinedTextField(
value = value,
onValueChange = {},
readOnly = true,
label = label,
trailingIcon = {
IconButton(
onClick = {
TimePickerDialog(
context,
{ _, hourOfDay, minute ->
onTimeSelected("%02d:%02d".format(hourOfDay, minute))
},
parsed.first,
parsed.second,
true
).show()
}
) {
Icon(Icons.Default.Schedule, contentDescription = "Pick time")
}
},
modifier = modifier
.clickable {
TimePickerDialog(
context,
{ _, hourOfDay, minute ->
onTimeSelected("%02d:%02d".format(hourOfDay, minute))
},
parsed.first,
parsed.second,
true
).show()
}
)
}

View File

@@ -4,12 +4,15 @@ import androidx.compose.foundation.layout.Column
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.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -37,64 +40,30 @@ internal fun BookingDateTimePickerDialog(
onDismiss: () -> Unit,
onConfirm: (LocalDate, String) -> Unit
) {
val today = remember { LocalDate.now() }
val currentMonth = remember { YearMonth.from(today) }
val startMonth = remember { currentMonth }
val endMonth = remember { currentMonth.plusMonths(24) }
val daysOfWeek = remember { daysOfWeek() }
val calendarState = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstVisibleMonth = currentMonth,
firstDayOfWeek = daysOfWeek.first()
val (selectedDate, timeValue, daysOfWeek, calendarState) = rememberBookingDateTimePickerState(
initialDate = initialDate,
initialTime = initialTime,
minDate = minDate
)
val selectedDate = remember { mutableStateOf(initialDate ?: today) }
val timeValue = remember { mutableStateOf(initialTime) }
val dateFormatter = remember { DateTimeFormatter.ISO_LOCAL_DATE }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(title) },
text = {
Column {
CalendarDaysOfWeekHeader(daysOfWeek)
HorizontalCalendar(
state = calendarState,
dayContent = { day ->
val selectable = day.position == DayPosition.MonthDate && !day.date.isBefore(minDate)
CalendarDayCell(
day = day,
isSelectedStart = selectedDate.value == day.date,
isSelectedEnd = false,
isInRange = false,
hasRate = false,
isSelectable = selectable,
onClick = { selectedDate.value = day.date }
)
},
monthHeader = { month ->
CalendarMonthHeader(month)
}
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Selected: ${selectedDate.value.format(dateFormatter)}",
style = MaterialTheme.typography.bodySmall
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = timeValue.value,
onValueChange = { timeValue.value = it },
label = { Text("Time (HH:MM)") },
modifier = Modifier.fillMaxWidth()
)
}
BookingDateTimePickerContent(
selectedDate = selectedDate,
timeValue = timeValue,
minDate = minDate,
daysOfWeek = daysOfWeek,
calendarState = calendarState
)
},
confirmButton = {
TextButton(
onClick = {
val time = timeValue.value.ifBlank { initialTime }
onConfirm(selectedDate.value, time)
val date = if (selectedDate.value.isBefore(minDate)) minDate else selectedDate.value
onConfirm(date, time)
}
) {
Text("OK")
@@ -108,6 +77,145 @@ internal fun BookingDateTimePickerDialog(
)
}
@Composable
internal fun BookingDateTimePickerInline(
title: String,
initialDate: LocalDate?,
initialTime: String,
minDate: LocalDate,
onValueChange: (LocalDate, String) -> Unit
) {
val (selectedDate, timeValue, daysOfWeek, calendarState) = rememberBookingDateTimePickerState(
initialDate = initialDate,
initialTime = initialTime,
minDate = minDate
)
Card(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.large
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 10.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
BookingDateTimePickerContent(
selectedDate = selectedDate,
timeValue = timeValue,
minDate = minDate,
daysOfWeek = daysOfWeek,
calendarState = calendarState,
onDateSelected = { date ->
val safeDate = if (date.isBefore(minDate)) minDate else date
onValueChange(safeDate, timeValue.value)
},
onTimeSelected = { time ->
val safeDate = if (selectedDate.value.isBefore(minDate)) minDate else selectedDate.value
onValueChange(safeDate, time)
}
)
}
}
}
@Composable
private fun BookingDateTimePickerContent(
selectedDate: MutableState<LocalDate>,
timeValue: MutableState<String>,
minDate: LocalDate,
daysOfWeek: List<java.time.DayOfWeek>,
calendarState: com.kizitonwose.calendar.compose.CalendarState,
onDateSelected: (LocalDate) -> Unit = {},
onTimeSelected: (String) -> Unit = {}
) {
val dateFormatter = remember { DateTimeFormatter.ISO_LOCAL_DATE }
Column {
CalendarDaysOfWeekHeader(daysOfWeek)
HorizontalCalendar(
state = calendarState,
dayContent = { day ->
val selectable = day.position == DayPosition.MonthDate && !day.date.isBefore(minDate)
CalendarDayCell(
day = day,
isSelectedStart = selectedDate.value == day.date,
isSelectedEnd = false,
isInRange = false,
hasRate = false,
isSelectable = selectable,
onClick = {
selectedDate.value = day.date
onDateSelected(day.date)
}
)
},
monthHeader = { month ->
CalendarMonthHeader(month)
}
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Selected: ${selectedDate.value.format(dateFormatter)}",
style = MaterialTheme.typography.bodySmall
)
Spacer(modifier = Modifier.height(8.dp))
BookingTimePickerTextField(
value = timeValue.value,
onTimeSelected = {
timeValue.value = it
onTimeSelected(it)
},
label = { Text("Time (HH:MM)") },
modifier = Modifier.fillMaxWidth()
)
}
}
@Composable
private fun rememberBookingDateTimePickerState(
initialDate: LocalDate?,
initialTime: String,
minDate: LocalDate
): PickerUiState {
val now = remember { LocalDate.now() }
val initialSelectedDate = remember(initialDate, minDate, now) {
val seed = initialDate ?: now
if (seed.isBefore(minDate)) minDate else seed
}
val selectedDate = remember(initialSelectedDate) { mutableStateOf(initialSelectedDate) }
val timeValue = remember(initialTime) { mutableStateOf(initialTime) }
val startMonth = remember(minDate) { YearMonth.from(minDate) }
val firstVisibleMonth = remember(initialSelectedDate) { YearMonth.from(initialSelectedDate) }
val endMonth = remember(startMonth) { startMonth.plusMonths(24) }
val daysOfWeek = remember { daysOfWeek() }
val calendarState = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstVisibleMonth = firstVisibleMonth,
firstDayOfWeek = daysOfWeek.first()
)
return PickerUiState(
selectedDate = selectedDate,
timeValue = timeValue,
daysOfWeek = daysOfWeek,
calendarState = calendarState
)
}
private data class PickerUiState(
val selectedDate: MutableState<LocalDate>,
val timeValue: MutableState<String>,
val daysOfWeek: List<java.time.DayOfWeek>,
val calendarState: com.kizitonwose.calendar.compose.CalendarState
)
internal fun formatBookingIso(date: LocalDate, time: String): String {
val parts = time.split(":")
val hour = parts.getOrNull(0)?.toIntOrNull() ?: 0

View File

@@ -1,15 +1,9 @@
package com.android.trisolarispms.ui.booking
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
@@ -40,8 +34,6 @@ fun BookingExpectedDatesScreen(
onBack: () -> Unit,
onDone: () -> Unit
) {
val showCheckInPicker = remember { mutableStateOf(false) }
val showCheckOutPicker = remember { mutableStateOf(false) }
val checkInDate = remember { mutableStateOf<LocalDate?>(null) }
val checkOutDate = remember { mutableStateOf<LocalDate?>(null) }
val checkInTime = remember { mutableStateOf("12:00") }
@@ -50,6 +42,7 @@ fun BookingExpectedDatesScreen(
val error = remember { mutableStateOf<String?>(null) }
val displayFormatter = remember { DateTimeFormatter.ofPattern("dd-MM-yy HH:mm") }
val displayZone = remember { ZoneId.of("Asia/Kolkata") }
val today = LocalDate.now(displayZone)
val editableCheckIn = status?.uppercase() == "OPEN"
val scope = rememberCoroutineScope()
@@ -127,13 +120,22 @@ fun BookingExpectedDatesScreen(
onValueChange = {},
readOnly = true,
label = { Text("Expected Check-in") },
trailingIcon = {
IconButton(onClick = { showCheckInPicker.value = true }) {
Icon(Icons.Default.CalendarMonth, contentDescription = "Pick date")
}
},
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
BookingDateTimePickerInline(
title = "Select check-in",
initialDate = checkInDate.value,
initialTime = checkInTime.value,
minDate = today,
onValueChange = { date, time ->
checkInDate.value = date
checkInTime.value = time
if (checkOutDate.value?.isBefore(date) == true) {
checkOutDate.value = date
}
}
)
Spacer(modifier = Modifier.height(12.dp))
}
OutlinedTextField(
@@ -147,13 +149,20 @@ fun BookingExpectedDatesScreen(
onValueChange = {},
readOnly = true,
label = { Text("Expected Check-out") },
trailingIcon = {
IconButton(onClick = { showCheckOutPicker.value = true }) {
Icon(Icons.Default.CalendarMonth, contentDescription = "Pick date")
}
},
modifier = Modifier.fillMaxWidth()
)
val checkOutMinDate = maxOf(checkInDate.value ?: today, today)
Spacer(modifier = Modifier.height(8.dp))
BookingDateTimePickerInline(
title = "Select check-out",
initialDate = checkOutDate.value,
initialTime = checkOutTime.value,
minDate = checkOutMinDate,
onValueChange = { date, time ->
checkOutDate.value = date
checkOutTime.value = time
}
)
if (isLoading.value) {
Spacer(modifier = Modifier.height(12.dp))
CircularProgressIndicator()
@@ -164,34 +173,4 @@ fun BookingExpectedDatesScreen(
}
}
}
if (showCheckInPicker.value && editableCheckIn) {
BookingDateTimePickerDialog(
title = "Select check-in",
initialDate = checkInDate.value,
initialTime = checkInTime.value,
minDate = LocalDate.now(),
onDismiss = { showCheckInPicker.value = false },
onConfirm = { date, time ->
checkInDate.value = date
checkInTime.value = time
showCheckInPicker.value = false
}
)
}
if (showCheckOutPicker.value) {
BookingDateTimePickerDialog(
title = "Select check-out",
initialDate = checkOutDate.value,
initialTime = checkOutTime.value,
minDate = checkInDate.value ?: LocalDate.now(),
onDismiss = { showCheckOutPicker.value = false },
onConfirm = { date, time ->
checkOutDate.value = date
checkOutTime.value = time
showCheckOutPicker.value = false
}
)
}
}

View File

@@ -0,0 +1,53 @@
package com.android.trisolarispms.ui.booking
import android.app.TimePickerDialog
import androidx.compose.foundation.clickable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@Composable
internal fun BookingTimePickerTextField(
value: String,
label: @Composable () -> Unit,
onTimeSelected: (String) -> Unit,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val parsed = runCatching {
val parts = value.split(":")
val hour = parts.getOrNull(0)?.toIntOrNull() ?: 12
val minute = parts.getOrNull(1)?.toIntOrNull() ?: 0
(hour.coerceIn(0, 23)) to (minute.coerceIn(0, 59))
}.getOrDefault(12 to 0)
fun openDialog() {
TimePickerDialog(
context,
{ _, hourOfDay, minute ->
onTimeSelected("%02d:%02d".format(hourOfDay, minute))
},
parsed.first,
parsed.second,
true
).show()
}
OutlinedTextField(
value = value,
onValueChange = {},
readOnly = true,
label = label,
trailingIcon = {
IconButton(onClick = ::openDialog) {
Icon(Icons.Default.Schedule, contentDescription = "Pick time")
}
},
modifier = modifier.clickable(onClick = ::openDialog)
)
}

View File

@@ -16,6 +16,7 @@ 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.style.TextAlign
import androidx.compose.ui.unit.dp
import com.kizitonwose.calendar.core.CalendarDay
import com.kizitonwose.calendar.core.CalendarMonth
@@ -29,11 +30,12 @@ fun CalendarDaysOfWeekHeader(daysOfWeek: List<java.time.DayOfWeek>) {
text = day.name.take(3),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.weight(1f),
color = MaterialTheme.colorScheme.onSurfaceVariant
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
}
Spacer(modifier = Modifier.height(4.dp))
Spacer(modifier = Modifier.height(6.dp))
}
@Composable
@@ -58,14 +60,15 @@ fun CalendarDayCell(
) {
val isInMonth = day.position == DayPosition.MonthDate
val background = when {
isSelectedStart || isSelectedEnd -> MaterialTheme.colorScheme.primary.copy(alpha = 0.35f)
isInRange -> MaterialTheme.colorScheme.primary.copy(alpha = 0.18f)
isSelectedStart || isSelectedEnd -> MaterialTheme.colorScheme.primaryContainer
isInRange -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.55f)
hasRate -> MaterialTheme.colorScheme.secondary.copy(alpha = 0.2f)
else -> Color.Transparent
}
val textColor = when {
isSelectedStart || isSelectedEnd -> MaterialTheme.colorScheme.onPrimaryContainer
!isInMonth -> MaterialTheme.colorScheme.onSurfaceVariant
!isSelectable -> MaterialTheme.colorScheme.error
!isSelectable -> MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.45f)
else -> MaterialTheme.colorScheme.onSurface
}
Column(