fix: compilation error in MainScreen (import & TextField)

This commit is contained in:
Rakha adi 2025-11-19 23:08:08 +07:00
parent b3c44df833
commit 04315b8e58
7 changed files with 188 additions and 150 deletions

View File

@ -22,6 +22,14 @@
<option value="4f53b359-f3df-449a-ab3e-d112d5df446a" />
<option value="e2907e39-fda1-4832-99ff-fb7407d64db3" />
<option value="0e6a533b-db2a-4977-8228-bfeaa867eef9" />
<option value="461c1e05-de5d-4220-af67-2f29e576b9ea" />
<option value="9ebc3bf3-3f18-425d-a4fb-c87a6a121c9a" />
<option value="0c019b23-843c-4710-8469-2dd1211d89a3" />
<option value="8861f660-2aea-480d-a773-4e1f58fe9ac0" />
<option value="9523ff40-4e2b-4c96-b52e-69093f946698" />
<option value="80ef4e51-2808-48f4-9166-bf883578c6f0" />
<option value="2a8432cf-e7cb-4250-9c7c-00664ec22a28" />
<option value="52e099a9-0cd7-4716-8310-8a43635ca894" />
</set>
</value>
</entry>
@ -36,6 +44,7 @@
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/PanicButton.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainViewModel.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/NotificationSender.kt" />
<option value="file://$PROJECT_DIR$/app/build.gradle.kts" />
</set>
</entry>
</pendingWorkingSetItems>

View File

@ -59,6 +59,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest)
//praktikum 1
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("androidx.compose.material3:material3:1.1.1")
implementation("androidx.compose.material3:material3:1.2.0")
}

View File

@ -4,7 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.material3.MaterialTheme
import id.ac.ubharajaya.panicbutton.ui.theme.PanicButtonTheme
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
@ -12,8 +12,8 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
MainScreen(viewModel)
PanicButtonTheme {
MainScreen(viewModel = viewModel)
}
}
}

View File

@ -1,99 +1,144 @@
package id.ac.ubharajaya.panicbutton
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(viewModel: MainViewModel) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(18.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
tonalElevation = 4.dp,
shape = RoundedCornerShape(10.dp),
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
PanicButton(onClick = { viewModel.sendAlert() })
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxWidth(0.92f)
.padding(horizontal = 12.dp)
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(modifier = Modifier.padding(14.dp)) {
Text(text = "Jenis Kondisi Darurat", fontSize = 18.sp, color = MaterialTheme.colorScheme.error)
Card(
shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Jenis Kondisi Darurat",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(10.dp))
Spacer(modifier = Modifier.height(16.dp))
// list options directly from viewModel
for (opt in viewModel.options) {
Row(
verticalAlignment = Alignment.CenterVertically,
viewModel.options.forEach { opt ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 6.dp)
) {
Box(
modifier = Modifier
.size(40.dp)
.background(
color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.1f),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Text(text = opt.icon, fontSize = 22.sp)
}
Spacer(modifier = Modifier.width(16.dp))
Text(
text = opt.label,
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge
)
Checkbox(
checked = viewModel.isChecked(opt.label),
onCheckedChange = { isChecked -> viewModel.setChecked(opt.label, isChecked) },
colors = CheckboxDefaults.colors(checkedColor = MaterialTheme.colorScheme.primary)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = viewModel.otherNote,
onValueChange = { viewModel.otherNote = it },
label = { Text("Catatan tambahan (opsional)") },
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 6.dp)
) {
Box(
modifier = Modifier
.size(34.dp)
.background(color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.06f), shape = CircleShape),
contentAlignment = Alignment.Center
) {
Text(text = opt.icon)
}
.heightIn(min = 56.dp, max = 120.dp),
colors = TextFieldDefaults.colors(
focusedIndicatorColor = MaterialTheme.colorScheme.primary,
cursorColor = MaterialTheme.colorScheme.primary
)
)
Spacer(modifier = Modifier.width(12.dp))
Text(text = opt.label, modifier = Modifier.weight(1f))
val checked = viewModel.isChecked(opt.label)
Checkbox(
checked = checked,
onCheckedChange = { checkedValue -> viewModel.setChecked(opt.label, checkedValue) }
if (viewModel.isChecked("Lainnya") && viewModel.otherNote.isBlank()) {
Text(
"Catatan wajib diisi jika memilih 'Lainnya'",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 4.dp, top = 4.dp)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = viewModel.otherNote,
onValueChange = { newValue -> viewModel.otherNote = newValue },
label = { Text("Catatan tambahan (opsional)") },
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 56.dp, max = 140.dp)
)
Spacer(modifier = Modifier.height(8.dp))
if (viewModel.isChecked("Lainnya") && viewModel.otherNote.isBlank()) {
Text("Catatan wajib jika Anda memilih 'Lainnya'", color = MaterialTheme.colorScheme.error)
}
}
}
Spacer(modifier = Modifier.height(20.dp))
PanicButton(onClick = { viewModel.sendAlert() })
Spacer(modifier = Modifier.height(14.dp))
val dialog = viewModel.dialogMessage
if (!dialog.isNullOrBlank()) {
val dialogMessage = viewModel.dialogMessage
if (!dialogMessage.isNullOrBlank()) {
AlertDialog(
onDismissRequest = { viewModel.clearDialog() },
confirmButton = { TextButton(onClick = { viewModel.clearDialog() }) { Text("OK") } },
title = { Text("Notifikasi") },
text = { Text(dialog) }
confirmButton = {
TextButton(onClick = { viewModel.clearDialog() }) {
Text("OK", color = MaterialTheme.colorScheme.primary)
}
},
title = { Text("Notifikasi", style = MaterialTheme.typography.titleMedium) },
text = { Text(dialogMessage, style = MaterialTheme.typography.bodyMedium) },
containerColor = MaterialTheme.colorScheme.surface
)
}
}

View File

@ -1,66 +1,49 @@
package id.ac.ubharajaya.panicbutton
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
val options = listOf(
ReportOption("Kebakaran", "🔥"),
ReportOption("Banjir", "🌊"),
ReportOption("Gempa Bumi", "🌍"),
ReportOption("Huru Hara/Demonstrasi", "📣"),
ReportOption("Lainnya", "📝")
ReportOption("Gempa Bumi", "🌋"),
ReportOption("Huru Hara/Demonstrasi", "💥"),
ReportOption("Lainnya", "✏️")
)
// simple state holders
var checkedMap by mutableStateOf(options.associate { it.label to false }.toMutableMap())
private set
var otherNote by mutableStateOf("")
var dialogMessage by mutableStateOf<String?>(null)
fun isChecked(label: String): Boolean = checkedMap[label] ?: false
fun setChecked(label: String, checked: Boolean) { toggleOption(label, checked) }
fun toggleOption(label: String, checked: Boolean) {
val copy = checkedMap.toMutableMap()
copy[label] = checked
checkedMap = copy
// observable map so Compose recomposes on changes
private val _checkedState = mutableStateMapOf<String, Boolean>().apply {
options.forEach { put(it.label, false) }
}
fun sendAlert(senderName: String = "Rakha adi saputro 202310715083") {
val selected = checkedMap.filterValues { it }.keys.toList()
if (selected.isEmpty()) {
dialogMessage = "Pilih minimal satu jenis laporan sebelum mengirim."
return
}
if (selected.contains("Lainnya") && otherNote.isBlank()) {
dialogMessage = "Untuk opsi 'Lainnya', harap tambahkan catatan yang menjelaskan keadaan."
var otherNote by mutableStateOf("")
var dialogMessage by mutableStateOf<String?>(null)
fun isChecked(label: String): Boolean = _checkedState[label] == true
fun setChecked(label: String, isChecked: Boolean) {
_checkedState[label] = isChecked
}
fun sendAlert() {
val selectedOptions = options.filter { isChecked(it.label) }.map { it.label }
if (selectedOptions.isEmpty()) {
dialogMessage = "Pilih setidaknya satu kondisi darurat."
return
}
val message = buildString {
append("Jenis Laporan: ")
append(selected.joinToString(", "))
append("\nKeterangan: ")
append(if (otherNote.isBlank()) "-" else otherNote.trim())
append("\nPengirim: ")
append(senderName)
if (isChecked("Lainnya") && otherNote.isBlank()) {
dialogMessage = "Catatan wajib diisi jika Anda memilih 'Lainnya'."
return
}
viewModelScope.launch {
try {
val result = NotificationSender.sendNotification(message)
dialogMessage = result
} catch (e: Exception) {
dialogMessage = "Error: ${e.message}"
}
}
// Simulate sending data
dialogMessage = "Laporan terkirim!\nKondisi: ${selectedOptions.joinToString() }"
}
fun clearDialog() {

View File

@ -2,10 +2,11 @@ package id.ac.ubharajaya.panicbutton.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Blue_primary = Color(0xFF007BFF)
val Blue_secondary = Color(0xFF00A2E8)
val Dark_blue = Color(0xFF0056b3)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val Grey_light = Color(0xFFF5F5F5)
val Grey_dark = Color(0xFF333333)
val Red_cancel = Color(0xFFDC3545)

View File

@ -1,53 +1,53 @@
package id.ac.ubharajaya.panicbutton.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
primary = Blue_secondary,
secondary = Blue_primary,
background = Grey_dark,
surface = Color(0xFF1E1E1E),
onPrimary = Grey_dark,
onSecondary = Grey_dark,
onBackground = Grey_light,
onSurface = Grey_light,
error = Red_cancel
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
primary = Blue_primary,
secondary = Blue_secondary,
tertiary = Dark_blue,
background = Grey_light,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
onBackground = Grey_dark,
onSurface = Grey_dark,
error = Red_cancel
)
@Composable
fun PanicButtonTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
dynamicColor: Boolean = false, // Disable dynamic color
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
val view = LocalView.current
if (!view.isInEditMode) {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
MaterialTheme(