Files
GridFlow/app/src/main/java/sh/sar/gridflow/LoginActivity.kt
2025-07-24 16:25:58 +05:00

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