Untitled
unknown
kotlin
a month ago
34 kB
3
Indexable
Never
package com.example.a366pi // Importing necessary packages import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.CalendarToday import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import androidx.lifecycle.viewmodel.compose.viewModel import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale import android.content.Context import android.content.Intent import android.net.Uri import android.os.Environment import androidx.compose.material.icons.filled.Share import androidx.core.content.FileProvider import com.itextpdf.io.font.constants.StandardFonts import com.itextpdf.kernel.pdf.PdfDocument import com.itextpdf.kernel.pdf.PdfWriter import com.itextpdf.layout.Document import com.itextpdf.layout.element.Paragraph import com.itextpdf.layout.element.Text import java.io.File import java.io.FileOutputStream import java.io.OutputStream // Driver code class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val userViewModel: UserViewModel = viewModel() MyApp(userViewModel) } } } @Composable fun MyApp(userViewModel: UserViewModel) { // initializing list of users val users by userViewModel.users.observeAsState(emptyList()) // UI components var showAddUserPage by remember { mutableStateOf(false) } var selectedUser by remember { mutableStateOf<User?>(null) } val errorMessage by userViewModel.errorMessage.observeAsState("") val snackbarHostState = remember { SnackbarHostState() } // Page Changing Logic if (showAddUserPage) { // Add New User AddUserPage( userViewModel = userViewModel, onBack = { showAddUserPage = false } ) } else if (selectedUser != null) { // Show User Details UserDetailPage( user = selectedUser!!, onBack = { selectedUser = null } ) } else { // Show homepage HomePage( users = users, errorMessage = errorMessage, onAddUserClicked = { showAddUserPage = true }, onUserClicked = { user -> selectedUser = user }, snackbarHostState = snackbarHostState ) } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun HomePage( users: List<User>, errorMessage: String, onAddUserClicked: () -> Unit, onUserClicked: (User) -> Unit, snackbarHostState: SnackbarHostState ) { Scaffold( // Top Bar Section topBar = { TopAppBar( // Top Bar - Title title = { Row(verticalAlignment = Alignment.CenterVertically) { // Top Bar - Title - Logo Icon( painter = painterResource(id = R.drawable.logo), contentDescription = "App Logo", tint = Color.Unspecified, modifier = Modifier .size(24.dp) .clip(CircleShape) ) // Top Bar - Title - App Name Spacer(modifier = Modifier.width(8.dp)) Text("366pi", color = Color.White) } }, // Top Bar - Title - Styling colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primary, titleContentColor = Color.White ) ) }, // Initializing SnackbarHost snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, // Adding Floating Add Button floatingActionButton = { FloatingActionButton(onClick = onAddUserClicked) { Icon(Icons.Default.Add, contentDescription = "Add User") } } ) { paddingValues -> Box( modifier = Modifier .fillMaxSize() .padding(paddingValues) ) { // If the error message is not empty if (errorMessage.isNotEmpty()) { // Show error message with a sad emoji Column( modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( painter = painterResource(id = R.drawable.ic_sad_emoji), contentDescription = "Sad Emoji", modifier = Modifier.size(64.dp) ) Spacer(modifier = Modifier.height(8.dp)) Text(errorMessage, color = Color.Red) } } else if (users.isEmpty()) { // If the list if users is empty // Show "No Users Available" message Column( modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally ) { Text( "No Users Available", color = Color.Gray, style = MaterialTheme.typography.bodyLarge ) } } // If the error message is empty proceed with the working else { Column(modifier = Modifier.padding(16.dp)) { // Passing fetched users data for displaying UserList(users, onUserClicked) } } } } } // Getting the list of users @Composable fun UserList(users: List<User>, onUserClicked: (User) -> Unit) { LazyColumn { // Iterating through each user items(users) { user -> // Passing each user data for displaying in HomePage UserItem(user, onClick = { onUserClicked(user) }) } } } // Displaying the fetched users data @Composable fun UserItem(user: User, onClick: () -> Unit) { // UserItem - Card Card( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 8.dp) .clickable(onClick = onClick), elevation = CardDefaults.cardElevation( defaultElevation = 8.dp ), shape = RoundedCornerShape(8.dp) ) { // UserItem - Card - Row Row( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { // UserItem - Card - Row - Column Column { // UserItem - Card - Row - Column - Text1 Text( text = "${user.firstName} ${user.lastName}", style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), color = MaterialTheme.colorScheme.onSurface ) // UserItem - Card - Row - Column - Text2 Text( text = user.email, style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurfaceVariant) ) } } } } // Using experimental material3 api for TopAppBar as old is depreciated @OptIn(ExperimentalMaterial3Api::class) @Composable fun AddUserPage(userViewModel: UserViewModel, onBack: () -> Unit) { // User Details var employeeFirstname by remember { mutableStateOf("") } var employeeLastname by remember { mutableStateOf("") } var employeeID by remember { mutableStateOf("") } var employeeEmail by remember { mutableStateOf("") } var employeeAddress by remember { mutableStateOf("") } var employeePhoneNumber by remember { mutableStateOf("") } var employeeCity by remember { mutableStateOf("") } var employeeState by remember { mutableStateOf("") } var employeePincode by remember { mutableStateOf("") } var employeeCountry by remember { mutableStateOf("") } val employeeDOB = remember { mutableStateOf("") } // Initial state setup for the DatePickerDialog. Specifies to show the picker initially val datePickerState = rememberDatePickerState(initialDisplayMode = DisplayMode.Picker) // State to control the visibility of the DatePickerDialog val openDialog = remember { mutableStateOf(false) } // Define the main color for the calendar picker val calendarPickerMainColor = Color(0xFF722276) // Misc val scope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } val errorMessage by userViewModel.errorMessage.observeAsState("") // Input Regex val namePattern = Regex("^[a-zA-Z]*$") val integerRegex = Regex("^[0-9]+\$") Scaffold( // topbar Section topBar = { TopAppBar( // topbar - title title = { Text("Add User") }, // topbar - styling colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primary, titleContentColor = Color.White ), // topbar - navigation [back button] navigationIcon = { IconButton(onClick = onBack) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = Color.White ) } } ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) } ) { paddingValues -> LazyColumn( modifier = Modifier .fillMaxSize() .padding(paddingValues) .padding(16.dp) ) { item { // Asking for first name OutlinedTextField( value = employeeFirstname, onValueChange = { // Checks for invalid characters scope.launch { if (namePattern.matches(it)) { employeeFirstname = it } else { snackbarHostState.showSnackbar("Name cannot contain special characters or numbers") } } }, label = { Text("First Name") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for last name OutlinedTextField( value = employeeLastname, onValueChange = { // Checks for invalid characters scope.launch { if (namePattern.matches(it)) { employeeLastname = it } else { snackbarHostState.showSnackbar("Name cannot contain special characters or numbers") } } }, label = { Text("Last Name") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for employee DOB OutlinedTextField( value = if (employeeDOB.value.isEmpty()) "Select Date of Birth" else employeeDOB.value, onValueChange = {}, readOnly = true, trailingIcon = { IconButton(onClick = { openDialog.value = true }) { Icon( imageVector = Icons.Filled.CalendarToday, contentDescription = "Select Date", tint = MaterialTheme.colorScheme.primary ) } }, label = { Text("Date of Birth") }, modifier = Modifier .fillMaxWidth() .clickable { openDialog.value = true } ) Spacer(modifier = Modifier.height(8.dp)) if (openDialog.value) { // DatePickerDialog component with custom colors and button behaviors DatePickerDialog( colors = DatePickerDefaults.colors( containerColor = Color(0xFFF5F0FF), ), onDismissRequest = { // Action when the dialog is dismissed without selecting a date openDialog.value = false }, confirmButton = { // Confirm button with custom action and styling TextButton( onClick = { // Action to set the selected date and close the dialog openDialog.value = false employeeDOB.value = datePickerState.selectedDateMillis?.convertMillisToDate() ?: "" } ) { Text("OK", color = calendarPickerMainColor) } }, dismissButton = { // Dismiss button to close the dialog without selecting a date TextButton( onClick = { openDialog.value = false } ) { Text("CANCEL", color = calendarPickerMainColor) } } ) { // The actual DatePicker component within the dialog DatePicker( state = datePickerState, colors = DatePickerDefaults.colors( selectedDayContainerColor = calendarPickerMainColor, selectedDayContentColor = Color.White, selectedYearContainerColor = calendarPickerMainColor, selectedYearContentColor = Color.White, todayContentColor = calendarPickerMainColor, todayDateBorderColor = calendarPickerMainColor ) ) } } // Asking for Employee ID OutlinedTextField( value = employeeID, onValueChange = { scope.launch { if (integerRegex.matches(it)) { employeeID = it } else { snackbarHostState.showSnackbar("Employee ID contains only numbers") } } }, label = { Text("Employee ID") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for Employee Email OutlinedTextField( value = employeeEmail, onValueChange = { employeeEmail = it }, label = { Text("Employee Email") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for Employee Address OutlinedTextField( value = employeeAddress, onValueChange = { employeeAddress = it }, label = { Text("Address") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for Employee Phone Number OutlinedTextField( value = employeePhoneNumber, onValueChange = { scope.launch { if (integerRegex.matches(it)) { employeePhoneNumber = it } else { snackbarHostState.showSnackbar("Phone number must be digits") } } }, label = { Text("Phone Number") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Phone) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for City OutlinedTextField( value = employeeCity, onValueChange = { scope.launch { if (namePattern.matches(it)) { employeeCity = it } else { snackbarHostState.showSnackbar("City Name cannot contain special characters or numbers") } } }, label = { Text("City") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for State OutlinedTextField( value = employeeState, onValueChange = { scope.launch { if (namePattern.matches(it)) { employeeState = it } else { snackbarHostState.showSnackbar("State Name cannot contain special characters or numbers") } } }, label = { Text("State") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for Zip Code OutlinedTextField( value = employeePincode, onValueChange = { scope.launch { if (integerRegex.matches(it)) { employeePincode = it } else { snackbarHostState.showSnackbar("Please Enter a valid input") } } }, label = { Text("Zip Code") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number) ) Spacer(modifier = Modifier.height(8.dp)) // Asking for Country OutlinedTextField( value = employeeCountry, onValueChange = { scope.launch { if (namePattern.matches(it)) { employeeCountry = it } else { snackbarHostState.showSnackbar("Country Name cannot contain special characters or numbers") } } }, label = { Text("Country") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text) ) Spacer(modifier = Modifier.height(16.dp)) // AddUser Button Button( onClick = { scope.launch { if (employeeFirstname.isEmpty()) { snackbarHostState.showSnackbar("First Name cannot be empty") } else if (employeeLastname.isEmpty()) { snackbarHostState.showSnackbar("Last Name cannot be empty") } else if (employeeID.isEmpty() || employeeID.length != 6) { snackbarHostState.showSnackbar("Employee ID must be of 6 digits") } else if (employeeDOB.value.isEmpty()) { snackbarHostState.showSnackbar("Employee DOB must be filled") } else if (employeeEmail.isEmpty()) { snackbarHostState.showSnackbar("Employee Email ID cannot be empty") } else if (employeeAddress.isEmpty()) { snackbarHostState.showSnackbar("Address cannot be empty") } else if (employeePhoneNumber.isEmpty() || employeePhoneNumber.length != 10) { snackbarHostState.showSnackbar("Phone number must be 10 digits") } else if (employeeCity.isEmpty()) { snackbarHostState.showSnackbar("City name cannot be empty") } else if (employeeState.isEmpty()) { snackbarHostState.showSnackbar("State name cannot be empty") } else if (employeePincode.isEmpty() || employeePincode.length != 6) { snackbarHostState.showSnackbar("Pincode must be of 6 digits") } else if (employeeCountry.isEmpty()) { snackbarHostState.showSnackbar("Country name cannot be empty") } else { // Creating a new user val newUser = User( id = employeeID.toInt(), firstName = employeeFirstname, lastName = employeeLastname, email = employeeEmail, address = employeeAddress, phoneNumber = employeePhoneNumber, city = employeeCity, state = employeeState, pinCode = employeePincode, country = employeeCountry, dob = employeeDOB.value ) userViewModel.addUser(newUser) if (errorMessage.isNotEmpty()) { snackbarHostState.showSnackbar(errorMessage) } else { snackbarHostState.showSnackbar("User created: ${newUser.firstName} ${newUser.lastName}") onBack() // Navigate back after successful addition } } } }, // AddUser Button - Styling modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp) .wrapContentWidth(Alignment.CenterHorizontally) ) { Text("Add User") } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun UserDetailPage(user: User, onBack: () -> Unit) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } Scaffold( // userdetail - topbar topBar = { TopAppBar( // userdetail - topbar - title title = { Text("User Details") }, // userdetail - topbar - styling colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primary, titleContentColor = Color.White ), // userdetail - topbar - navigation navigationIcon = { IconButton(onClick = onBack) { // userdetail - topbar - navigation - icon Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = Color.White ) } } ) }, floatingActionButton = { FloatingActionButton( onClick = { coroutineScope.launch { try { // Generate PDF and share it val pdfUri = generatePdfAndGetUri(user, context) sharePdf(pdfUri, context) } catch (e: Exception) { snackbarHostState.showSnackbar("Error exporting PDF: ${e.message}") } } } ) { Icon(Icons.Filled.Share, contentDescription = "Share") } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) } ) { paddingValues -> // userdetail - column Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { // userdetail - column - card Card( modifier = Modifier .size(120.dp) .clip(CircleShape), elevation = CardDefaults.cardElevation(8.dp) ) { // userdetail - column - card - box Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize() ) { // userdetail - column - card - box - icon (Profile Pic) Icon( painter = painterResource(id = R.drawable.ic_happy_emoji), // displaying dummy profile pic until functionality added contentDescription = "User Profile Picture", tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(72.dp) ) } } Spacer(modifier = Modifier.height(16.dp)) // Display user name Text( text = "${user.firstName} ${user.lastName}", style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), color = MaterialTheme.colorScheme.onBackground ) Spacer(modifier = Modifier.height(8.dp)) // Displaying user details DetailRow(label = "Date of Birth", value = user.dob) DetailRow(label = "Email", value = user.email) DetailRow(label = "Employee ID", value = user.id.toString()) DetailRow(label = "Address", value = user.address) DetailRow(label = "Phone Number", value = user.phoneNumber) DetailRow(label = "City", value = user.city) DetailRow(label = "State", value = user.state) DetailRow(label = "Zip Code", value = user.pinCode) DetailRow(label = "Country", value = user.country) } } } @Composable fun DetailRow(label: String, value: String) { // detailrow - card Card( modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp), elevation = CardDefaults.cardElevation(2.dp), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) ) { // detailrow - card - row Row( modifier = Modifier .fillMaxWidth() .padding(12.dp), horizontalArrangement = Arrangement.SpaceBetween ) { // detailrow - card - text1 (Parameter) Text( text = label, style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold), color = MaterialTheme.colorScheme.onSurface ) // detailrow - card - text2 (Value) Text( text = value, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } fun Long.convertMillisToDate(): String { // Create a calendar instance in the default time zone val calendar = Calendar.getInstance().apply { timeInMillis = this@convertMillisToDate // Adjust for the time zone offset to get the correct local date val zoneOffset = get(Calendar.ZONE_OFFSET) val dstOffset = get(Calendar.DST_OFFSET) add(Calendar.MILLISECOND, -(zoneOffset + dstOffset)) } // Format the calendar time in the specified format val sdf = SimpleDateFormat("MMM dd, yyyy", Locale.US) return sdf.format(calendar.time) } fun generatePdfAndGetUri(user: User, context: Context): Uri { // Create PDF file val pdfFile = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "${user.firstName}_${user.lastName}_${user.id}.pdf") PdfWriter(pdfFile.absolutePath).use { writer -> PdfDocument(writer).use { pdfDoc -> Document(pdfDoc).use { document -> document.add(Paragraph("User Details").setFontSize(20f).setBold()) document.add(Paragraph("Name: ${user.firstName} ${user.lastName}")) document.add(Paragraph("Date of Birth: ${user.dob}")) document.add(Paragraph("Email: ${user.email}")) document.add(Paragraph("Employee ID: ${user.id}")) document.add(Paragraph("Address: ${user.address}")) document.add(Paragraph("Phone Number: ${user.phoneNumber}")) document.add(Paragraph("City: ${user.city}")) document.add(Paragraph("State: ${user.state}")) document.add(Paragraph("Zip Code: ${user.pinCode}")) document.add(Paragraph("Country: ${user.country}")) } } } // Get URI for the file return FileProvider.getUriForFile( context, "${context.packageName}.provider", pdfFile ) } fun sharePdf(pdfUri: Uri, context: Context) { val shareIntent = Intent().apply { action = Intent.ACTION_SEND type = "application/pdf" putExtra(Intent.EXTRA_STREAM, pdfUri) setPackage("com.whatsapp") // Share via WhatsApp } context.startActivity(Intent.createChooser(shareIntent, "Share PDF")) }
Leave a Comment