Compare commits
No commits in common. "8ea8dc66a612de72e4195494966a2b9878c2db38" and "14c4c19a4f0b56fabb25d2cd44d5e395224ce7c6" have entirely different histories.
8ea8dc66a6
...
14c4c19a4f
@ -1,65 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
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
@ -1,71 +0,0 @@
|
|||||||
# 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
@ -1,101 +1,95 @@
|
|||||||
# 📱 Aplikasi Absensi Akademik UBHARA Jaya (Mobile & Backend)
|
# 📱 Aplikasi Absensi Akademik Berbasis Koordinat dan Foto (Mobile)
|
||||||
|
|
||||||
[📋 Changelog](./CHANGELOG.md)
|
|
||||||
|
|
||||||
## 📌 Deskripsi Proyek
|
## 📌 Deskripsi Proyek
|
||||||
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**.
|
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**.
|
||||||
|
|
||||||
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.
|
Aplikasi ini dirancang untuk meningkatkan **validitas kehadiran mahasiswa**, dengan memastikan bahwa absensi hanya dapat dilakukan apabila mahasiswa:
|
||||||
|
1. Berada pada **lokasi yang telah ditentukan**, dan
|
||||||
|
2. Melakukan **pengambilan foto (selfie) secara langsung saat absensi**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Tujuan Proyek
|
## 🎯 Tujuan Proyek
|
||||||
- Mengimplementasikan arsitektur **REST API** antara Android dan Python Flask.
|
- Mengimplementasikan **Location-Based Service (LBS)** pada aplikasi mobile
|
||||||
- Menerapkan **Face Detection** menggunakan CameraX untuk memvalidasi kehadiran fisik mahasiswa.
|
- Mengintegrasikan **kamera perangkat** untuk dokumentasi absensi
|
||||||
- Mencegah kecurangan absensi dengan fitur deteksi **Fake GPS**.
|
- Mencegah kecurangan absensi (titip absen)
|
||||||
- Mengelola status kehadiran otomatis (**Auto-Alfa**) pada server jika mahasiswa lupa absen.
|
- Mengembangkan aplikasi mobile akademik berbasis Android
|
||||||
- Melatih kemampuan Fullstack Mobile Development (Backend, Database, & Mobile UI).
|
- Melatih kemampuan perancangan dan implementasi aplikasi mobile
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Fitur Utama
|
## 🚀 Fitur Utama
|
||||||
|
- 🔐 **Login Pengguna (Mahasiswa)**
|
||||||
### 📱 Sisi Mobile (Android - Jetpack Compose)
|
- 📍 **Pengambilan Koordinat Lokasi (Latitude & Longitude)**
|
||||||
- **Modern UI**: Antarmuka modern menggunakan **Material3** & **Jetpack Compose**.
|
- 🏫 **Validasi Lokasi Absensi (Radius Area)**
|
||||||
- **Smart Attendance**:
|
- 📸 **Pengambilan Foto Mahasiswa Saat Absensi**
|
||||||
- **Hadir**: Wajib berada di radius kampus & Wajib deteksi wajah (Real-time).
|
- 🕒 **Pencatatan Waktu Absensi**
|
||||||
- **Sakit/Izin**: Wajib upload foto surat dokter/bukti (Tanpa validasi radius).
|
- 📄 **Riwayat Kehadiran Mahasiswa**
|
||||||
- **Anti-Fraud**: Sistem mendeteksi dan menolak jika pengguna mengaktifkan **Fake GPS/Mock Location**.
|
- ⚠️ **Notifikasi Absensi Ditolak jika Tidak Valid**
|
||||||
- **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
|
## 🗺️ Mekanisme Absensi Berbasis Lokasi dan Foto
|
||||||
|
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.
|
|
||||||
|
|
||||||
### 2. Absensi Status "SAKIT / IZIN"
|
## 📸 Pengambilan Foto Saat Absensi
|
||||||
1. Mahasiswa memilih status Sakit atau Izin.
|
- Foto diambil menggunakan **kamera depan (selfie)**
|
||||||
2. Validasi lokasi dilewati (bisa absen dari rumah/RS).
|
- Foto hanya dapat diambil **saat proses absensi**
|
||||||
3. Sistem membuka **Kamera Dokumen** (Tanpa deteksi wajah).
|
- Foto disimpan sebagai **bukti kehadiran**
|
||||||
4. Mahasiswa memfoto surat keterangan.
|
- Foto dapat digunakan untuk:
|
||||||
5. Data dikirim ke server.
|
- Verifikasi manual oleh dosen
|
||||||
|
- 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
|
||||||
- **Mobile Platform**: Android (Kotlin)
|
- **Platform** : Android
|
||||||
- **UI Framework**: Jetpack Compose
|
- **Bahasa Pemrograman** : Kotlin / Java
|
||||||
- **Camera Engine**: CameraX + ML Kit (Face Analysis)
|
- **Location Service** :
|
||||||
- **Backend Framework**: Python Flask
|
- Google Maps API
|
||||||
- **Database**: MySQL
|
- Fused Location Provider
|
||||||
- **Integrasi**: N8N Webhook
|
- **Camera API** : CameraX / Camera2
|
||||||
- **Protocol**: HTTP/REST (JSON)
|
- **Database** : Firebase / SQLite / MySQL
|
||||||
|
- **Storage** : Firebase Storage / Local Storage
|
||||||
|
- **IDE** : Android Studio
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 Izin Aplikasi (Permissions)
|
## 🔐 Izin Aplikasi (Permissions)
|
||||||
Aplikasi memerlukan izin berikut:
|
Aplikasi memerlukan izin berikut:
|
||||||
- `INTERNET`: Koneksi ke Server API.
|
- `ACCESS_FINE_LOCATION`
|
||||||
- `ACCESS_FINE_LOCATION`: Validasi koordinat presisi tinggi.
|
- `ACCESS_COARSE_LOCATION`
|
||||||
- `ACCESS_COARSE_LOCATION`: Validasi koordinat jaringan.
|
- `CAMERA`
|
||||||
- `CAMERA`: Pengambilan foto bukti kehadiran & deteksi wajah.
|
- `INTERNET`
|
||||||
|
- `WRITE_EXTERNAL_STORAGE` (jika diperlukan)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 Mockup
|
## 📂 Mockup
|
||||||

|

|
||||||
|
|
||||||
## Catatan Konfigurasi:
|
## Catatan:
|
||||||
- **Server**: Pastikan `app.py` berjalan (`python app.py`) pada jaringan yang sama dengan HP.
|
- Kembangkan project dari starter yang sudah disediakan, tidak membuat dari awal.
|
||||||
- **Endpoint**: Sesuaikan `BASE_URL` pada file `MainActivity.kt` (Object `AppConstants`) dengan IP Laptop Anda.
|
- Untuk koordinat bisa ditambah/kurangi angka tertentu agar tidak memunculkan koordinat rumah masing-masing, data awal tetap dari GPS.
|
||||||
```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 & Monitoring:
|
## Pengecekan:
|
||||||
- **Monitoring**: https://ntfy.ubharajaya.ac.id/EAS
|
- https://ntfy.ubharajaya.ac.id/EAS
|
||||||
- **Data Spreadsheet**: https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0
|
- https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0
|
||||||
|
|
||||||
## Webhook N8N:
|
## Webhook:
|
||||||
- **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
|
||||||
@ -55,10 +55,6 @@ 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)
|
||||||
@ -68,13 +64,4 @@ 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")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 172 KiB |
@ -1,36 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<?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>
|
|
||||||
6
app/src/main/res/mipmap-anydpi/ic_launcher.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?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>
|
||||||
6
app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?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>
|
||||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 7.6 KiB |
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#FFFFFF</color>
|
|
||||||
</resources>
|
|
||||||
1073
backend/app.py
@ -6,4 +6,3 @@ 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
|
|
||||||
@ -9,10 +9,6 @@ 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" }
|
||||||
@ -30,10 +26,6 @@ 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" }
|
||||||
|
|||||||