/** * Jetpack Compose UI — Practical Exercises * Mobile Application Development | SUZA | Semester II 2025/2026 * * Drop each composable into an Android Studio Compose project. * Every exercise includes an @Preview so you can inspect it without running. */ import androidx.compose.foundation.background 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.material3.* import androidx.compose.runtime.* 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.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp // 1. Simple Greeting @Composable fun Greeting1(name: String) { Text(text = "Hello, $name!", fontSize = 24.sp, fontWeight = FontWeight.Bold) } @Preview @Composable fun Greeting1Preview() { Greeting1("SUZA") } // 2. Business Card with Column + Row @Composable fun BusinessCard2() { Column( modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Box( Modifier.size(100.dp).clip(CircleShape).background(Color.Gray), contentAlignment = Alignment.Center ) { Text("MH", color = Color.White, fontSize = 32.sp) } Spacer(Modifier.height(16.dp)) Text("Masoud Hamad", fontSize = 22.sp, fontWeight = FontWeight.Bold) Text("Lecturer, SUZA", color = Color.Gray) } } @Preview @Composable fun BusinessCard2Preview() { BusinessCard2() } // 3. Counter with state @Composable fun Counter3() { var count by rememberSaveable { mutableStateOf(0) } Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp)) { Text("Count: $count", fontSize = 24.sp) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Button(onClick = { count-- }, enabled = count > 0) { Text("-") } Button(onClick = { count++ }) { Text("+") } OutlinedButton(onClick = { count = 0 }) { Text("Reset") } } } } @Preview @Composable fun Counter3Preview() { Counter3() } // 4. Tip Calculator @Composable fun TipCalc4() { var amount by remember { mutableStateOf("") } var percent by remember { mutableStateOf("15") } val tip = (amount.toDoubleOrNull() ?: 0.0) * (percent.toDoubleOrNull() ?: 0.0) / 100 Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { OutlinedTextField(value = amount, onValueChange = { amount = it }, label = { Text("Bill amount") }) OutlinedTextField(value = percent, onValueChange = { percent = it }, label = { Text("Tip %") }) Text("Tip: %.2f".format(tip), fontSize = 20.sp, fontWeight = FontWeight.Bold) } } @Preview @Composable fun TipCalc4Preview() { TipCalc4() } // 5. LazyColumn of items data class Item(val id: Int, val label: String) @Composable fun SimpleList5() { val items = remember { (1..20).map { Item(it, "Item #$it") } } LazyColumn(contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { items(items, key = { it.id }) { item -> Card(Modifier.fillMaxWidth()) { Text(item.label, modifier = Modifier.padding(16.dp)) } } } } @Preview @Composable fun SimpleList5Preview() { SimpleList5() } // 6. Stateless child + hoisted state — reusable SearchBar @Composable fun SearchBar6(query: String, onQueryChange: (String) -> Unit) { OutlinedTextField( value = query, onValueChange = onQueryChange, placeholder = { Text("Search…") }, modifier = Modifier.fillMaxWidth() ) } @Composable fun SearchableList6() { val all = remember { listOf("Apple", "Banana", "Cherry", "Date", "Grape", "Mango") } var q by remember { mutableStateOf("") } Column(Modifier.padding(16.dp)) { SearchBar6(q) { q = it } Spacer(Modifier.height(8.dp)) all.filter { it.contains(q, ignoreCase = true) }.forEach { Text(it, Modifier.fillMaxWidth().clickable { }.padding(8.dp)) } } } @Preview @Composable fun SearchableList6Preview() { SearchableList6() } // 7. Toggle theme preview (demo — real impl needs MaterialTheme colours) @Composable fun ThemedBox7(dark: Boolean, onToggle: (Boolean) -> Unit) { Surface(color = if (dark) Color.Black else Color.White, modifier = Modifier.fillMaxSize()) { Column(Modifier.padding(16.dp)) { Text( "Mode: ${if (dark) "Dark" else "Light"}", color = if (dark) Color.White else Color.Black, fontSize = 20.sp ) Switch(checked = dark, onCheckedChange = onToggle) } } } @Preview @Composable fun ThemedBox7Preview() { var d by remember { mutableStateOf(false) } ThemedBox7(d) { d = it } } // 8. Conditional composition — loading / success / error sealed interface UiState { object Loading : UiState data class Success(val data: String) : UiState data class Error(val msg: String) : UiState } @Composable fun StatefulScreen8(state: UiState, onRetry: () -> Unit) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { when (state) { UiState.Loading -> CircularProgressIndicator() is UiState.Success -> Text(state.data, fontSize = 22.sp) is UiState.Error -> Column(horizontalAlignment = Alignment.CenterHorizontally) { Text(state.msg, color = Color.Red) Button(onClick = onRetry) { Text("Retry") } } } } } @Preview @Composable fun Loading() { StatefulScreen8(UiState.Loading) {} } @Preview @Composable fun SuccessS() { StatefulScreen8(UiState.Success("Hello!")) {} } @Preview @Composable fun ErrorS() { StatefulScreen8(UiState.Error("Oops")) {} } // 9. List with animated add/remove (conceptual — see Lab 5 for full example) @Composable fun TodoList9() { val items = remember { mutableStateListOf("Read Lecture 3", "Complete Lab 2") } var input by remember { mutableStateOf("") } Column(Modifier.padding(16.dp)) { Row { OutlinedTextField(value = input, onValueChange = { input = it }, modifier = Modifier.weight(1f)) Button(onClick = { if (input.isNotBlank()) { items.add(input); input = "" } }) { Text("Add") } } Spacer(Modifier.height(8.dp)) items.forEach { i -> Row(Modifier.fillMaxWidth().padding(4.dp), verticalAlignment = Alignment.CenterVertically) { Text(i, Modifier.weight(1f)) TextButton(onClick = { items.remove(i) }) { Text("✕") } } } } } @Preview @Composable fun TodoList9Preview() { TodoList9() } // 10. Modifier ordering demo @Composable fun ModifierOrder10() { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { // padding THEN background -> background fills only the inner area Text( "Padding then background", Modifier.padding(16.dp).background(Color.Yellow) ) // background THEN padding -> background fills outside, padding inside Text( "Background then padding", Modifier.background(Color.Yellow).padding(16.dp) ) // Observe the visual difference. } } @Preview @Composable fun ModifierOrder10Preview() { ModifierOrder10() }