diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8ce50c4..fd2922f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,6 +25,11 @@
+
+ // Retry the login with the same credentials
+ handleSignIn()
+ }
+ .setNegativeButton("Cancel") { dialog, _ ->
+ dialog.dismiss()
+ }
+ .setCancelable(true)
+ .create()
+
+ dialog.show()
+ }
+
+ 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")
+
+ // Check if account already exists
+ val existingAccounts = secureStorage.getAllAccounts()
+ if (existingAccounts.any { it.mobile == mobile }) {
+ Toast.makeText(this, "Account already exists", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ 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}")
+
+ // Create new account object
+ val newAccount = Account(
+ id = mobile,
+ name = result.data.name,
+ mobile = mobile,
+ email = result.data.email,
+ password = password,
+ cookie = extractSessionId(result.cookie ?: ""),
+ userId = result.data.id,
+ isActive = false // Will be set as active when switched to
+ )
+
+ // Save the new account
+ secureStorage.saveAccount(newAccount)
+
+ setLoading(false)
+
+ // Show success message and finish
+ Toast.makeText(this@AddAccountActivity, "Account added successfully", Toast.LENGTH_SHORT).show()
+
+ // Return to main activity
+ val intent = Intent(this@AddAccountActivity, MainActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+ startActivity(intent)
+ finish()
+ }
+ is ApiResult.Error -> {
+ Log.d(TAG, "Login failed: ${result.message} (code: ${result.code})")
+ setLoading(false)
+ showLoginError(result.message)
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in performLogin coroutine", e)
+ setLoading(false)
+ showLoginError("Login failed: ${e.message}")
+ }
+ }
+ }
+
+ private fun extractSessionId(setCookieHeader: String): String {
+ // Extract the session ID from Set-Cookie header
+ 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
+
+ // Show/hide progress indicator
+ binding.etMobileNumber.isEnabled = !isLoading
+ binding.etPassword.isEnabled = !isLoading
+ }
+
+ private fun handleRegister() {
+ Log.d(TAG, "Register button clicked")
+ val intent = Intent(this, RegisterActivity::class.java)
+ startActivity(intent)
+ }
+
+ private fun handleForgotPassword() {
+ Log.d(TAG, "Forgot password button clicked")
+ val intent = Intent(this, ForgotPasswordActivity::class.java)
+ startActivity(intent)
+ }
+
+ 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
+ }
+}
diff --git a/app/src/main/java/sh/sar/gridflow/LoginActivity.kt b/app/src/main/java/sh/sar/gridflow/LoginActivity.kt
index 8e74b09..dd59ff1 100644
--- a/app/src/main/java/sh/sar/gridflow/LoginActivity.kt
+++ b/app/src/main/java/sh/sar/gridflow/LoginActivity.kt
@@ -12,6 +12,7 @@ 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.Account
import sh.sar.gridflow.utils.SecureStorage
class LoginActivity : AppCompatActivity() {
@@ -124,19 +125,21 @@ class LoginActivity : AppCompatActivity() {
// 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
+ // Create new account object
+ val newAccount = Account(
+ id = mobile,
+ name = result.data.name,
+ mobile = mobile,
+ email = result.data.email,
+ password = password,
+ cookie = extractSessionId(result.cookie ?: ""),
+ userId = result.data.id,
+ isActive = true
)
- // Extract and save cookie
- result.cookie?.let { cookie ->
- val sessionId = extractSessionId(cookie)
- Log.d(TAG, "Extracted session ID: $sessionId")
- secureStorage.saveCookie(sessionId)
- }
+ // Save the account and set as active
+ secureStorage.saveAccount(newAccount)
+ secureStorage.setActiveAccount(mobile)
} catch (e: Exception) {
Log.e(TAG, "Failed to save credentials securely", e)
}
diff --git a/app/src/main/java/sh/sar/gridflow/MainActivity.kt b/app/src/main/java/sh/sar/gridflow/MainActivity.kt
index 64feec9..f7de399 100644
--- a/app/src/main/java/sh/sar/gridflow/MainActivity.kt
+++ b/app/src/main/java/sh/sar/gridflow/MainActivity.kt
@@ -1,10 +1,14 @@
package sh.sar.gridflow
+import android.animation.ObjectAnimator
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
@@ -14,8 +18,11 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.navigation.NavigationView
import sh.sar.gridflow.databinding.ActivityMainBinding
+import sh.sar.gridflow.ui.accounts.AccountsAdapter
import sh.sar.gridflow.utils.SecureStorage
class MainActivity : AppCompatActivity() {
@@ -23,6 +30,14 @@ class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
private lateinit var secureStorage: SecureStorage
+ private lateinit var accountsAdapter: AccountsAdapter
+
+ private lateinit var accountSwitcherArrow: ImageView
+ private lateinit var accountListSection: LinearLayout
+ private lateinit var addAccountButton: LinearLayout
+ private lateinit var accountsRecyclerView: RecyclerView
+
+ private var isAccountListVisible = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -41,6 +56,9 @@ class MainActivity : AppCompatActivity() {
val navView: NavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_content_main)
+ // Initialize navigation header components
+ initializeNavigationHeader(navView)
+
// Update navigation header with user info
updateNavHeader(navView)
@@ -48,9 +66,9 @@ class MainActivity : AppCompatActivity() {
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_dashboard,
+ R.id.nav_bill_history,
R.id.nav_subscriptions,
R.id.nav_band_rates,
- R.id.nav_bill_history,
R.id.nav_pay_any_bill
), drawerLayout
)
@@ -85,6 +103,133 @@ class MainActivity : AppCompatActivity() {
}
}
+ private fun initializeNavigationHeader(navView: NavigationView) {
+ val headerView = navView.getHeaderView(0)
+
+ try {
+ accountSwitcherArrow = headerView.findViewById(R.id.account_switcher_arrow)
+ accountListSection = headerView.findViewById(R.id.account_list_section)
+ addAccountButton = headerView.findViewById(R.id.add_account_button)
+ accountsRecyclerView = headerView.findViewById(R.id.accounts_recycler_view)
+
+ android.util.Log.d("MainActivity", "Navigation header views initialized successfully")
+
+ // Set theme-appropriate background for account list
+ setAccountListTheme()
+
+ // Set up RecyclerView for accounts
+ accountsRecyclerView.layoutManager = LinearLayoutManager(this)
+ accountsAdapter = AccountsAdapter(
+ accounts = emptyList(),
+ activeAccountId = null,
+ onAccountClick = { account ->
+ switchToAccount(account.id)
+ toggleAccountList(false)
+ }
+ )
+ accountsRecyclerView.adapter = accountsAdapter
+
+ // Set up click listeners
+ accountSwitcherArrow.setOnClickListener {
+ android.util.Log.d("MainActivity", "Account switcher arrow clicked")
+ toggleAccountList()
+ }
+
+ addAccountButton.setOnClickListener {
+ android.util.Log.d("MainActivity", "Add account button clicked")
+ val intent = Intent(this, AddAccountActivity::class.java)
+ startActivity(intent)
+ }
+
+ android.util.Log.d("MainActivity", "Click listeners set up successfully")
+
+ } catch (e: Exception) {
+ android.util.Log.e("MainActivity", "Error initializing navigation header", e)
+ }
+ }
+
+ private fun setAccountListTheme() {
+ // Check if we're in dark mode
+ val nightModeFlags = resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK
+ val headerView = binding.navView.getHeaderView(0)
+
+ when (nightModeFlags) {
+ android.content.res.Configuration.UI_MODE_NIGHT_YES -> {
+ // Dark theme - use darker background
+ accountListSection.setBackgroundColor(android.graphics.Color.parseColor("#2C2C2C"))
+
+ // Update divider color for dark theme
+ val divider = headerView.findViewById(R.id.account_list_divider)
+ divider?.setBackgroundColor(android.graphics.Color.parseColor("#555555"))
+ }
+ android.content.res.Configuration.UI_MODE_NIGHT_NO -> {
+ // Light theme - use white background
+ accountListSection.setBackgroundColor(android.graphics.Color.WHITE)
+
+ // Update divider color for light theme
+ val divider = headerView.findViewById(R.id.account_list_divider)
+ divider?.setBackgroundColor(android.graphics.Color.parseColor("#E0E0E0"))
+ }
+ else -> {
+ // Default to white
+ accountListSection.setBackgroundColor(android.graphics.Color.WHITE)
+ }
+ }
+ }
+
+ private fun toggleAccountList(forceState: Boolean? = null) {
+ val targetVisibility = forceState ?: !isAccountListVisible
+
+ if (targetVisibility) {
+ // Show account list
+ updateAccountsList()
+ accountListSection.visibility = View.VISIBLE
+ isAccountListVisible = true
+
+ // Rotate arrow down
+ ObjectAnimator.ofFloat(accountSwitcherArrow, "rotation", 0f, 180f).apply {
+ duration = 200
+ start()
+ }
+ } else {
+ // Hide account list
+ accountListSection.visibility = View.GONE
+ isAccountListVisible = false
+
+ // Rotate arrow up
+ ObjectAnimator.ofFloat(accountSwitcherArrow, "rotation", 180f, 0f).apply {
+ duration = 200
+ start()
+ }
+ }
+ }
+
+ private fun updateAccountsList() {
+ val allAccounts = secureStorage.getAllAccounts()
+ val activeAccount = secureStorage.getActiveAccount()
+
+ // Show only other accounts (not the currently active one)
+ val otherAccounts = allAccounts.filter { it.id != activeAccount?.id }
+
+ accountsAdapter.updateAccounts(otherAccounts, activeAccount?.id)
+
+ // Show/hide add account button based on whether we have multiple accounts
+ addAccountButton.visibility = View.VISIBLE
+ }
+
+ private fun switchToAccount(accountId: String) {
+ android.util.Log.d("MainActivity", "Switching to account: $accountId")
+ secureStorage.setActiveAccount(accountId)
+ updateNavHeader(binding.navView)
+
+ // Navigate to dashboard to show selected account data
+ val navController = findNavController(R.id.nav_host_fragment_content_main)
+ navController.navigate(R.id.nav_dashboard)
+
+ // Close the drawer
+ binding.drawerLayout.closeDrawers()
+ }
+
private fun openUrl(url: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
@@ -96,10 +241,22 @@ class MainActivity : AppCompatActivity() {
val mobileTextView = headerView.findViewById(R.id.nav_header_mobile)
val emailTextView = headerView.findViewById(R.id.nav_header_email)
- // Load user info from secure storage
- nameTextView.text = secureStorage.getUserName() ?: "User"
- mobileTextView.text = secureStorage.getMobile() ?: ""
- emailTextView.text = secureStorage.getUserEmail() ?: ""
+ // Load user info from secure storage (active account)
+ val activeAccount = secureStorage.getActiveAccount()
+ nameTextView.text = activeAccount?.name ?: "User"
+ mobileTextView.text = activeAccount?.mobile ?: ""
+ emailTextView.text = activeAccount?.email ?: ""
+
+ // Always show account switcher arrow for now (for testing)
+ // Will hide it only when we're sure there's only one account
+ val allAccounts = secureStorage.getAllAccounts()
+ val hasMultipleAccounts = allAccounts.size > 1
+
+ // For debugging: always show the arrow initially
+ accountSwitcherArrow.visibility = View.VISIBLE
+
+ // Log for debugging
+ android.util.Log.d("MainActivity", "Total accounts: ${allAccounts.size}, Active: ${activeAccount?.mobile}")
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -112,4 +269,10 @@ class MainActivity : AppCompatActivity() {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
+
+ override fun onResume() {
+ super.onResume()
+ // Update navigation header in case accounts were added/removed
+ updateNavHeader(binding.navView)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/sh/sar/gridflow/ui/accounts/AccountsAdapter.kt b/app/src/main/java/sh/sar/gridflow/ui/accounts/AccountsAdapter.kt
new file mode 100644
index 0000000..ca8c7e7
--- /dev/null
+++ b/app/src/main/java/sh/sar/gridflow/ui/accounts/AccountsAdapter.kt
@@ -0,0 +1,71 @@
+package sh.sar.gridflow.ui.accounts
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import sh.sar.gridflow.R
+import sh.sar.gridflow.utils.Account
+
+class AccountsAdapter(
+ private var accounts: List,
+ private var activeAccountId: String?,
+ private val onAccountClick: (Account) -> Unit
+) : RecyclerView.Adapter() {
+
+ class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val accountName: TextView = itemView.findViewById(R.id.account_name)
+ val accountMobile: TextView = itemView.findViewById(R.id.account_mobile)
+ val activeIndicator: ImageView = itemView.findViewById(R.id.active_indicator)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_account, parent, false)
+ return AccountViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: AccountViewHolder, position: Int) {
+ val account = accounts[position]
+
+ holder.accountName.text = account.name
+ holder.accountMobile.text = account.mobile
+
+ // Set theme-appropriate text colors
+ val context = holder.itemView.context
+ val nightModeFlags = context.resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK
+ when (nightModeFlags) {
+ android.content.res.Configuration.UI_MODE_NIGHT_YES -> {
+ // Dark theme - use light text
+ holder.accountName.setTextColor(android.graphics.Color.WHITE)
+ holder.accountMobile.setTextColor(android.graphics.Color.parseColor("#CCCCCC"))
+ }
+ else -> {
+ // Light theme - use dark text
+ holder.accountName.setTextColor(android.graphics.Color.BLACK)
+ holder.accountMobile.setTextColor(android.graphics.Color.parseColor("#666666"))
+ }
+ }
+
+ // Show active indicator for current account
+ holder.activeIndicator.visibility = if (account.id == activeAccountId) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+
+ holder.itemView.setOnClickListener {
+ onAccountClick(account)
+ }
+ }
+
+ override fun getItemCount(): Int = accounts.size
+
+ fun updateAccounts(newAccounts: List, newActiveAccountId: String?) {
+ accounts = newAccounts
+ activeAccountId = newActiveAccountId
+ notifyDataSetChanged()
+ }
+}
diff --git a/app/src/main/java/sh/sar/gridflow/utils/SecureStorage.kt b/app/src/main/java/sh/sar/gridflow/utils/SecureStorage.kt
index c680bf4..6211ab1 100644
--- a/app/src/main/java/sh/sar/gridflow/utils/SecureStorage.kt
+++ b/app/src/main/java/sh/sar/gridflow/utils/SecureStorage.kt
@@ -5,14 +5,32 @@ import android.content.SharedPreferences
import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+
+data class Account(
+ val id: String, // mobile number as unique ID
+ val name: String,
+ val mobile: String,
+ val email: String,
+ val password: String,
+ val cookie: String?,
+ val userId: Int,
+ val isActive: Boolean = false
+)
class SecureStorage(context: Context) {
private val sharedPreferences: SharedPreferences
+ private val gson = Gson()
companion object {
private const val TAG = "SecureStorage"
private const val PREFS_NAME = "gridflow_secure_prefs"
+ private const val KEY_ACCOUNTS = "accounts"
+ private const val KEY_ACTIVE_ACCOUNT_ID = "active_account_id"
+
+ // Legacy keys for backward compatibility
private const val KEY_MOBILE = "mobile"
private const val KEY_PASSWORD = "password"
private const val KEY_COOKIE = "cookie"
@@ -36,49 +54,157 @@ class SecureStorage(context: Context) {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
Log.d(TAG, "SecureStorage initialized successfully")
+
+ // Migrate legacy data if exists
+ migrateLegacyData()
} catch (e: Exception) {
Log.e(TAG, "Failed to initialize SecureStorage", e)
throw e
}
}
- fun saveCredentials(mobile: String, password: String) {
- Log.d(TAG, "Saving credentials for mobile: $mobile")
+ private fun migrateLegacyData() {
+ val legacyMobile = sharedPreferences.getString(KEY_MOBILE, null)
+ if (legacyMobile != null && getAllAccounts().isEmpty()) {
+ Log.d(TAG, "Migrating legacy account data")
+ val legacyAccount = Account(
+ id = legacyMobile,
+ name = sharedPreferences.getString(KEY_USER_NAME, "") ?: "",
+ mobile = legacyMobile,
+ email = sharedPreferences.getString(KEY_USER_EMAIL, "") ?: "",
+ password = sharedPreferences.getString(KEY_PASSWORD, "") ?: "",
+ cookie = sharedPreferences.getString(KEY_COOKIE, null),
+ userId = sharedPreferences.getInt(KEY_USER_ID, -1),
+ isActive = true
+ )
+ saveAccount(legacyAccount)
+ setActiveAccount(legacyMobile)
+
+ // Clear legacy keys
+ sharedPreferences.edit()
+ .remove(KEY_MOBILE)
+ .remove(KEY_PASSWORD)
+ .remove(KEY_COOKIE)
+ .remove(KEY_USER_NAME)
+ .remove(KEY_USER_EMAIL)
+ .remove(KEY_USER_ID)
+ .apply()
+ }
+ }
+
+ fun saveAccount(account: Account) {
+ Log.d(TAG, "Saving account: ${account.mobile}")
+ val accounts = getAllAccounts().toMutableList()
+
+ // Remove existing account with same ID if exists
+ accounts.removeAll { it.id == account.id }
+
+ // Add the new/updated account
+ accounts.add(account)
+
+ // Save accounts list
+ val accountsJson = gson.toJson(accounts)
sharedPreferences.edit()
- .putString(KEY_MOBILE, mobile)
- .putString(KEY_PASSWORD, password)
+ .putString(KEY_ACCOUNTS, accountsJson)
.apply()
}
+ fun getAllAccounts(): List {
+ val accountsJson = sharedPreferences.getString(KEY_ACCOUNTS, null)
+ return if (accountsJson != null) {
+ try {
+ val type = object : TypeToken>() {}.type
+ gson.fromJson(accountsJson, type)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to parse accounts JSON", e)
+ emptyList()
+ }
+ } else {
+ emptyList()
+ }
+ }
+
+ fun getActiveAccount(): Account? {
+ val activeAccountId = sharedPreferences.getString(KEY_ACTIVE_ACCOUNT_ID, null)
+ return if (activeAccountId != null) {
+ getAllAccounts().find { it.id == activeAccountId }
+ } else {
+ // Fallback to first account if no active account set
+ getAllAccounts().firstOrNull()
+ }
+ }
+
+ fun setActiveAccount(accountId: String) {
+ Log.d(TAG, "Setting active account: $accountId")
+ sharedPreferences.edit()
+ .putString(KEY_ACTIVE_ACCOUNT_ID, accountId)
+ .apply()
+ }
+
+ fun removeAccount(accountId: String) {
+ Log.d(TAG, "Removing account: $accountId")
+ val accounts = getAllAccounts().toMutableList()
+ accounts.removeAll { it.id == accountId }
+
+ val accountsJson = gson.toJson(accounts)
+ sharedPreferences.edit()
+ .putString(KEY_ACCOUNTS, accountsJson)
+ .apply()
+
+ // If this was the active account, set another one as active
+ val currentActiveId = sharedPreferences.getString(KEY_ACTIVE_ACCOUNT_ID, null)
+ if (currentActiveId == accountId) {
+ val remainingAccounts = getAllAccounts()
+ if (remainingAccounts.isNotEmpty()) {
+ setActiveAccount(remainingAccounts.first().id)
+ } else {
+ sharedPreferences.edit().remove(KEY_ACTIVE_ACCOUNT_ID).apply()
+ }
+ }
+ }
+
+ // Legacy compatibility methods - these now work with the active account
+ fun saveCredentials(mobile: String, password: String) {
+ val activeAccount = getActiveAccount()
+ if (activeAccount != null) {
+ val updatedAccount = activeAccount.copy(mobile = mobile, password = password)
+ saveAccount(updatedAccount)
+ }
+ }
+
fun saveCookie(cookie: String) {
- Log.d(TAG, "Saving cookie: $cookie")
- sharedPreferences.edit()
- .putString(KEY_COOKIE, cookie)
- .apply()
+ val activeAccount = getActiveAccount()
+ if (activeAccount != null) {
+ val updatedAccount = activeAccount.copy(cookie = cookie)
+ saveAccount(updatedAccount)
+ }
}
fun saveUserInfo(name: String, email: String, userId: Int) {
- Log.d(TAG, "Saving user info: name=$name, email=$email, userId=$userId")
- sharedPreferences.edit()
- .putString(KEY_USER_NAME, name)
- .putString(KEY_USER_EMAIL, email)
- .putInt(KEY_USER_ID, userId)
- .apply()
+ val activeAccount = getActiveAccount()
+ if (activeAccount != null) {
+ val updatedAccount = activeAccount.copy(name = name, email = email, userId = userId)
+ saveAccount(updatedAccount)
+ }
}
- fun getMobile(): String? = sharedPreferences.getString(KEY_MOBILE, null)
- fun getPassword(): String? = sharedPreferences.getString(KEY_PASSWORD, null)
- fun getCookie(): String? = sharedPreferences.getString(KEY_COOKIE, null)
- fun getUserName(): String? = sharedPreferences.getString(KEY_USER_NAME, null)
- fun getUserEmail(): String? = sharedPreferences.getString(KEY_USER_EMAIL, null)
- fun getUserId(): Int = sharedPreferences.getInt(KEY_USER_ID, -1)
+ fun getMobile(): String? = getActiveAccount()?.mobile
+ fun getPassword(): String? = getActiveAccount()?.password
+ fun getCookie(): String? = getActiveAccount()?.cookie
+ fun getUserName(): String? = getActiveAccount()?.name
+ fun getUserEmail(): String? = getActiveAccount()?.email
+ fun getUserId(): Int = getActiveAccount()?.userId ?: -1
fun isLoggedIn(): Boolean {
- return getCookie() != null && getUserName() != null
+ return getActiveAccount()?.cookie != null && getActiveAccount()?.name?.isNotEmpty() == true
}
fun clearCredentials() {
Log.d(TAG, "Clearing all credentials")
sharedPreferences.edit().clear().apply()
}
+
+ fun hasMultipleAccounts(): Boolean {
+ return getAllAccounts().size > 1
+ }
}
diff --git a/app/src/main/res/color/account_dropdown_background.xml b/app/src/main/res/color/account_dropdown_background.xml
new file mode 100644
index 0000000..7d75e13
--- /dev/null
+++ b/app/src/main/res/color/account_dropdown_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/color/account_dropdown_text.xml b/app/src/main/res/color/account_dropdown_text.xml
new file mode 100644
index 0000000..df0cd70
--- /dev/null
+++ b/app/src/main/res/color/account_dropdown_text.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/color/account_dropdown_text_secondary.xml b/app/src/main/res/color/account_dropdown_text_secondary.xml
new file mode 100644
index 0000000..e098382
--- /dev/null
+++ b/app/src/main/res/color/account_dropdown_text_secondary.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/color/account_list_background.xml b/app/src/main/res/color/account_list_background.xml
new file mode 100644
index 0000000..3227908
--- /dev/null
+++ b/app/src/main/res/color/account_list_background.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/color/account_list_text_color.xml b/app/src/main/res/color/account_list_text_color.xml
new file mode 100644
index 0000000..32da1f8
--- /dev/null
+++ b/app/src/main/res/color/account_list_text_color.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/color/nav_header_arrow_tint.xml b/app/src/main/res/color/nav_header_arrow_tint.xml
new file mode 100644
index 0000000..a9eea66
--- /dev/null
+++ b/app/src/main/res/color/nav_header_arrow_tint.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/color/nav_header_text_color.xml b/app/src/main/res/color/nav_header_text_color.xml
new file mode 100644
index 0000000..282713b
--- /dev/null
+++ b/app/src/main/res/color/nav_header_text_color.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/account_circle_background.xml b/app/src/main/res/drawable/account_circle_background.xml
new file mode 100644
index 0000000..b489954
--- /dev/null
+++ b/app/src/main/res/drawable/account_circle_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_check_24.xml b/app/src/main/res/drawable/ic_check_24.xml
new file mode 100644
index 0000000..0346c59
--- /dev/null
+++ b/app/src/main/res/drawable/ic_check_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_expand_more_24.xml b/app/src/main/res/drawable/ic_expand_more_24.xml
new file mode 100644
index 0000000..e27707e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_expand_more_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_add_account.xml b/app/src/main/res/layout/activity_add_account.xml
new file mode 100644
index 0000000..6e9cc48
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_account.xml
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 1eb85e3..6995e26 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
- android:background="?android:attr/colorBackground">
+ android:background="@android:color/white">
@@ -150,7 +150,7 @@
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
- android:background="?android:attr/textColorSecondary"
+ android:background="#CCCCCC"
android:alpha="0.3" />
@@ -167,7 +167,7 @@
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
- android:background="?android:attr/textColorSecondary"
+ android:background="#CCCCCC"
android:alpha="0.3" />
diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml
new file mode 100644
index 0000000..16aecef
--- /dev/null
+++ b/app/src/main/res/layout/item_account.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml
index 43cdb2d..b1ddaa7 100644
--- a/app/src/main/res/layout/nav_header_main.xml
+++ b/app/src/main/res/layout/nav_header_main.xml
@@ -2,42 +2,134 @@
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:orientation="vertical"
+ android:visibility="gone"
+ android:background="@android:color/white">
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml
index 9e77f69..6627d1b 100644
--- a/app/src/main/res/menu/activity_main_drawer.xml
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -8,18 +8,21 @@
android:id="@+id/nav_dashboard"
android:icon="@drawable/ic_dashboard_24"
android:title="@string/menu_dashboard" />
+
+
+
+
-