Frist Commit

This commit is contained in:
202310715280 FADLAN RIVALDI 2026-01-14 20:26:15 +07:00
parent e88ada64b2
commit 5b39cd9673
6 changed files with 147 additions and 91 deletions

View File

@ -1,2 +1,6 @@
package id.ac.ubharajaya.sistemakademik.data
data class MataKuliah(
val kode: String,
val nama: String
)

View File

@ -11,6 +11,12 @@ data class AbsensiData(
val longitude: Double,
val timestamp: Long,
val fotoBase64: String,
// 🔽 FIELD BARU (WAJIB DITARUH SEBELUM DEFAULT VALUE)
val kodeMatkul: String,
val namaMatkul: String,
// 🔽 FIELD DENGAN DEFAULT VALUE HARUS PALING BAWAH
val alamat: String = "Alamat tidak tersedia"
)

View File

@ -11,10 +11,7 @@ import android.util.Base64
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
@ -23,6 +20,8 @@ import com.google.android.gms.location.LocationServices
import id.ac.ubharajaya.sistemakademik.R
import id.ac.ubharajaya.sistemakademik.data.AbsensiData
import id.ac.ubharajaya.sistemakademik.data.DataHolder
import id.ac.ubharajaya.sistemakademik.data.MataKuliah
import id.ac.ubharajaya.sistemakademik.utils.DummyData
import java.io.ByteArrayOutputStream
class AbsensiFragment : Fragment() {
@ -31,59 +30,48 @@ class AbsensiFragment : Fragment() {
private lateinit var tvLokasi: TextView
private lateinit var btnAmbilFoto: Button
private lateinit var btnLanjut: Button
private lateinit var spinnerMatkul: Spinner
private var foto: Bitmap? = null
private var latitude: Double? = null
private var longitude: Double? = null
private var selectedMatkul: MataKuliah? = null
private val fusedLocationClient by lazy {
LocationServices.getFusedLocationProviderClient(requireActivity())
}
// Permission launcher untuk lokasi
private val locationPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) {
ambilLokasiGPS()
} else {
Toast.makeText(context, "Izin lokasi ditolak", Toast.LENGTH_SHORT).show()
// ===== Permission Launcher =====
private val locationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) ambilLokasiGPS()
else Toast.makeText(context, "Izin lokasi ditolak", Toast.LENGTH_SHORT).show()
}
}
// Camera launcher
private val cameraLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val bitmap = result.data?.extras?.get("data") as? Bitmap
if (bitmap != null) {
foto = bitmap
ivPreview.setImageBitmap(bitmap)
cekKelengkapanData()
Toast.makeText(context, "Foto berhasil diambil!", Toast.LENGTH_SHORT).show()
private val cameraPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) bukaKamera()
else Toast.makeText(context, "Izin kamera ditolak", Toast.LENGTH_SHORT).show()
}
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val bitmap = result.data?.extras?.get("data") as? Bitmap
bitmap?.let {
foto = it
ivPreview.setImageBitmap(it)
cekKelengkapanData()
Toast.makeText(context, "Foto berhasil diambil!", Toast.LENGTH_SHORT).show()
}
}
}
}
// Permission launcher untuk kamera
private val cameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) {
bukaKamera()
} else {
Toast.makeText(context, "Izin kamera ditolak", Toast.LENGTH_SHORT).show()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_absensi, container, false)
}
): View = inflater.inflate(R.layout.fragment_absensi, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -92,87 +80,106 @@ class AbsensiFragment : Fragment() {
tvLokasi = view.findViewById(R.id.tvLokasi)
btnAmbilFoto = view.findViewById(R.id.btnAmbilFoto)
btnLanjut = view.findViewById(R.id.btnLanjutPreview)
spinnerMatkul = view.findViewById(R.id.spinnerMatkul)
// Cek profile sudah diisi atau belum
if (DataHolder.userProfile.nama.isEmpty()) {
Toast.makeText(context, "Isi profile dulu di tab Profil!", Toast.LENGTH_LONG).show()
}
// Request permission lokasi
setupSpinnerMatkul()
requestLocationPermission()
btnAmbilFoto.setOnClickListener {
requestCameraPermission()
}
btnAmbilFoto.setOnClickListener { requestCameraPermission() }
btnLanjut.setOnClickListener {
if (foto != null && latitude != null && longitude != null) {
simpanDataSementara()
findNavController().navigate(R.id.action_absensi_to_preview)
}
simpanDataSementara()
findNavController().navigate(R.id.action_absensi_to_preview)
}
}
// ===== Mata Kuliah =====
private fun setupSpinnerMatkul() {
val matkulList = DummyData.mataKuliahList
val adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
matkulList.map { "${it.kode} - ${it.nama}" }
)
spinnerMatkul.adapter = adapter
spinnerMatkul.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>, view: View?, position: Int, id: Long
) {
selectedMatkul = matkulList[position]
cekKelengkapanData()
}
override fun onNothingSelected(parent: AdapterView<*>) {
selectedMatkul = null
cekKelengkapanData()
}
}
}
// ===== Location =====
private fun requestLocationPermission() {
when {
ContextCompat.checkSelfPermission(
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
ambilLokasiGPS()
}
else -> {
locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
) == PackageManager.PERMISSION_GRANTED
) {
ambilLokasiGPS()
} else {
locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
}
private fun ambilLokasiGPS() {
try {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
latitude = location.latitude
longitude = location.longitude
tvLokasi.text = "📍 GPS Ready\nLat: ${location.latitude}\nLon: ${location.longitude}"
cekKelengkapanData()
} else {
tvLokasi.text = "❌ Lokasi tidak tersedia\nHidupkan GPS"
}
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
latitude = location.latitude
longitude = location.longitude
tvLokasi.text =
"📍 GPS Ready\nLat: ${location.latitude}\nLon: ${location.longitude}"
cekKelengkapanData()
} else {
tvLokasi.text = "❌ Lokasi tidak tersedia"
}
} catch (e: SecurityException) {
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
// ===== Camera =====
private fun requestCameraPermission() {
when {
ContextCompat.checkSelfPermission(
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
bukaKamera()
}
else -> {
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
) == PackageManager.PERMISSION_GRANTED
) {
bukaKamera()
} else {
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
private fun bukaKamera() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
cameraLauncher.launch(intent)
cameraLauncher.launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE))
}
// ===== Validation =====
private fun cekKelengkapanData() {
btnLanjut.isEnabled = foto != null && latitude != null && longitude != null
btnLanjut.isEnabled =
foto != null &&
latitude != null &&
longitude != null &&
selectedMatkul != null
}
// ===== Save =====
private fun simpanDataSementara() {
val bitmap = foto ?: return
val lat = latitude ?: return
val lon = longitude ?: return
val fotoBase64 = bitmapToBase64(bitmap)
val matkul = selectedMatkul ?: return
DataHolder.currentAbsensi = AbsensiData(
nama = DataHolder.userProfile.nama,
@ -180,8 +187,10 @@ class AbsensiFragment : Fragment() {
latitude = lat,
longitude = lon,
timestamp = System.currentTimeMillis(),
fotoBase64 = fotoBase64,
alamat = "Alamat akan diambil saat preview"
fotoBase64 = bitmapToBase64(bitmap),
alamat = "Alamat akan diambil saat preview",
kodeMatkul = matkul.kode,
namaMatkul = matkul.nama
)
}

View File

@ -123,17 +123,38 @@ class PreviewFragment : Fragment() {
conn.doOutput = true
val json = JSONObject().apply {
// 1⃣ HARUS ADA
put("timestamp", absensi.timestamp)
// 2⃣ IP address (kalau belum ada, kirim dummy)
put("ip_addr", "android")
// 3⃣ Identitas
put("npm", absensi.npm)
put("nama", absensi.nama)
// 4⃣ Lokasi
put("latitude", absensi.latitude)
put("longitude", absensi.longitude)
put("timestamp", absensi.timestamp)
put("foto_base64", absensi.fotoBase64)
put("address", absensi.alamat)
put("distance_from_campus", 0)
// 5⃣ MATA KULIAH (INI YANG KOSONG KEMARIN)
put("mata_kuliah", absensi.namaMatkul)
// atau kalau mau sekalian kode:
// put("mata_kuliah", "${absensi.kodeMatkul} - ${absensi.namaMatkul}")
// 6⃣ Photo (boleh string apa aja / URL / label)
put("photo", "camera")
// 7⃣ Status
put("status", "hadir")
// 8⃣ FOTO BASE64
put("foto_base64", absensi.fotoBase64)
}
conn.outputStream.use {
it.write(json.toString().toByteArray())
}

View File

@ -1,2 +1,11 @@
package id.ac.ubharajaya.sistemakademik.utils
import id.ac.ubharajaya.sistemakademik.data.MataKuliah
object DummyData {
val mataKuliahList = listOf(
MataKuliah("IF301", "Pemrograman Mobile"),
MataKuliah("IF302", "Kecerdasan Buatan"),
MataKuliah("IF303", "Basis Data")
)
}

View File

@ -32,6 +32,13 @@
android:textAlignment="center"
android:layout_marginBottom="32dp"/>
<Spinner
android:id="@+id/spinnerMatkul"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"/>
<Button
android:id="@+id/btnAmbilFoto"
android:layout_width="match_parent"