diff --git a/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt b/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt index 6f9478e..ef5e177 100644 --- a/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt +++ b/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt @@ -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 + }) } } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsFragment.kt new file mode 100644 index 0000000..77a7461 --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsFragment.kt @@ -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 + } +} diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt new file mode 100644 index 0000000..0b025f2 --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt @@ -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) { + 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 + } +} diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index 321b910..886f937 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -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 } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeViewModel.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeViewModel.kt new file mode 100644 index 0000000..8867359 --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeViewModel.kt @@ -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>(emptyList()) +} diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt new file mode 100644 index 0000000..17c2157 --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt @@ -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 + } +} diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt index e513344..165a323 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt @@ -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" diff --git a/app/src/main/res/drawable/ic_nav_accounts.xml b/app/src/main/res/drawable/ic_nav_accounts.xml new file mode 100644 index 0000000..d9d72e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_accounts.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/ic_nav_add_account.xml b/app/src/main/res/drawable/ic_nav_add_account.xml new file mode 100644 index 0000000..5083a69 --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_add_account.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/ic_nav_dashboard.xml b/app/src/main/res/drawable/ic_nav_dashboard.xml new file mode 100644 index 0000000..c58baca --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_dashboard.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/ic_nav_generic.xml b/app/src/main/res/drawable/ic_nav_generic.xml new file mode 100644 index 0000000..f69f287 --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_generic.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/ic_nav_settings.xml b/app/src/main/res/drawable/ic_nav_settings.xml new file mode 100644 index 0000000..861a528 --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_settings.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index e8a0f10..24b2055 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -1,41 +1,51 @@ - + android:layout_height="match_parent"> - - - - - - - - - + + android:background="?attr/colorSurface"> - + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_accounts.xml b/app/src/main/res/layout/fragment_accounts.xml new file mode 100644 index 0000000..eb483e6 --- /dev/null +++ b/app/src/main/res/layout/fragment_accounts.xml @@ -0,0 +1,11 @@ + + diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 0000000..a8d807f --- /dev/null +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..4ec3325 --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/drawer_menu.xml b/app/src/main/res/menu/drawer_menu.xml new file mode 100644 index 0000000..ddb6b35 --- /dev/null +++ b/app/src/main/res/menu/drawer_menu.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-b+dv/strings.xml b/app/src/main/res/values-b+dv/strings.xml index 62caf73..f84c72a 100644 --- a/app/src/main/res/values-b+dv/strings.xml +++ b/app/src/main/res/values-b+dv/strings.xml @@ -60,6 +60,33 @@ ސްކިޕް — PIN/ޕެޓަން ބޭނުން ކުރޭ ފަހަތަށް + + ޑޭޝްބޯޑް + އެކައުންޓް އިތުރު ކުރޭ + އެކައުންޓްތައް + ކޮންޓެކްޓްތައް + ހަރަކާތްތައް + ޓްރާންސްފަ ތާރީހް + ފައިނޭންސް + ކާޑް ސެޓިންގ + ސެޓިންގ + ނެވިގޭޝަން ހުޅުވާ + ނެވިގޭޝަން ލައްޕާ + ތައްޔާރުވަމުން ދަނީ + + + ޖުމްލަ MVR + ޖުމްލަ USD + ކާޑް ސަޕޯޓް + ޓްރާންސްފަ + PayMV QR + + + ތީމް + ސިސްޓަމް + ލައިޓް + ޑާކް + އެކައުންޓްތައް ލިބެން ހުރި ބެލެންސް diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c85cb25..907b608 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,6 +59,33 @@ Skip — use PIN/Pattern only Back + + Dashboard + Add Account + Accounts + Contacts + Activities + Transfer History + Finances + Card Settings + Settings + Open navigation + Close navigation + Work in progress + + + MVR Total + USD Total + Card Support + Transfer + PayMV QR + + + Theme + System + Light + Dark + Accounts Available Balance