add dashboard and navigation menu
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package sh.sar.basedbank
|
||||
|
||||
import android.app.Application
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import sh.sar.basedbank.api.mib.MibAccount
|
||||
|
||||
@@ -13,5 +14,12 @@ class BasedBankApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
|
||||
val theme = getSharedPreferences("prefs", MODE_PRIVATE).getString("theme", "system")
|
||||
AppCompatDelegate.setDefaultNightMode(when (theme) {
|
||||
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
"light" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package sh.sar.basedbank.ui.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.databinding.FragmentAccountsBinding
|
||||
|
||||
class AccountsFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentAccountsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val viewModel: HomeViewModel by activityViewModels()
|
||||
private lateinit var adapter: AccountsAdapter
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentAccountsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
adapter = AccountsAdapter(emptyList())
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
viewModel.accounts.observe(viewLifecycleOwner) { adapter.updateAccounts(it) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
requireActivity().title = getString(R.string.nav_accounts)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package sh.sar.basedbank.ui.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.mib.MibAccount
|
||||
import sh.sar.basedbank.databinding.FragmentDashboardBinding
|
||||
|
||||
class DashboardFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentDashboardBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val viewModel: HomeViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
viewModel.accounts.observe(viewLifecycleOwner) { updateBalances(it) }
|
||||
|
||||
val wip = { Toast.makeText(requireContext(), R.string.work_in_progress, Toast.LENGTH_SHORT).show() }
|
||||
binding.btnTransfer.setOnClickListener { wip() }
|
||||
binding.btnPayMvQr.setOnClickListener { wip() }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
requireActivity().title = getString(R.string.nav_dashboard)
|
||||
}
|
||||
|
||||
private fun updateBalances(accounts: List<MibAccount>) {
|
||||
val mvrTotal = accounts
|
||||
.filter { it.currencyName.equals("MVR", ignoreCase = true) }
|
||||
.sumOf { it.availableBalance.replace(",", "").toDoubleOrNull() ?: 0.0 }
|
||||
val usdTotal = accounts
|
||||
.filter { it.currencyName.equals("USD", ignoreCase = true) }
|
||||
.sumOf { it.availableBalance.replace(",", "").toDoubleOrNull() ?: 0.0 }
|
||||
|
||||
binding.tvMvrBalance.text = "MVR %,.2f".format(mvrTotal)
|
||||
binding.tvUsdBalance.text = "USD %,.2f".format(usdTotal)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,29 @@
|
||||
package sh.sar.basedbank.ui.home
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import sh.sar.basedbank.BasedBankApp
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.mib.MibLoginFlow
|
||||
import sh.sar.basedbank.databinding.ActivityHomeBinding
|
||||
import sh.sar.basedbank.ui.login.LoginActivity
|
||||
import sh.sar.basedbank.util.AccountCache
|
||||
import sh.sar.basedbank.util.CredentialStore
|
||||
|
||||
class HomeActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityHomeBinding
|
||||
private lateinit var adapter: AccountsAdapter
|
||||
private val viewModel: HomeViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -25,23 +31,50 @@ class HomeActivity : AppCompatActivity() {
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this, binding.drawerLayout, binding.toolbar,
|
||||
R.string.nav_open_drawer, R.string.nav_close_drawer
|
||||
)
|
||||
binding.drawerLayout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
|
||||
binding.navigationView.setNavigationItemSelectedListener { item ->
|
||||
binding.drawerLayout.closeDrawers()
|
||||
when (item.itemId) {
|
||||
R.id.nav_dashboard -> show(DashboardFragment())
|
||||
R.id.nav_add_account -> startActivity(Intent(this, LoginActivity::class.java))
|
||||
R.id.nav_accounts -> show(AccountsFragment())
|
||||
R.id.nav_settings -> show(SettingsFragment())
|
||||
else -> Toast.makeText(this, R.string.work_in_progress, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// Load data
|
||||
val app = application as BasedBankApp
|
||||
|
||||
// If we arrived here from a fresh manual login, accounts are already in memory.
|
||||
// Otherwise load the last-known cache so the UI is instant.
|
||||
val initial = if (app.accounts.isNotEmpty()) app.accounts else AccountCache.load(this)
|
||||
adapter = AccountsAdapter(initial)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
if (app.accounts.isNotEmpty()) {
|
||||
// Just logged in — persist the fresh data and we're done
|
||||
// Came from fresh manual login
|
||||
viewModel.accounts.value = app.accounts
|
||||
AccountCache.save(this, app.accounts)
|
||||
} else {
|
||||
// Came from lock screen — refresh in background
|
||||
// Came from lock screen — show cache immediately, refresh in background
|
||||
val cached = AccountCache.load(this)
|
||||
if (cached.isNotEmpty()) viewModel.accounts.value = cached
|
||||
val creds = CredentialStore(this).loadMibCredentials()
|
||||
if (creds != null) autoRefresh(creds)
|
||||
}
|
||||
|
||||
// Show dashboard on first create
|
||||
if (savedInstanceState == null) {
|
||||
show(DashboardFragment())
|
||||
binding.navigationView.setCheckedItem(R.id.nav_dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
private fun show(fragment: Fragment) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.contentFrame, fragment)
|
||||
.commit()
|
||||
}
|
||||
|
||||
private fun autoRefresh(creds: CredentialStore.MibCredentials) {
|
||||
@@ -55,9 +88,9 @@ class HomeActivity : AppCompatActivity() {
|
||||
}
|
||||
(application as BasedBankApp).accounts = accounts
|
||||
AccountCache.save(this@HomeActivity, accounts)
|
||||
adapter.updateAccounts(accounts)
|
||||
viewModel.accounts.postValue(accounts)
|
||||
} catch (_: Exception) {
|
||||
// Keep showing cached data silently
|
||||
// Keep cached data silently
|
||||
} finally {
|
||||
binding.refreshIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package sh.sar.basedbank.ui.home
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import sh.sar.basedbank.api.mib.MibAccount
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
val accounts = MutableLiveData<List<MibAccount>>(emptyList())
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package sh.sar.basedbank.ui.home
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.fragment.app.Fragment
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.databinding.FragmentSettingsBinding
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||
|
||||
// Set initial selection
|
||||
val saved = prefs.getString("theme", "system")
|
||||
val initialId = when (saved) {
|
||||
"light" -> R.id.btnThemeLight
|
||||
"dark" -> R.id.btnThemeDark
|
||||
else -> R.id.btnThemeSystem
|
||||
}
|
||||
binding.themeToggle.check(initialId)
|
||||
|
||||
binding.themeToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||
if (!isChecked) return@addOnButtonCheckedListener
|
||||
val (key, mode) = when (checkedId) {
|
||||
R.id.btnThemeLight -> "light" to AppCompatDelegate.MODE_NIGHT_NO
|
||||
R.id.btnThemeDark -> "dark" to AppCompatDelegate.MODE_NIGHT_YES
|
||||
else -> "system" to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
prefs.edit().putString("theme", key).apply()
|
||||
AppCompatDelegate.setDefaultNightMode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
requireActivity().title = getString(R.string.nav_settings)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -114,8 +114,9 @@ class CredentialsFragment : Fragment() {
|
||||
CredentialStore(requireContext()).saveMibCredentials(username, passwordHash, otpSeed)
|
||||
AccountCache.save(requireContext(), accounts)
|
||||
(requireActivity().application as BasedBankApp).accounts = accounts
|
||||
startActivity(Intent(requireContext(), HomeActivity::class.java))
|
||||
requireActivity().finish()
|
||||
val intent = Intent(requireContext(), HomeActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Login failed: ${e.message}", e)
|
||||
binding.tvError.text = e.message ?: "Login failed"
|
||||
|
||||
Reference in New Issue
Block a user