diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml
new file mode 100644
index 0000000..4ea72a9
--- /dev/null
+++ b/.idea/copilot.data.migration.agent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml
new file mode 100644
index 0000000..7ef04e2
--- /dev/null
+++ b/.idea/copilot.data.migration.ask.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml
new file mode 100644
index 0000000..1f2ea11
--- /dev/null
+++ b/.idea/copilot.data.migration.ask2agent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml
new file mode 100644
index 0000000..8648f94
--- /dev/null
+++ b/.idea/copilot.data.migration.edit.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 26d2e8c..bb9b38f 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,10 +4,10 @@
-
+
-
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..f0c6ad0
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..8b1c65d
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 9871f13..e49d5fb 100644
--- a/README.md
+++ b/README.md
@@ -1,97 +1,81 @@
-# π± Aplikasi Absensi Akademik Berbasis Koordinat dan Foto (Mobile)
+Nama : Hadi Guna Prakoso
+NPM : 202310715312
-## π 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**.
+# Sistem Akademik Ubhara Jaya - Absensi LBS & Foto
-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**
+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**.
+
+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
-- Mengimplementasikan **Location-Based Service (LBS)** pada aplikasi mobile
-- Mengintegrasikan **kamera perangkat** untuk dokumentasi absensi
-- Mencegah kecurangan absensi (titip absen)
-- Mengembangkan aplikasi mobile akademik berbasis Android
-- Melatih kemampuan perancangan dan implementasi aplikasi mobile
+## Pengembangan Berbasis AI
+Project ini dikembangkan dengan pendekatan **AI-Assisted Development**. Bantuan kecerdasan buatan digunakan dalam:
+* **Transformasi UI/UX**: Merancang tampilan modern menggunakan Jetpack Compose dengan prinsip Fresh & Premium Design.
+* **Optimasi Logika**: Implementasi perhitungan jarak Haversine, validasi waktu perkuliahan yang presisi, dan otomatisasi status kehadiran.
+* **Integrasi Webhook**: Mempermudah sinkronisasi data antara aplikasi mobile dengan backend n8n, Google Sheets, dan sistem notifikasi ntfy.
+* **Troubleshooting**: Mempercepat penyelesaian error build, penyesuaian tipe data, dan pembersihan kode (Clean Code).
---
-## π Fitur Utama
-- π **Login Pengguna (Mahasiswa)**
-- π **Pengambilan Koordinat Lokasi (Latitude & Longitude)**
-- π« **Validasi Lokasi Absensi (Radius Area)**
-- πΈ **Pengambilan Foto Mahasiswa Saat Absensi**
-- π **Pencatatan Waktu Absensi**
-- π **Riwayat Kehadiran Mahasiswa**
-- β οΈ **Notifikasi Absensi Ditolak jika Tidak Valid**
+## Fitur Utama
+
+### 1. Keamanan Akses (NPM Based)
+* **Registrasi & Login**: Mahasiswa mendaftar menggunakan NPM, Nama, dan Password.
+* **Sesi Terjaga**: Menggunakan `SharedPreferences` agar user tidak perlu login ulang setiap kali membuka aplikasi.
+
+### 2. Presensi Berbasis Lokasi (GPS)
+* **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
-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**
+## Teknologi yang Digunakan
+
+* **UI Framework**: Jetpack Compose (Material 3)
+* **Language**: Kotlin
+* **Navigation**: Jetpack Navigation Compose
+* **Location**: Google Play Services Location (Fused Location Provider)
+* **Networking**: HttpURLConnection (Integration with n8n Webhook)
+* **Architecture**: Clean UI Components & Preferences Management
+* **Backend & Data**:
+ * **n8n**: Sebagai automation engine.
+ * **Google Sheets**: Sebagai database utama penyimpanan laporan.
+ * **ntfy**: Sebagai sistem notifikasi real-time.
---
-## πΈ Pengambilan Foto Saat Absensi
-- Foto diambil menggunakan **kamera depan (selfie)**
-- Foto hanya dapat diambil **saat proses absensi**
-- Foto disimpan sebagai **bukti kehadiran**
-- Foto dapat digunakan untuk:
- - Verifikasi manual oleh dosen
- - Dokumentasi akademik
+## Mekanisme Kerja
+
+1. **Pilih Jadwal**: Mahasiswa memilih mata kuliah dari menu "Jadwal Kuliah".
+2. **Validasi Otomatis**: Sistem mengecek apakah jam saat ini sesuai dengan jam matkul yang dipilih (khusus status "Hadir").
+3. **Identifikasi Lokasi**: GPS mendeteksi koordinat dan menghitung jarak ke kampus.
+4. **Verifikasi Foto**: Mahasiswa mengambil foto selfie.
+5. **Pengiriman Data**: Data dikirim ke Webhook n8n dan diteruskan ke Google Spreadsheet secara otomatis.
---
-## π οΈ Teknologi yang Digunakan
-- **Platform** : Android
-- **Bahasa Pemrograman** : Kotlin / Java
-- **Location Service** :
- - Google Maps API
- - Fused Location Provider
-- **Camera API** : CameraX / Camera2
-- **Database** : Firebase / SQLite / MySQL
-- **Storage** : Firebase Storage / Local Storage
-- **IDE** : Android Studio
+## Mockup & Audit
+Aplikasi ini telah melewati audit fitur sesuai dengan starter project dan dikembangkan lebih lanjut dengan standar UI modern.
+
+## Pengecekan Data
+Seluruh data absensi yang masuk dapat dipantau melalui:
+* **Spreadsheet**: [Data Absensi Mahasiswa](https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0)
+* **Notifikasi**: [ntfy.ubharajaya.ac.id/EAS](https://ntfy.ubharajaya.ac.id/EAS)
---
-## π Izin Aplikasi (Permissions)
-Aplikasi memerlukan izin berikut:
-- `ACCESS_FINE_LOCATION`
-- `ACCESS_COARSE_LOCATION`
-- `CAMERA`
-- `INTERNET`
-- `WRITE_EXTERNAL_STORAGE` (jika diperlukan)
-
----
-
-## π Mockup
-
-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
\ No newline at end of file
+**Β© 2026 - Project Akhir Pemrograman Mobile**
+*Developed with β€οΈ U.*
diff --git a/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt b/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt
index b9a5a97..c4b6201 100644
--- a/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt
+++ b/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt
@@ -1,3 +1,5 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
package id.ac.ubharajaya.sistemakademik
import android.Manifest
@@ -50,13 +52,16 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
+import androidx.core.content.edit
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -64,6 +69,9 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.google.android.gms.location.LocationServices
import id.ac.ubharajaya.sistemakademik.ui.theme.SistemAkademikTheme
+import id.ac.ubharajaya.sistemakademik.ui.theme.GreenLight
+import id.ac.ubharajaya.sistemakademik.ui.theme.PrimaryGreen
+import id.ac.ubharajaya.sistemakademik.ui.theme.PrimaryOrange
import org.json.JSONArray
import org.json.JSONObject
import java.io.ByteArrayOutputStream
@@ -72,6 +80,7 @@ import java.net.URL
import java.text.SimpleDateFormat
import java.util.*
import kotlin.concurrent.thread
+import kotlin.math.*
import kotlin.random.Random
/* ================= ROUTES ================= */
@@ -93,9 +102,24 @@ data class AttendanceLog(
val status: String,
val fotoBase64: String? = null,
val latitude: Double? = null,
- val longitude: Double? = null
+ val longitude: Double? = null,
+ val distance: Double? = null
)
-data class ScheduleItem(val day: String, val time: String, val course: String, val room: String)
+data class ScheduleItem(
+ val kode: String,
+ val course: String,
+ val kelas: String,
+ val sks: Int,
+ val dosen: String,
+ val room: String,
+ val day: String,
+ val timeStart: String,
+ val timeEnd: String
+)
+
+// Koordinat Kampus Ubhara Jaya
+const val CAMPUS_LAT = -6.2349
+const val CAMPUS_LON = 107.0011
class PreferenceManager(context: Context) {
private val prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
@@ -117,7 +141,7 @@ class PreferenceManager(context: Context) {
}
fun setLoggedInUser(npm: String) {
- prefs.edit().putString("current_user_npm", npm).apply()
+ prefs.edit { putString("current_user_npm", npm) }
}
fun getLoggedInUser(): User? {
@@ -137,10 +161,11 @@ class PreferenceManager(context: Context) {
put("fotoBase64", it.fotoBase64)
put("latitude", it.latitude)
put("longitude", it.longitude)
+ put("distance", it.distance)
}
jsonArray.put(obj)
}
- prefs.edit().putString("history_$npm", jsonArray.toString()).apply()
+ prefs.edit { putString("history_$npm", jsonArray.toString()) }
}
fun getAttendanceLogs(npm: String): List {
@@ -153,16 +178,17 @@ class PreferenceManager(context: Context) {
obj.getLong("timestamp"),
obj.getString("mataKuliah"),
obj.getString("status"),
- obj.optString("fotoBase64", null),
+ if (obj.has("fotoBase64")) obj.getString("fotoBase64") else null,
if (obj.has("latitude")) obj.getDouble("latitude") else null,
- if (obj.has("longitude")) obj.getDouble("longitude") else null
+ if (obj.has("longitude")) obj.getDouble("longitude") else null,
+ if (obj.has("distance")) obj.getDouble("distance") else null
))
}
return list
}
fun logout() {
- prefs.edit().remove("current_user_npm").apply()
+ prefs.edit { remove("current_user_npm") }
}
}
@@ -178,25 +204,58 @@ fun base64ToBitmap(base64Str: String): Bitmap? {
return try {
val decodedBytes = Base64.decode(base64Str, Base64.DEFAULT)
BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
- } catch (e: Exception) {
+ } catch (_: Exception) {
null
}
}
+fun calculateDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
+ val R = 6371.0 // Radius bumi dalam KM
+ val dLat = Math.toRadians(lat2 - lat1)
+ val dLon = Math.toRadians(lon2 - lon1)
+ val a = sin(dLat / 2) * sin(dLat / 2) +
+ cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) *
+ sin(dLon / 2) * sin(dLon / 2)
+ val c = 2 * atan2(sqrt(a), sqrt(1 - a))
+ return R * c
+}
+
+// Check if current time is within schedule (with 15 min buffer before and after)
+fun isWithinSchedule(day: String, startStr: String, endStr: String): Boolean {
+ val calendar = Calendar.getInstance()
+ val currentDay = SimpleDateFormat("EEEE", Locale("id", "ID")).format(calendar.time)
+ if (!day.equals(currentDay, ignoreCase = true)) return false
+
+ val currentTimeStr = SimpleDateFormat("HH:mm", Locale.getDefault()).format(calendar.time)
+ val sdf = SimpleDateFormat("HH:mm", Locale.getDefault())
+
+ val current = sdf.parse(currentTimeStr)?.time ?: 0
+ val start = sdf.parse(startStr)?.time ?: 0
+ val end = sdf.parse(endStr)?.time ?: 0
+
+ val buffer = 15 * 60 * 1000 // 15 minutes buffer
+ return current in (start - buffer)..(end + buffer)
+}
+
+data class AttendancePayload(
+ val npm: String,
+ val nama: String,
+ val latitude: Double,
+ val longitude: Double,
+ val mataKuliah: String,
+ val status: String,
+ val foto: Bitmap,
+ val distance: Double
+)
+
fun kirimKeN8n(
context: Activity,
- npm: String,
- nama: String,
- latitude: Double,
- longitude: Double,
- mataKuliah: String,
- status: String,
- foto: Bitmap,
+ payload: AttendancePayload,
onSuccess: (String) -> Unit,
onLoading: (Boolean) -> Unit
) {
onLoading(true)
- val fotoBase64 = bitmapToBase64(foto)
+ val fotoBase64 = bitmapToBase64(payload.foto)
thread {
try {
val url = URL("https://n8n.lab.ubharajaya.ac.id/webhook/23c6993d-1792-48fb-ad1c-ffc78a3e6254")
@@ -207,13 +266,14 @@ fun kirimKeN8n(
val json = JSONObject().apply {
put("timestamp", System.currentTimeMillis())
- put("npm", npm)
- put("nama", nama)
- put("latitude", latitude)
- put("longitude", longitude)
- put("mata_kuliah", mataKuliah)
- put("status", status)
+ put("npm", payload.npm)
+ put("nama", payload.nama)
+ put("latitude", payload.latitude)
+ put("longitude", payload.longitude)
+ put("mata_kuliah", payload.mataKuliah)
+ put("status", payload.status)
put("foto_base64", fotoBase64)
+ put("distance_km", payload.distance)
}
conn.outputStream.use { it.write(json.toString().toByteArray()) }
@@ -225,10 +285,10 @@ fun kirimKeN8n(
else Toast.makeText(context, "Server Error ($responseCode)", Toast.LENGTH_SHORT).show()
}
conn.disconnect()
- } catch (e: Exception) {
+ } catch (_: Exception) {
context.runOnUiThread {
onLoading(false)
- Toast.makeText(context, "Network Error: ${e.message}", Toast.LENGTH_SHORT).show()
+ Toast.makeText(context, "Network Error", Toast.LENGTH_SHORT).show()
}
}
}
@@ -257,15 +317,24 @@ class MainActivity : ComponentActivity() {
composable(Screen.Register.route) { RegisterScreen(navController, prefs) }
composable(Screen.Home.route) { HomeScreen(navController, prefs) }
composable(
- route = Screen.Attendance.route + "?course={course}",
- arguments = listOf(navArgument("course") { nullable = true; defaultValue = null })
+ route = Screen.Attendance.route + "?course={course}&day={day}&start={start}&end={end}",
+ arguments = listOf(
+ navArgument("course") { nullable = true; defaultValue = null },
+ navArgument("day") { nullable = true; defaultValue = null },
+ navArgument("start") { nullable = true; defaultValue = null },
+ navArgument("end") { nullable = true; defaultValue = null }
+ )
) { backStackEntry ->
val preselectedCourse = backStackEntry.arguments?.getString("course")
- AttendanceScreen(navController, this@MainActivity, prefs, preselectedCourse)
+ val day = backStackEntry.arguments?.getString("day")
+ val start = backStackEntry.arguments?.getString("start")
+ val end = backStackEntry.arguments?.getString("end")
+ AttendanceScreen(navController, this@MainActivity, prefs, preselectedCourse, day, start, end)
}
composable(Screen.Success.route) { SuccessScreen(navController) }
composable(Screen.HistoryLogs.route) { HistoryScreen(navController, prefs) }
composable(Screen.Schedule.route) { ScheduleScreen(navController) }
+ composable("edit_profile") { EditProfileScreen(navController, prefs) }
}
}
}
@@ -279,7 +348,8 @@ class MainActivity : ComponentActivity() {
fun AppBackground(content: @Composable ColumnScope.() -> Unit) {
val gradientBrush = Brush.verticalGradient(
colors = listOf(
- MaterialTheme.colorScheme.primary.copy(alpha = 0.05f),
+ Color(0xFFE8F5E9),
+ Color(0xFFFFF8E1),
MaterialTheme.colorScheme.background
)
)
@@ -293,6 +363,15 @@ fun AppBackground(content: @Composable ColumnScope.() -> Unit) {
)
}
+@Composable
+fun LogoHeader() {
+ Image(
+ painter = painterResource(id = R.drawable.logo_ubhara),
+ contentDescription = "Logo Ubhara",
+ modifier = Modifier.size(120.dp).padding(bottom = 16.dp)
+ )
+}
+
/* ================= SCREENS ================= */
@Composable
@@ -311,16 +390,12 @@ fun RegisterScreen(navController: NavHostController, prefs: PreferenceManager) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
- Icon(
- Icons.Default.AppRegistration, null,
- Modifier.size(80.dp).padding(bottom = 16.dp),
- MaterialTheme.colorScheme.primary
- )
+ LogoHeader()
Text(
"Daftar Akun",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
- color = MaterialTheme.colorScheme.primary
+ color = PrimaryGreen
)
Text(
"Silakan isi data untuk bergabung",
@@ -332,7 +407,7 @@ fun RegisterScreen(navController: NavHostController, prefs: PreferenceManager) {
OutlinedTextField(
npm, { npm = it },
label = { Text("NPM") },
- leadingIcon = { Icon(Icons.Outlined.Badge, null) },
+ leadingIcon = { Icon(Icons.Outlined.Badge, null, tint = PrimaryGreen) },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
@@ -341,7 +416,7 @@ fun RegisterScreen(navController: NavHostController, prefs: PreferenceManager) {
OutlinedTextField(
nama, { nama = it },
label = { Text("Nama Lengkap") },
- leadingIcon = { Icon(Icons.Outlined.Person, null) },
+ leadingIcon = { Icon(Icons.Outlined.Person, null, tint = PrimaryGreen) },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp)
)
@@ -349,10 +424,11 @@ fun RegisterScreen(navController: NavHostController, prefs: PreferenceManager) {
OutlinedTextField(
password, { password = it },
label = { Text("Password") },
- leadingIcon = { Icon(Icons.Outlined.Lock, null) },
+ leadingIcon = { Icon(Icons.Outlined.Lock, null, tint = PrimaryGreen) },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(),
- shape = RoundedCornerShape(16.dp)
+ shape = RoundedCornerShape(16.dp),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done)
)
Spacer(modifier = Modifier.height(32.dp))
Button(
@@ -365,11 +441,12 @@ fun RegisterScreen(navController: NavHostController, prefs: PreferenceManager) {
},
Modifier.fillMaxWidth().height(56.dp),
shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = PrimaryGreen),
elevation = ButtonDefaults.buttonElevation(defaultElevation = 4.dp)
) { Text("DAFTAR", fontWeight = FontWeight.Bold) }
TextButton({ navController.popBackStack() }) {
- Text("Sudah punya akun? Login di sini", color = MaterialTheme.colorScheme.primary)
+ Text("Sudah punya akun? Login di sini", color = PrimaryGreen)
}
Spacer(modifier = Modifier.height(48.dp))
}
@@ -388,22 +465,12 @@ fun LoginScreen(navController: NavHostController, prefs: PreferenceManager) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
- Surface(
- modifier = Modifier.size(120.dp).padding(bottom = 16.dp),
- shape = CircleShape,
- color = MaterialTheme.colorScheme.primaryContainer
- ) {
- Icon(
- Icons.Default.School, null,
- Modifier.padding(24.dp),
- MaterialTheme.colorScheme.primary
- )
- }
+ LogoHeader()
Text(
"Sistem Akademik",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.ExtraBold,
- color = MaterialTheme.colorScheme.primary
+ color = PrimaryGreen
)
Text(
"Aplikasi Absensi Mahasiswa",
@@ -415,7 +482,7 @@ fun LoginScreen(navController: NavHostController, prefs: PreferenceManager) {
OutlinedTextField(
npm, { npm = it },
label = { Text("NPM") },
- leadingIcon = { Icon(Icons.Outlined.Badge, null) },
+ leadingIcon = { Icon(Icons.Outlined.Badge, null, tint = PrimaryGreen) },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
@@ -424,10 +491,11 @@ fun LoginScreen(navController: NavHostController, prefs: PreferenceManager) {
OutlinedTextField(
password, { password = it },
label = { Text("Password") },
- leadingIcon = { Icon(Icons.Outlined.Lock, null) },
+ leadingIcon = { Icon(Icons.Outlined.Lock, null, tint = PrimaryGreen) },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(),
- shape = RoundedCornerShape(16.dp)
+ shape = RoundedCornerShape(16.dp),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done)
)
Spacer(modifier = Modifier.height(32.dp))
Button(
@@ -440,11 +508,12 @@ fun LoginScreen(navController: NavHostController, prefs: PreferenceManager) {
},
Modifier.fillMaxWidth().height(56.dp),
shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = PrimaryGreen),
elevation = ButtonDefaults.buttonElevation(defaultElevation = 4.dp)
) { Text("MASUK", fontWeight = FontWeight.Bold, fontSize = 16.sp) }
TextButton({ navController.navigate(Screen.Register.route) }) {
- Text("Belum punya akun? Daftar Sekarang", color = MaterialTheme.colorScheme.secondary)
+ Text("Belum punya akun? Daftar Sekarang", color = PrimaryOrange)
}
}
}
@@ -458,49 +527,73 @@ fun HomeScreen(navController: NavHostController, prefs: PreferenceManager) {
topBar = {
TopAppBar(
title = {
- Column {
- Text("Akademik", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White)
- Text("Ubhara Jaya", style = MaterialTheme.typography.labelMedium, color = Color.White.copy(alpha = 0.8f))
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Image(
+ painter = painterResource(id = R.drawable.logo_ubhara),
+ contentDescription = null,
+ modifier = Modifier.size(32.dp)
+ )
+ Spacer(Modifier.width(12.dp))
+ Column {
+ Text("Akademik", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White)
+ Text("Ubhara Jaya", style = MaterialTheme.typography.labelMedium, color = Color.White.copy(alpha = 0.9f))
+ }
}
},
actions = {
+ IconButton({ navController.navigate("edit_profile") }) {
+ Icon(Icons.Default.Edit, null, tint = Color.White)
+ }
IconButton({
- prefs.logout();
- navController.navigate(Screen.Login.route) { popUpTo(Screen.Home.route) { inclusive = true } }
+ prefs.logout()
+ navController.navigate(Screen.Login.route) { popUpTo(Screen.Home.route) { inclusive = true } }
}) {
Icon(Icons.AutoMirrored.Filled.Logout, null, tint = Color.White)
}
},
- colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary)
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = PrimaryGreen)
)
}
) { p ->
AppBackground {
Column(
- Modifier.padding(p).fillMaxSize().padding(24.dp),
+ Modifier.padding(p).fillMaxSize().verticalScroll(rememberScrollState()).padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ElevatedCard(
- modifier = Modifier.fillMaxWidth().padding(bottom = 32.dp),
+ modifier = Modifier.fillMaxWidth().padding(bottom = 32.dp).shadow(8.dp, RoundedCornerShape(28.dp)),
shape = RoundedCornerShape(24.dp),
- colors = CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colorScheme.surface)
+ colors = CardDefaults.elevatedCardColors(containerColor = Color.White)
) {
- Row(
- modifier = Modifier.padding(20.dp),
- verticalAlignment = Alignment.CenterVertically
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ PrimaryGreen.copy(alpha = 0.08f),
+ PrimaryOrange.copy(alpha = 0.04f)
+ )
+ )
+ )
) {
- Surface(
- modifier = Modifier.size(64.dp),
- shape = CircleShape,
- color = MaterialTheme.colorScheme.secondaryContainer
+ Row(
+ modifier = Modifier.padding(24.dp),
+ verticalAlignment = Alignment.CenterVertically
) {
- Icon(Icons.Default.Person, null, Modifier.padding(16.dp), MaterialTheme.colorScheme.onSecondaryContainer)
- }
- Spacer(Modifier.width(16.dp))
- Column {
- Text("Selamat Datang,", style = MaterialTheme.typography.bodyMedium, color = Color.Gray)
- Text(user.nama, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
- Text("NPM: ${user.npm}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary)
+ Surface(
+ modifier = Modifier.size(72.dp),
+ shape = CircleShape,
+ color = PrimaryGreen.copy(alpha = 0.15f)
+ ) {
+ Icon(Icons.Default.Person, null, Modifier.padding(20.dp), PrimaryGreen)
+ }
+ Spacer(Modifier.width(18.dp))
+ Column {
+ Text("Selamat Datang,", style = MaterialTheme.typography.bodyMedium, color = Color.Gray)
+ Text(user.nama, style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.ExtraBold, color = Color(0xFF1B5E20))
+ Text("NPM: ${user.npm}", style = MaterialTheme.typography.bodySmall, color = PrimaryGreen)
+ }
}
}
}
@@ -508,20 +601,23 @@ fun HomeScreen(navController: NavHostController, prefs: PreferenceManager) {
Text(
"Layanan Mahasiswa",
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp, start = 4.dp),
- style = MaterialTheme.typography.titleMedium,
- fontWeight = FontWeight.Bold
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF1B5E20)
)
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
DashboardButton(
"Jadwal",
Icons.Default.CalendarMonth,
- Modifier.weight(1f)
+ Modifier.weight(1f),
+ PrimaryGreen
) { navController.navigate(Screen.Schedule.route) }
DashboardButton(
"Riwayat",
Icons.Default.History,
- Modifier.weight(1f)
+ Modifier.weight(1f),
+ PrimaryOrange
) { navController.navigate(Screen.HistoryLogs.route) }
}
@@ -529,17 +625,17 @@ fun HomeScreen(navController: NavHostController, prefs: PreferenceManager) {
Button(
{ navController.navigate(Screen.Attendance.route) },
- Modifier.fillMaxWidth().height(72.dp),
- shape = RoundedCornerShape(24.dp),
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary),
- elevation = ButtonDefaults.buttonElevation(defaultElevation = 8.dp)
+ Modifier.fillMaxWidth().height(76.dp),
+ shape = RoundedCornerShape(28.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = PrimaryOrange),
+ elevation = ButtonDefaults.buttonElevation(defaultElevation = 10.dp)
) {
- Icon(Icons.Default.CameraAlt, null, Modifier.size(28.dp))
+ Icon(Icons.Default.CameraAlt, null, Modifier.size(28.dp), tint = Color.White)
Spacer(Modifier.width(12.dp))
- Text("MULAI ABSENSI", fontWeight = FontWeight.ExtraBold, fontSize = 18.sp)
+ Text("MULAI ABSENSI", fontWeight = FontWeight.ExtraBold, fontSize = 18.sp, color = Color.White)
}
- Spacer(Modifier.weight(1f))
+ Spacer(Modifier.height(32.dp))
ElevatedCard(
Modifier.fillMaxWidth().height(160.dp),
@@ -570,55 +666,65 @@ fun HomeScreen(navController: NavHostController, prefs: PreferenceManager) {
}
@Composable
-fun DashboardButton(text: String, icon: ImageVector, modifier: Modifier = Modifier, onClick: () -> Unit) {
+fun DashboardButton(text: String, icon: ImageVector, modifier: Modifier = Modifier, color: Color, onClick: () -> Unit) {
ElevatedCard(
onClick = onClick,
- modifier = modifier.height(100.dp),
+ modifier = modifier.height(120.dp),
shape = RoundedCornerShape(20.dp),
- colors = CardDefaults.elevatedCardColors(containerColor = Color.White)
+ colors = CardDefaults.elevatedCardColors(containerColor = Color.White),
+ elevation = CardDefaults.elevatedCardElevation(defaultElevation = 6.dp)
) {
- Column(
- Modifier.fillMaxSize(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
+ Box(
+ modifier = Modifier.fillMaxSize().background(
+ Brush.verticalGradient(
+ colors = listOf(color.copy(alpha = 0.08f), Color.Transparent)
+ )
+ ),
+ contentAlignment = Alignment.Center
) {
- Icon(icon, null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(32.dp))
- Spacer(Modifier.height(8.dp))
- Text(text, style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Bold)
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Surface(
+ modifier = Modifier.size(56.dp),
+ shape = CircleShape,
+ color = color.copy(alpha = 0.15f)
+ ) {
+ Icon(icon, null, tint = color, modifier = Modifier.padding(12.dp))
+ }
+ Spacer(Modifier.height(12.dp))
+ Text(text, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = color)
+ }
}
}
}
-@Composable
-fun MenuButton(text: String, icon: ImageVector, onClick: () -> Unit) {
- OutlinedButton(
- onClick,
- Modifier.fillMaxWidth().height(56.dp),
- shape = RoundedCornerShape(16.dp),
- border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f))
- ) {
- Icon(icon, null, tint = MaterialTheme.colorScheme.primary);
- Spacer(Modifier.width(16.dp));
- Text(text, Modifier.weight(1f), color = MaterialTheme.colorScheme.onSurface);
- Icon(Icons.Default.ChevronRight, null, tint = Color.LightGray)
- }
-}
-
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs: PreferenceManager, preselectedCourse: String? = null) {
+fun AttendanceScreen(
+ navController: NavHostController,
+ activity: Activity,
+ prefs: PreferenceManager,
+ preselectedCourse: String? = null,
+ schedDay: String? = null,
+ schedStart: String? = null,
+ schedEnd: String? = null
+) {
val context = LocalContext.current
val user = prefs.getLoggedInUser() ?: User("0", "Guest", "")
-
+
var foto by remember { mutableStateOf(null) }
var userLoc by remember { mutableStateOf(null) }
var isLoading by remember { mutableStateOf(false) }
var privacyMode by remember { mutableStateOf(false) }
+ var distanceToCampus by remember { mutableStateOf(null) }
- val courseList = listOf("Pemrograman Perangkat Bergerak", "Basis Data", "Kecerdasan Buatan", "Jaringan Komputer")
- var expanded by remember { mutableStateOf(false) }
+ val courseList = listOf("Pembelajaran Mesin", "Interaksi Manusia dan Komputer", "Pemrograman Perangkat Bergerak", "Kecerdasan Buatan", "Keamanan Siber")
+ var expandedCourse by remember { mutableStateOf(false) }
var selectedCourse by remember { mutableStateOf(preselectedCourse ?: courseList[0]) }
+ val statusList = listOf("Hadir", "Izin", "Sakit")
+ var expandedStatus by remember { mutableStateOf(false) }
+ var selectedStatus by remember { mutableStateOf("Hadir") }
+
val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) }
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { res ->
if (res.resultCode == Activity.RESULT_OK) {
@@ -627,9 +733,16 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
}
}
val cameraPerm = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { if (it) cameraLauncher.launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE)) }
- val locPerm = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { if (it && ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
- fusedLocationClient.lastLocation.addOnSuccessListener { loc -> if (loc != null) userLoc = loc }
- }}
+ val locPerm = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
+ if (it && ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ fusedLocationClient.lastLocation.addOnSuccessListener { loc ->
+ if (loc != null) {
+ userLoc = loc
+ distanceToCampus = calculateDistance(loc.latitude, loc.longitude, CAMPUS_LAT, CAMPUS_LON)
+ }
+ }
+ }
+ }
LaunchedEffect(Unit) { locPerm.launch(Manifest.permission.ACCESS_FINE_LOCATION) }
@@ -638,7 +751,7 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
TopAppBar(
title = { Text("Presensi Kehadiran", color = Color.White, fontWeight = FontWeight.Bold) },
navigationIcon = { IconButton({ navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = Color.White) } },
- colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary)
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = PrimaryGreen)
)
}
) { p ->
@@ -648,25 +761,76 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
Card(
Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
- colors = CardDefaults.cardColors(containerColor = if (userLoc != null) Color(0xFFE8F5E9) else MaterialTheme.colorScheme.surfaceVariant)
+ colors = CardDefaults.cardColors(containerColor = if (userLoc != null) Color(0xFFE8F5E9) else Color(0xFFFFF3E0))
) {
- Row(Modifier.padding(20.dp), verticalAlignment = Alignment.CenterVertically) {
- Surface(
- modifier = Modifier.size(48.dp),
- shape = CircleShape,
- color = if (userLoc != null) Color(0xFFC8E6C9) else Color.LightGray
- ) {
- Icon(
- if (userLoc != null) Icons.Default.GpsFixed else Icons.Default.LocationSearching,
- null,
- Modifier.padding(12.dp),
- tint = if (userLoc != null) Color(0xFF2E7D32) else Color.Gray
- )
+ Column(Modifier.padding(20.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Surface(
+ modifier = Modifier.size(52.dp),
+ shape = CircleShape,
+ color = if (userLoc != null) PrimaryGreen.copy(alpha = 0.2f) else PrimaryOrange.copy(alpha = 0.2f)
+ ) {
+ Icon(
+ if (userLoc != null) Icons.Default.GpsFixed else Icons.Default.LocationSearching,
+ null,
+ Modifier.padding(14.dp),
+ tint = if (userLoc != null) PrimaryGreen else PrimaryOrange
+ )
+ }
+ Spacer(Modifier.width(16.dp))
+ Column(Modifier.weight(1f)) {
+ Text("Status Lokasi", fontWeight = FontWeight.Bold, fontSize = 16.sp)
+ Text(
+ if (userLoc == null) "Mendapatkan koordinat..." else "Lokasi Berhasil Terdeteksi",
+ style = MaterialTheme.typography.bodySmall,
+ color = if (userLoc != null) PrimaryGreen else PrimaryOrange
+ )
+ }
}
- Spacer(Modifier.width(16.dp))
- Column(Modifier.weight(1f)) {
- Text("Status Lokasi", fontWeight = FontWeight.Bold)
- Text(if (userLoc == null) "Mendapatkan koordinat..." else "Lokasi Berhasil Terdeteksi", style = MaterialTheme.typography.bodySmall)
+
+ if (userLoc != null && distanceToCampus != null) {
+ Spacer(Modifier.height(16.dp))
+ HorizontalDivider(color = Color.Gray.copy(alpha = 0.2f))
+ Spacer(Modifier.height(16.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column {
+ Text("Lokasi Anda", style = MaterialTheme.typography.labelMedium, color = Color.Gray)
+ Text(
+ String.format("%.5f, %.5f", userLoc!!.latitude, userLoc!!.longitude),
+ style = MaterialTheme.typography.bodyMedium,
+ fontWeight = FontWeight.Medium
+ )
+ }
+ Surface(
+ shape = RoundedCornerShape(12.dp),
+ color = when {
+ distanceToCampus!! < 0.5 -> Color(0xFF4CAF50)
+ distanceToCampus!! < 2.0 -> Color(0xFFFF9800)
+ else -> Color(0xFFF44336)
+ }.copy(alpha = 0.15f)
+ ) {
+ Column(
+ modifier = Modifier.padding(12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ String.format("%.2f KM", distanceToCampus),
+ fontWeight = FontWeight.ExtraBold,
+ fontSize = 18.sp,
+ color = when {
+ distanceToCampus!! < 0.5 -> Color(0xFF4CAF50)
+ distanceToCampus!! < 2.0 -> Color(0xFFFF9800)
+ else -> Color(0xFFF44336)
+ }
+ )
+ Text("ke kampus", style = MaterialTheme.typography.labelSmall, color = Color.Gray)
+ }
+ }
+ }
}
}
}
@@ -674,8 +838,8 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
Spacer(Modifier.height(20.dp))
ExposedDropdownMenuBox(
- expanded = expanded,
- onExpandedChange = { expanded = !expanded },
+ expanded = expandedCourse,
+ onExpandedChange = { expandedCourse = !expandedCourse },
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
@@ -683,18 +847,82 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
onValueChange = {},
readOnly = true,
label = { Text("Mata Kuliah") },
- trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
+ leadingIcon = { Icon(Icons.Default.Book, null, tint = PrimaryGreen) },
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedCourse) },
modifier = Modifier.menuAnchor().fillMaxWidth(),
shape = RoundedCornerShape(16.dp)
)
ExposedDropdownMenu(
- expanded = expanded,
- onDismissRequest = { expanded = false }
+ expanded = expandedCourse,
+ onDismissRequest = { expandedCourse = false }
) {
courseList.forEach { course ->
DropdownMenuItem(
text = { Text(course) },
- onClick = { selectedCourse = course; expanded = false }
+ onClick = { selectedCourse = course; expandedCourse = false }
+ )
+ }
+ }
+ }
+
+ Spacer(Modifier.height(16.dp))
+
+ ExposedDropdownMenuBox(
+ expanded = expandedStatus,
+ onExpandedChange = { expandedStatus = !expandedStatus },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ OutlinedTextField(
+ value = selectedStatus,
+ onValueChange = {},
+ readOnly = true,
+ label = { Text("Status Kehadiran") },
+ leadingIcon = {
+ Icon(
+ when(selectedStatus) {
+ "Hadir" -> Icons.Default.CheckCircle
+ "Izin" -> Icons.Default.EventBusy
+ else -> Icons.Default.LocalHospital
+ },
+ null,
+ tint = when(selectedStatus) {
+ "Hadir" -> Color(0xFF4CAF50)
+ "Izin" -> Color(0xFFFF9800)
+ else -> Color(0xFFF44336)
+ }
+ )
+ },
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedStatus) },
+ modifier = Modifier.menuAnchor().fillMaxWidth(),
+ shape = RoundedCornerShape(16.dp)
+ )
+ ExposedDropdownMenu(
+ expanded = expandedStatus,
+ onDismissRequest = { expandedStatus = false }
+ ) {
+ statusList.forEach { status ->
+ DropdownMenuItem(
+ text = {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ when(status) {
+ "Hadir" -> Icons.Default.CheckCircle
+ "Izin" -> Icons.Default.EventBusy
+ else -> Icons.Default.LocalHospital
+ },
+ null,
+ tint = when(status) {
+ "Hadir" -> Color(0xFF4CAF50)
+ "Izin" -> Color(0xFFFF9800)
+ else -> Color(0xFFF44336)
+ },
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(Modifier.width(12.dp))
+ Text(status)
+ }
+ },
+ onClick = { selectedStatus = status; expandedStatus = false }
)
}
}
@@ -708,7 +936,7 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
.shadow(16.dp, CircleShape)
.clip(CircleShape)
.background(Color.White)
- .border(6.dp, MaterialTheme.colorScheme.primaryContainer, CircleShape),
+ .border(6.dp, PrimaryGreen.copy(alpha = 0.3f), CircleShape),
Alignment.Center
) {
if (foto != null) {
@@ -726,20 +954,18 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
OutlinedButton(
{ cameraPerm.launch(Manifest.permission.CAMERA) },
Modifier.fillMaxWidth().height(56.dp),
- shape = RoundedCornerShape(16.dp)
+ shape = RoundedCornerShape(16.dp),
+ border = BorderStroke(2.dp, PrimaryGreen)
) {
Icon(Icons.Default.CameraAlt, null)
Spacer(Modifier.width(8.dp))
- Text("AMBIL FOTO ULANG")
+ Text("AMBIL FOTO ULANG", fontWeight = FontWeight.Bold)
}
Spacer(Modifier.height(16.dp))
- Row(
- modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Icon(Icons.Default.Shield, null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp))
+ Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically) {
+ Icon(Icons.Default.Shield, null, tint = PrimaryGreen, modifier = Modifier.size(20.dp))
Spacer(Modifier.width(8.dp))
Text("Samarkan Lokasi (Privasi)", Modifier.weight(1f), fontSize = 14.sp)
Switch(checked = privacyMode, onCheckedChange = { privacyMode = it })
@@ -750,28 +976,57 @@ fun AttendanceScreen(navController: NavHostController, activity: Activity, prefs
Button(
{
if (foto != null && userLoc != null) {
+ // Check schedule if coming from schedule screen
+ if (schedDay != null && schedStart != null && schedEnd != null && selectedStatus == "Hadir") {
+ if (!isWithinSchedule(schedDay, schedStart, schedEnd)) {
+ Toast.makeText(context, "Absensi ditolak: Diluar waktu mata kuliah!", Toast.LENGTH_LONG).show()
+ return@Button
+ }
+ }
+
var finalLat = userLoc!!.latitude
var finalLon = userLoc!!.longitude
+ var finalDistance = distanceToCampus ?: 0.0
if (privacyMode) {
finalLat += (Random.nextDouble() - 0.5) * 0.001
finalLon += (Random.nextDouble() - 0.5) * 0.001
+ finalDistance = calculateDistance(finalLat, finalLon, CAMPUS_LAT, CAMPUS_LON)
}
- kirimKeN8n(activity, user.npm, user.nama, finalLat, finalLon, selectedCourse, "Hadir", foto!!, { base64 ->
- prefs.saveAttendance(user.npm, AttendanceLog(System.currentTimeMillis(), selectedCourse, "Hadir", base64, finalLat, finalLon))
- navController.navigate(Screen.Success.route)
+ val payload = AttendancePayload(
+ npm = user.npm,
+ nama = user.nama,
+ latitude = finalLat,
+ longitude = finalLon,
+ mataKuliah = selectedCourse,
+ status = selectedStatus,
+ foto = foto!!,
+ distance = finalDistance
+ )
+
+ kirimKeN8n(activity, payload, { base64 ->
+ prefs.saveAttendance(user.npm, AttendanceLog(System.currentTimeMillis(), selectedCourse, selectedStatus, base64, finalLat, finalLon, finalDistance))
+ activity.runOnUiThread { navController.navigate(Screen.Success.route) }
}, { isLoading = it })
} else Toast.makeText(context, "Harap ambil foto & aktifkan GPS", Toast.LENGTH_SHORT).show()
},
Modifier.fillMaxWidth().height(64.dp),
shape = RoundedCornerShape(20.dp),
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = when(selectedStatus) {
+ "Hadir" -> Color(0xFF4CAF50)
+ "Izin" -> Color(0xFFFF9800)
+ else -> Color(0xFFF44336)
+ }
+ ),
enabled = !isLoading
) {
if (isLoading) {
CircularProgressIndicator(Modifier.size(24.dp), color = Color.White, strokeWidth = 3.dp)
} else {
- Text("KIRIM PRESENSI", fontWeight = FontWeight.Bold, fontSize = 16.sp)
+ Icon(Icons.Default.Send, null, tint = Color.White)
+ Spacer(Modifier.width(12.dp))
+ Text("KIRIM PRESENSI - $selectedStatus", fontWeight = FontWeight.Bold, fontSize = 16.sp, color = Color.White)
}
}
Spacer(Modifier.height(40.dp))
@@ -791,32 +1046,19 @@ fun SuccessScreen(navController: NavHostController) {
Surface(
modifier = Modifier.size(160.dp),
shape = CircleShape,
- color = Color(0xFFE8F5E9)
+ color = PrimaryGreen.copy(alpha = 0.12f)
) {
- Icon(
- Icons.Default.CheckCircle, null,
- Modifier.padding(32.dp),
- Color(0xFF4CAF50)
- )
+ Icon(Icons.Default.CheckCircle, null, Modifier.padding(32.dp), PrimaryGreen)
}
Spacer(Modifier.height(32.dp))
- Text(
- "Absensi Berhasil!",
- style = MaterialTheme.typography.headlineMedium,
- fontWeight = FontWeight.Bold,
- color = Color(0xFF2E7D32)
- )
- Text(
- "Data Anda telah tercatat di sistem.",
- style = MaterialTheme.typography.bodyMedium,
- color = Color.Gray,
- textAlign = TextAlign.Center
- )
+ Text("Absensi Berhasil!", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.ExtraBold, color = PrimaryGreen)
+ Text("Data Anda telah tercatat di sistem.", style = MaterialTheme.typography.bodyMedium, color = Color.Gray, textAlign = TextAlign.Center)
Spacer(Modifier.height(64.dp))
Button(
{ navController.navigate(Screen.Home.route) { popUpTo(Screen.Home.route) { inclusive = true } } },
Modifier.fillMaxWidth().height(56.dp),
- shape = RoundedCornerShape(16.dp)
+ shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = PrimaryGreen)
) { Text("KEMBALI KE BERANDA", fontWeight = FontWeight.Bold) }
}
}
@@ -832,7 +1074,7 @@ fun HistoryScreen(navController: NavHostController, prefs: PreferenceManager) {
TopAppBar(
title = { Text("Riwayat Absensi", color = Color.White, fontWeight = FontWeight.Bold) },
navigationIcon = { IconButton({ navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = Color.White) } },
- colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary)
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = PrimaryGreen)
)
}
) { p ->
@@ -876,24 +1118,47 @@ fun HistoryScreen(navController: NavHostController, prefs: PreferenceManager) {
style = MaterialTheme.typography.bodySmall,
color = Color.Gray
)
- Text(
- SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(log.timestamp)) + " WIB",
- style = MaterialTheme.typography.labelSmall,
- color = MaterialTheme.colorScheme.primary
- )
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(log.timestamp)) + " WIB",
+ style = MaterialTheme.typography.labelSmall,
+ color = PrimaryGreen
+ )
+ Spacer(Modifier.width(8.dp))
+ Surface(
+ shape = RoundedCornerShape(4.dp),
+ color = when(log.status) {
+ "Hadir" -> Color(0xFF4CAF50)
+ "Izin" -> Color(0xFFFF9800)
+ else -> Color(0xFFF44336)
+ }.copy(alpha = 0.15f)
+ ) {
+ Text(
+ log.status,
+ modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
+ style = MaterialTheme.typography.labelSmall,
+ fontWeight = FontWeight.Bold,
+ color = when(log.status) {
+ "Hadir" -> Color(0xFF4CAF50)
+ "Izin" -> Color(0xFFFF9800)
+ else -> Color(0xFFF44336)
+ }
+ )
+ }
+ }
if (log.latitude != null && log.longitude != null) {
Spacer(modifier = Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.05f), RoundedCornerShape(4.dp)).padding(horizontal = 6.dp, vertical = 2.dp)
+ modifier = Modifier.background(PrimaryGreen.copy(alpha = 0.05f), RoundedCornerShape(4.dp)).padding(horizontal = 6.dp, vertical = 2.dp)
) {
- Icon(Icons.Default.LocationOn, null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(12.dp))
+ Icon(Icons.Default.LocationOn, null, tint = PrimaryGreen, modifier = Modifier.size(12.dp))
Spacer(modifier = Modifier.width(4.dp))
Text(
text = String.format("%.5f, %.5f", log.latitude, log.longitude),
style = MaterialTheme.typography.labelSmall,
- color = MaterialTheme.colorScheme.primary,
+ color = PrimaryGreen,
fontWeight = FontWeight.Medium
)
}
@@ -912,10 +1177,14 @@ fun HistoryScreen(navController: NavHostController, prefs: PreferenceManager) {
@Composable
fun ScheduleScreen(navController: NavHostController) {
val schedules = listOf(
- ScheduleItem("Senin", "08:00 - 10:30", "Pemrograman Perangkat Bergerak", "Lab Komputer 1"),
- ScheduleItem("Selasa", "13:00 - 15:30", "Basis Data", "Ruang 302"),
- ScheduleItem("Rabu", "10:00 - 12:30", "Kecerdasan Buatan", "Ruang 405"),
- ScheduleItem("Kamis", "08:00 - 10:30", "Jaringan Komputer", "Lab Jaringan")
+ ScheduleItem("INFO-3527", "Pembelajaran Mesin", "F5A1", 3, "Wowon Priatna, S.T., M.Ti", "UBJ-BKS || R. Said Soekanto || SS-411", "Selasa", "08:00", "10:30"),
+ ScheduleItem("INFO-3528", "Interaksi Manusia dan Komputer", "F5A5", 3, "Hendarman Lubis, S.Kom., M.Kom.", "UBJ-BKS || R. Said Soekanto || SS-405", "Rabu", "16:15", "18:45"),
+ ScheduleItem("INFO-3529", "Pemrograman Perangkat Bergerak", "F5A5", 3, "Arif Rifai Dwiyanto, ST., MTI", "UBJ-BKS || Grha Tanoto || W-104", "Kamis", "13:30", "16:00"),
+ ScheduleItem("INFO-3530", "Kecerdasan Buatan", "F5A8", 3, "Kusdarnowo Hantoro, S.Kom, M.Kom.", "UBJ-BKS || R. Said Soekanto || SS-426", "Senin", "10:45", "13:15"),
+ ScheduleItem("INFO-3531", "Keamanan Siber", "F5A6", 3, "Arif Rifai Dwiyanto, ST., MTI", "UBJ-BKS || R. Said Soekanto || SS-405", "Kamis", "16:15", "18:45"),
+ ScheduleItem("INFO-3532", "Manajemen Proyek Perangkat Lunak", "F5A2", 3, "R Wisnu Prio Pamungkas", "UBJ-BKS || DR. H. M. Yasin || MY-104", "Rabu", "13:30", "16:00"),
+ ScheduleItem("INFO-4746", "Etika Profesi", "F7A3", 2, "Dr. Dra. Tyastuti Sri Lestari, M.M.", "UBJ-BKS || R. Said Soekanto || SS-206", "Selasa", "13:30", "15:10"),
+ ScheduleItem("MKDU-2007", "Manajemen Sekuriti", "F5A7", 2, "Ratna Salkiawati, S.T., M.Kom", "UBJ-BKS || Grha Tanoto || W-105", "Kamis", "08:00", "09:40")
)
Scaffold(
@@ -923,7 +1192,7 @@ fun ScheduleScreen(navController: NavHostController) {
TopAppBar(
title = { Text("Jadwal Perkuliahan", color = Color.White, fontWeight = FontWeight.Bold) },
navigationIcon = { IconButton({ navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = Color.White) } },
- colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary)
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = PrimaryGreen)
)
}
) { p ->
@@ -931,11 +1200,11 @@ fun ScheduleScreen(navController: NavHostController) {
Column(modifier = Modifier.padding(p).fillMaxSize()) {
Surface(
modifier = Modifier.fillMaxWidth().padding(16.dp),
- color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f),
+ color = PrimaryOrange.copy(alpha = 0.15f),
shape = RoundedCornerShape(12.dp)
) {
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
- Icon(Icons.Default.Info, null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp))
+ Icon(Icons.Default.Info, null, tint = PrimaryOrange, modifier = Modifier.size(20.dp))
Spacer(Modifier.width(8.dp))
Text("Pilih mata kuliah untuk melakukan absensi.", style = MaterialTheme.typography.bodySmall)
}
@@ -947,7 +1216,7 @@ fun ScheduleScreen(navController: NavHostController) {
.fillMaxWidth()
.padding(vertical = 8.dp)
.clickable {
- navController.navigate(Screen.Attendance.route + "?course=${item.course}")
+ navController.navigate(Screen.Attendance.route + "?course=${item.course}&day=${item.day}&start=${item.timeStart}&end=${item.timeEnd}")
},
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.elevatedCardColors(containerColor = Color.White)
@@ -962,17 +1231,27 @@ fun ScheduleScreen(navController: NavHostController) {
onClick = {},
label = { Text(item.day) },
shape = RoundedCornerShape(8.dp),
- colors = SuggestionChipDefaults.suggestionChipColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
+ colors = SuggestionChipDefaults.suggestionChipColors(containerColor = PrimaryGreen.copy(alpha = 0.1f))
)
- Text(item.time, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.bodyMedium)
+ Text("${item.timeStart} - ${item.timeEnd}", fontWeight = FontWeight.Bold, color = PrimaryGreen, style = MaterialTheme.typography.bodyMedium)
}
+
+ Spacer(modifier = Modifier.height(12.dp))
+ Text(item.course, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.ExtraBold, color = Color(0xFF1B5E20))
+ Text("Kode: ${item.kode} β’ Kelas: ${item.kelas} (${item.sks} SKS)", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
+
Spacer(modifier = Modifier.height(12.dp))
- Text(item.course, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.ExtraBold)
- Spacer(modifier = Modifier.height(8.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(Icons.Default.MeetingRoom, null, tint = Color.Gray, modifier = Modifier.size(18.dp))
+ Icon(Icons.Default.Person, null, tint = PrimaryOrange, modifier = Modifier.size(16.dp))
Spacer(modifier = Modifier.width(6.dp))
- Text(item.room, style = MaterialTheme.typography.bodyMedium, color = Color.Gray)
+ Text(item.dosen, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(Icons.Default.MeetingRoom, null, tint = Color.Gray, modifier = Modifier.size(16.dp))
+ Spacer(modifier = Modifier.width(6.dp))
+ Text(item.room, style = MaterialTheme.typography.bodySmall, color = Color.Gray)
}
}
}
@@ -983,3 +1262,63 @@ fun ScheduleScreen(navController: NavHostController) {
}
}
}
+
+@Composable
+fun EditProfileScreen(navController: NavHostController, prefs: PreferenceManager) {
+ val user = prefs.getLoggedInUser() ?: User("", "Guest", "")
+ var name by remember { mutableStateOf(user.nama) }
+ var password by remember { mutableStateOf(user.password) }
+ val context = LocalContext.current
+
+ AppBackground {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 32.dp)
+ .verticalScroll(rememberScrollState()),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Icon(Icons.Default.Person, null, Modifier.size(80.dp).padding(bottom = 16.dp), PrimaryGreen)
+ Text("Edit Profile", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, color = PrimaryGreen)
+ Text("Update your account details", style = MaterialTheme.typography.bodyMedium, color = Color.Gray, modifier = Modifier.padding(bottom = 32.dp))
+
+ OutlinedTextField(
+ value = name,
+ onValueChange = { name = it },
+ label = { Text("Name") },
+ leadingIcon = { Icon(Icons.Outlined.Person, null, tint = PrimaryGreen) },
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(16.dp)
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ OutlinedTextField(
+ value = password,
+ onValueChange = { password = it },
+ label = { Text("Password") },
+ leadingIcon = { Icon(Icons.Outlined.Lock, null, tint = PrimaryGreen) },
+ visualTransformation = PasswordVisualTransformation(),
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(16.dp),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done)
+ )
+ Spacer(modifier = Modifier.height(32.dp))
+ Button(
+ onClick = {
+ if (name.isNotEmpty() && password.isNotEmpty()) {
+ prefs.saveUser(user.copy(nama = name, password = password))
+ Toast.makeText(context, "Profile updated successfully!", Toast.LENGTH_SHORT).show()
+ navController.popBackStack()
+ } else {
+ Toast.makeText(context, "Please fill all fields", Toast.LENGTH_SHORT).show()
+ }
+ },
+ modifier = Modifier.fillMaxWidth().height(56.dp),
+ shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = PrimaryGreen)
+ ) {
+ Text("SAVE", fontWeight = FontWeight.Bold)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Color.kt b/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Color.kt
index fe2a319..3282ce3 100644
--- a/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Color.kt
+++ b/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Color.kt
@@ -2,15 +2,21 @@ package id.ac.ubharajaya.sistemakademik.ui.theme
import androidx.compose.ui.graphics.Color
-val GreenPrimary = Color(0xFF2E7D32)
-val GreenSecondary = Color(0xFF4CAF50)
-val GreenTertiary = Color(0xFF81C784)
-val GreenLight = Color(0xFFE8F5E9)
+// Premium Academic Palette
+val PrimaryGreen = Color(0xFF1B5E20)
+val PrimaryOrange = Color(0xFFE65100)
+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 PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
-
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
+val GreenLight = Color(0xFFE8F5E9)
diff --git a/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Theme.kt b/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Theme.kt
index 44fbcfd..4e8f1bf 100644
--- a/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Theme.kt
+++ b/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Theme.kt
@@ -12,29 +12,31 @@ import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
- primary = GreenSecondary,
- secondary = GreenTertiary,
- tertiary = GreenLight,
- background = Color(0xFF1B1C1B),
- surface = Color(0xFF1B1C1B),
- onPrimary = Color.Black,
- onSecondary = Color.Black,
- onTertiary = Color.Black,
+ primary = PrimaryGreen,
+ secondary = PrimaryOrange,
+ tertiary = SuccessGreen,
+ background = Color(0xFF0D1117),
+ surface = Color(0xFF161B22),
+ surfaceVariant = Color(0xFF30363D),
+ onPrimary = Color.White,
+ onSecondary = Color.White,
+ onTertiary = Color.White,
onBackground = Color.White,
onSurface = Color.White,
)
private val LightColorScheme = lightColorScheme(
- primary = GreenPrimary,
- secondary = GreenSecondary,
- tertiary = GreenTertiary,
- background = Color(0xFFF6FBF6),
- surface = Color.White,
+ primary = PrimaryGreen,
+ secondary = PrimaryOrange,
+ tertiary = SuccessGreen,
+ background = BackgroundLight,
+ surface = SurfaceWhite,
+ surfaceVariant = Color(0xFFF0F2F5),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
- onBackground = Color(0xFF1B1C1B),
- onSurface = Color(0xFF1B1C1B),
+ onBackground = TextDark,
+ onSurface = TextDark,
)
@Composable
@@ -46,6 +48,7 @@ fun SistemAkademikTheme(
val view = LocalView.current
if (!view.isInEditMode) {
val window = (view.context as Activity).window
+ @Suppress("DEPRECATION")
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = false
}
diff --git a/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Type.kt b/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Type.kt
index e2982e7..8e24d7e 100644
--- a/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Type.kt
+++ b/app/src/main/java/id/ac/ubharajaya/sistemakademik/ui/theme/Type.kt
@@ -6,29 +6,43 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
-// Set of Material typography styles to start with
+// Modernized Material typography for the app
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(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
- lineHeight = 24.sp,
- letterSpacing = 0.5.sp
- )
- /* Other default text styles to override
- titleLarge = TextStyle(
+ lineHeight = 22.sp
+ ),
+ bodyMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
- fontSize = 22.sp,
- lineHeight = 28.sp,
- letterSpacing = 0.sp
+ fontSize = 14.sp
+ ),
+ labelLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
- fontSize = 11.sp,
- lineHeight = 16.sp,
- letterSpacing = 0.5.sp
+ fontSize = 12.sp
)
- */
)
\ No newline at end of file
diff --git a/app/src/main/res/drawable/logo_ubhara.png b/app/src/main/res/drawable/logo_ubhara.png
new file mode 100644
index 0000000..a7ce38b
Binary files /dev/null and b/app/src/main/res/drawable/logo_ubhara.png differ