Perbaikan UI/UX, Batas Waktu, Jarak, Perbaikan Riwayat, Status Presensi

This commit is contained in:
HadiPrakosou-HD 2026-01-14 23:33:34 +07:00
parent fc673ccd20
commit 033bc90b12
13 changed files with 745 additions and 315 deletions

6
.idea/copilot.data.migration.agent.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.ask.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AskMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.edit.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EditMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-13T15:21:18.861135600Z"> <DropdownSelection timestamp="2026-01-14T14:36:34.061641400Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=RR8RB0A0F3R" /> <DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\HadeGP\.android\avd\Pixel_4.avd" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -0,0 +1,50 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
</profile>
</component>

10
.idea/material_theme_project_new.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="userId" value="-23b48593:19bb9e50117:-7fff" />
</MTProjectMetadataState>
</option>
</component>
</project>

138
README.md
View File

@ -1,97 +1,81 @@
# 📱 Aplikasi Absensi Akademik Berbasis Koordinat dan Foto (Mobile) Nama : Hadi Guna Prakoso
NPM : 202310715312
## 📌 Deskripsi Proyek # Sistem Akademik Ubhara Jaya - Absensi LBS & Foto
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**.
Aplikasi ini dirancang untuk meningkatkan **validitas kehadiran mahasiswa**, dengan memastikan bahwa absensi hanya dapat dilakukan apabila mahasiswa: Sistem Akademik Ubhara Jaya adalah aplikasi mobile berbasis Android yang dirancang khusus untuk meningkatkan validitas kehadiran mahasiswa. Proyek ini merupakan **Tugas Project Akhir Mata Kuliah Pemrograman Mobile**.
1. Berada pada **lokasi yang telah ditentukan**, dan
2. Melakukan **pengambilan foto (selfie) secara langsung saat absensi** Aplikasi ini mengintegrasikan teknologi **Location-Based Service (LBS)** dan **Real-time Camera** untuk memastikan mahasiswa melakukan absensi dengan data lokasi dan bukti visual yang akurat.
--- ---
## 🎯 Tujuan Proyek ## Pengembangan Berbasis AI
- Mengimplementasikan **Location-Based Service (LBS)** pada aplikasi mobile Project ini dikembangkan dengan pendekatan **AI-Assisted Development**. Bantuan kecerdasan buatan digunakan dalam:
- Mengintegrasikan **kamera perangkat** untuk dokumentasi absensi * **Transformasi UI/UX**: Merancang tampilan modern menggunakan Jetpack Compose dengan prinsip Fresh & Premium Design.
- Mencegah kecurangan absensi (titip absen) * **Optimasi Logika**: Implementasi perhitungan jarak Haversine, validasi waktu perkuliahan yang presisi, dan otomatisasi status kehadiran.
- Mengembangkan aplikasi mobile akademik berbasis Android * **Integrasi Webhook**: Mempermudah sinkronisasi data antara aplikasi mobile dengan backend n8n, Google Sheets, dan sistem notifikasi ntfy.
- Melatih kemampuan perancangan dan implementasi aplikasi mobile * **Troubleshooting**: Mempercepat penyelesaian error build, penyesuaian tipe data, dan pembersihan kode (Clean Code).
--- ---
## 🚀 Fitur Utama ## Fitur Utama
- 🔐 **Login Pengguna (Mahasiswa)**
- 📍 **Pengambilan Koordinat Lokasi (Latitude & Longitude)** ### 1. Keamanan Akses (NPM Based)
- 🏫 **Validasi Lokasi Absensi (Radius Area)** * **Registrasi & Login**: Mahasiswa mendaftar menggunakan NPM, Nama, dan Password.
- 📸 **Pengambilan Foto Mahasiswa Saat Absensi** * **Sesi Terjaga**: Menggunakan `SharedPreferences` agar user tidak perlu login ulang setiap kali membuka aplikasi.
- 🕒 **Pencatatan Waktu Absensi**
- 📄 **Riwayat Kehadiran Mahasiswa** ### 2. Presensi Berbasis Lokasi (GPS)
- ⚠️ **Notifikasi Absensi Ditolak jika Tidak Valid** * **Real-time Tracking**: Mengambil koordinat Latitude dan Longitude secara instan.
* **Distance Calculation**: Menghitung jarak mahasiswa ke titik koordinat kampus secara otomatis.
* **Privacy Mode (Obfuscation)**: Fitur untuk menyamarkan lokasi presisi mahasiswa demi keamanan privasi rumah saat melakukan absen jarak jauh.
### 3. Validasi Waktu & Jadwal
* **Time Blocking**: Absensi dengan status "Hadir" hanya dapat dikirim jika waktu saat ini sesuai dengan jadwal mata kuliah (dengan toleransi 15 menit).
* **Flexible Attendance**: Status "Izin" atau "Sakit" diberikan dispensasi untuk absen di luar area kampus dan di luar jam perkuliahan.
### 4. Bukti Visual (Selfie)
* **In-App Camera**: Integrasi langsung dengan kamera depan perangkat.
* **Anti-Fraud**: Foto diambil secara live saat itu juga sebagai syarat utama pengiriman data.
### 5. Riwayat Absensi Terintegrasi
* Daftar riwayat lengkap menampilkan: Nama Matkul, Waktu (WIB), Foto Selfie, Koordinat GPS, Jarak ke Kampus, dan Status (Hadir/Izin/Sakit).
--- ---
## 🗺️ Mekanisme Absensi Berbasis Lokasi dan Foto ## Teknologi yang Digunakan
1. Mahasiswa melakukan **login**
2. Memilih menu **Absensi** * **UI Framework**: Jetpack Compose (Material 3)
3. Sistem meminta: * **Language**: Kotlin
- Izin **akses lokasi** * **Navigation**: Jetpack Navigation Compose
- Izin **akses kamera** * **Location**: Google Play Services Location (Fused Location Provider)
4. Aplikasi mengambil: * **Networking**: HttpURLConnection (Integration with n8n Webhook)
- 📍 **Koordinat lokasi mahasiswa** * **Architecture**: Clean UI Components & Preferences Management
- 📸 **Foto mahasiswa secara real-time** * **Backend & Data**:
5. Sistem melakukan validasi: * **n8n**: Sebagai automation engine.
- Lokasi berada dalam **radius absensi** * **Google Sheets**: Sebagai database utama penyimpanan laporan.
- Foto berhasil diambil * **ntfy**: Sebagai sistem notifikasi real-time.
6. Jika valid → **Absensi berhasil**
7. Jika tidak valid → **Absensi ditolak**
--- ---
## 📸 Pengambilan Foto Saat Absensi ## Mekanisme Kerja
- Foto diambil menggunakan **kamera depan (selfie)**
- Foto hanya dapat diambil **saat proses absensi** 1. **Pilih Jadwal**: Mahasiswa memilih mata kuliah dari menu "Jadwal Kuliah".
- Foto disimpan sebagai **bukti kehadiran** 2. **Validasi Otomatis**: Sistem mengecek apakah jam saat ini sesuai dengan jam matkul yang dipilih (khusus status "Hadir").
- Foto dapat digunakan untuk: 3. **Identifikasi Lokasi**: GPS mendeteksi koordinat dan menghitung jarak ke kampus.
- Verifikasi manual oleh dosen 4. **Verifikasi Foto**: Mahasiswa mengambil foto selfie.
- Dokumentasi akademik 5. **Pengiriman Data**: Data dikirim ke Webhook n8n dan diteruskan ke Google Spreadsheet secara otomatis.
--- ---
## 🛠️ Teknologi yang Digunakan ## Mockup & Audit
- **Platform** : Android Aplikasi ini telah melewati audit fitur sesuai dengan starter project dan dikembangkan lebih lanjut dengan standar UI modern.
- **Bahasa Pemrograman** : Kotlin / Java
- **Location Service** : ## Pengecekan Data
- Google Maps API Seluruh data absensi yang masuk dapat dipantau melalui:
- Fused Location Provider * **Spreadsheet**: [Data Absensi Mahasiswa](https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0)
- **Camera API** : CameraX / Camera2 * **Notifikasi**: [ntfy.ubharajaya.ac.id/EAS](https://ntfy.ubharajaya.ac.id/EAS)
- **Database** : Firebase / SQLite / MySQL
- **Storage** : Firebase Storage / Local Storage
- **IDE** : Android Studio
--- ---
## 🔐 Izin Aplikasi (Permissions) **© 2026 - Project Akhir Pemrograman Mobile**
Aplikasi memerlukan izin berikut: *Developed with ❤️ U.*
- `ACCESS_FINE_LOCATION`
- `ACCESS_COARSE_LOCATION`
- `CAMERA`
- `INTERNET`
- `WRITE_EXTERNAL_STORAGE` (jika diperlukan)
---
## 📂 Mockup
![mockup](Mockup.png)
gambar mockup dibuat oleh AI
## Catatan:
- Starter project ini dibuat berbantukan AI
- Kembangkan project dari starter yang sudah disediakan, jangan membuat dari awal.
- Untuk koordinat bisa ditambah/kurangi angka tertentu agar tidak memunculkan koordinat rumah masing-masing, data awal tetap dari GPS.
## Pengecekan:
- https://ntfy.ubharajaya.ac.id/EAS
- https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0
## Webhook:
- 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

View File

@ -2,15 +2,21 @@ package id.ac.ubharajaya.sistemakademik.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val GreenPrimary = Color(0xFF2E7D32) // Premium Academic Palette
val GreenSecondary = Color(0xFF4CAF50) val PrimaryGreen = Color(0xFF1B5E20)
val GreenTertiary = Color(0xFF81C784) val PrimaryOrange = Color(0xFFE65100)
val GreenLight = Color(0xFFE8F5E9) val BackgroundLight = Color(0xFFF8F9FA)
val SurfaceWhite = Color(0xFFFFFFFF)
val TextDark = Color(0xFF212121)
val TextGray = Color(0xFF757575)
// Functional Colors
val SuccessGreen = Color(0xFF43A047)
val ErrorRed = Color(0xFFD32F2F)
val InfoBlue = Color(0xFF1976D2)
// Material 3 defaults (fallback)
val Purple80 = Color(0xFFD0BCFF) val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC) val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8) val Pink80 = Color(0xFFEFB8C8)
val GreenLight = Color(0xFFE8F5E9)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -12,29 +12,31 @@ import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = GreenSecondary, primary = PrimaryGreen,
secondary = GreenTertiary, secondary = PrimaryOrange,
tertiary = GreenLight, tertiary = SuccessGreen,
background = Color(0xFF1B1C1B), background = Color(0xFF0D1117),
surface = Color(0xFF1B1C1B), surface = Color(0xFF161B22),
onPrimary = Color.Black, surfaceVariant = Color(0xFF30363D),
onSecondary = Color.Black, onPrimary = Color.White,
onTertiary = Color.Black, onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color.White, onBackground = Color.White,
onSurface = Color.White, onSurface = Color.White,
) )
private val LightColorScheme = lightColorScheme( private val LightColorScheme = lightColorScheme(
primary = GreenPrimary, primary = PrimaryGreen,
secondary = GreenSecondary, secondary = PrimaryOrange,
tertiary = GreenTertiary, tertiary = SuccessGreen,
background = Color(0xFFF6FBF6), background = BackgroundLight,
surface = Color.White, surface = SurfaceWhite,
surfaceVariant = Color(0xFFF0F2F5),
onPrimary = Color.White, onPrimary = Color.White,
onSecondary = Color.White, onSecondary = Color.White,
onTertiary = Color.White, onTertiary = Color.White,
onBackground = Color(0xFF1B1C1B), onBackground = TextDark,
onSurface = Color(0xFF1B1C1B), onSurface = TextDark,
) )
@Composable @Composable
@ -46,6 +48,7 @@ fun SistemAkademikTheme(
val view = LocalView.current val view = LocalView.current
if (!view.isInEditMode) { if (!view.isInEditMode) {
val window = (view.context as Activity).window val window = (view.context as Activity).window
@Suppress("DEPRECATION")
window.statusBarColor = colorScheme.primary.toArgb() window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = false WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = false
} }

View File

@ -6,29 +6,43 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with // Modernized Material typography for the app
val Typography = Typography( val Typography = Typography(
headlineLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.ExtraBold,
fontSize = 28.sp,
letterSpacing = 0.sp
),
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
),
titleMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
),
bodyLarge = TextStyle( bodyLarge = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 16.sp, fontSize = 16.sp,
lineHeight = 24.sp, lineHeight = 22.sp
letterSpacing = 0.5.sp ),
) bodyMedium = TextStyle(
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 22.sp, fontSize = 14.sp
lineHeight = 28.sp, ),
letterSpacing = 0.sp labelLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 14.sp
), ),
labelSmall = TextStyle( labelSmall = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 11.sp, fontSize = 12.sp
lineHeight = 16.sp,
letterSpacing = 0.5.sp
) )
*/
) )

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB