From 95522acdcbb841e8e493c7077c0f2d12bec84840 Mon Sep 17 00:00:00 2001 From: IRedDragonICY Date: Sat, 16 Mar 2024 02:29:24 +0700 Subject: [PATCH] feat: refactored and optimized various classes The main changes in this commit include: - Refactored SessionManager and made its methods more generic. - Simplified the logout method in MainViewModel. - Updated the AttendanceService, AttendanceReceiver and Auth classes for better error handling and code readability. - Made minor adjustments in MainActivity and SettingsView for better UI management. - Optimized Auth class for better data handling during login and logout operations. This commit is aimed at improving overall code quality and efficiency. --- .../java/com/uad/portal/AttendanceReceiver.kt | 17 +++--- .../java/com/uad/portal/AttendanceService.kt | 8 +-- app/src/main/java/com/uad/portal/Auth.kt | 47 ++++++++++----- .../main/java/com/uad/portal/MainActivity.kt | 6 +- .../main/java/com/uad/portal/MainViewModel.kt | 18 +++--- .../java/com/uad/portal/SessionManager.kt | 60 +++++-------------- .../main/java/com/uad/portal/SettingsView.kt | 24 ++++++-- 7 files changed, 86 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/com/uad/portal/AttendanceReceiver.kt b/app/src/main/java/com/uad/portal/AttendanceReceiver.kt index d959a8b..24fd550 100644 --- a/app/src/main/java/com/uad/portal/AttendanceReceiver.kt +++ b/app/src/main/java/com/uad/portal/AttendanceReceiver.kt @@ -1,6 +1,5 @@ -import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -10,12 +9,13 @@ import com.uad.portal.SessionManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class AttendanceReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - val klsdtId = intent.getStringExtra("klsdtId") - val presklsId = intent.getStringExtra("presklsId") + val klsdtId = intent.getStringExtra("klsdtId") ?: return + val presklsId = intent.getStringExtra("presklsId") ?: return val sessionManager = SessionManager(context) val viewModel = MainViewModel().apply { @@ -23,13 +23,10 @@ class AttendanceReceiver : BroadcastReceiver() { } CoroutineScope(Dispatchers.IO).launch { - val isAttendanceMarked = viewModel.markAttendance(klsdtId!!, presklsId!!) - (context as Activity).runOnUiThread { - if (isAttendanceMarked) { - Toast.makeText(context, "Attendance marked!", Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(context, "Failed to mark attendance!", Toast.LENGTH_SHORT).show() - } + val isAttendanceMarked = viewModel.markAttendance(klsdtId, presklsId) + withContext(Dispatchers.Main) { + val message = if (isAttendanceMarked) "Attendance marked!" else "Failed to mark attendance!" + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } } } diff --git a/app/src/main/java/com/uad/portal/AttendanceService.kt b/app/src/main/java/com/uad/portal/AttendanceService.kt index 56d6815..6e705fa 100644 --- a/app/src/main/java/com/uad/portal/AttendanceService.kt +++ b/app/src/main/java/com/uad/portal/AttendanceService.kt @@ -1,7 +1,6 @@ import android.app.IntentService import android.content.Intent import com.uad.portal.MainViewModel -import com.uad.portal.SessionManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -9,16 +8,15 @@ import kotlinx.coroutines.launch class AttendanceService : IntentService("AttendanceService") { override fun onHandleIntent(intent: Intent?) { - val klsdtId = intent?.getStringExtra("klsdtId") - val presklsId = intent?.getStringExtra("presklsId") + val klsdtId = intent?.getStringExtra("klsdtId") ?: return + val presklsId = intent?.getStringExtra("presklsId") ?: return - val sessionManager = SessionManager(this) val viewModel = MainViewModel().apply { initSessionManager(this@AttendanceService) } CoroutineScope(Dispatchers.IO).launch { - val isAttendanceMarked = viewModel.markAttendance(klsdtId!!, presklsId!!) + viewModel.markAttendance(klsdtId, presklsId) } } } diff --git a/app/src/main/java/com/uad/portal/Auth.kt b/app/src/main/java/com/uad/portal/Auth.kt index 6b1f46e..7c5b384 100644 --- a/app/src/main/java/com/uad/portal/Auth.kt +++ b/app/src/main/java/com/uad/portal/Auth.kt @@ -11,6 +11,7 @@ data class ReglabCredentials(val username: String, val password: String) data class LoginResult(val userInfo: UserInfo?, val errorMessage: String?) data class ReglabLoginResult(val success: Boolean, val errorMessage: String?) + class Auth { companion object { private const val PORTAL_LOGIN_URL = "https://portal.uad.ac.id/login" @@ -21,8 +22,14 @@ class Auth { } // Portal Auth Methods - private suspend fun loginPortal(credentials: Credentials) = executeConnection(PORTAL_LOGIN_URL, Connection.Method.POST, credentials) + private suspend fun loginPortal(credentials: Credentials) = executeConnection( + PORTAL_LOGIN_URL, + Connection.Method.POST, + portalCredentials = credentials + ) + suspend fun logoutPortal(): Boolean = executeConnection(PORTAL_LOGOUT_URL, Connection.Method.GET).statusCode() == 200 + private fun checkLogin(response: Connection.Response): LoginResult { val doc = response.parse() val loginForm = doc.select("div.form-login") @@ -33,6 +40,7 @@ class Auth { } return LoginResult(UserInfo(), "") } + private fun getResponseWithCookie(url: String, cookieName: String, cookieValue: String): Connection.Response { return Jsoup.connect(url) .cookie(cookieName, cookieValue) @@ -40,7 +48,6 @@ class Auth { .execute() } - private suspend fun executeConnection( url: String, method: Connection.Method, @@ -54,22 +61,26 @@ class Auth { .apply { when { method == Connection.Method.POST && portalCredentials != null -> { - data("login", portalCredentials.username, "password", portalCredentials.password, "remember", "1") + data( + "login", portalCredentials.username, + "password", portalCredentials.password, + "remember", "1" + ) } method == Connection.Method.POST && reglabCredentials != null && token != null && cookies != null -> { cookies(cookies) - data("_token", token) - data("email", "${reglabCredentials.username}@webmail.uad.ac.id") - data("password", reglabCredentials.password) - data("remember", "on") + data( + "_token", token, + "email", "${reglabCredentials.username}@webmail.uad.ac.id", + "password", reglabCredentials.password, + "remember", "on" + ) } } } .execute() } - - private fun getUserInfo(sessionCookie: String): UserInfo { val response = getResponseWithCookie(PORTAL_DASHBOARD_URL, "portal_session", sessionCookie) val doc = response.parse() @@ -85,6 +96,7 @@ class Auth { return UserInfo(username, avatarUrl, ipk, sks) } + private suspend fun processLogin(sessionManager: SessionManager, credentials: Credentials): LoginResult { val response = loginPortal(credentials) val sessionCookie = response.cookie("portal_session") @@ -96,6 +108,7 @@ class Auth { } return loginResult } + suspend fun login(sessionManager: SessionManager, credentials: Credentials): LoginResult { val loginResult = processLogin(sessionManager, credentials) if (loginResult.userInfo != null) { @@ -110,22 +123,27 @@ class Auth { .method(Connection.Method.GET) .execute() } + private fun getToken(response: Connection.Response): String { val doc = response.parse() return doc.select("input[name=_token]").first()?.attr("value") ?: "" } + private fun loginReglab(credentials: ReglabCredentials, token: String, cookies: Map): Connection.Response { return Jsoup.connect(REGLAB_LOGIN_URL) .method(Connection.Method.POST) .cookies(cookies) .apply { - data("_token", token) - data("email", "${credentials.username}@webmail.uad.ac.id") - data("password", credentials.password) - data("remember", "on") + data( + "_token", token, + "email", "${credentials.username}@webmail.uad.ac.id", + "password", credentials.password, + "remember", "on" + ) } .execute() } + private fun checkLoginReglab(response: Connection.Response): ReglabLoginResult { val doc = response.parse() val loginForm = doc.select("form.py-2") @@ -134,6 +152,7 @@ class Auth { } return ReglabLoginResult(true, null) } + fun loginReglab(credentials: ReglabCredentials, sessionManager: SessionManager): ReglabLoginResult { val session = sessionManager.loadReglabSession() if (session != null) { @@ -162,4 +181,4 @@ class Auth { } return ReglabLoginResult(false, "Failed to get cookie") } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/uad/portal/MainActivity.kt b/app/src/main/java/com/uad/portal/MainActivity.kt index 368040d..6cb51a4 100644 --- a/app/src/main/java/com/uad/portal/MainActivity.kt +++ b/app/src/main/java/com/uad/portal/MainActivity.kt @@ -53,13 +53,11 @@ class MainActivity : ComponentActivity() { @Composable private fun AppContent(mainViewModel: MainViewModel) { - when(mainViewModel.currentScreen.value) { + when (mainViewModel.currentScreen.value) { Screen.Attendance -> AttendanceView(mainViewModel) Screen.Settings -> SettingsView(mainViewModel) Screen.Reglab -> ReglabView(mainViewModel) Screen.Home -> if (mainViewModel.isLoggedInState.value) HomeView(mainViewModel) else LoginView(mainViewModel) } } -} - - +} \ No newline at end of file diff --git a/app/src/main/java/com/uad/portal/MainViewModel.kt b/app/src/main/java/com/uad/portal/MainViewModel.kt index cf2c010..22311c3 100644 --- a/app/src/main/java/com/uad/portal/MainViewModel.kt +++ b/app/src/main/java/com/uad/portal/MainViewModel.kt @@ -39,16 +39,15 @@ class MainViewModel : ViewModel() { autoLogin() } } + fun initAttendanceWorker(context: Context) { val attendanceWorkRequest = PeriodicWorkRequestBuilder(3, TimeUnit.MINUTES).build() WorkManager.getInstance(context).enqueue(attendanceWorkRequest) } - fun autoLogin() = viewModelScope.launch { + private fun autoLogin() = viewModelScope.launch { val credentials = sessionManager.loadCredentials() - credentials?.let { - login(it) - } + credentials?.let { login(it) } } fun navigate(screen: Screen) { @@ -56,12 +55,10 @@ class MainViewModel : ViewModel() { } fun logout() = viewModelScope.launch { - auth.logoutPortal().let { isLoggedOut -> - if (isLoggedOut) { - sessionManager.clearSession() - isLoggedInState.value = false - userInfoState.value = null - } + if (auth.logoutPortal()) { + sessionManager.clearSession() + isLoggedInState.value = false + userInfoState.value = null } } @@ -73,6 +70,7 @@ class MainViewModel : ViewModel() { userInfoState.value = it } } + val reglabCredentials = ReglabCredentials(credentials.username, credentials.password) val reglabLoginResult = auth.loginReglab(reglabCredentials, sessionManager) if (reglabLoginResult.success) { diff --git a/app/src/main/java/com/uad/portal/SessionManager.kt b/app/src/main/java/com/uad/portal/SessionManager.kt index 66fcad4..60bfd77 100644 --- a/app/src/main/java/com/uad/portal/SessionManager.kt +++ b/app/src/main/java/com/uad/portal/SessionManager.kt @@ -9,11 +9,12 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken class SessionManager(context: Context) { + private val masterKey = MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() - private val sharedPref: SharedPreferences = EncryptedSharedPreferences.create( + val sharedPref: SharedPreferences = EncryptedSharedPreferences.create( context, "Portal UAD", masterKey, @@ -21,64 +22,33 @@ class SessionManager(context: Context) { EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) - private val gson = Gson() + val gson = Gson() - fun savePortalSession(session: Session) { + inline fun saveSession(key: String, session: T) { val sessionJson = gson.toJson(session) - sharedPref.edit { - putString("portal_session", sessionJson) - } + sharedPref.edit { putString(key, sessionJson) } } - fun loadPortalSession(): Session? { - val sessionJson = sharedPref.getString("portal_session", null) + inline fun loadSession(key: String): T? { + val sessionJson = sharedPref.getString(key, null) return if (sessionJson != null) { - val type = object : TypeToken() {}.type + val type = object : TypeToken() {}.type gson.fromJson(sessionJson, type) } else { null } } - fun saveReglabSession(session: ReglabSession) { - val sessionJson = gson.toJson(session) - sharedPref.edit { - putString("reglab_session", sessionJson) - } - } - - fun saveCredentials(credentials: Credentials) { - val credentialsJson = gson.toJson(credentials) - sharedPref.edit { - putString("credentials", credentialsJson) - } - } - - fun loadCredentials(): Credentials? { - val credentialsJson = sharedPref.getString("credentials", null) - return if (credentialsJson != null) { - val type = object : TypeToken() {}.type - gson.fromJson(credentialsJson, type) - } else { - null - } - } + fun savePortalSession(session: Session) = saveSession("portal_session", session) + fun loadPortalSession(): Session? = loadSession("portal_session") - fun loadReglabSession(): ReglabSession? { - val sessionJson = sharedPref.getString("reglab_session", null) - return if (sessionJson != null) { - val type = object : TypeToken() {}.type - gson.fromJson(sessionJson, type) - } else { - null - } - } + fun saveReglabSession(session: ReglabSession) = saveSession("reglab_session", session) + fun loadReglabSession(): ReglabSession? = loadSession("reglab_session") + fun saveCredentials(credentials: Credentials) = saveSession("credentials", credentials) + fun loadCredentials(): Credentials? = loadSession("credentials") fun clearSession() { - sharedPref.edit { - clear() - apply() - } + sharedPref.edit { clear() } } } \ No newline at end of file diff --git a/app/src/main/java/com/uad/portal/SettingsView.kt b/app/src/main/java/com/uad/portal/SettingsView.kt index e1cbdaf..77f5feb 100644 --- a/app/src/main/java/com/uad/portal/SettingsView.kt +++ b/app/src/main/java/com/uad/portal/SettingsView.kt @@ -3,17 +3,29 @@ package com.uad.portal import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsView(mainViewModel: MainViewModel) { - Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 32.dp)) { - Button(onClick = { mainViewModel.navigate(Screen.Home) }) { - Text("Back") - } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + ) { + TopAppBar( + title = { Text("Settings") }, + navigationIcon = { + IconButton(onClick = { mainViewModel.navigate(Screen.Home) }) { + Icon(Icons.Default.ArrowBack, contentDescription = "Back") + } + } + ) + // Add other settings options here } }