diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..94a25f7f4cb416c083d265558da75d457237d671 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dba15af3b7bee221d9fa0d6a55c73c7d9b0f0a63..438742f4f00ace0c8417109795c2bb68c120d65a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -73,4 +73,5 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + implementation(libs.jetbrains.kotlinx.datetime) } \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/MainActivity.kt b/app/src/main/java/at/ac/fhstp/pawwatch/MainActivity.kt index 01ee35251d0156ab1aed8e5d7c3814e16015c12f..2ae6fa236f8a07b7b5ab44b9e64b1a5de0f261b2 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/MainActivity.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/MainActivity.kt @@ -16,10 +16,11 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import at.ac.fhstp.pawwatch.data.AppointmentRepository import at.ac.fhstp.pawwatch.data.db.AppointmentDatabase -import at.ac.fhstp.pawwatch.ui.AppointmentDetailScreen -import at.ac.fhstp.pawwatch.ui.AppointmentEditScreen +import at.ac.fhstp.pawwatch.ui.DetailScreen +import at.ac.fhstp.pawwatch.ui.EditScreen import at.ac.fhstp.pawwatch.ui.AppointmentViewModelFactory -import at.ac.fhstp.pawwatch.ui.AddAppointmentScreen +import at.ac.fhstp.pawwatch.ui.AddScreen +import at.ac.fhstp.pawwatch.ui.OverviewScreen import at.ac.fhstp.pawwatch.ui.theme.PawWatchTheme class MainActivity : ComponentActivity() { @@ -53,7 +54,7 @@ class MainActivity : ComponentActivity() { OverviewScreen(viewModel, navController) } composable(Screens.AddAppointment.route) { - AddAppointmentScreen( + AddScreen( viewModel = viewModel, onAppointmentClick = { appointment -> // Navigate to the appointment details screen @@ -62,9 +63,9 @@ class MainActivity : ComponentActivity() { ) navController.navigate(route) }, - onAddAppointmentClick = { petName, description, date, category -> + onAddAppointmentClick = { petName, description, dateTime, category -> // Add a new appointment via the ViewModel - viewModel.addAppointment(petName, description, date, category) + viewModel.addAppointment(petName, description, dateTime, category) // Navigate back to the overview screen navController.popBackStack() }, @@ -73,7 +74,7 @@ class MainActivity : ComponentActivity() { } composable(Screens.AppointmentDetails("appointmentId").route) { backStackEntry -> val appointmentId = backStackEntry.arguments?.getString("appointmentId") - AppointmentDetailScreen( + DetailScreen( appointmentId = appointmentId, viewModel = viewModel, navController = navController @@ -82,7 +83,7 @@ class MainActivity : ComponentActivity() { composable(Screens.EditAppointment("{appointmentId}").route) { backStackEntry -> val appointmentId = backStackEntry.arguments?.getString("appointmentId") - AppointmentEditScreen( + EditScreen( appointmentId = appointmentId, viewModel = viewModel, navController = navController diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/Navigation.kt b/app/src/main/java/at/ac/fhstp/pawwatch/Navigation.kt index 584bf57e3e3312c19d4a52f62c73ac073157b4ef..d03c9e066865c467f33f673dd3d62b086f01662d 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/Navigation.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/Navigation.kt @@ -1,33 +1,12 @@ package at.ac.fhstp.pawwatch -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.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.OutlinedCard -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.navigation.compose.currentBackStackEntryAsState -import at.ac.fhstp.pawwatch.data.db.AppointmentEntity -import at.ac.fhstp.pawwatch.ui.AppointmentViewModel -import java.sql.Date -import java.text.DateFormat sealed class Screens(val route: String) { @@ -52,7 +31,7 @@ fun BottomNavigationBar(navController: NavController) { selected = currentRoute == Screens.AddAppointment.route, onClick = { navController.navigate(Screens.AddAppointment.route) }, icon = { Icon(Icons.Filled.Add, contentDescription = "Add Appointment") }, - label = { Text("Add Appointment") } + label = { Text("Add") } ) NavigationBarItem( selected = currentRoute == Screens.Overview.route, @@ -61,61 +40,4 @@ fun BottomNavigationBar(navController: NavController) { label = { Text("Appointments") } ) } -} - -@Composable -fun OverviewScreen(viewModel: AppointmentViewModel, navController: NavController) { - val appointments by viewModel.appointments.collectAsState() - - LazyColumn( - modifier = Modifier.fillMaxWidth().padding(16.dp) - ) { - items(appointments) { appointment -> - AppointmentCard(appointment = appointment) { - navController.navigate(Screens.AppointmentDetails(appointmentId = appointment.id.toString()).createRoute( - appointment.id.toString() - )) - } - Spacer(modifier = Modifier.height(14.dp)) - } - } -} - -@Composable -fun AppointmentCard(appointment: AppointmentEntity, onClick: () -> Unit) { - OutlinedCard( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 0.dp), - onClick = onClick, - colors = CardDefaults.outlinedCardColors( - containerColor = MaterialTheme.colorScheme.secondary, - contentColor = MaterialTheme.colorScheme.onPrimary - ) - ) { - Column(modifier = Modifier.padding(16.dp)) { - Text( - appointment.petName, - style = MaterialTheme.typography.bodyLarge - ) - Spacer(modifier = Modifier.height(3.dp)) - - Text( - appointment.category, - style = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(10.dp)) - - Text( - appointment.description, - style = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(14.dp)) - - Text( - "Date: ${DateFormat.getDateInstance().format(Date(appointment.appointmentDate))}", - style = MaterialTheme.typography.bodyMedium - ) - } - } } \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDao.kt b/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDao.kt index d9e03093c70757c71dbceaec8b6bae8fa6442897..ec987da0dad8bf22b7cd0879e4d12c0cb6f567df 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDao.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDao.kt @@ -3,6 +3,7 @@ package at.ac.fhstp.pawwatch.data.db import androidx.room.Dao import androidx.room.Insert import androidx.room.Query +import java.sql.Time @Dao interface AppointmentDao { @@ -16,12 +17,12 @@ interface AppointmentDao { @Query("DELETE FROM appointment_table WHERE id = :appointmentId") suspend fun deleteAppointmentById(appointmentId: Long) - @Query("UPDATE appointment_table SET petName = :petName, description = :description, appointmentDate = :appointmentDate, category = :category WHERE id = :appointmentId") + @Query("UPDATE appointment_table SET petName = :petName, description = :description, appointmentDateTime = :appointmentDateTime, category = :category WHERE id = :appointmentId") suspend fun updateAppointment( appointmentId: Long, petName: String, description: String, - appointmentDate: Long, + appointmentDateTime: Long, category: String ) diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDatabase.kt b/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDatabase.kt index 169f89ec51906b42302aa62490989328a2cf6e44..5c6406ae69ba2190e8c28ef27f0591fdfafe528f 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDatabase.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentDatabase.kt @@ -5,7 +5,7 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase -@Database(entities = [AppointmentEntity::class], version = 2) +@Database(entities = [AppointmentEntity::class], version = 4) abstract class AppointmentDatabase : RoomDatabase() { abstract fun appointmentDao() : AppointmentDao @@ -24,6 +24,5 @@ abstract class AppointmentDatabase : RoomDatabase() { return tempInstance } } - } } \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentEntity.kt b/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentEntity.kt index 586a57a2081544166c3297dbe015de30c411b939..6cdc0c16c1590afe73f7c09e22dcee47ad7e45bd 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentEntity.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/data/db/AppointmentEntity.kt @@ -9,6 +9,6 @@ data class AppointmentEntity( val id: Int = 0, val petName: String, val description: String, - val appointmentDate: Long, + val appointmentDateTime: Long, val category: String ) \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AddAppointmentScreen.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/AddAppointmentScreen.kt deleted file mode 100644 index 729b76d88495ca6d2d418d5d6aea6790e402a88d..0000000000000000000000000000000000000000 --- a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AddAppointmentScreen.kt +++ /dev/null @@ -1,219 +0,0 @@ -package at.ac.fhstp.pawwatch.ui - -import android.widget.CalendarView -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.compose.ui.window.Dialog -import androidx.navigation.NavController -import at.ac.fhstp.pawwatch.data.db.AppointmentEntity -import java.text.DateFormat -import java.util.* - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AddAppointmentScreen( - viewModel: AppointmentViewModel, - onAppointmentClick: (AppointmentEntity) -> Unit, - onAddAppointmentClick: (String, String, Long, String) -> Unit, - navController: NavController -) { - // Collect the appointments as a state - val appointments by viewModel.appointments.collectAsState() - - // Store the selected appointment date - var selectedAppointmentDate by remember { mutableStateOf<Long?>(null) } - - // DatePicker Dialog state - var showDatePicker by remember { mutableStateOf(false) } - - val petName = remember { mutableStateOf("") } - val category = remember { mutableStateOf("") } - val description = remember { mutableStateOf("") } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(35.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(10.dp)) - - Text( - text = "Welcome to PawWatch!", - style = MaterialTheme.typography.headlineMedium - ) - - Spacer(modifier = Modifier.height(25.dp)) - - Text( - text = "Pet Name:", - modifier = Modifier.align(Alignment.Start) - ) - - OutlinedTextField( - value = petName.value, - onValueChange = { petName.value = it }, - modifier = Modifier.fillMaxWidth(), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondary - ), - textStyle = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Appointment Category:", - modifier = Modifier.align(Alignment.Start) - ) - - OutlinedTextField( - value = category.value, - onValueChange = { category.value = it }, - modifier = Modifier.fillMaxWidth(), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondary - ), - textStyle = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Description:", - modifier = Modifier.align(Alignment.Start) - ) - - OutlinedTextField( - value = description.value, - onValueChange = { description.value = it }, - modifier = Modifier.fillMaxWidth(), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondary - ), - textStyle = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(16.dp)) - - - if (showDatePicker) { - CustomDatePickerDialog( - selectedDate = selectedAppointmentDate, // Pass the previously selected date - onDismiss = { showDatePicker = false }, - onDateSelected = { date -> - selectedAppointmentDate = date // Update the selected date - } - ) - } - - OutlinedButton( - onClick = { showDatePicker = true }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary - ) - ) { - Text( - text = "Select Appointment Date", - style = MaterialTheme.typography.labelMedium - ) - } - - // for displaying the selected appointment date below the button - Spacer(modifier = Modifier.height(8.dp)) - selectedAppointmentDate?.let { - val dateString = DateFormat.getDateInstance().format(Date(it)) - Text( - text = "Selected Appointment Date: $dateString", - modifier = Modifier.padding(2.dp), - style = MaterialTheme.typography.labelMedium - ) - } - - Spacer(modifier = Modifier.height(22.dp)) - - // Button to submit new appointment - OutlinedButton( - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), - onClick = { - if (petName.value.isNotBlank() && selectedAppointmentDate != null) { - if (description.value.isBlank()) { - description.value = "No Description" - } - if (category.value.isBlank()) { - category.value = "General" - } - // Calls the onAddAppointmentClick with selected date - onAddAppointmentClick( - petName.value, - description.value, - selectedAppointmentDate!!, - category.value - ) - } - } - ) { - Text( - text = "Add Appointment", - style = MaterialTheme.typography.labelMedium - ) - } - } -} - -@Composable -fun CustomDatePickerDialog( - onDismiss: () -> Unit, - selectedDate: Long?, // Pass the currently selected date - onDateSelected: (Long) -> Unit -) { - val calendar = Calendar.getInstance(Locale.getDefault()).apply { - firstDayOfWeek = Calendar.MONDAY - } - - Dialog(onDismissRequest = onDismiss) { - Surface( - shape = MaterialTheme.shapes.medium, - tonalElevation = 8.dp - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - AndroidView( - factory = { context -> - CalendarView(context).apply { - this.firstDayOfWeek = Calendar.MONDAY - - // Set the calendar to the selected date or today's date - if (selectedDate != null) { - calendar.timeInMillis = selectedDate - } - this.date = calendar.timeInMillis - - - setOnDateChangeListener { _, year, month, dayOfMonth -> - calendar.set(year, month, dayOfMonth) - onDateSelected(calendar.timeInMillis) - onDismiss() // Close the dialog after selecting a date - } - } - }, - modifier = Modifier.wrapContentHeight() - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = onDismiss, - modifier = Modifier.align(Alignment.End) - ) { - Text("Cancel") - } - } - } - } -} diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AddScreen.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/AddScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..e46112398da2907a8e5e4567270bc9fa7be276d1 --- /dev/null +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/AddScreen.kt @@ -0,0 +1,337 @@ +package at.ac.fhstp.pawwatch.ui + +import android.widget.CalendarView +import android.widget.NumberPicker +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.navigation.NavController +import at.ac.fhstp.pawwatch.data.db.AppointmentEntity +import java.text.SimpleDateFormat +import java.util.* + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddScreen( + viewModel: AppointmentViewModel, + onAppointmentClick: (AppointmentEntity) -> Unit, + onAddAppointmentClick: (String, String, Long, String) -> Unit, + navController: NavController +) { + + // Store the selected appointment date + var selectedAppointmentDateTime by remember { mutableStateOf<Long?>(null) } + var errorAddAppointment by remember { mutableStateOf(false) } + + // DatePicker Dialog state + var showDatePicker by remember { mutableStateOf(false) } + + val petName = remember { mutableStateOf("") } + val category = remember { mutableStateOf("") } + val description = remember { mutableStateOf("") } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(35.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = "Welcome to PawWatch!", + style = MaterialTheme.typography.headlineMedium + ) + + Spacer(modifier = Modifier.height(45.dp)) + + Text( + text = "Pet Name:", + modifier = Modifier.align(Alignment.Start) + ) + Spacer(modifier = Modifier.height(5.dp)) + + OutlinedTextField( + value = petName.value, + onValueChange = { petName.value = it }, + modifier = Modifier.fillMaxWidth(), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = MaterialTheme.colorScheme.secondary + ), + textStyle = MaterialTheme.typography.bodyMedium, + placeholder = { + Text( + text = "Enter your pet name, e.g. Cupcake", + style = MaterialTheme.typography.bodyMedium + ) + } + ) + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = "Appointment Category:", + modifier = Modifier.align(Alignment.Start) + ) + Spacer(modifier = Modifier.height(5.dp)) + + OutlinedTextField( + value = category.value, + onValueChange = { category.value = it }, + modifier = Modifier.fillMaxWidth(), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = MaterialTheme.colorScheme.secondary + ), + textStyle = MaterialTheme.typography.bodyMedium, + placeholder = { + Text( + text = "Select a category, e.g. Grooming", + style = MaterialTheme.typography.bodyMedium + ) + } + ) + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = "Description (Optional):", + modifier = Modifier.align(Alignment.Start) + ) + Spacer(modifier = Modifier.height(5.dp)) + + OutlinedTextField( + value = description.value, + onValueChange = { description.value = it }, + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 100.dp) // Restrict the height + .let { modifier -> + if (description.value.length > 200) { + modifier.verticalScroll(rememberScrollState()) // Enable scroll if content exceeds 200 characters + } else { + modifier + } + }, + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = MaterialTheme.colorScheme.secondary + ), + textStyle = MaterialTheme.typography.bodyMedium, + placeholder = { + Text( + text = "Enter a description, e.g. address, phone number", + style = MaterialTheme.typography.bodyMedium + ) + } + ) + + Spacer(modifier = Modifier.height(20.dp)) + + + if (showDatePicker) { + CustomDateTimePickerDialog( + selectedDateTime = selectedAppointmentDateTime, // Pass the previously selected date + onDismiss = { showDatePicker = false }, + onDateTimeSelected = { dateTime -> + selectedAppointmentDateTime = dateTime + } + + ) + } + + if (errorAddAppointment) { + AlertDialog( + onDismissRequest = { errorAddAppointment = false }, + title = { Text("Could not add an appointment") }, + text = { Text("Please fill in all required fields and select a date and time") }, + confirmButton = { + Button( + onClick = { errorAddAppointment = false } + ) { + Text("OK") + } + } + ) + } + + OutlinedButton( + onClick = { showDatePicker = true }, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiary, + contentColor = MaterialTheme.colorScheme.onPrimary + ), + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 4.dp) + ) { + Text( + text = "Select Appointment Date and Time", + style = MaterialTheme.typography.labelMedium + ) + } + + // for displaying the selected appointment date below the button + Spacer(modifier = Modifier.height(8.dp)) + selectedAppointmentDateTime?.let { + val dateTimeString = SimpleDateFormat("MMM dd, yyyy 'at' HH:mm", Locale.getDefault()).format(Date(it)) + Text( + text = "Selected Date and Time: $dateTimeString", + modifier = Modifier.padding(2.dp), + style = MaterialTheme.typography.labelSmall + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + + // Button to submit new appointment + OutlinedButton( + modifier = Modifier.width(200.dp), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), + onClick = { + if (petName.value.isNotBlank() && category.value.isNotBlank() && selectedAppointmentDateTime != null) { + if (description.value.isBlank()) { description.value = "No Description" } + //if (category.value.isBlank()) { category.value = "General" } + // Calls the onAddAppointmentClick with selected date and time + onAddAppointmentClick( + petName.value, + description.value, + selectedAppointmentDateTime!!, + category.value + ) + } + else errorAddAppointment = true + }, + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 3.dp) + ) { + Text( + text = "Add Appointment", + style = MaterialTheme.typography.labelMedium + ) + } + } +} + +@Composable +fun CustomDateTimePickerDialog( + onDismiss: () -> Unit, + selectedDateTime: Long?, // Pass the currently selected date + onDateTimeSelected: (Long) -> Unit +) { + val calendar = Calendar.getInstance(Locale.getDefault()).apply { + firstDayOfWeek = Calendar.MONDAY + } + + // Time selection state + var showTimePicker by remember { mutableStateOf(false) } + + Dialog(onDismissRequest = onDismiss) { + Surface( + shape = MaterialTheme.shapes.medium, + tonalElevation = 8.dp + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + if (!showTimePicker) { + // Calendar View + AndroidView( + factory = { context -> + CalendarView(context).apply { + this.firstDayOfWeek = Calendar.MONDAY + + // Set the calendar to the selected date or today's date + if (selectedDateTime != null) { + calendar.timeInMillis = selectedDateTime + } + this.date = calendar.timeInMillis + + setOnDateChangeListener { _, year, month, dayOfMonth -> + calendar.set(year, month, dayOfMonth) + showTimePicker = true // Show time picker after date selection + } + } + }, + modifier = Modifier.wrapContentHeight() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = onDismiss, + modifier = Modifier.align(Alignment.End) + ) { + Text("Cancel") + } + } else { + // Time Picker + Text("Select Time", style = MaterialTheme.typography.bodyLarge) + Spacer(modifier = Modifier.height(16.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier.fillMaxWidth() + ) { + // Hour Picker + AndroidView( + factory = { context -> + NumberPicker(context).apply { + minValue = 0 + maxValue = 23 + //value = selectedHour + value = calendar.get(Calendar.HOUR_OF_DAY) + setOnValueChangedListener { _, _, newValue -> + //selectedHour = newValue + calendar.set(Calendar.HOUR_OF_DAY, newValue) + } + } + }, + modifier = Modifier.wrapContentHeight() + ) + + // Minute Picker + AndroidView( + factory = { context -> + NumberPicker(context).apply { + minValue = 0 + maxValue = 59 + //value = selectedMinute + value = calendar.get(Calendar.MINUTE) + setOnValueChangedListener { _, _, newValue -> + //selectedMinute = newValue + calendar.set(Calendar.MINUTE, newValue) + } + } + }, + modifier = Modifier.wrapContentHeight() + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button(onClick = { showTimePicker = false }) { + Text("Back") + } + Button( + onClick = { + // Pass the selected date and time + onDateTimeSelected(calendar.timeInMillis) + onDismiss() + } + ) { + Text("OK") + } + } + } + } + } + } +} diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentDetailScreen.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentDetailScreen.kt deleted file mode 100644 index f4f0c2fded9853e38d8941218b0abf83e05bdd32..0000000000000000000000000000000000000000 --- a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentDetailScreen.kt +++ /dev/null @@ -1,112 +0,0 @@ -package at.ac.fhstp.pawwatch.ui - -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import at.ac.fhstp.pawwatch.Screens -import at.ac.fhstp.pawwatch.ui.theme.text -import java.text.DateFormat -import java.util.Date - -@Composable -fun AppointmentDetailScreen( - appointmentId: String?, - viewModel: AppointmentViewModel, - navController: NavController -) { - val appointment = appointmentId?.let { id -> viewModel.getAppointmentById(id.toLong()) } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(35.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - // Back Button - OutlinedButton( - onClick = { - navController.navigateUp() - }, - modifier = Modifier.align(Alignment.Start), - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background) - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back", - tint = text // "text" is the variable which contains black colour in Color.kt - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = "Back", - color = text // same colour variable as for the icon - ) - } - - Spacer(modifier = Modifier.height(60.dp)) - - appointment?.let { - Text( - it.petName, - style = MaterialTheme.typography.headlineMedium, - modifier = Modifier.padding(2.dp) - ) - - Text( - it.category, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(2.dp) - ) - Spacer(modifier = Modifier.height(10.dp)) - - Text( - DateFormat.getDateInstance().format(Date(it.appointmentDate)), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(2.dp) - ) - - Spacer(modifier = Modifier.height(20.dp)) - - Text( - it.description, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(2.dp) - ) - - Spacer(modifier = Modifier.height(30.dp)) - - Row { - // Edit Button - OutlinedButton( - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), - onClick = { - // Navigate using the sealed class route - navController.navigate(Screens.EditAppointment(it.id.toString()).createRoute(it.id.toString())) - }, - modifier = Modifier.width(150.dp) - ) { - Text("Edit") - } - - Spacer(modifier = Modifier.width(40.dp)) - - // Delete Button - OutlinedButton( - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), - onClick = { - viewModel.deleteAppointment(it.id.toLong()) // Call delete logic - navController.navigateUp() - }, - modifier = Modifier.width(150.dp) - ) { - Text("Delete") - } - } - } - } -} diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentEditScreen.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentEditScreen.kt deleted file mode 100644 index bda19e37b7a721af9a2b71e578c1a6b3f4643345..0000000000000000000000000000000000000000 --- a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentEditScreen.kt +++ /dev/null @@ -1,158 +0,0 @@ -package at.ac.fhstp.pawwatch.ui - -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import at.ac.fhstp.pawwatch.ui.theme.text -import java.sql.Date -import java.text.DateFormat - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AppointmentEditScreen( - appointmentId: String?, - viewModel: AppointmentViewModel, - navController: NavController -) { - val appointment = appointmentId?.let { id -> viewModel.getAppointmentById(id.toLong()) } - var petName by remember { mutableStateOf(appointment?.petName ?: "") } - var description by remember { mutableStateOf(appointment?.description ?: "") } - var appointmentDate by remember { mutableLongStateOf(appointment?.appointmentDate ?: System.currentTimeMillis()) } - var category by remember { mutableStateOf(appointment?.category ?: "") } - - var showDatePicker by remember { mutableStateOf(false) } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(35.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - // Back Button - OutlinedButton( - onClick = { - navController.navigateUp() - }, - modifier = Modifier.align(Alignment.Start), - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background) - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back", - tint = text // "text" is the variable which contains black colour in Color.kt - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = "Back", - color = text // same colour variable as for the icon - ) - } - - Spacer(modifier = Modifier.height(40.dp)) - - Text( - text = "Pet Name:", - modifier = Modifier.align(Alignment.Start) - ) - - OutlinedTextField( - value = petName, - onValueChange = { petName = it }, - modifier = Modifier.fillMaxWidth(), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondary - ), - textStyle = MaterialTheme.typography.bodyMedium - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Appointment Category:", - modifier = Modifier.align(Alignment.Start) - ) - - OutlinedTextField( - value = category, - onValueChange = { category = it }, - modifier = Modifier.fillMaxWidth(), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondary - ), - textStyle = MaterialTheme.typography.bodyMedium - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Description:", - modifier = Modifier.align(Alignment.Start) - ) - - OutlinedTextField( - value = description, - onValueChange = { description = it }, - modifier = Modifier.fillMaxWidth(), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondary - ), - textStyle = MaterialTheme.typography.bodyMedium - ) - - Spacer(modifier = Modifier.height(16.dp)) - - if (showDatePicker) { - CustomDatePickerDialog( - selectedDate = appointmentDate, - onDismiss = { showDatePicker = false }, - onDateSelected = { date -> - appointmentDate = date // Update appointmentDate - showDatePicker = false - } - ) - } - - val formattedDate = DateFormat.getDateInstance().format(Date(appointmentDate)) - Text( - "Selected Appointment Date: $formattedDate", - modifier = Modifier.padding(2.dp), - style = MaterialTheme.typography.labelMedium - ) - - Spacer(modifier = Modifier.height(8.dp)) - - OutlinedButton( - onClick = { showDatePicker = true }, - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), - ) { - Text( - "Change Appointment Date", - style = MaterialTheme.typography.labelMedium - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - OutlinedButton( - onClick = { - if (petName.isNotBlank() && description.isNotBlank()) { - viewModel.editAppointment(appointmentId!!.toLong(), petName, description, appointmentDate, category) - navController.navigateUp() - } - }, - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), - ) { - Text( - "Save Changes", - style = MaterialTheme.typography.labelMedium - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentViewModel.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentViewModel.kt index 74c1192339c0ce2b4a53ddfe6d8177133f6e8cf4..6839d118584fca89ac27bb4f952714cc353a033e 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentViewModel.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/AppointmentViewModel.kt @@ -33,8 +33,8 @@ class AppointmentViewModel(private val appointmentRepository: AppointmentReposit } // Add a new appointment - fun addAppointment(petName: String, description: String, appointmentDate: Long, category: String) { - val appointment = AppointmentEntity(petName = petName, description = description, appointmentDate = appointmentDate, category = category) + fun addAppointment(petName: String, description: String, appointmentDateTime: Long, category: String) { + val appointment = AppointmentEntity(petName = petName, description = description, appointmentDateTime = appointmentDateTime, category = category) viewModelScope.launch { appointmentRepository.insertAppointment(appointment) loadAppointments() // Reload appointments from the repository to update the list @@ -58,9 +58,9 @@ class AppointmentViewModel(private val appointmentRepository: AppointmentReposit } } - fun editAppointment(appointmentId: Long, petName: String, description: String, appointmentDate: Long, category: String) { + fun editAppointment(appointmentId: Long, petName: String, description: String, appointmentDateTime: Long, category: String) { viewModelScope.launch { - appointmentRepository.updateAppointment(appointmentId, petName, description, appointmentDate, category) + appointmentRepository.updateAppointment(appointmentId, petName, description, appointmentDateTime, category) loadAppointments() // Refresh the appointment list } } diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/DetailScreen.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/DetailScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..dcb6987d916039d818046c93ec617fe442b8bf5f --- /dev/null +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/DetailScreen.kt @@ -0,0 +1,141 @@ +package at.ac.fhstp.pawwatch.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import at.ac.fhstp.pawwatch.Screens +import at.ac.fhstp.pawwatch.ui.theme.text +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Composable +fun DetailScreen( + appointmentId: String?, + viewModel: AppointmentViewModel, + navController: NavController +) { + val appointment = appointmentId?.let { id -> viewModel.getAppointmentById(id.toLong()) } + + Box( + modifier = Modifier + .fillMaxSize() + .padding(35.dp) // Padding for spacing within the screen + ) { + // Back Button at Top-Start + OutlinedButton( + onClick = { + navController.navigateUp() + }, + modifier = Modifier + .align(Alignment.TopStart), // Aligns the button at the top-left corner + //.padding(8.dp), // Add padding around the button + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background), + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 3.dp), + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = text + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = "Back", + color = text, + style = MaterialTheme.typography.labelMedium + ) + } + + //Spacer(modifier = Modifier.height(60.dp)) + + // Rest of the content + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = 45.dp), // Leave space for the back button + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + appointment?.let { + Text( + it.petName, + style = MaterialTheme.typography.headlineMedium, + //modifier = Modifier.padding(2.dp) + ) + Spacer(modifier = Modifier.height(9.dp)) + + Text( + it.category, + style = MaterialTheme.typography.bodyLarge, + //modifier = Modifier.padding(2.dp) + ) + Spacer(modifier = Modifier.height(20.dp)) + + val date = Date(appointment.appointmentDateTime) + + // Use SimpleDateFormat for 24-hour time format (HH:mm) + val dateFormat = SimpleDateFormat("MMM dd, yyyy 'at' HH:mm", Locale.getDefault()) + + Text( + dateFormat.format(date), + style = MaterialTheme.typography.labelSmall + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + it.description, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(2.dp) + ) + + Spacer(modifier = Modifier.height(50.dp)) + + Row { + // Edit Button + OutlinedButton( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), + onClick = { + // Navigate using the sealed class route + navController.navigate( + Screens.EditAppointment(it.id.toString()) + .createRoute(it.id.toString()) + ) + }, + modifier = Modifier.width(150.dp), + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 3.dp) + ) { + Text( + text = "Edit", + style = MaterialTheme.typography.labelMedium + ) + } + + Spacer(modifier = Modifier.width(40.dp)) + + // Delete Button + OutlinedButton( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), + onClick = { + viewModel.deleteAppointment(it.id.toLong()) // Call delete logic + navController.navigateUp() + }, + modifier = Modifier.width(150.dp), + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 3.dp) + ) { + Text( + text = "Delete", + style = MaterialTheme.typography.labelMedium + ) + } + } + } + } + } +} diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/EditScreen.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/EditScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..0e91f39bf1cbe1aa6138044736d09702c3dd31d7 --- /dev/null +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/EditScreen.kt @@ -0,0 +1,206 @@ +package at.ac.fhstp.pawwatch.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import at.ac.fhstp.pawwatch.ui.theme.text +import java.sql.Date +import java.text.SimpleDateFormat +import java.util.Locale + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EditScreen( + appointmentId: String?, + viewModel: AppointmentViewModel, + navController: NavController +) { + val appointment = appointmentId?.let { id -> viewModel.getAppointmentById(id.toLong()) } + var petName by remember { mutableStateOf(appointment?.petName ?: "") } + var description by remember { mutableStateOf(appointment?.description ?: "") } + var selectedAppointmentDateTime by remember { mutableLongStateOf(appointment?.appointmentDateTime ?: System.currentTimeMillis()) } + var category by remember { mutableStateOf(appointment?.category ?: "") } + + var showDatePicker by remember { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxSize() + .padding(35.dp) // Padding for spacing within the screen + ) { + // Back Button at Top-Start + OutlinedButton( + onClick = { + navController.navigateUp() + }, + modifier = Modifier + .align(Alignment.TopStart), // Aligns the button at the top-left corner + //.padding(8.dp), // Add padding around the button + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background), + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 3.dp), + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = text + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = "Back", + color = text, + style = MaterialTheme.typography.labelMedium + ) + } + + // Rest of the content + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = 45.dp), // Leave space for the back button + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "Edit Appointment", + style = MaterialTheme.typography.headlineMedium + ) + + Spacer(modifier = Modifier.height(25.dp)) + + Text( + text = "Pet Name:", + modifier = Modifier.align(Alignment.Start) + ) + Spacer(modifier = Modifier.height(5.dp)) + + OutlinedTextField( + value = petName, + onValueChange = { petName = it }, + modifier = Modifier.fillMaxWidth(), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = MaterialTheme.colorScheme.secondary + ), + textStyle = MaterialTheme.typography.bodyMedium + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = "Appointment Category:", + modifier = Modifier.align(Alignment.Start) + ) + Spacer(modifier = Modifier.height(5.dp)) + + OutlinedTextField( + value = category, + onValueChange = { category = it }, + modifier = Modifier.fillMaxWidth(), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = MaterialTheme.colorScheme.secondary + ), + textStyle = MaterialTheme.typography.bodyMedium + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = "Description (Optional):", + modifier = Modifier.align(Alignment.Start) + ) + Spacer(modifier = Modifier.height(5.dp)) + + OutlinedTextField( + value = description, + onValueChange = { description = it }, + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 100.dp) // Restrict the height + .let { modifier -> + if (description.length > 200) { + modifier.verticalScroll(rememberScrollState()) // Enable scroll if content exceeds 200 characters + } else { + modifier + } + }, + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = MaterialTheme.colorScheme.secondary + ), + textStyle = MaterialTheme.typography.bodyMedium + ) + + + Spacer(modifier = Modifier.height(20.dp)) + + if (showDatePicker) { + CustomDateTimePickerDialog( + selectedDateTime = selectedAppointmentDateTime, + onDismiss = { showDatePicker = false }, + onDateTimeSelected = { dateTime -> + selectedAppointmentDateTime = dateTime + } + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedButton( + onClick = { showDatePicker = true }, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiary, + contentColor = MaterialTheme.colorScheme.onPrimary + ), + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 3.dp) + ) { + Text( + text = "Change Date or Time", + style = MaterialTheme.typography.labelMedium + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + selectedAppointmentDateTime.let { + val dateTimeString = + SimpleDateFormat("MMM dd, yyyy 'at' HH:mm", Locale.getDefault()).format(Date(it)) + Text( + text = "Selected Date and Time: $dateTimeString", + modifier = Modifier.padding(2.dp), + style = MaterialTheme.typography.labelSmall + ) + } + + Spacer(modifier = Modifier.height(35.dp)) + + OutlinedButton( + modifier = Modifier.width(200.dp), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), + onClick = { + if (petName.isNotBlank()) { + viewModel.editAppointment( + appointmentId!!.toLong(), + petName, + description, + selectedAppointmentDateTime, + category + ) + navController.navigateUp() + } + }, + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 3.dp) + ) { + Text( + text = "Save Changes", + style = MaterialTheme.typography.labelMedium + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/OverviewScreen.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/OverviewScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..2c96abf2b012ade6600b50fb8f156fd5a00b5342 --- /dev/null +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/OverviewScreen.kt @@ -0,0 +1,215 @@ +package at.ac.fhstp.pawwatch.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import at.ac.fhstp.pawwatch.Screens +import at.ac.fhstp.pawwatch.data.db.AppointmentEntity +import java.sql.Date +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +@Composable +fun OverviewScreen( + viewModel: AppointmentViewModel, + navController: NavController +) { + val appointments by viewModel.appointments.collectAsState() + // State to store the filter type + var filterType by remember { mutableStateOf(FilterType.ALL) } + + // Filter appointments based on the selected filter type + val filteredAppointments = when (filterType) { + FilterType.PAST -> appointments.filter { it.appointmentDateTime < System.currentTimeMillis() } + FilterType.UPCOMING -> appointments.filter { it.appointmentDateTime >= System.currentTimeMillis() } + else -> appointments // ALL + } + + // Sort appointments by date + val sortedAppointments = filteredAppointments.sortedBy { it.appointmentDateTime } + + + Column( + modifier = Modifier + .fillMaxSize() + .padding(35.dp), + horizontalAlignment = Alignment.CenterHorizontally, + //verticalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = "Your Appointments", + style = MaterialTheme.typography.headlineMedium + ) + + Spacer(modifier = Modifier.height(30.dp)) + + // Filter buttons + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + FilterButton("All", FilterType.ALL, filterType) { filterType = it } + FilterButton("Past", FilterType.PAST, filterType) { filterType = it } + FilterButton("Upcoming", FilterType.UPCOMING, filterType) { filterType = it } + } + + Spacer(modifier = Modifier.height(20.dp)) + + // Display sorted and filtered appointments + LazyColumn( + modifier = Modifier.fillMaxWidth() + ) { + items(sortedAppointments) { appointment -> + AppointmentCard(appointment = appointment) { + navController.navigate( + Screens.AppointmentDetails(appointmentId = appointment.id.toString()).createRoute( + appointment.id.toString() + )) + } + Spacer(modifier = Modifier.height(14.dp)) + } + } + } + +} + +@Composable +fun AppointmentCard( + appointment: AppointmentEntity, + onClick: () -> Unit +) { + val date = Date(appointment.appointmentDateTime) + val dateFormat = SimpleDateFormat("MMM dd, yyyy 'at' HH:mm", Locale.getDefault()) + + // Get today's date and the appointment date + val calendar = Calendar.getInstance() + val today = calendar.apply { set(Calendar.HOUR_OF_DAY, 0); set(Calendar.MINUTE, 0); set(Calendar.SECOND, 0) } + val tomorrow = calendar.apply { add(Calendar.DATE, 1) } + val appointmentCalendar = Calendar.getInstance().apply { time = date } + + // Check if the appointment is tomorrow + val isTomorrow = appointmentCalendar.get(Calendar.YEAR) == tomorrow.get(Calendar.YEAR) && + appointmentCalendar.get(Calendar.DAY_OF_YEAR) == tomorrow.get(Calendar.DAY_OF_YEAR) + + // Check if the appointment is missed + val isMissed = appointment.appointmentDateTime < System.currentTimeMillis() + + OutlinedCard( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + onClick = onClick, + colors = CardDefaults.outlinedCardColors( + containerColor = MaterialTheme.colorScheme.secondary, + contentColor = MaterialTheme.colorScheme.onPrimary + ), + elevation = CardDefaults.elevatedCardElevation(defaultElevation = 3.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + appointment.petName, + style = MaterialTheme.typography.bodyLarge + ) + Spacer(modifier = Modifier.height(3.dp)) + + Text( + appointment.category, + style = MaterialTheme.typography.bodyMedium + ) + Spacer(modifier = Modifier.height(10.dp)) + + val shortenDescription = if (appointment.description.length > 70) { + "${appointment.description.take(70)}..." + } else { + appointment.description + } + + Text( + shortenDescription, + style = MaterialTheme.typography.bodyMedium + ) + Spacer(modifier = Modifier.height(14.dp)) + + // Appointment date with conditional coloring + val dateColor = when { + isMissed -> MaterialTheme.colorScheme.error // Red for missed appointments + isTomorrow -> MaterialTheme.colorScheme.onSecondary // Orange for tomorrow + else -> MaterialTheme.colorScheme.onPrimary // Default color + } + + Text( + dateFormat.format(date), + style = MaterialTheme.typography.labelSmall, + color = dateColor + ) + + // Missed label + if (isMissed) { + Text( + text = "Missed", + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.padding(top = 4.dp), + color = MaterialTheme.colorScheme.error + ) + } + + if (isTomorrow) { + Text( + text = "Tomorrow", + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.padding(top = 4.dp), + color = MaterialTheme.colorScheme.onSecondary + ) + } + } + } +} + +@Composable +fun FilterButton( + text: String, + filterType: FilterType, + currentFilterType: FilterType, + onClick: (FilterType) -> Unit +) { + OutlinedButton( + onClick = { onClick(filterType) }, + colors = ButtonDefaults.buttonColors( + containerColor = if (filterType == currentFilterType) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary + ), + modifier = Modifier + .padding(horizontal = 5.dp) // Add some horizontal padding to make buttons smaller + .height(40.dp), // Set a fixed height to control the button size + elevation = ButtonDefaults.elevatedButtonElevation(defaultElevation = 4.dp) + ) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium + ) + } +} + +enum class FilterType { + ALL, PAST, UPCOMING +} \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Color.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Color.kt index 45ca9f590d916c43a59ea23096a5e5c79ae5ebb0..954cfcc7b799ecd593a18a5e1c8c24b5bab9e506 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Color.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Color.kt @@ -4,6 +4,9 @@ import androidx.compose.ui.graphics.Color val bg = Color(0xFFFDE9C3) val buttonBg = Color(0xFFFFBA66) +val lightButtonBg = Color(0xFFFFCE93) +val appointmentBg = Color(0xFFF8F8F8) val text = Color(0xFF000000) val fieldBg = Color(0xFFFFF4DE) -val deleteButtonBg = Color(0xFFbf4342) \ No newline at end of file +val deleteButtonBg = Color(0xFFE04F47) +val dateLabel = Color(0xFFFF9800) \ No newline at end of file diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Theme.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Theme.kt index cc6be4e3a25cb8023b3560b01c80adc218babd63..17bcd78bef5bb8cbfdaf04b06c17c589eb40a3ed 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Theme.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Theme.kt @@ -3,13 +3,15 @@ package at.ac.fhstp.pawwatch.ui.theme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color private val AppColorScheme = lightColorScheme( background = bg, primary = buttonBg, onPrimary = text, secondary = fieldBg, + onSecondary = dateLabel, + tertiary = lightButtonBg, + tertiaryContainer = appointmentBg, error = deleteButtonBg ) diff --git a/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Type.kt b/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Type.kt index b6a201efbbdb612dc8f056f31f6c42a67cea7228..f6d5aae2a59e04ce30700986c698c6c5068202d5 100644 --- a/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Type.kt +++ b/app/src/main/java/at/ac/fhstp/pawwatch/ui/theme/Type.kt @@ -42,6 +42,14 @@ val Typography = Typography( ), labelMedium = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Bold, + fontSize = 17.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ), + + labelSmall = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Bold, fontSize = 15.sp, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4684332876f91e74f487ef9acc35b511aa91b668..6140e4305fa04bc45d023fc19bfb1f74e5198622 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,8 @@ coreKtx = "1.13.1" junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" +kotlinxDatetime = "0.6.1" +kotlinxDatetimeVersion = "0.6.1" lifecycleRuntimeKtx = "2.8.6" activityCompose = "1.9.2" composeBom = "2024.04.01" @@ -17,6 +19,7 @@ navigationCompose = "2.8.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" } +jetbrains-kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetimeVersion" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -33,6 +36,7 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" androidx-room-common = { group = "androidx.room", name = "room-common", version.ref = "roomCommon" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomKtx" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } +kotlinx-datetime = { module = "kotlinx.datetime:kotlinx-datetime", version.ref = "kotlinxDatetime" } materialdatetimepicker = { module = "com.wdullaer:materialdatetimepicker", version.ref = "materialdatetimepicker" } [plugins]