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="4f53b359-f3df-449a-ab3e-d112d5df446a" />
<option value="e2907e39-fda1-4832-99ff-fb7407d64db3" /> <option value="e2907e39-fda1-4832-99ff-fb7407d64db3" />
<option value="0e6a533b-db2a-4977-8228-bfeaa867eef9" /> <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> </set>
</value> </value>
</entry> </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/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/MainViewModel.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/NotificationSender.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> </set>
</entry> </entry>
</pendingWorkingSetItems> </pendingWorkingSetItems>

View File

@ -59,6 +59,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)
//praktikum 1 //praktikum 1
implementation("com.squareup.okhttp3:okhttp:4.11.0") 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.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.material3.MaterialTheme import id.ac.ubharajaya.panicbutton.ui.theme.PanicButtonTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels() private val viewModel: MainViewModel by viewModels()
@ -12,8 +12,8 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
MaterialTheme { PanicButtonTheme {
MainScreen(viewModel) MainScreen(viewModel = viewModel)
} }
} }
} }

View File

@ -1,99 +1,144 @@
package id.ac.ubharajaya.panicbutton package id.ac.ubharajaya.panicbutton
import androidx.compose.foundation.background 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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape 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.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MainScreen(viewModel: MainViewModel) { fun MainScreen(viewModel: MainViewModel) {
Column( Scaffold(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize() bottomBar = {
.padding(18.dp), PanicButton(onClick = { viewModel.sendAlert() })
verticalArrangement = Arrangement.Center, }
horizontalAlignment = Alignment.CenterHorizontally ) { paddingValues ->
) { Column(
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
tonalElevation = 4.dp,
shape = RoundedCornerShape(10.dp),
modifier = Modifier modifier = Modifier
.fillMaxWidth(0.92f) .fillMaxSize()
.padding(horizontal = 12.dp) .padding(paddingValues)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Column(modifier = Modifier.padding(14.dp)) { Card(
Text(text = "Jenis Kondisi Darurat", fontSize = 18.sp, color = MaterialTheme.colorScheme.error) 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 viewModel.options.forEach { opt ->
for (opt in viewModel.options) { Row(
Row( verticalAlignment = Alignment.CenterVertically,
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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 6.dp) .heightIn(min = 56.dp, max = 120.dp),
) { colors = TextFieldDefaults.colors(
Box( focusedIndicatorColor = MaterialTheme.colorScheme.primary,
modifier = Modifier cursorColor = MaterialTheme.colorScheme.primary
.size(34.dp) )
.background(color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.06f), shape = CircleShape), )
contentAlignment = Alignment.Center
) {
Text(text = opt.icon)
}
Spacer(modifier = Modifier.width(12.dp)) if (viewModel.isChecked("Lainnya") && viewModel.otherNote.isBlank()) {
Text(
Text(text = opt.label, modifier = Modifier.weight(1f)) "Catatan wajib diisi jika memilih 'Lainnya'",
color = MaterialTheme.colorScheme.error,
val checked = viewModel.isChecked(opt.label) style = MaterialTheme.typography.bodySmall,
Checkbox( modifier = Modifier.padding(start = 4.dp, top = 4.dp)
checked = checked,
onCheckedChange = { checkedValue -> viewModel.setChecked(opt.label, checkedValue) }
) )
} }
} }
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)) val dialogMessage = viewModel.dialogMessage
if (!dialogMessage.isNullOrBlank()) {
PanicButton(onClick = { viewModel.sendAlert() })
Spacer(modifier = Modifier.height(14.dp))
val dialog = viewModel.dialogMessage
if (!dialog.isNullOrBlank()) {
AlertDialog( AlertDialog(
onDismissRequest = { viewModel.clearDialog() }, onDismissRequest = { viewModel.clearDialog() },
confirmButton = { TextButton(onClick = { viewModel.clearDialog() }) { Text("OK") } }, confirmButton = {
title = { Text("Notifikasi") }, TextButton(onClick = { viewModel.clearDialog() }) {
text = { Text(dialog) } 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 package id.ac.ubharajaya.panicbutton
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() { class MainViewModel : ViewModel() {
val options = listOf( val options = listOf(
ReportOption("Kebakaran", "🔥"), ReportOption("Kebakaran", "🔥"),
ReportOption("Banjir", "🌊"), ReportOption("Banjir", "🌊"),
ReportOption("Gempa Bumi", "🌍"), ReportOption("Gempa Bumi", "🌋"),
ReportOption("Huru Hara/Demonstrasi", "📣"), ReportOption("Huru Hara/Demonstrasi", "💥"),
ReportOption("Lainnya", "📝") ReportOption("Lainnya", "✏️")
) )
// simple state holders // observable map so Compose recomposes on changes
var checkedMap by mutableStateOf(options.associate { it.label to false }.toMutableMap()) private val _checkedState = mutableStateMapOf<String, Boolean>().apply {
private set options.forEach { put(it.label, false) }
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
} }
fun sendAlert(senderName: String = "Rakha adi saputro 202310715083") { var otherNote by mutableStateOf("")
val selected = checkedMap.filterValues { it }.keys.toList() var dialogMessage by mutableStateOf<String?>(null)
if (selected.isEmpty()) {
dialogMessage = "Pilih minimal satu jenis laporan sebelum mengirim." fun isChecked(label: String): Boolean = _checkedState[label] == true
return
} fun setChecked(label: String, isChecked: Boolean) {
if (selected.contains("Lainnya") && otherNote.isBlank()) { _checkedState[label] = isChecked
dialogMessage = "Untuk opsi 'Lainnya', harap tambahkan catatan yang menjelaskan keadaan." }
fun sendAlert() {
val selectedOptions = options.filter { isChecked(it.label) }.map { it.label }
if (selectedOptions.isEmpty()) {
dialogMessage = "Pilih setidaknya satu kondisi darurat."
return return
} }
val message = buildString { if (isChecked("Lainnya") && otherNote.isBlank()) {
append("Jenis Laporan: ") dialogMessage = "Catatan wajib diisi jika Anda memilih 'Lainnya'."
append(selected.joinToString(", ")) return
append("\nKeterangan: ")
append(if (otherNote.isBlank()) "-" else otherNote.trim())
append("\nPengirim: ")
append(senderName)
} }
viewModelScope.launch { // Simulate sending data
try { dialogMessage = "Laporan terkirim!\nKondisi: ${selectedOptions.joinToString() }"
val result = NotificationSender.sendNotification(message)
dialogMessage = result
} catch (e: Exception) {
dialogMessage = "Error: ${e.message}"
}
}
} }
fun clearDialog() { fun clearDialog() {

View File

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

View File

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