Skip to content

Commit

Permalink
feat: refactored and optimized various classes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
IRedDragonICY committed Mar 15, 2024
1 parent 9d424d3 commit 95522ac
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 94 deletions.
17 changes: 7 additions & 10 deletions app/src/main/java/com/uad/portal/AttendanceReceiver.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@


import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
Expand All @@ -10,26 +9,24 @@ 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 {
initSessionManager(context)
}

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()
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions app/src/main/java/com/uad/portal/AttendanceService.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
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

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)
}
}
}
47 changes: 33 additions & 14 deletions app/src/main/java/com/uad/portal/Auth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")
Expand All @@ -33,14 +40,14 @@ class Auth {
}
return LoginResult(UserInfo(), "")
}

private fun getResponseWithCookie(url: String, cookieName: String, cookieValue: String): Connection.Response {
return Jsoup.connect(url)
.cookie(cookieName, cookieValue)
.method(Connection.Method.GET)
.execute()
}


private suspend fun executeConnection(
url: String,
method: Connection.Method,
Expand All @@ -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()
Expand All @@ -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")
Expand All @@ -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) {
Expand All @@ -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<String, String>): 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")
Expand All @@ -134,6 +152,7 @@ class Auth {
}
return ReglabLoginResult(true, null)
}

fun loginReglab(credentials: ReglabCredentials, sessionManager: SessionManager): ReglabLoginResult {
val session = sessionManager.loadReglabSession()
if (session != null) {
Expand Down Expand Up @@ -162,4 +181,4 @@ class Auth {
}
return ReglabLoginResult(false, "Failed to get cookie")
}
}
}
6 changes: 2 additions & 4 deletions app/src/main/java/com/uad/portal/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}


}
18 changes: 8 additions & 10 deletions app/src/main/java/com/uad/portal/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,26 @@ class MainViewModel : ViewModel() {
autoLogin()
}
}

fun initAttendanceWorker(context: Context) {
val attendanceWorkRequest = PeriodicWorkRequestBuilder<AttendanceWorker>(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) {
currentScreen.value = screen
}

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
}
}

Expand All @@ -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) {
Expand Down
60 changes: 15 additions & 45 deletions app/src/main/java/com/uad/portal/SessionManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,46 @@ 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,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

private val gson = Gson()
val gson = Gson()

fun savePortalSession(session: Session) {
inline fun <reified T> 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 <reified T> loadSession(key: String): T? {
val sessionJson = sharedPref.getString(key, null)
return if (sessionJson != null) {
val type = object : TypeToken<Session>() {}.type
val type = object : TypeToken<T>() {}.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<Credentials>() {}.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<ReglabSession>() {}.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() }
}
}
24 changes: 18 additions & 6 deletions app/src/main/java/com/uad/portal/SettingsView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit 95522ac

Please sign in to comment.