Compare commits

..

5 Commits

Author SHA1 Message Date
8ea8dc66a6 Branch sebelum Redesign 2026-01-14 22:20:21 +07:00
17af72cbab Merge branch 'redesign' 2026-01-14 21:38:39 +07:00
e35fdd36d1 Penyesuaian Fungsi Submit Absensi 2026-01-14 21:32:31 +07:00
080b265607 Fitur Kamera deteksi wajah 2026-01-14 18:23:41 +07:00
f928fa47d9 UI/UX Redesign 2026-01-14 02:53:17 +07:00
31 changed files with 2300 additions and 1954 deletions

View File

@ -0,0 +1,65 @@
kotlin version: 2.0.21
error message: Daemon compilation failed: Connection to the Kotlin daemon has been unexpectedly lost. This might be caused by the daemon being killed by another process or the operating system, or by JVM crash.
org.jetbrains.kotlin.gradle.tasks.DaemonCrashedException: Connection to the Kotlin daemon has been unexpectedly lost. This might be caused by the daemon being killed by another process or the operating system, or by JVM crash.
at org.jetbrains.kotlin.gradle.tasks.TasksUtilsKt.wrapAndRethrowCompilationException(tasksUtils.kt:55)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:243)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.rmi.UnmarshalException: Error unmarshaling return header; nested exception is:
java.net.SocketException: Connection reset
at java.rmi/sun.rmi.transport.StreamRemoteCall.executeCall(Unknown Source)
at java.rmi/sun.rmi.server.UnicastRef.invoke(Unknown Source)
at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)
at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
at jdk.proxy22/jdk.proxy22.$Proxy449.compile(Unknown Source)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.incrementalCompilationWithDaemon(GradleKotlinCompilerWork.kt:331)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:235)
... 37 more
Caused by: java.net.SocketException: Connection reset
at java.base/sun.nio.ch.NioSocketImpl.implRead(Unknown Source)
at java.base/sun.nio.ch.NioSocketImpl.read(Unknown Source)
at java.base/sun.nio.ch.NioSocketImpl$1.read(Unknown Source)
at java.base/java.net.Socket$SocketInputStream.read(Unknown Source)
at java.base/java.io.BufferedInputStream.fill(Unknown Source)
at java.base/java.io.BufferedInputStream.implRead(Unknown Source)
at java.base/java.io.BufferedInputStream.read(Unknown Source)
at java.base/java.io.DataInputStream.readUnsignedByte(Unknown Source)
at java.base/java.io.DataInputStream.readByte(Unknown Source)
... 44 more

View File

@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

71
CHANGELOG.md Normal file
View File

@ -0,0 +1,71 @@
# Changelog
Semua perubahan penting pada proyek Aplikasi Absensi Akademik didokumentasikan dalam file ini.
## [Unreleased] - Versi Terbaru (Redesign & Backend Integration)
Perubahan besar-besaran dilakukan pada arsitektur aplikasi, beralih dari aplikasi *single-screen* sederhana menjadi aplikasi manajemen akademik yang komprehensif dengan autentikasi dan validasi keamanan.
### ✨ Fitur Baru (Added)
- **Sistem Autentikasi**:
- Menambahkan layar **Login** dan **Register**.
- Integrasi **JWT Authentication** (penyimpanan token via `SharedPreferences`).
- Manajemen sesi pengguna (Auto-login jika token tersimpan).
- **Manajemen Jadwal & Mata Kuliah**:
- Menambahkan fitur pengambilan data **Jadwal Kuliah** hari ini dari database.
- Dropdown pemilihan mata kuliah saat melakukan absensi.
- **Validasi Keamanan Tingkat Lanjut**:
- **Geofencing**: Validasi lokasi dalam radius 500m dari koordinat kampus.
- **Anti-Fake GPS**: Logika deteksi aplikasi *Mock Location* untuk mencegah kecurangan.
- **Face Detection**: Integrasi **CameraX + ML Kit** (tersirat dalam logika kamera baru) untuk mewajibkan deteksi wajah saat status "HADIR".
- **Fitur Kamera Kustom**:
- Mengganti intent kamera bawaan (`MediaStore`) dengan **CameraX** tertanam dalam UI.
- Fitur overlay kamera untuk panduan posisi wajah/dokumen.
- Dukungan kamera depan (selfie) dan belakang (dokumen).
- **Riwayat & Profil**:
- Menambahkan tab **Riwayat Absensi** untuk melihat log kehadiran dan bukti foto.
- Menambahkan tab **Profil** yang menampilkan data mahasiswa dan fitur Logout.
- **Multi-Status Absensi**:
- Dukungan untuk status **HADIR**, **SAKIT**, dan **IZIN**.
- Logika validasi berbeda untuk setiap status (Sakit/Izin tidak butuh radius lokasi).
### 🎨 Antarmuka & UX (Changed)
- **UI Overhaul (Material3)**:
- Redesign total menggunakan **Jetpack Compose Material3**.
- Penerapan tema identitas kampus (**Gold & Maroon**).
- Penggunaan komponen UI modern: `Card`, `NavigationBar` (Bottom Nav), `Gradient Button`.
- **Navigasi**:
- Implementasi **Bottom Navigation Bar** dengan 4 menu utama (Absensi, Kelas, Riwayat, Profil).
- Transisi antar layar (Login <-> Main <-> Register).
- **Feedback User**:
- Loading indicator saat proses API berjalan.
- Dialog error dan sukses yang lebih informatif dibandingkan `Toast` sederhana.
### ⚙️ Teknis & Backend (Changed)
- **Migrasi API**:
- **Sebelumnya**: Mengirim data hardcoded langsung ke Webhook N8N via `HttpURLConnection`.
- **Sekarang**: Berkomunikasi dengan **Backend Python Flask** (`/api/auth`, `/api/absensi`, `/api/jadwal`). Backend yang kemudian meneruskan data ke N8N.
- **Struktur Kode**:
- Refactoring dari *Single Activity Monolith* menjadi struktur modular.
- Pemisahan logic ke dalam:
- `AppConstants` (Konfigurasi URL & Koordinat).
- `UserPreferences` (Manajemen sesi lokal).
- `Data Classes` (Mahasiswa, Jadwal, Riwayat).
- Helper functions (Bitmap Converter, Distance Calculation).
### 🔥 Dihapus (Removed)
- Menghapus pengiriman data hardcoded (NPM "12345") pada fungsi `kirimKeN8n`.
- Menghapus penggunaan `MediaStore.ACTION_IMAGE_CAPTURE` (Intent kamera eksternal).
- Menghapus tampilan *single-screen* sederhana tanpa navigasi.
---
## [Legacy] - Versi Awal (Sebelum Redesign)
Versi purwarupa (prototype) untuk pengujian fungsionalitas dasar.
### Fitur
- Pengambilan titik koordinat GPS sederhana (`FusedLocationProvider`).
- Pengambilan foto menggunakan aplikasi kamera bawaan HP (`Intent`).
- Pengiriman data dummy langsung ke Webhook N8N.
- Tampilan UI dasar menggunakan `Column` dan `Button` standar.

130
README.md
View File

@ -1,95 +1,101 @@
# 📱 Aplikasi Absensi Akademik Berbasis Koordinat dan Foto (Mobile) # 📱 Aplikasi Absensi Akademik UBHARA Jaya (Mobile & Backend)
[📋 Changelog](./CHANGELOG.md)
## 📌 Deskripsi Proyek ## 📌 Deskripsi Proyek
Proyek ini merupakan **Tugas Project Akhir Mata Kuliah Pemrograman Mobile** yang bertujuan untuk membangun **aplikasi akademik berbasis mobile** dengan fokus pada **fitur absensi menggunakan data koordinat (GPS) dan pengambilan foto mahasiswa**. Proyek ini merupakan **Tugas Project Akhir Mata Kuliah Pemrograman Mobile** yang mengimplementasikan sistem absensi akademik berbasis **Client-Server**. Aplikasi mobile dibangun menggunakan **Kotlin (Jetpack Compose)**, sedangkan sisi server menggunakan **Python Flask** dengan database **MySQL**.
Aplikasi ini dirancang untuk meningkatkan **validitas kehadiran mahasiswa**, dengan memastikan bahwa absensi hanya dapat dilakukan apabila mahasiswa: Sistem ini dirancang dengan validasi ketat menggunakan **Geofencing (GPS)** dan **Deteksi Wajah (Face Detection)** untuk status kehadiran "Hadir", serta mendukung pengiriman bukti dokumen untuk status "Sakit/Izin". Seluruh data terintegrasi secara real-time ke **N8N Webhook** untuk pelaporan otomatis.
1. Berada pada **lokasi yang telah ditentukan**, dan
2. Melakukan **pengambilan foto (selfie) secara langsung saat absensi**
--- ---
## 🎯 Tujuan Proyek ## 🎯 Tujuan Proyek
- Mengimplementasikan **Location-Based Service (LBS)** pada aplikasi mobile - Mengimplementasikan arsitektur **REST API** antara Android dan Python Flask.
- Mengintegrasikan **kamera perangkat** untuk dokumentasi absensi - Menerapkan **Face Detection** menggunakan CameraX untuk memvalidasi kehadiran fisik mahasiswa.
- Mencegah kecurangan absensi (titip absen) - Mencegah kecurangan absensi dengan fitur deteksi **Fake GPS**.
- Mengembangkan aplikasi mobile akademik berbasis Android - Mengelola status kehadiran otomatis (**Auto-Alfa**) pada server jika mahasiswa lupa absen.
- Melatih kemampuan perancangan dan implementasi aplikasi mobile - Melatih kemampuan Fullstack Mobile Development (Backend, Database, & Mobile UI).
--- ---
## 🚀 Fitur Utama ## 🚀 Fitur Utama
- 🔐 **Login Pengguna (Mahasiswa)**
- 📍 **Pengambilan Koordinat Lokasi (Latitude & Longitude)** ### 📱 Sisi Mobile (Android - Jetpack Compose)
- 🏫 **Validasi Lokasi Absensi (Radius Area)** - **Modern UI**: Antarmuka modern menggunakan **Material3** & **Jetpack Compose**.
- 📸 **Pengambilan Foto Mahasiswa Saat Absensi** - **Smart Attendance**:
- 🕒 **Pencatatan Waktu Absensi** - **Hadir**: Wajib berada di radius kampus & Wajib deteksi wajah (Real-time).
- 📄 **Riwayat Kehadiran Mahasiswa** - **Sakit/Izin**: Wajib upload foto surat dokter/bukti (Tanpa validasi radius).
- ⚠️ **Notifikasi Absensi Ditolak jika Tidak Valid** - **Anti-Fraud**: Sistem mendeteksi dan menolak jika pengguna mengaktifkan **Fake GPS/Mock Location**.
- **Riwayat & Bukti**: Mahasiswa dapat melihat riwayat kehadiran beserta foto bukti yang tersimpan di server.
- **Profil Mahasiswa**: Menampilkan data akademik dan fitur logout.
### 💻 Sisi Backend (Python Flask)
- **JWT Authentication**: Keamanan berbasis token untuk setiap request.
- **Auto-Alfa Trigger**: Sistem otomatis menandai "TIDAK HADIR" jika jam kuliah berakhir dan mahasiswa belum absen.
- **N8N Integration**: Data absensi yang valid dikirim otomatis ke webhook N8N untuk notifikasi/reporting.
- **Image Handling**: Kompresi foto (Base64) otomatis untuk efisiensi penyimpanan database.
--- ---
## 🗺️ Mekanisme Absensi Berbasis Lokasi dan Foto ## 🗺️ Mekanisme Absensi
1. Mahasiswa melakukan **login**
2. Memilih menu **Absensi**
3. Sistem meminta:
- Izin **akses lokasi**
- Izin **akses kamera**
4. Aplikasi mengambil:
- 📍 **Koordinat lokasi mahasiswa**
- 📸 **Foto mahasiswa secara real-time**
5. Sistem melakukan validasi:
- Lokasi berada dalam **radius absensi**
- Foto berhasil diambil
6. Jika valid → **Absensi berhasil**
7. Jika tidak valid → **Absensi ditolak**
--- ### 1. Absensi Status "HADIR"
1. Mahasiswa memilih Jadwal Kelas yang aktif.
2. Sistem mengecek **GPS**:
- Apakah di dalam radius **500m** dari kampus?
- Apakah terdeteksi aplikasi **Fake GPS**?
3. Sistem membuka **Kamera Deteksi Wajah**:
- Tombol shutter hanya aktif jika wajah terdeteksi.
4. Data (Lokasi + Foto Wajah) dikirim ke server.
## 📸 Pengambilan Foto Saat Absensi ### 2. Absensi Status "SAKIT / IZIN"
- Foto diambil menggunakan **kamera depan (selfie)** 1. Mahasiswa memilih status Sakit atau Izin.
- Foto hanya dapat diambil **saat proses absensi** 2. Validasi lokasi dilewati (bisa absen dari rumah/RS).
- Foto disimpan sebagai **bukti kehadiran** 3. Sistem membuka **Kamera Dokumen** (Tanpa deteksi wajah).
- Foto dapat digunakan untuk: 4. Mahasiswa memfoto surat keterangan.
- Verifikasi manual oleh dosen 5. Data dikirim ke server.
- Dokumentasi akademik
### 3. Logika Auto-Alfa (Server)
- Setiap kali jadwal dimuat, server mengecek jadwal yang `jam_selesai < jam_sekarang`.
- Jika mahasiswa belum ada record absensi pada jam tersebut, server otomatis menginput status **"TIDAK HADIR"**.
--- ---
## 🛠️ Teknologi yang Digunakan ## 🛠️ Teknologi yang Digunakan
- **Platform** : Android - **Mobile Platform**: Android (Kotlin)
- **Bahasa Pemrograman** : Kotlin / Java - **UI Framework**: Jetpack Compose
- **Location Service** : - **Camera Engine**: CameraX + ML Kit (Face Analysis)
- Google Maps API - **Backend Framework**: Python Flask
- Fused Location Provider - **Database**: MySQL
- **Camera API** : CameraX / Camera2 - **Integrasi**: N8N Webhook
- **Database** : Firebase / SQLite / MySQL - **Protocol**: HTTP/REST (JSON)
- **Storage** : Firebase Storage / Local Storage
- **IDE** : Android Studio
--- ---
## 🔐 Izin Aplikasi (Permissions) ## 🔐 Izin Aplikasi (Permissions)
Aplikasi memerlukan izin berikut: Aplikasi memerlukan izin berikut:
- `ACCESS_FINE_LOCATION` - `INTERNET`: Koneksi ke Server API.
- `ACCESS_COARSE_LOCATION` - `ACCESS_FINE_LOCATION`: Validasi koordinat presisi tinggi.
- `CAMERA` - `ACCESS_COARSE_LOCATION`: Validasi koordinat jaringan.
- `INTERNET` - `CAMERA`: Pengambilan foto bukti kehadiran & deteksi wajah.
- `WRITE_EXTERNAL_STORAGE` (jika diperlukan)
--- ---
## 📂 Mockup ## 📂 Mockup
![mockup](Mockup.png) ![mockup](Mockup.png)
## Catatan: ## Catatan Konfigurasi:
- Kembangkan project dari starter yang sudah disediakan, tidak membuat dari awal. - **Server**: Pastikan `app.py` berjalan (`python app.py`) pada jaringan yang sama dengan HP.
- Untuk koordinat bisa ditambah/kurangi angka tertentu agar tidak memunculkan koordinat rumah masing-masing, data awal tetap dari GPS. - **Endpoint**: Sesuaikan `BASE_URL` pada file `MainActivity.kt` (Object `AppConstants`) dengan IP Laptop Anda.
```kotlin
const val BASE_URL = "[http://192.168.](http://192.168.)x.x:5000"
```
- **Fake GPS**: Pastikan mematikan aplikasi Mock Location saat pengujian fitur Hadir.
## Pengecekan: ## Pengecekan & Monitoring:
- https://ntfy.ubharajaya.ac.id/EAS - **Monitoring**: https://ntfy.ubharajaya.ac.id/EAS
- https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0 - **Data Spreadsheet**: https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0
## Webhook: ## Webhook N8N:
- test: https://n8n.lab.ubharajaya.ac.id/webhook-test/23c6993d-1792-48fb-ad1c-ffc78a3e6254 - **Test**: https://n8n.lab.ubharajaya.ac.id/webhook-test/23c6993d-1792-48fb-ad1c-ffc78a3e6254
- production: https://n8n.lab.ubharajaya.ac.id/webhook/23c6993d-1792-48fb-ad1c-ffc78a3e6254 - **Production**: https://n8n.lab.ubharajaya.ac.id/webhook/23c6993d-1792-48fb-ad1c-ffc78a3e6254

View File

@ -55,6 +55,10 @@ dependencies {
implementation("com.google.android.gms:play-services-location:21.0.1") implementation("com.google.android.gms:play-services-location:21.0.1")
implementation("androidx.compose.material:material-icons-extended:1.6.0") implementation("androidx.compose.material:material-icons-extended:1.6.0")
implementation(libs.androidx.compose.animation.core.lint) implementation(libs.androidx.compose.animation.core.lint)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.compose.ui.text)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
@ -64,4 +68,13 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)
// CameraX (Pastikan Anda sudah punya ini)
implementation("androidx.camera:camera-core:1.3.0")
implementation("androidx.camera:camera-camera2:1.3.0")
implementation("androidx.camera:camera-lifecycle:1.3.0")
implementation("androidx.camera:camera-view:1.3.0")
// Google ML Kit Face Detection
implementation("com.google.android.gms:play-services-mlkit-face-detection:17.1.0")
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@ -0,0 +1,36 @@
package id.ac.ubharajaya.sistemakademik
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.face.FaceDetection
import com.google.mlkit.vision.face.FaceDetectorOptions
class WajahAnalyzer(private val onFaceDetected: (Boolean) -> Unit) : ImageAnalysis.Analyzer {
// Settingan deteksi cepat (Performance Mode)
private val options = FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_NONE)
.build()
private val detector = FaceDetection.getClient(options)
@ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
detector.process(image)
.addOnSuccessListener { faces ->
// Callback: True jika ada wajah, False jika kosong
onFaceDetected(faces.isNotEmpty())
}
.addOnFailureListener { onFaceDetected(false) }
.addOnCompleteListener { imageProxy.close() } // Wajib tutup image
} else {
imageProxy.close()
}
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

File diff suppressed because it is too large Load Diff

View File

@ -6,3 +6,4 @@ PyJWT==2.8.0
bcrypt==4.1.2 bcrypt==4.1.2
python-dotenv==1.0.0 python-dotenv==1.0.0
requests==2.31.0 requests==2.31.0
Flask-APScheduler==1.13.1

View File

@ -9,6 +9,10 @@ lifecycleRuntimeKtx = "2.9.4"
activityCompose = "1.11.0" activityCompose = "1.11.0"
composeBom = "2024.09.00" composeBom = "2024.09.00"
animationCoreLint = "1.10.0" animationCoreLint = "1.10.0"
foundation = "1.10.0"
ui = "1.10.0"
uiGraphics = "1.10.0"
uiText = "1.10.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -26,6 +30,10 @@ androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-animation-core-lint = { group = "androidx.compose.animation", name = "animation-core-lint", version.ref = "animationCoreLint" } androidx-compose-animation-core-lint = { group = "androidx.compose.animation", name = "animation-core-lint", version.ref = "animationCoreLint" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "uiGraphics" }
androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }