forked from LibreMV/GridFlow
208 lines
7.7 KiB
Kotlin
208 lines
7.7 KiB
Kotlin
package sh.sar.gridflow
|
|
|
|
import android.content.Intent
|
|
import android.os.Bundle
|
|
import android.util.Log
|
|
import android.view.View
|
|
import android.widget.Toast
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.lifecycle.lifecycleScope
|
|
import kotlinx.coroutines.launch
|
|
import sh.sar.gridflow.databinding.ActivityLoginBinding
|
|
import sh.sar.gridflow.network.ApiResult
|
|
import sh.sar.gridflow.network.FenakaApiService
|
|
import sh.sar.gridflow.utils.SecureStorage
|
|
|
|
class LoginActivity : AppCompatActivity() {
|
|
|
|
private lateinit var binding: ActivityLoginBinding
|
|
private lateinit var secureStorage: SecureStorage
|
|
private lateinit var apiService: FenakaApiService
|
|
|
|
companion object {
|
|
private const val TAG = "LoginActivity"
|
|
}
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
|
|
Log.d(TAG, "LoginActivity onCreate")
|
|
|
|
binding = ActivityLoginBinding.inflate(layoutInflater)
|
|
setContentView(binding.root)
|
|
|
|
try {
|
|
secureStorage = SecureStorage(this)
|
|
apiService = FenakaApiService()
|
|
|
|
// Check if already logged in
|
|
if (secureStorage.isLoggedIn()) {
|
|
Log.d(TAG, "User already logged in, navigating to main")
|
|
navigateToMain()
|
|
return
|
|
}
|
|
|
|
// Pre-fill saved credentials
|
|
secureStorage.getMobile()?.let { mobile ->
|
|
Log.d(TAG, "Pre-filling mobile number: $mobile")
|
|
binding.etMobileNumber.setText(mobile)
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Failed to initialize SecureStorage, continuing without it", e)
|
|
apiService = FenakaApiService()
|
|
Toast.makeText(this, "Warning: Secure storage not available", Toast.LENGTH_SHORT).show()
|
|
}
|
|
|
|
setupClickListeners()
|
|
}
|
|
|
|
private fun setupClickListeners() {
|
|
binding.btnSignIn.setOnClickListener {
|
|
handleSignIn()
|
|
}
|
|
|
|
binding.btnRegister.setOnClickListener {
|
|
handleRegister()
|
|
}
|
|
|
|
binding.btnForgotPassword.setOnClickListener {
|
|
handleForgotPassword()
|
|
}
|
|
|
|
binding.btnPayWithoutAccount.setOnClickListener {
|
|
handlePayWithoutAccount()
|
|
}
|
|
}
|
|
|
|
private fun handleSignIn() {
|
|
val mobileNumber = binding.etMobileNumber.text.toString().trim()
|
|
val password = binding.etPassword.text.toString().trim()
|
|
|
|
Log.d(TAG, "handleSignIn called with mobile: $mobileNumber")
|
|
|
|
if (validateInput(mobileNumber, password)) {
|
|
Log.d(TAG, "Input validation passed, calling performLogin")
|
|
performLogin(mobileNumber, password)
|
|
} else {
|
|
Log.d(TAG, "Input validation failed")
|
|
}
|
|
}
|
|
|
|
private fun performLogin(mobile: String, password: String) {
|
|
Log.d(TAG, "performLogin called with mobile: $mobile")
|
|
setLoading(true)
|
|
|
|
lifecycleScope.launch {
|
|
Log.d(TAG, "Starting coroutine for API call")
|
|
try {
|
|
when (val result = apiService.login(mobile, password)) {
|
|
is ApiResult.Success -> {
|
|
Log.d(TAG, "Login successful: ${result.data}")
|
|
|
|
// Save credentials and user info (if SecureStorage is available)
|
|
if (this@LoginActivity::secureStorage.isInitialized) {
|
|
try {
|
|
secureStorage.saveCredentials(mobile, password)
|
|
secureStorage.saveUserInfo(
|
|
result.data.name,
|
|
result.data.email,
|
|
result.data.id
|
|
)
|
|
|
|
// Extract and save cookie
|
|
result.cookie?.let { cookie ->
|
|
val sessionId = extractSessionId(cookie)
|
|
Log.d(TAG, "Extracted session ID: $sessionId")
|
|
secureStorage.saveCookie(sessionId)
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Failed to save credentials securely", e)
|
|
}
|
|
}
|
|
|
|
setLoading(false)
|
|
navigateToMain()
|
|
}
|
|
is ApiResult.Error -> {
|
|
Log.d(TAG, "Login failed: ${result.message} (code: ${result.code})")
|
|
setLoading(false)
|
|
Toast.makeText(this@LoginActivity, result.message, Toast.LENGTH_LONG).show()
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Exception in performLogin coroutine", e)
|
|
setLoading(false)
|
|
Toast.makeText(this@LoginActivity, "Login failed: ${e.message}", Toast.LENGTH_LONG).show()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun extractSessionId(setCookieHeader: String): String {
|
|
// Extract the session ID from Set-Cookie header
|
|
// Format: connect.sid=s%3A-vUZGRtHZZygj5Xm1Xg9nKdcZqanQCWm.JVdk7%2Bv63292vx5TWsOiws4QiGwKCYKjh%2FUhLWGEYVs; HttpOnly; Path=/; Expires=...
|
|
return setCookieHeader.substringBefore(";").substringAfter("connect.sid=")
|
|
}
|
|
|
|
private fun setLoading(isLoading: Boolean) {
|
|
binding.btnSignIn.isEnabled = !isLoading
|
|
binding.btnSignIn.text = if (isLoading) "Signing in..." else "Sign In"
|
|
|
|
// Disable other buttons during loading
|
|
binding.btnRegister.isEnabled = !isLoading
|
|
binding.btnForgotPassword.isEnabled = !isLoading
|
|
binding.btnPayWithoutAccount.isEnabled = !isLoading
|
|
|
|
// Show/hide progress indicator
|
|
binding.etMobileNumber.isEnabled = !isLoading
|
|
binding.etPassword.isEnabled = !isLoading
|
|
}
|
|
|
|
private fun navigateToMain() {
|
|
val intent = Intent(this, MainActivity::class.java)
|
|
startActivity(intent)
|
|
finish()
|
|
}
|
|
|
|
private fun handleRegister() {
|
|
Toast.makeText(this, "Register functionality coming soon", Toast.LENGTH_SHORT).show()
|
|
// TODO: Navigate to registration screen
|
|
}
|
|
|
|
private fun handleForgotPassword() {
|
|
Toast.makeText(this, "Forgot password functionality coming soon", Toast.LENGTH_SHORT).show()
|
|
// TODO: Navigate to forgot password screen
|
|
}
|
|
|
|
private fun handlePayWithoutAccount() {
|
|
Toast.makeText(this, "Guest payment functionality coming soon", Toast.LENGTH_SHORT).show()
|
|
// TODO: Navigate to guest payment flow
|
|
}
|
|
|
|
private fun validateInput(mobileNumber: String, password: String): Boolean {
|
|
if (mobileNumber.isEmpty()) {
|
|
binding.etMobileNumber.error = "Mobile number is required"
|
|
return false
|
|
}
|
|
|
|
if (password.isEmpty()) {
|
|
binding.etPassword.error = "Password is required"
|
|
return false
|
|
}
|
|
|
|
// Client-side password validation - must be at least 8 characters
|
|
if (password.length < 8) {
|
|
binding.etPassword.error = "Password must be at least 8 characters"
|
|
return false
|
|
}
|
|
|
|
// Basic Maldives mobile number validation (7xxxxxx format)
|
|
if (!mobileNumber.matches(Regex("^[79]\\d{6}$"))) {
|
|
binding.etMobileNumber.error = "Enter a valid Maldives mobile number"
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|