rearrange nav menu
This commit is contained in:
@@ -25,6 +25,11 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".AddAccountActivity"
|
||||
android:exported="false"
|
||||
android:label="Add Account"
|
||||
android:theme="@style/Theme.GridFlow.NoActionBar" />
|
||||
<activity
|
||||
android:name=".DebugLoginActivity"
|
||||
android:exported="false"
|
||||
|
219
app/src/main/java/sh/sar/gridflow/AddAccountActivity.kt
Normal file
219
app/src/main/java/sh/sar/gridflow/AddAccountActivity.kt
Normal file
@@ -0,0 +1,219 @@
|
||||
package sh.sar.gridflow
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import sh.sar.gridflow.databinding.ActivityAddAccountBinding
|
||||
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 AddAccountActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityAddAccountBinding
|
||||
private lateinit var secureStorage: SecureStorage
|
||||
private lateinit var apiService: FenakaApiService
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AddAccountActivity"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Force system theme (follows device dark mode setting)
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
|
||||
Log.d(TAG, "AddAccountActivity onCreate")
|
||||
|
||||
binding = ActivityAddAccountBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Set up the toolbar
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.title = "Add Account"
|
||||
|
||||
try {
|
||||
secureStorage = SecureStorage(this)
|
||||
apiService = FenakaApiService()
|
||||
} 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()
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
binding.btnSignIn.setOnClickListener {
|
||||
handleSignIn()
|
||||
}
|
||||
|
||||
binding.btnRegister.setOnClickListener {
|
||||
handleRegister()
|
||||
}
|
||||
|
||||
binding.btnForgotPassword.setOnClickListener {
|
||||
handleForgotPassword()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLoginError(message: String) {
|
||||
val dialog = androidx.appcompat.app.AlertDialog.Builder(this)
|
||||
.setTitle("Login Failed")
|
||||
.setMessage(message)
|
||||
.setPositiveButton("Retry") { _, _ ->
|
||||
// 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
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
|
@@ -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<View>(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<View>(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<TextView>(R.id.nav_header_mobile)
|
||||
val emailTextView = headerView.findViewById<TextView>(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)
|
||||
}
|
||||
}
|
@@ -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<Account>,
|
||||
private var activeAccountId: String?,
|
||||
private val onAccountClick: (Account) -> Unit
|
||||
) : RecyclerView.Adapter<AccountsAdapter.AccountViewHolder>() {
|
||||
|
||||
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<Account>, newActiveAccountId: String?) {
|
||||
accounts = newAccounts
|
||||
activeAccountId = newActiveAccountId
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
@@ -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<Account> {
|
||||
val accountsJson = sharedPreferences.getString(KEY_ACCOUNTS, null)
|
||||
return if (accountsJson != null) {
|
||||
try {
|
||||
val type = object : TypeToken<List<Account>>() {}.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
|
||||
}
|
||||
}
|
||||
|
5
app/src/main/res/color/account_dropdown_background.xml
Normal file
5
app/src/main/res/color/account_dropdown_background.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Safe system background color -->
|
||||
<item android:color="@android:color/background_light"/>
|
||||
</selector>
|
5
app/src/main/res/color/account_dropdown_text.xml
Normal file
5
app/src/main/res/color/account_dropdown_text.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Safe system text color -->
|
||||
<item android:color="@android:color/primary_text_light"/>
|
||||
</selector>
|
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Safe secondary text color -->
|
||||
<item android:color="@android:color/secondary_text_light"/>
|
||||
</selector>
|
4
app/src/main/res/color/account_list_background.xml
Normal file
4
app/src/main/res/color/account_list_background.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/background_light" />
|
||||
</selector>
|
4
app/src/main/res/color/account_list_text_color.xml
Normal file
4
app/src/main/res/color/account_list_text_color.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/primary_text_light" />
|
||||
</selector>
|
7
app/src/main/res/color/nav_header_arrow_tint.xml
Normal file
7
app/src/main/res/color/nav_header_arrow_tint.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Dark theme -->
|
||||
<item android:color="#FFFFFF" android:state_selected="false"/>
|
||||
<!-- Light theme fallback -->
|
||||
<item android:color="#FFFFFF"/>
|
||||
</selector>
|
4
app/src/main/res/color/nav_header_text_color.xml
Normal file
4
app/src/main/res/color/nav_header_text_color.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/white" />
|
||||
</selector>
|
5
app/src/main/res/drawable/account_circle_background.xml
Normal file
5
app/src/main/res/drawable/account_circle_background.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="?attr/colorPrimary" />
|
||||
</shape>
|
10
app/src/main/res/drawable/ic_check_24.xml
Normal file
10
app/src/main/res/drawable/ic_check_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorOnSurface">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_expand_more_24.xml
Normal file
10
app/src/main/res/drawable/ic_expand_more_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorOnSurface">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
|
||||
</vector>
|
167
app/src/main/res/layout/activity_add_account.xml
Normal file
167
app/src/main/res/layout/activity_add_account.xml
Normal file
@@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<!-- Toolbar -->
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
<!-- Content ScrollView -->
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- App Logo Section -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_app_logo"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
android:contentDescription="GridFlow Logo" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Add Account"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sign in with another Fenaka account"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:alpha="0.7" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Login Form Section -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="32dp">
|
||||
|
||||
<!-- Mobile Number Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="Mobile Number"
|
||||
app:boxStrokeColor="@color/design_default_color_primary"
|
||||
app:hintTextColor="@color/design_default_color_primary"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_mobile_number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="phone"
|
||||
android:maxLength="7"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Password Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:hint="Password"
|
||||
app:boxStrokeColor="@color/design_default_color_primary"
|
||||
app:hintTextColor="@color/design_default_color_primary"
|
||||
app:passwordToggleEnabled="true"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Action Buttons Row -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:weightSum="3">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sign_in"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="Sign In"
|
||||
android:textSize="12sp"
|
||||
app:cornerRadius="8dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_register"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="Register"
|
||||
android:textSize="12sp"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
app:cornerRadius="8dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_forgot_password"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="Forgot?"
|
||||
android:textSize="12sp"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
app:cornerRadius="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Spacer for bottom -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
@@ -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">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -35,7 +35,7 @@
|
||||
android:text="GridFlow"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColor="#000000"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
@@ -43,7 +43,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Your Personal Fenaka Client"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textColor="#666666"
|
||||
android:alpha="0.7" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -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" />
|
||||
|
||||
<TextView
|
||||
@@ -158,7 +158,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="OR"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textColor="#666666"
|
||||
android:alpha="0.7"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp" />
|
||||
@@ -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" />
|
||||
|
||||
</LinearLayout>
|
||||
|
48
app/src/main/res/layout/item_account.xml
Normal file
48
app/src/main/res/layout/item_account.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<!-- Account info -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Shihaam Abdul Rahman"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_mobile"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="9198026"
|
||||
android:textColor="#666666"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Active indicator -->
|
||||
<ImageView
|
||||
android:id="@+id/active_indicator"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_check_24"
|
||||
android:tint="?attr/colorPrimary"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
@@ -2,42 +2,134 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_header_height"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/side_nav_bar"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/nav_header_desc"
|
||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||
app:srcCompat="@mipmap/ic_launcher_round" />
|
||||
<!-- Main Account Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/main_account_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_header_height"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_header_name"
|
||||
<!-- Top section with app logo and account switcher -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/account_switcher_arrow"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Account switcher"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_expand_more_24"
|
||||
android:tint="@color/nav_header_text_color" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Account info section -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_header_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Shihaam Abdul Rahman"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_header_mobile"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="9198026"
|
||||
android:textSize="12sp"
|
||||
android:alpha="0.8" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_header_email"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="shihaam_ab_r@outlook.com"
|
||||
android:textSize="11sp"
|
||||
android:alpha="0.7" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Account List Section (initially hidden) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/account_list_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||
android:text="Shihaam Abdul Rahman"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_header_mobile"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="9198026" />
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/accounts_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_header_email"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="shihaam_ab_r@outlook.com" />
|
||||
<!-- Add Account Button -->
|
||||
<LinearLayout
|
||||
android:id="@+id/add_account_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:src="@drawable/ic_add_24"
|
||||
android:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Add account"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Divider -->
|
||||
<View
|
||||
android:id="@+id/account_list_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="#E0E0E0" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@@ -8,18 +8,21 @@
|
||||
android:id="@+id/nav_dashboard"
|
||||
android:icon="@drawable/ic_dashboard_24"
|
||||
android:title="@string/menu_dashboard" />
|
||||
<item
|
||||
android:id="@+id/nav_bill_history"
|
||||
android:icon="@drawable/ic_bill_history_24"
|
||||
android:title="@string/menu_bill_history" />
|
||||
<item
|
||||
android:id="@+id/nav_subscriptions"
|
||||
android:icon="@drawable/ic_subscriptions_24"
|
||||
android:title="@string/menu_subscriptions" />
|
||||
</group>
|
||||
|
||||
<group android:id="@+id/group_utilities">
|
||||
<item
|
||||
android:id="@+id/nav_band_rates"
|
||||
android:icon="@drawable/ic_band_rates_24"
|
||||
android:title="@string/menu_band_rates" />
|
||||
<item
|
||||
android:id="@+id/nav_bill_history"
|
||||
android:icon="@drawable/ic_bill_history_24"
|
||||
android:title="@string/menu_bill_history" />
|
||||
<item
|
||||
android:id="@+id/nav_pay_any_bill"
|
||||
android:icon="@drawable/ic_pay_any_bill_24"
|
||||
|
Reference in New Issue
Block a user