475 lines
39 KiB
Markdown
475 lines
39 KiB
Markdown
# 🏗️ Architecture & Component Diagram
|
|
|
|
## System Architecture Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ APLIKASI ABSENSI AKADEMIK │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ PRESENTATION LAYER │
|
|
│ (Jetpack Compose UI) │
|
|
│ │
|
|
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
│ │ MainActivity (AbsensiScreen) │ │
|
|
│ │ • State Management (AttendanceState) │ │
|
|
│ │ • Permission Handling (Location + Camera) │ │
|
|
│ │ • User Interactions (Button clicks, form inputs) │ │
|
|
│ └────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────┬──────────────────────┬───────────────────┐ │
|
|
│ │ PhotoPreviewCard │ LocationStatusCard │ ErrorAlertCard │ │
|
|
│ │ │ │ │ │
|
|
│ │ • Show captured │ • Display latitude │ • Show error │ │
|
|
│ │ photo │ • Display longitude │ messages │ │
|
|
│ │ • Retake button │ • Show validation │ • Dismissable │ │
|
|
│ │ │ status │ │ │
|
|
│ └──────────────────────┴──────────────────────┴───────────────────┘ │
|
|
│ │
|
|
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
│ │ SubmitButtonWithLoader │ │
|
|
│ │ • Loading spinner during submission │ │
|
|
│ │ • Disabled until all validations pass │ │
|
|
│ └────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ BUSINESS LOGIC LAYER │
|
|
│ (Models + Utilities) │
|
|
│ │
|
|
│ ┌──────────────────────────────┐ ┌────────────────────────────────┐ │
|
|
│ │ AttendanceState │ │ LocationValidator │ │
|
|
│ │ │ │ │ │
|
|
│ │ • location: LocationData │ │ • calculateDistance() │ │
|
|
│ │ • foto: Bitmap │ │ • isLocationValid() │ │
|
|
│ │ • validationResult │ │ • getValidationMessage() │ │
|
|
│ │ • errorMessage │ │ • adjustCoordinates() │ │
|
|
│ │ • loading states │ │ │ │
|
|
│ └──────────────────────────────┘ │ [Haversine Formula] │ │
|
|
│ └────────────────────────────────┘ │
|
|
│ ┌──────────────────────────────┐ ┌────────────────────────────────┐ │
|
|
│ │ AttendanceRecord │ │ ErrorHandler │ │
|
|
│ │ │ │ │ │
|
|
│ │ • npm │ │ • getErrorMessage() │ │
|
|
│ │ • nama │ │ • getUserFriendlyMessage() │ │
|
|
│ │ • latitude/longitude │ │ │ │
|
|
│ │ • timestamp │ │ Error Types: │ │
|
|
│ │ • foto │ │ • NetworkError │ │
|
|
│ │ • validation info │ │ • LocationError │ │
|
|
│ └──────────────────────────────┘ │ • PermissionError │ │
|
|
│ │ • ValidationError │ │
|
|
│ │ • UnknownError │ │
|
|
│ └────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ AttendanceConfig (Configuration) │ │
|
|
│ │ │ │
|
|
│ │ • REFERENCE_LATITUDE / REFERENCE_LONGITUDE │ │
|
|
│ │ • ALLOWED_RADIUS_METERS │ │
|
|
│ │ • STUDENT_NPM / STUDENT_NAMA │ │
|
|
│ │ • WEBHOOK URLs (PRODUCTION + TEST) │ │
|
|
│ │ • API_TIMEOUT_MS │ │
|
|
│ │ • PHOTO_QUALITY │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ NETWORK LAYER │
|
|
│ (API Communication) │
|
|
│ │
|
|
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
│ │ N8nService │ │
|
|
│ │ │ │
|
|
│ │ submitAttendance() │ │
|
|
│ │ ├─ Serialize data to JSON │ │
|
|
│ │ ├─ Encode photo to Base64 │ │
|
|
│ │ ├─ POST to webhook URL │ │
|
|
│ │ ├─ Parse response code │ │
|
|
│ │ └─ Call callback (onSuccess / onError) │ │
|
|
│ │ │ │
|
|
│ │ submitCallback Interface: │ │
|
|
│ │ ├─ onSuccess(responseCode, message) │ │
|
|
│ │ └─ onError(throwable, message) │ │
|
|
│ └────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ EXTERNAL SERVICES LAYER │
|
|
│ │
|
|
│ ┌─────────────────────┬──────────────────┬────────────────────────┐ │
|
|
│ │ Location Services │ Camera Services │ N8n Webhook Server │ │
|
|
│ │ │ │ │ │
|
|
│ │ • Google Play │ • Android Camera │ • Receive JSON data │ │
|
|
│ │ Services │ • Camera Intent │ • Process attendance │ │
|
|
│ │ • Fused Location │ • Photo capture │ • Store in database │ │
|
|
│ │ Provider │ │ • Return HTTP status │ │
|
|
│ │ • GPS coordinates │ │ │ │
|
|
│ │ │ │ │ │
|
|
│ └─────────────────────┴──────────────────┴────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Data Flow Diagram
|
|
|
|
```
|
|
┌────────────┐
|
|
│ App Start │
|
|
└─────┬──────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Request Location Permission │
|
|
│ [LocationPermissionLauncher] │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
├─ GRANTED ──────┐
|
|
│ │ DENIED
|
|
│ ▼
|
|
│ ┌─────────────────┐
|
|
│ │ Show Error │
|
|
│ │ Message │
|
|
│ └─────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Acquire GPS Location │
|
|
│ [FusedLocationProvider.lastLocation]│
|
|
└─────────────────────────────────────┘
|
|
│
|
|
├─ SUCCESS ──────────────┐
|
|
│ │ FAILED
|
|
│ ▼
|
|
│ ┌────────────────────┐
|
|
│ │ LocationError │
|
|
│ │ Show in ErrorAlert │
|
|
│ └────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Validate Location │
|
|
│ [LocationValidator.isLocationValid] │
|
|
│ - Calculate distance using │
|
|
│ Haversine formula │
|
|
│ - Compare with allowed radius │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
├─ VALID ────┐
|
|
│ │ INVALID
|
|
│ ▼
|
|
│ ┌─────────────────────┐
|
|
│ │ Show Invalid Status │
|
|
│ │ Disable Submit Button│
|
|
│ └─────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Display LocationStatusCard │
|
|
│ - Show latitude/longitude │
|
|
│ - Show validation message │
|
|
│ - Show distance from reference │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ User clicks "Ambil Foto" │
|
|
│ [Request Camera Permission] │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
├─ GRANTED ──────────────┐
|
|
│ │ DENIED
|
|
│ ▼
|
|
│ ┌────────────────────┐
|
|
│ │ PermissionError │
|
|
│ │ Show in ErrorAlert │
|
|
│ └────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Launch Camera Intent │
|
|
│ [MediaStore.ACTION_IMAGE_CAPTURE] │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
├─ CAPTURED ─────┐
|
|
│ │ CANCELLED
|
|
│ ▼
|
|
│ ┌─────────────────┐
|
|
│ │ Retry or Skip │
|
|
│ └─────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Display Photo Preview │
|
|
│ [PhotoPreviewCard] │
|
|
│ - Show Bitmap │
|
|
│ - "Ambil Ulang" button │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Validate All Data │
|
|
│ ✓ Location acquired? │
|
|
│ ✓ Location valid? │
|
|
│ ✓ Photo captured? │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
├─ ALL VALID ──┐
|
|
│ │ MISSING DATA
|
|
│ ▼
|
|
│ ┌──────────────────────┐
|
|
│ │ Show ValidationError │
|
|
│ │ Keep Submit Disabled │
|
|
│ └──────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Enable Submit Button │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ User clicks "Kirim Absensi" │
|
|
│ Show Loading Spinner │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Prepare Request │
|
|
│ [N8nService.submitAttendance] │
|
|
│ - Serialize to JSON: │
|
|
│ {npm, nama, lat, lon, ts, foto} │
|
|
│ - Encode photo to Base64 │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ POST to N8n Webhook │
|
|
│ [HttpURLConnection] │
|
|
│ Timeout: 30 seconds │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
├─ RESPONSE 200 ──┐
|
|
│ │ RESPONSE 4xx/5xx
|
|
│ │ or TIMEOUT
|
|
│ ▼
|
|
│ ┌────────────────────────┐
|
|
│ │ onError Called │
|
|
│ │ Show NetworkError │
|
|
│ │ Suggest Retry │
|
|
│ └────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ onSuccess Called │
|
|
│ - Hide loading spinner │
|
|
│ - Show success toast │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Wait 2 seconds │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Reset Form │
|
|
│ - Clear photo │
|
|
│ - Clear location │
|
|
│ - Reset validation state │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Ready for Next Attendance │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Class Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ DATA MODELS │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌───────────────────────────┐ ┌──────────────────────────────┐ │
|
|
│ │ AttendanceState │ │ LocationData │ │
|
|
│ ├───────────────────────────┤ ├──────────────────────────────┤ │
|
|
│ │ + location: LocationData? │ │ + latitude: Double │ │
|
|
│ │ + foto: Bitmap? │ │ + longitude: Double │ │
|
|
│ │ + isLoadingLocation: Bool │ │ + accuracy: Float │ │
|
|
│ │ + isLoadingSubmit: Bool │ │ + timestamp: Long │ │
|
|
│ │ + validationResult │ └──────────────────────────────┘ │
|
|
│ │ + errorMessage: String? │ │
|
|
│ │ + isLocationPermission │ ┌──────────────────────────────┐ │
|
|
│ │ + isCameraPermission │ │ ValidationResult │ │
|
|
│ └───────────────────────────┘ ├──────────────────────────────┤ │
|
|
│ │ + isValid: Boolean │ │
|
|
│ ┌───────────────────────────┐ │ + message: String │ │
|
|
│ │ AttendanceRecord │ │ + status: ValidationStatus │ │
|
|
│ ├───────────────────────────┤ └──────────────────────────────┘ │
|
|
│ │ + npm: String │ │
|
|
│ │ + nama: String │ ┌──────────────────────────────┐ │
|
|
│ │ + latitude: Double │ │ ValidationStatus (enum) │ │
|
|
│ │ + longitude: Double │ ├──────────────────────────────┤ │
|
|
│ │ + timestamp: Long │ │ IDLE │ │
|
|
│ │ + foto: Bitmap? │ │ ACQUIRING │ │
|
|
│ │ + isValid: Boolean │ │ VALIDATING │ │
|
|
│ │ + validationMessage │ │ SUCCESS │ │
|
|
│ └───────────────────────────┘ │ OUT_OF_RANGE │ │
|
|
│ │ ERROR │ │
|
|
│ └──────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ SERVICES & UTILITIES │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ LocationValidator (object) │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ + calculateDistance(lat1,lon1,lat2,lon2): Double │ │
|
|
│ │ + isLocationValid(lat,lon,radius): Boolean │ │
|
|
│ │ + getValidationMessage(lat,lon,radius): String │ │
|
|
│ │ + adjustCoordinates(lat,lon,latOff,lonOff): Pair<Double> │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ N8nService │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ - activity: ComponentActivity │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ + submitAttendance(npm,nama,lat,lon,foto,isTest,callback) │ │
|
|
│ │ - bitmapToBase64(bitmap): String │ │
|
|
│ │ + interface SubmitCallback │ │
|
|
│ │ - onSuccess(responseCode, message) │ │
|
|
│ │ - onError(error, message) │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ ErrorHandler (object) │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ + sealed class AttendanceError │ │
|
|
│ │ - NetworkError(message) │ │
|
|
│ │ - LocationError(message) │ │
|
|
│ │ - PermissionError(message) │ │
|
|
│ │ - ValidationError(message) │ │
|
|
│ │ - UnknownError(throwable) │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ + getErrorMessage(error): String │ │
|
|
│ │ + getUserFriendlyMessage(error): String │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ AttendanceConfig (object) │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ + REFERENCE_LATITUDE: Double │ │
|
|
│ │ + REFERENCE_LONGITUDE: Double │ │
|
|
│ │ + ALLOWED_RADIUS_METERS: Double │ │
|
|
│ │ + STUDENT_NPM: String │ │
|
|
│ │ + STUDENT_NAMA: String │ │
|
|
│ │ + WEBHOOK_PRODUCTION: String │ │
|
|
│ │ + WEBHOOK_TEST: String │ │
|
|
│ │ + API_TIMEOUT_MS: Int │ │
|
|
│ │ + PHOTO_QUALITY: Int │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ UI COMPONENTS (Compose) │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ PhotoPreviewCard │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ Parameters: │ │
|
|
│ │ - bitmap: Bitmap? │ │
|
|
│ │ - onRetake: () -> Unit │ │
|
|
│ │ - modifier: Modifier │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ LocationStatusCard │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ Parameters: │ │
|
|
│ │ - latitude: Double? │ │
|
|
│ │ - longitude: Double? │ │
|
|
│ │ - validationMessage: String │ │
|
|
│ │ - isLoading: Boolean │ │
|
|
│ │ - modifier: Modifier │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ ErrorAlertCard │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ Parameters: │ │
|
|
│ │ - message: String? │ │
|
|
│ │ - onDismiss: () -> Unit │ │
|
|
│ │ - modifier: Modifier │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ SubmitButtonWithLoader │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ Parameters: │ │
|
|
│ │ - text: String │ │
|
|
│ │ - onClick: () -> Unit │ │
|
|
│ │ - isLoading: Boolean │ │
|
|
│ │ - isEnabled: Boolean │ │
|
|
│ │ - modifier: Modifier │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ AbsensiScreen (Composable) │ │
|
|
│ ├──────────────────────────────────────────────────────────────────┤ │
|
|
│ │ - Manages AttendanceState │ │
|
|
│ │ - Handles permissions │ │
|
|
│ │ - Orchestrates all UI components │ │
|
|
│ │ - Manages location acquisition │ │
|
|
│ │ - Handles photo capture │ │
|
|
│ │ - Manages form submission │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Dependencies & Imports
|
|
|
|
```
|
|
AndroidX
|
|
├── androidx.core:core-ktx
|
|
├── androidx.lifecycle:lifecycle-runtime-ktx
|
|
├── androidx.activity:activity-compose
|
|
├── androidx.compose.* (UI, Material3, Tooling)
|
|
│
|
|
Android Platform
|
|
├── android.location (Location APIs)
|
|
├── android.hardware.camera (Camera)
|
|
├── android.content.pm (Permissions)
|
|
│
|
|
Google Play Services
|
|
├── com.google.android.gms:play-services-location
|
|
│ └── Fused Location Provider
|
|
│
|
|
Standard Libraries
|
|
├── java.net.HttpURLConnection (API calls)
|
|
├── org.json.JSONObject (JSON serialization)
|
|
├── java.util.Base64 (Photo encoding)
|
|
│
|
|
Testing
|
|
└── junit, androidx.test (Unit tests)
|
|
```
|
|
|
|
---
|
|
|
|
**Architecture Design**: ✅ Clean, Modular, Testable
|
|
**Data Flow**: ✅ Unidirectional with State Management
|
|
**UI Pattern**: ✅ Compose with Material 3
|
|
**Error Handling**: ✅ Sealed Classes + Callbacks
|
|
**Scalability**: ✅ Ready for future enhancements
|
|
|