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 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 longitude: Double,
val timestamp: Long, val timestamp: Long,
val fotoBase64: String, 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" val alamat: String = "Alamat tidak tersedia"
) )

View File

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

View File

@ -123,17 +123,38 @@ class PreviewFragment : Fragment() {
conn.doOutput = true conn.doOutput = true
val json = JSONObject().apply { 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("npm", absensi.npm)
put("nama", absensi.nama) put("nama", absensi.nama)
// 4⃣ Lokasi
put("latitude", absensi.latitude) put("latitude", absensi.latitude)
put("longitude", absensi.longitude) put("longitude", absensi.longitude)
put("timestamp", absensi.timestamp)
put("foto_base64", absensi.fotoBase64) // 5⃣ MATA KULIAH (INI YANG KOSONG KEMARIN)
put("address", absensi.alamat) put("mata_kuliah", absensi.namaMatkul)
put("distance_from_campus", 0) // 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") put("status", "hadir")
// 8⃣ FOTO BASE64
put("foto_base64", absensi.fotoBase64)
} }
conn.outputStream.use { conn.outputStream.use {
it.write(json.toString().toByteArray()) it.write(json.toString().toByteArray())
} }

View File

@ -1,2 +1,11 @@
package id.ac.ubharajaya.sistemakademik.utils 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:textAlignment="center"
android:layout_marginBottom="32dp"/> android:layout_marginBottom="32dp"/>
<Spinner
android:id="@+id/spinnerMatkul"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"/>
<Button <Button
android:id="@+id/btnAmbilFoto" android:id="@+id/btnAmbilFoto"
android:layout_width="match_parent" android:layout_width="match_parent"