diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 0c0c3383890637b4721df1f49d0b229e55c0f361..9c96b2b40b2f15a3e834147dc7a64dcf30dcc9fd 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -2,6 +2,9 @@ <project version="4"> <component name="deploymentTargetDropDown"> <value> + <entry key="MainActivity"> + <State /> + </entry> <entry key="app"> <State /> </entry> diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 27d5e77ab2769464f2226620d9dad003606f0a93..6b8ed3ddb2236f73b9c3b5c6a5b330363c34f30e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,10 +14,9 @@ android:supportsRtl="true" android:theme="@style/Theme.Bondoman" tools:targetApi="34"> -<!-- <activity--> -<!-- android:name=".MainActivity"--> -<!-- android:exported="false"--> -<!-- android:label="@string/title_activity_transaction" />--> + <activity + android:name=".MainActivity" + android:exported="false"/> <activity android:name=".LoginActivity" android:exported="false" /> @@ -25,7 +24,7 @@ android:name=".LogoutActivity" android:exported="false" /> <activity - android:name=".MainActivity" + android:name=".SplashScreenActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/app/src/main/java/itb/bos/bondoman/LoginActivity.kt b/app/src/main/java/itb/bos/bondoman/LoginActivity.kt index 9b066d108e044cdff3d2afffd6b9bf015d5f7c06..2f4ab17430664c7945cd77c9884d6254ec7842d0 100644 --- a/app/src/main/java/itb/bos/bondoman/LoginActivity.kt +++ b/app/src/main/java/itb/bos/bondoman/LoginActivity.kt @@ -1,5 +1,6 @@ package itb.bos.bondoman +import android.content.Intent import android.os.Bundle import android.widget.Button import android.widget.EditText @@ -8,17 +9,19 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.ViewModelProvider +import itb.bos.bondoman.databinding.ActivityLoginBinding import itb.bos.bondoman.viewModel.AuthViewModel class LoginActivity : AppCompatActivity() { - + private lateinit var binding: ActivityLoginBinding private lateinit var authViewModel: AuthViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - setContentView(R.layout.activity_login) + binding = ActivityLoginBinding.inflate(layoutInflater) + setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) @@ -28,11 +31,12 @@ class LoginActivity : AppCompatActivity() { authViewModel = ViewModelProvider(this).get(AuthViewModel::class.java) authViewModel.init(applicationContext) - val loginButton = findViewById<Button>(R.id.btn_login) + val loginButton = binding.btnLogin loginButton.setOnClickListener { val email = findViewById<EditText>(R.id.email).text.toString() val password = findViewById<EditText>(R.id.password).text.toString() authViewModel.login(email, password) + } } diff --git a/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt b/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt index ea4e8cd051f4854505a1b3c13683ffdbf35592ba..c3fc41ace1c662f21c43843bae112dda1a1145d6 100644 --- a/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt +++ b/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt @@ -20,14 +20,7 @@ class LogoutActivity : AppCompatActivity() { val logoutButton = findViewById<Button>(R.id.btn_logout) logoutButton.setOnClickListener { - logout() + authViewModel.logout() } } - - private fun logout() { - authViewModel.removeToken() - val intent = Intent(this, LoginActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(intent) - } } diff --git a/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt b/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt index 72881000dd6ffdce5ad9ac05b677f2ab34e00b4b..63bf5275ae3d4705f9c6f1bb9798ea9c471a661d 100644 --- a/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt +++ b/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt @@ -17,11 +17,12 @@ suspend fun performLogin(email: String, password: String): String { } } -suspend fun getToken(): String { +suspend fun checkExpireToken(token: String): Long { return withContext(Dispatchers.IO) { val retrofit = RetrofitClient.getInstance() val endpoint = retrofit.create(AuthEndpoint::class.java) - val response = endpoint.getToken() - response.token + val response = endpoint.checkExpireToken("Bearer $token") + + response.expiresAt } } diff --git a/app/src/main/java/itb/bos/bondoman/receivers/TokenExpiryBroadcastReceiver.kt b/app/src/main/java/itb/bos/bondoman/receivers/TokenExpiryBroadcastReceiver.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e0f6c5c80fb7438d2221eeac313ab0611b7894a --- /dev/null +++ b/app/src/main/java/itb/bos/bondoman/receivers/TokenExpiryBroadcastReceiver.kt @@ -0,0 +1,23 @@ +package itb.bos.bondoman.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import itb.bos.bondoman.LoginActivity +import itb.bos.bondoman.helper.TokenHelper + +class TokenExpiryBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + context?.let { + val tokenHelper = TokenHelper(it.applicationContext) + // Perform logout action + Log.d("TokenExpiredBroadcast", "Received token expired broadcast. Logging out...") + // You can implement logout logic here, for example: + tokenHelper.removeToken() + it.startActivity(Intent(it, LoginActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + }) + } + } +} diff --git a/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt b/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt index 6f5b22b5ccd2cbf074f10f8227058f7c9550506e..39f0a29f68e66402b568116ce61ee2e39acf262e 100644 --- a/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt +++ b/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt @@ -10,3 +10,9 @@ data class LoginRequest ( data class TokenResponse ( @SerializedName("token") val token: String ) + +data class CheckTokenResponse ( + @SerializedName("nim") val nim: String, + @SerializedName("iat") val issuedAt: Long, + @SerializedName("exp") val expiresAt: Long +) \ No newline at end of file diff --git a/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt b/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt index 766e1a7be4ea0e6add5edeaa2bcf9cc8fae19870..35d69b7522657a3a0ec0ff8281ebb8836c540c5d 100644 --- a/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt +++ b/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt @@ -1,8 +1,10 @@ package itb.bos.bondoman.retrofit.endpoint +import itb.bos.bondoman.retrofit.data.CheckTokenResponse import itb.bos.bondoman.retrofit.data.LoginRequest import itb.bos.bondoman.retrofit.data.TokenResponse import retrofit2.http.Body +import retrofit2.http.Header import retrofit2.http.POST interface AuthEndpoint { @@ -10,5 +12,5 @@ interface AuthEndpoint { suspend fun login(@Body request: LoginRequest): TokenResponse @POST("/api/auth/token") - suspend fun getToken(): TokenResponse + suspend fun checkExpireToken(@Header("Authorization") bearerToken: String): CheckTokenResponse } \ No newline at end of file diff --git a/app/src/main/java/itb/bos/bondoman/services/JWTService.kt b/app/src/main/java/itb/bos/bondoman/services/JWTService.kt new file mode 100644 index 0000000000000000000000000000000000000000..e0fe824c630d2223b5d51c2b9af837621f0d4573 --- /dev/null +++ b/app/src/main/java/itb/bos/bondoman/services/JWTService.kt @@ -0,0 +1,87 @@ +package itb.bos.bondoman.services + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.IBinder +import android.util.Log +import itb.bos.bondoman.LoginActivity +import itb.bos.bondoman.helper.TokenHelper +import itb.bos.bondoman.helper.checkExpireToken +import itb.bos.bondoman.receivers.TokenExpiryBroadcastReceiver +import kotlinx.coroutines.* +import java.util.concurrent.TimeUnit + +class JWTService : Service() { + + private var isServiceRunning = false + private lateinit var tokenHelper: TokenHelper + private val broadcastReceiver = TokenExpiryBroadcastReceiver() + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (!isServiceRunning) { + isServiceRunning = true + tokenHelper = TokenHelper(applicationContext) + startTokenExpirationCheck() + } + return START_STICKY + } + + override fun onDestroy() { + isServiceRunning = false +// unregisterReceiver(broadcastReceiver) + super.onDestroy() + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + private fun startTokenExpirationCheck() { + CoroutineScope(Dispatchers.IO).launch { + while (isServiceRunning) { + // Perform token expiration check + checkTokenExpiry() + // delay(TimeUnit.MINUTES.toMillis(5)) + delay(TimeUnit.SECONDS.toMillis(10)) + } + } + } + + private suspend fun checkTokenExpiry() { + try { + Log.d("TokenExpirationCheck", "Checking token expiry...") + + val token = tokenHelper.getToken() + if (token != null) { + // Call the checkExpireToken function with the token + val expireTime = checkExpireToken(token) + + // If the token is retrieved successfully, log the expiry time + Log.d("TokenExpirationCheck", "Expiry time is: $expireTime") + + // Check if the current time has passed the expiry time + val currentTime = System.currentTimeMillis() / 1000 // Convert to seconds + if (currentTime > expireTime) { + // Token has expired, send broadcast to notify the application to logout + Log.d("TokenExpirationCheck", "Token has expired, sending broadcast...") + val intent = Intent(ACTION_TOKEN_EXPIRED) + sendBroadcast(intent) + } else { + Log.d("TokenExpirationCheck", "Token is still valid") + } + } else { + Log.d("TokenExpirationCheck", "No token found") + } + } catch (e: Exception) { + // Handle any exceptions + Log.e("TokenExpirationCheck", "Error checking token expiry: ${e.message}") + } + } + + + companion object { + const val ACTION_TOKEN_EXPIRED = "itb.bos.bondoman.ACTION_TOKEN_EXPIRED" + } +} diff --git a/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt b/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt index fabb06a15e42b4f41ed2242fb6a1b9a434f5e20d..abfa637e20389b28c6ca8c6e5b388df870bf678f 100644 --- a/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt +++ b/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt @@ -6,18 +6,20 @@ import android.content.Intent import android.util.Log import android.widget.Toast import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys import androidx.lifecycle.ViewModel +import itb.bos.bondoman.LoginActivity +import itb.bos.bondoman.LogoutActivity import itb.bos.bondoman.MainActivity import itb.bos.bondoman.helper.TokenHelper import itb.bos.bondoman.helper.performLogin +import itb.bos.bondoman.services.JWTService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class AuthViewModel : ViewModel() { - private lateinit var encryptedSharedPreferences: EncryptedSharedPreferences - @SuppressLint("StaticFieldLeak") private lateinit var context: Context private lateinit var tokenHelper : TokenHelper @@ -44,9 +46,18 @@ class AuthViewModel : ViewModel() { CoroutineScope(Dispatchers.Main).launch { try { val token = performLogin(email, password) + // Store the token and start jwt service storeToken(token) - checkToken() - navigateToTransaksiActivity() // Start LogoutActivity after successful login + // checkToken() + startJWTService() + + // change to logout activity -> temporary +// val intent = Intent(context, LogoutActivity::class.java) +// intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK +// context.startActivity(intent) + val toMainActivity = Intent(context, MainActivity::class.java) + toMainActivity.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + context.startActivity(toMainActivity) Toast.makeText(context, "Login Success", Toast.LENGTH_SHORT).show() } catch (e: Exception) { @@ -57,18 +68,30 @@ class AuthViewModel : ViewModel() { Toast.makeText(context, "Invalid email or password format", Toast.LENGTH_SHORT).show() } } -// private fun navigateToLogoutActivity() { -// val intent = Intent(context, LogoutActivity::class.java) -// intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK -// context.startActivity(intent) -// } -// - private fun navigateToTransaksiActivity(){ - val intent = Intent(context, MainActivity::class.java) + + fun logout() { + // Stop jwt service and remove token + stopJWTService() + removeToken() + + // Change to login activity + val intent = Intent(context, LoginActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK context.startActivity(intent) } + private fun stopJWTService() { + Log.d("JWTStop", "JWT service has stopped") + val intent = Intent(context, JWTService::class.java) + context.stopService(intent) + } + + private fun startJWTService() { + Log.d("JWTStart", "JWT service has started") + val intent = Intent(context, JWTService::class.java) + context.startService(intent) + } + private fun handleLoginFailure(e: Exception) { val errorMessage = e.message ?: "Unknown error occurred" Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()