feat: Add login activity

This commit is contained in:
Peter Vacho 2024-12-23 15:50:35 +01:00
parent 7f4de4cb30
commit afa253420a
Signed by: school
GPG key ID: 8CFC3837052871B4
5 changed files with 209 additions and 24 deletions

View file

@ -5,22 +5,26 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NeatCalendar"
tools:targetApi="31">
<activity
android:name=".LoginActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.NeatCalendar.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

View file

@ -0,0 +1,81 @@
package com.p_vacho.neat_calendar
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.p_vacho.neat_calendar.auth.AuthRepository
import com.p_vacho.neat_calendar.auth.TokenManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class LoginActivity : AppCompatActivity() {
private lateinit var authRepository: AuthRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_login)
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)
insets
}
val tokenManager = TokenManager(this)
authRepository = AuthRepository(tokenManager)
val loginButton = findViewById<Button>(R.id.loginButton)
val registerButton = findViewById<Button>(R.id.registerButton)
loginButton.setOnClickListener {
handleLoginSubmit()
}
registerButton.setOnClickListener {
navigateToRegisterActivity()
}
}
private fun handleLoginSubmit() {
val usernameInput = findViewById<EditText>(R.id.usernameInput)
val passwordInput = findViewById<EditText>(R.id.passwordInput)
val username = usernameInput.text.toString().trim()
val password = passwordInput.text.toString().trim()
if (username.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Please fill in all fields", Toast.LENGTH_SHORT).show()
} else {
performLogin(username, password)
}
}
private fun performLogin(username: String, password: String) {
CoroutineScope(Dispatchers.Main).launch {
val success = authRepository.login(username, password)
if (success) {
navigateToMainActivity()
} else {
Toast.makeText(this@LoginActivity, "Login failed. Please try again.", Toast.LENGTH_LONG).show()
}
}
}
private fun navigateToMainActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
finish() // Close the login screen
}
private fun navigateToRegisterActivity() {
TODO("Not yet implemented")
}
}

View file

@ -1,5 +1,6 @@
package com.p_vacho.neat_calendar
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
@ -7,6 +8,8 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.p_vacho.neat_calendar.api.RetrofitClient
import com.p_vacho.neat_calendar.auth.AuthRepository
import com.p_vacho.neat_calendar.auth.TokenManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -14,28 +17,28 @@ import kotlinx.coroutines.withContext
class MainActivity : AppCompatActivity() {
private var apiReachable = false // Variable to track API reachability
private var apiReachable = false
private var loggedIn = false
private lateinit var authRepository: AuthRepository
override fun onCreate(savedInstanceState: Bundle?) {
// Attach the splash screen to the activity before initialization begins
val splashScreen = installSplashScreen()
// Keep the splash screen visible until initialization is complete
splashScreen.setKeepOnScreenCondition { !apiReachable }
splashScreen.setKeepOnScreenCondition { !apiReachable || !loggedIn }
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
// Check API availability in a coroutine
CoroutineScope(Dispatchers.Main).launch {
apiReachable = RetrofitClient.isApiReachable()
if (!apiReachable) {
// Handle unreachable API, e.g., show an error message or retry
showApiErrorDialog()
}
}
var tokenManager = TokenManager(this)
authRepository = AuthRepository(tokenManager)
// Perform initialization (api reachability check & login)
CoroutineScope(Dispatchers.Main).launch {
initialize()
}
// Handle window insets for proper layout adjustment
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
@ -44,21 +47,47 @@ class MainActivity : AppCompatActivity() {
}
}
private suspend fun initialize() {
if (!apiReachable) {
apiReachable = checkApiReachability()
}
if (!apiReachable) showApiErrorDialog()
loggedIn = checkUserLogin()
if (!loggedIn) navigateToLoginActivity()
}
private suspend fun checkApiReachability(): Boolean {
return RetrofitClient.isApiReachable()
}
private suspend fun checkUserLogin(): Boolean {
if (authRepository.validateRefreshToken() != null) {
// Get a brand new access token (even if the old access token is still valid,
// it's better to just get a new one so it doesn't expire as quickly on us)
return authRepository.refreshAccessToken()
}
return false;
}
private fun navigateToLoginActivity() {
val intent = Intent(this, LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
finish() // Close MainActivity
}
// Show an error dialog if the API is unreachable
private fun showApiErrorDialog() {
var retryCount = 0;
val dialog = androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Error")
.setMessage(
"The API is unreachable.\n" +
"Please check your network connection."
)
.setPositiveButton("Retry", null) // Listener set later for custom handling
.setNegativeButton("Close") { _, _ ->
// Close the app or navigate to an error screen
finish()
}
.setMessage("The API is unreachable.\nPlease check your network connection.")
.setPositiveButton("Retry", null)
.setNegativeButton("Close") { _, _ -> finish() } // Close the app
.setCancelable(false)
.create()
@ -68,12 +97,11 @@ class MainActivity : AppCompatActivity() {
retryButton.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
retryCount++
val reachable = RetrofitClient.isApiReachable()
if (reachable) {
apiReachable = true
apiReachable = checkApiReachability()
if (apiReachable) {
dialog.dismiss() // Close dialog and proceed
initialize() // re-run initialization
} else {
// Update dialog message for failed retry
dialog.setMessage(
"Retry failed. Attempts: $retryCount\n" +
"Please check you network and try again."

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="48dp"
tools:context=".LoginActivity">
<!-- Username Field -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/usernameInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:hint="@string/username">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/usernameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Password Field -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/usernameInputLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:hint="@string/password">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Login Button -->
<Button
android:id="@+id/loginButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/login"
app:layout_constraintTop_toBottomOf="@id/passwordInputLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Register Button -->
<Button
android:id="@+id/registerButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="@string/register"
app:layout_constraintTop_toBottomOf="@id/loginButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,3 +1,7 @@
<resources>
<string name="app_name">NeatCalendar</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="login">Login</string>
<string name="register">Register</string>
</resources>