Compare commits

..

6 Commits

41 changed files with 413 additions and 260 deletions
@@ -115,7 +115,11 @@ class BasedBankApp : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this) // Only apply wallpaper-based dynamic colors in system theme mode.
// Light/dark modes use content-based accent colors applied per-activity via ThemeHelper.
DynamicColors.applyToActivitiesIfAvailable(this) { _, _ ->
getSharedPreferences("prefs", MODE_PRIVATE).getString("theme", "system") == "system"
}
val theme = getSharedPreferences("prefs", MODE_PRIVATE).getString("theme", "system") val theme = getSharedPreferences("prefs", MODE_PRIVATE).getString("theme", "system")
AppCompatDelegate.setDefaultNightMode(when (theme) { AppCompatDelegate.setDefaultNightMode(when (theme) {
@@ -21,6 +21,7 @@ import kotlinx.coroutines.withContext
import sh.sar.basedbank.databinding.ActivityLockBinding import sh.sar.basedbank.databinding.ActivityLockBinding
import sh.sar.basedbank.ui.home.HomeActivity import sh.sar.basedbank.ui.home.HomeActivity
import sh.sar.basedbank.util.CredentialStore import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.util.ThemeHelper
import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.BasedBankApp
import javax.crypto.SecretKeyFactory import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEKeySpec
@@ -46,6 +47,7 @@ class LockActivity : AppCompatActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.applyAccent(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
binding = ActivityLockBinding.inflate(layoutInflater) binding = ActivityLockBinding.inflate(layoutInflater)
@@ -55,6 +57,9 @@ class LockActivity : AppCompatActivity() {
isAppearanceLightStatusBars = isLight isAppearanceLightStatusBars = isLight
isAppearanceLightNavigationBars = isLight isAppearanceLightNavigationBars = isLight
} }
val ta = obtainStyledAttributes(intArrayOf(android.R.attr.colorBackground))
window.statusBarColor = ta.getColor(0, if (isLight) android.graphics.Color.WHITE else android.graphics.Color.BLACK)
ta.recycle()
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(bars.left, bars.top, bars.right, bars.bottom) view.setPadding(bars.left, bars.top, bars.right, bars.bottom)
@@ -142,8 +142,6 @@ class AccountHistoryFragment : Fragment() {
} }
adapter.setTransactions(filtered) adapter.setTransactions(filtered)
binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE
binding.skeletonContainer.visibility =
if (allTransactions.isEmpty() && isLoading) View.VISIBLE else View.GONE
} }
private fun resetAndReload() { private fun resetAndReload() {
@@ -170,9 +168,6 @@ class AccountHistoryFragment : Fragment() {
if (firstPageDone && allTransactions.isNotEmpty()) { if (firstPageDone && allTransactions.isNotEmpty()) {
adapter.showLoadingFooter = true adapter.showLoadingFooter = true
} else if (allTransactions.isEmpty()) {
binding.skeletonContainer.visibility = View.VISIBLE
binding.emptyView.visibility = View.GONE
} }
val app = requireActivity().application as BasedBankApp val app = requireActivity().application as BasedBankApp
@@ -192,7 +187,6 @@ class AccountHistoryFragment : Fragment() {
isLoading = false isLoading = false
if (_binding == null) return@launch if (_binding == null) return@launch
binding.skeletonContainer.visibility = View.GONE
if (!firstPageDone) { if (!firstPageDone) {
firstPageDone = true firstPageDone = true
@@ -9,7 +9,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
@@ -195,10 +194,10 @@ class ContactsFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
val success = withContext(Dispatchers.IO) { ContactManager.delete(contact, app) } val success = withContext(Dispatchers.IO) { ContactManager.delete(contact, app) }
if (success) { if (success) {
Toast.makeText(requireContext(), R.string.contact_deleted, Toast.LENGTH_SHORT).show()
removeFromViewModel(contact) removeFromViewModel(contact)
Snackbar.make(binding.root, R.string.contact_deleted, Snackbar.LENGTH_SHORT).show()
} else { } else {
Snackbar.make(binding.root, R.string.contact_delete_failed, Snackbar.LENGTH_LONG).show() Toast.makeText(requireContext(), R.string.contact_delete_failed, Toast.LENGTH_SHORT).show()
} }
} }
} }
@@ -68,6 +68,7 @@ import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.util.CardsCache import sh.sar.basedbank.util.CardsCache
import sh.sar.basedbank.util.FinancingCache import sh.sar.basedbank.util.FinancingCache
import sh.sar.basedbank.util.ForeignLimitsCache import sh.sar.basedbank.util.ForeignLimitsCache
import sh.sar.basedbank.util.ThemeHelper
class HomeActivity : AppCompatActivity() { class HomeActivity : AppCompatActivity() {
@@ -106,6 +107,7 @@ class HomeActivity : AppCompatActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.applyAccent(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
binding = ActivityHomeBinding.inflate(layoutInflater) binding = ActivityHomeBinding.inflate(layoutInflater)
@@ -118,6 +120,9 @@ class HomeActivity : AppCompatActivity() {
isAppearanceLightStatusBars = isLight isAppearanceLightStatusBars = isLight
isAppearanceLightNavigationBars = isLight isAppearanceLightNavigationBars = isLight
} }
val ta = obtainStyledAttributes(intArrayOf(android.R.attr.colorBackground))
window.statusBarColor = ta.getColor(0, if (isLight) android.graphics.Color.WHITE else android.graphics.Color.BLACK)
ta.recycle()
// Auth guard: HomeActivity must only be reachable after LockActivity or fresh login. // Auth guard: HomeActivity must only be reachable after LockActivity or fresh login.
// Using loadSecurityHash() (EncryptedSharedPreferences) instead of plain prefs so // Using loadSecurityHash() (EncryptedSharedPreferences) instead of plain prefs so
// a rooted device cannot bypass this by editing security_method in plain prefs. // a rooted device cannot bypass this by editing security_method in plain prefs.
@@ -512,9 +517,8 @@ fun applyNavLabelVisibility() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.toolbar_menu, menu) menuInflater.inflate(R.menu.toolbar_menu, menu)
val eyeEnabled = getSharedPreferences("prefs", MODE_PRIVATE).getBoolean("hide_sensitive_info", false)
val eyeItem = menu.findItem(R.id.action_hide_amounts) val eyeItem = menu.findItem(R.id.action_hide_amounts)
eyeItem?.isVisible = eyeEnabled eyeItem?.isVisible = true
val hidden = viewModel.hideAmounts.value ?: false val hidden = viewModel.hideAmounts.value ?: false
eyeItem?.setIcon(if (hidden) R.drawable.ic_visibility_off else R.drawable.ic_visibility) eyeItem?.setIcon(if (hidden) R.drawable.ic_visibility_off else R.drawable.ic_visibility)
return true return true
@@ -305,7 +305,12 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets ->
if (statusLabel == null) { tv.visibility = View.GONE; return } if (statusLabel == null) { tv.visibility = View.GONE; return }
tv.visibility = View.VISIBLE tv.visibility = View.VISIBLE
tv.text = statusLabel tv.text = statusLabel
tv.setBackgroundResource(R.drawable.card_overlay_pill_bg) val dp = tv.context.resources.displayMetrics.density
tv.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 12 * dp
setColor(0xCC212121.toInt())
}
} }
fun formatMasked(masked: String): String { fun formatMasked(masked: String): String {
@@ -2,7 +2,9 @@ package sh.sar.basedbank.ui.home
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -10,6 +12,7 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.setApplicationLocales import androidx.appcompat.app.AppCompatDelegate.setApplicationLocales
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
@@ -18,8 +21,11 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import sh.sar.basedbank.R import sh.sar.basedbank.R
import sh.sar.basedbank.databinding.FragmentSettingsAppearanceBinding import sh.sar.basedbank.databinding.FragmentSettingsAppearanceBinding
import sh.sar.basedbank.util.ThemeHelper
import java.util.Collections import java.util.Collections
class SettingsAppearanceFragment : Fragment() { class SettingsAppearanceFragment : Fragment() {
@@ -103,8 +109,45 @@ class SettingsAppearanceFragment : Fragment() {
} }
prefs.edit().putString("theme", key).apply() prefs.edit().putString("theme", key).apply()
AppCompatDelegate.setDefaultNightMode(mode) AppCompatDelegate.setDefaultNightMode(mode)
updateAccentState(key == "system")
updatePitchBlackState(key == "dark")
} }
// Pitch black
binding.switchPitchBlack.isChecked = prefs.getBoolean("pitch_black", false)
binding.switchPitchBlack.setOnCheckedChangeListener { _, checked ->
prefs.edit().putBoolean("pitch_black", checked).apply()
requireActivity().recreate()
}
val isDark = prefs.getString("theme", "system") == "dark"
updatePitchBlackState(isDark)
// Accent color
val savedPreset = prefs.getString("accent_preset", ThemeHelper.PRESET_BLUE)
binding.accentToggle.check(when (savedPreset) {
ThemeHelper.PRESET_RED -> R.id.btnAccentOrange
ThemeHelper.PRESET_GREEN -> R.id.btnAccentGreen
ThemeHelper.PRESET_CUSTOM -> R.id.btnAccentCustom
else -> R.id.btnAccentBlue
})
binding.accentToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
if (!isChecked) return@addOnButtonCheckedListener
val preset = when (checkedId) {
R.id.btnAccentOrange -> ThemeHelper.PRESET_RED
R.id.btnAccentGreen -> ThemeHelper.PRESET_GREEN
R.id.btnAccentCustom -> ThemeHelper.PRESET_CUSTOM
else -> ThemeHelper.PRESET_BLUE
}
if (preset == ThemeHelper.PRESET_CUSTOM) {
showCustomColorPicker()
} else {
prefs.edit().putString("accent_preset", preset).apply()
requireActivity().recreate()
}
}
val isSystem = prefs.getString("theme", "system") == "system"
updateAccentState(isSystem)
// Language // Language
val currentLocales = AppCompatDelegate.getApplicationLocales() val currentLocales = AppCompatDelegate.getApplicationLocales()
val currentLang = if (currentLocales.isEmpty) "en" else currentLocales[0]?.language ?: "en" val currentLang = if (currentLocales.isEmpty) "en" else currentLocales[0]?.language ?: "en"
@@ -156,6 +199,68 @@ class SettingsAppearanceFragment : Fragment() {
slotAdapter.notifyDataSetChanged() slotAdapter.notifyDataSetChanged()
} }
private fun updatePitchBlackState(isDark: Boolean) {
binding.rowPitchBlack.alpha = if (isDark) 1f else 0.38f
binding.switchPitchBlack.isEnabled = isDark
}
private fun updateAccentState(isSystem: Boolean) {
binding.sectionAccentColor.alpha = if (isSystem) 0.38f else 1f
for (i in 0 until binding.accentToggle.childCount) {
binding.accentToggle.getChildAt(i)?.isEnabled = !isSystem
}
}
private fun showCustomColorPicker() {
val ctx = requireContext()
val currentHex = prefs.getString("accent_custom_color", "") ?: ""
val inputLayout = TextInputLayout(ctx).apply {
hint = getString(R.string.accent_custom_hint)
val pad = (16 * resources.displayMetrics.density).toInt()
setPadding(pad, pad / 2, pad, 0)
}
val input = TextInputEditText(ctx).apply {
setText(currentHex)
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
setSingleLine(true)
}
inputLayout.addView(input)
val dialog = MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.accent_custom_pick)
.setView(inputLayout)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(R.string.cancel) { _, _ -> revertAccentToggle() }
.setOnCancelListener { revertAccentToggle() }
.show()
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val raw = input.text.toString().trim()
val hex = if (raw.startsWith("#")) raw else "#$raw"
try {
Color.parseColor(hex)
prefs.edit()
.putString("accent_preset", ThemeHelper.PRESET_CUSTOM)
.putString("accent_custom_color", hex)
.apply()
dialog.dismiss()
requireActivity().recreate()
} catch (_: Exception) {
Toast.makeText(ctx, R.string.accent_invalid_color, Toast.LENGTH_SHORT).show()
}
}
}
private fun revertAccentToggle() {
val saved = prefs.getString("accent_preset", ThemeHelper.PRESET_BLUE)
binding.accentToggle.check(when (saved) {
ThemeHelper.PRESET_RED -> R.id.btnAccentOrange
ThemeHelper.PRESET_GREEN -> R.id.btnAccentGreen
ThemeHelper.PRESET_CUSTOM -> R.id.btnAccentCustom
else -> R.id.btnAccentBlue
})
}
private fun showItemPicker(items: MutableList<Int>, slotIndex: Int, adapter: NavItemAdapter) { private fun showItemPicker(items: MutableList<Int>, slotIndex: Int, adapter: NavItemAdapter) {
val isBottom = prefs.getBoolean("bottom_nav", false) val isBottom = prefs.getBoolean("bottom_nav", false)
if (items === slots && !isBottom) return if (items === slots && !isBottom) return
@@ -168,6 +273,7 @@ class SettingsAppearanceFragment : Fragment() {
LayoutInflater.from(ctx).inflate(R.layout.item_more_nav, listLayout, false).also { row -> LayoutInflater.from(ctx).inflate(R.layout.item_more_nav, listLayout, false).also { row ->
row.findViewById<ImageView>(R.id.ivIcon).setImageResource(item.iconRes) row.findViewById<ImageView>(R.id.ivIcon).setImageResource(item.iconRes)
row.findViewById<TextView>(R.id.tvLabel).setText(item.titleRes) row.findViewById<TextView>(R.id.tvLabel).setText(item.titleRes)
row.findViewById<TextView>(R.id.tvDescription).visibility = View.GONE
listLayout.addView(row) listLayout.addView(row)
} }
} }
@@ -86,17 +86,6 @@ class SettingsSecurityFragment : Fragment() {
(activity as? HomeActivity)?.resetAutolockTimer() (activity as? HomeActivity)?.resetAutolockTimer()
} }
// Hide sensitive information (enables/disables the eye icon in toolbar)
val viewModel = (requireActivity() as HomeActivity).let {
androidx.lifecycle.ViewModelProvider(it)[HomeViewModel::class.java]
}
binding.switchHideAmounts.isChecked = prefs.getBoolean("hide_sensitive_info", false)
binding.switchHideAmounts.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean("hide_sensitive_info", isChecked).apply()
if (!isChecked) viewModel.hideAmounts.value = false
requireActivity().invalidateOptionsMenu()
}
// Block screenshots // Block screenshots
val blockScreenshots = prefs.getBoolean("block_screenshots", true) val blockScreenshots = prefs.getBoolean("block_screenshots", true)
binding.switchBlockScreenshots.isChecked = blockScreenshots binding.switchBlockScreenshots.isChecked = blockScreenshots
@@ -181,9 +181,6 @@ class TransferHistoryFragment : Fragment() {
if (firstBatchDone && allTransactions.isNotEmpty()) { if (firstBatchDone && allTransactions.isNotEmpty()) {
adapter.showLoadingFooter = true adapter.showLoadingFooter = true
} else if (allTransactions.isEmpty()) {
binding.skeletonContainer.visibility = View.VISIBLE
binding.emptyView.visibility = View.GONE
} }
val app = requireActivity().application as BasedBankApp val app = requireActivity().application as BasedBankApp
@@ -289,7 +286,6 @@ class TransferHistoryFragment : Fragment() {
isLoading = false isLoading = false
if (_binding == null) return@launch if (_binding == null) return@launch
binding.skeletonContainer.visibility = View.GONE
val raw = bannerMsg.get() val raw = bannerMsg.get()
when { when {
@@ -333,8 +329,6 @@ class TransferHistoryFragment : Fragment() {
} }
adapter.setTransactions(filtered) adapter.setTransactions(filtered)
binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE
binding.skeletonContainer.visibility =
if (allTransactions.isEmpty() && isLoading) View.VISIBLE else View.GONE
} }
private fun loadContactImage(name: String) { private fun loadContactImage(name: String) {
@@ -9,12 +9,14 @@ import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.LockActivity import sh.sar.basedbank.LockActivity
import sh.sar.basedbank.databinding.ActivityLoginBinding import sh.sar.basedbank.databinding.ActivityLoginBinding
import sh.sar.basedbank.util.CredentialStore import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.util.ThemeHelper
class LoginActivity : AppCompatActivity() { class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.applyAccent(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// If security is configured and the user hasn't unlocked this session, // If security is configured and the user hasn't unlocked this session,
@@ -34,5 +36,8 @@ class LoginActivity : AppCompatActivity() {
isAppearanceLightStatusBars = isLight isAppearanceLightStatusBars = isLight
isAppearanceLightNavigationBars = isLight isAppearanceLightNavigationBars = isLight
} }
val ta = obtainStyledAttributes(intArrayOf(android.R.attr.colorBackground))
window.statusBarColor = ta.getColor(0, if (isLight) android.graphics.Color.WHITE else android.graphics.Color.BLACK)
ta.recycle()
} }
} }
@@ -19,6 +19,7 @@ import sh.sar.basedbank.R
import sh.sar.basedbank.databinding.ActivityOnboardingBinding import sh.sar.basedbank.databinding.ActivityOnboardingBinding
import sh.sar.basedbank.ui.login.LoginActivity import sh.sar.basedbank.ui.login.LoginActivity
import sh.sar.basedbank.util.CredentialStore import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.util.ThemeHelper
class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback { class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
@@ -27,6 +28,7 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
private var countDownTimer: CountDownTimer? = null private var countDownTimer: CountDownTimer? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.applyAccent(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// If security is already configured, onboarding is complete. Redirect to lock screen // If security is already configured, onboarding is complete. Redirect to lock screen
@@ -45,6 +47,9 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
isAppearanceLightStatusBars = isLight isAppearanceLightStatusBars = isLight
isAppearanceLightNavigationBars = isLight isAppearanceLightNavigationBars = isLight
} }
val ta = obtainStyledAttributes(intArrayOf(android.R.attr.colorBackground))
window.statusBarColor = ta.getColor(0, if (isLight) android.graphics.Color.WHITE else android.graphics.Color.BLACK)
ta.recycle()
prefs = getSharedPreferences("prefs", MODE_PRIVATE) prefs = getSharedPreferences("prefs", MODE_PRIVATE)
val originalBottomPadding = binding.bottomBar.paddingBottom val originalBottomPadding = binding.bottomBar.paddingBottom
ViewCompat.setOnApplyWindowInsetsListener(binding.bottomBar) { view, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.bottomBar) { view, insets ->
@@ -0,0 +1,78 @@
package sh.sar.basedbank.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.color.DynamicColors
import com.google.android.material.color.DynamicColorsOptions
import sh.sar.basedbank.R
object ThemeHelper {
const val PRESET_BLUE = "blue"
const val PRESET_RED = "red"
const val PRESET_GREEN = "green"
const val PRESET_CUSTOM = "custom"
fun isSystemTheme(context: Context): Boolean =
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.getString("theme", "system") == "system"
fun getAccentPreset(context: Context): String =
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.getString("accent_preset", PRESET_BLUE) ?: PRESET_BLUE
fun getCustomColor(context: Context): Int? {
val hex = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.getString("accent_custom_color", null) ?: return null
return try { Color.parseColor(hex) } catch (_: Exception) { null }
}
fun presetSeedColor(context: Context, preset: String): Int = when (preset) {
PRESET_RED -> Color.parseColor("#D32F2F")
PRESET_GREEN -> Color.parseColor("#4CAF50")
PRESET_CUSTOM -> getCustomColor(context) ?: Color.parseColor("#3F65AD")
else -> Color.parseColor("#3F65AD")
}
fun isPitchBlackEnabled(context: Context): Boolean =
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.getBoolean("pitch_black", false)
/**
* Apply the user-chosen accent theme to the given activity.
* Must be called BEFORE super.onCreate() so window-level attributes
* (status bar color, etc.) are resolved from the correct overlay.
* Has no effect when the theme is set to "system" (dynamic colors are
* handled by BasedBankApp via DynamicColors.applyToActivitiesIfAvailable).
*/
fun applyAccent(activity: AppCompatActivity) {
if (isSystemTheme(activity)) return
val preset = getAccentPreset(activity)
val seed = presetSeedColor(activity, preset)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
bitmap.setPixel(0, 0, seed)
DynamicColors.applyToActivityIfAvailable(
activity,
DynamicColorsOptions.Builder().setContentBasedSource(bitmap).build()
)
} else {
// API < 31: apply a simple style overlay (partial palette, but functional)
val styleRes = when (preset) {
PRESET_RED -> R.style.ThemeOverlay_Accent_Orange
PRESET_GREEN -> R.style.ThemeOverlay_Accent_Green
else -> R.style.ThemeOverlay_Accent_Blue
}
activity.theme.applyStyle(styleRes, true)
}
val prefs = activity.getSharedPreferences("prefs", Context.MODE_PRIVATE)
val isDark = prefs.getString("theme", "system") == "dark"
if (isDark && prefs.getBoolean("pitch_black", false)) {
activity.theme.applyStyle(R.style.ThemeOverlay_PitchBlack, true)
}
}
}
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Translucent dark pill used as a status overlay on card-visual surfaces
(item_card_dashboard / _stack / _wallet / _settings_entry). Theme-independent
by design: it sits on top of card artwork, not the surface color. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12dp" />
<solid android:color="#CC212121" />
</shape>
+1 -1
View File
@@ -4,6 +4,6 @@
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?attr/colorOnSurface" android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M9.5,6.5v3h-3v-3H9.5zM11,5L5,5v5.5h6L11,5zM9.5,14.5v3h-3v-3H9.5zM11,13L5,13v5.5h6L11,13zM17.5,6.5v3h-3v-3H17.5zM19,5h-6v5.5h6L19,5zM13,13h1.5v1.5L13,14.5zM14.5,14.5L16,14.5L16,16h-1.5zM16,13h1.5v1.5L16,14.5zM13,16h1.5v1.5L13,17.5zM14.5,17.5L16,17.5L16,19h-1.5zM16,16h1.5v1.5L16,17.5zM17.5,14.5L19,14.5L19,16h-1.5zM17.5,17.5L19,17.5L19,19h-1.5z"/> android:pathData="M9.5,6.5v3h-3v-3H9.5zM11,5L5,5v5.5h6L11,5zM9.5,14.5v3h-3v-3H9.5zM11,13L5,13v5.5h6L11,13zM17.5,6.5v3h-3v-3H17.5zM19,5h-6v5.5h6L19,5zM13,13h1.5v1.5L13,14.5zM14.5,14.5L16,14.5L16,16h-1.5zM16,13h1.5v1.5L16,14.5zM13,16h1.5v1.5L13,17.5zM14.5,17.5L16,17.5L16,19h-1.5zM16,16h1.5v1.5L16,17.5zM17.5,14.5L19,14.5L19,16h-1.5zM17.5,17.5L19,17.5L19,19h-1.5z"/>
</vector> </vector>
+2 -2
View File
@@ -44,8 +44,8 @@
android:id="@+id/connectivityBanner" android:id="@+id/connectivityBanner"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorErrorContainer" android:background="#C62828"
android:textColor="?attr/colorOnErrorContainer" android:textColor="#FFFFFF"
android:gravity="center" android:gravity="center"
android:paddingTop="6dp" android:paddingTop="6dp"
android:paddingBottom="6dp" android:paddingBottom="6dp"
@@ -48,28 +48,12 @@
android:paddingBottom="16dp" android:paddingBottom="16dp"
android:clipToPadding="false" /> android:clipToPadding="false" />
<LinearLayout
android:id="@+id/skeletonContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
</LinearLayout>
<TextView <TextView
android:id="@+id/emptyView" android:id="@+id/emptyView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:text="@string/transactions_empty" android:text="No transactions found"
android:textAppearance="?attr/textAppearanceBodyMedium" android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant" android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" /> android:visibility="gone" />
@@ -49,7 +49,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:text="@string/activities_empty" android:text="No recent transfers"
android:textAppearance="?attr/textAppearanceBodyMedium" android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant" android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" /> android:visibility="gone" />
@@ -38,7 +38,7 @@
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:cardCornerRadius="@dimen/card_radius_lg" app:cardCornerRadius="16dp"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline"> app:strokeColor="?attr/colorOutline">
@@ -81,7 +81,7 @@
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:cardCornerRadius="@dimen/card_radius_lg" app:cardCornerRadius="16dp"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline"> app:strokeColor="?attr/colorOutline">
@@ -125,7 +125,7 @@
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:cardCornerRadius="@dimen/card_radius_lg" app:cardCornerRadius="16dp"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline"> app:strokeColor="?attr/colorOutline">
@@ -117,8 +117,8 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:visibility="invisible" android:visibility="invisible"
app:cardBackgroundColor="?attr/colorSecondaryContainer" app:cardBackgroundColor="?attr/colorSecondaryContainer"
app:cardCornerRadius="@dimen/card_radius_md" app:cardCornerRadius="12dp"
app:cardElevation="@dimen/card_elevation_flat"> app:cardElevation="0dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
+22 -46
View File
@@ -21,7 +21,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/screen_margin"> android:padding="16dp">
<!-- Balance cards row --> <!-- Balance cards row -->
<LinearLayout <LinearLayout
@@ -34,8 +34,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:cardElevation="@dimen/card_elevation_raised" app:cardElevation="1dp"
app:cardCornerRadius="@dimen/card_radius_md"> app:cardCornerRadius="12dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -56,8 +56,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="MVR —" android:text="MVR —"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -68,8 +67,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
app:cardElevation="@dimen/card_elevation_raised" app:cardElevation="1dp"
app:cardCornerRadius="@dimen/card_radius_md"> app:cardCornerRadius="12dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -90,8 +89,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="USD —" android:text="USD —"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -114,8 +112,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:cardElevation="@dimen/card_elevation_raised" app:cardElevation="1dp"
app:cardCornerRadius="@dimen/card_radius_md"> app:cardCornerRadius="12dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -136,8 +134,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="MVR —" android:text="MVR —"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -149,8 +146,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
app:cardElevation="@dimen/card_elevation_raised" app:cardElevation="1dp"
app:cardCornerRadius="@dimen/card_radius_md"> app:cardCornerRadius="12dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -171,8 +168,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="USD —" android:text="USD —"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -195,8 +191,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:cardElevation="@dimen/card_elevation_raised" app:cardElevation="1dp"
app:cardCornerRadius="@dimen/card_radius_md" app:cardCornerRadius="12dp"
app:cardBackgroundColor="?attr/colorErrorContainer" app:cardBackgroundColor="?attr/colorErrorContainer"
android:visibility="gone"> android:visibility="gone">
@@ -220,8 +216,7 @@
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="MVR —" android:text="MVR —"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnErrorContainer" android:textColor="?attr/colorOnErrorContainer" />
android:fontFeatureSettings="tnum" />
<TextView <TextView
android:id="@+id/tvBlockedSecondary" android:id="@+id/tvBlockedSecondary"
@@ -230,7 +225,6 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:textAppearance="?attr/textAppearanceLabelSmall" android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnErrorContainer" android:textColor="?attr/colorOnErrorContainer"
android:fontFeatureSettings="tnum"
android:alpha="0.75" android:alpha="0.75"
android:visibility="gone" /> android:visibility="gone" />
@@ -244,8 +238,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
app:cardElevation="@dimen/card_elevation_raised" app:cardElevation="1dp"
app:cardCornerRadius="@dimen/card_radius_md" app:cardCornerRadius="12dp"
app:cardBackgroundColor="?attr/colorErrorContainer" app:cardBackgroundColor="?attr/colorErrorContainer"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@@ -271,8 +265,7 @@
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="MVR —" android:text="MVR —"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnErrorContainer" android:textColor="?attr/colorOnErrorContainer" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -286,8 +279,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:cardElevation="@dimen/card_elevation_raised" app:cardElevation="1dp"
app:cardCornerRadius="@dimen/card_radius_md" app:cardCornerRadius="12dp"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
@@ -310,8 +303,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="MVR —" android:text="MVR —"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -333,14 +325,6 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:visibility="gone"> android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nav_pay_with_card"
android:textAppearance="?attr/textAppearanceLabelMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginBottom="8dp"/>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCards" android:id="@+id/rvCards"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -366,14 +350,6 @@
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="16dp"> android:paddingBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dashboard_quick_actions"
android:textAppearance="?attr/textAppearanceLabelMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginBottom="8dp" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -52,7 +52,7 @@
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:cardCornerRadius="@dimen/card_radius_lg" app:cardCornerRadius="16dp"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline"> app:strokeColor="?attr/colorOutline">
<LinearLayout <LinearLayout
@@ -94,7 +94,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:cardCornerRadius="@dimen/card_radius_lg" app:cardCornerRadius="16dp"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline"> app:strokeColor="?attr/colorOutline">
<LinearLayout <LinearLayout
@@ -156,6 +156,87 @@
</com.google.android.material.button.MaterialButtonToggleGroup> </com.google.android.material.button.MaterialButtonToggleGroup>
<!-- Pitch black — enabled only in explicit dark mode -->
<LinearLayout
android:id="@+id/rowPitchBlack"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/settings_pitch_black"
android:textAppearance="?attr/textAppearanceBodyLarge" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchPitchBlack"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<!-- Accent color — always shown, disabled/grayed in system theme mode -->
<LinearLayout
android:id="@+id/sectionAccentColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settings_accent_color"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:layout_marginBottom="12dp" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/accentToggle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:singleSelection="true"
app:selectionRequired="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAccentBlue"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/accent_blue" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAccentOrange"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/accent_orange" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAccentGreen"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/accent_green" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAccentCustom"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/accent_custom" />
</com.google.android.material.button.MaterialButtonToggleGroup>
</LinearLayout>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -194,44 +194,6 @@
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginBottom="8dp" /> android:layout_marginBottom="8dp" />
<LinearLayout
android:id="@+id/rowHideAmounts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="4dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settings_hide_amounts"
android:textAppearance="?attr/textAppearanceBodyLarge"
android:textColor="?attr/colorOnSurface" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settings_hide_amounts_desc"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchHideAmounts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/rowBlockScreenshots" android:id="@+id/rowBlockScreenshots"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -49,28 +49,12 @@
android:paddingBottom="16dp" android:paddingBottom="16dp"
android:clipToPadding="false" /> android:clipToPadding="false" />
<LinearLayout
android:id="@+id/skeletonContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
<include layout="@layout/item_skeleton_transaction" />
</LinearLayout>
<TextView <TextView
android:id="@+id/emptyView" android:id="@+id/emptyView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:text="@string/transactions_empty" android:text="No transactions found"
android:textAppearance="?attr/textAppearanceBodyMedium" android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant" android:textColor="?attr/colorOnSurfaceVariant"
android:visibility="gone" /> android:visibility="gone" />
+3 -5
View File
@@ -9,8 +9,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingHorizontal="@dimen/list_item_padding_h" android:paddingHorizontal="16dp"
android:paddingVertical="@dimen/list_item_padding_v" android:paddingVertical="14dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:foreground="?attr/selectableItemBackground"> android:foreground="?attr/selectableItemBackground">
@@ -70,8 +70,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleSmall" android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface" />
android:fontFeatureSettings="tnum" />
<TextView <TextView
android:id="@+id/tvBlocked" android:id="@+id/tvBlocked"
@@ -80,7 +79,6 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:textAppearance="?attr/textAppearanceLabelSmall" android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorError" android:textColor="?attr/colorError"
android:fontFeatureSettings="tnum"
android:visibility="gone" /> android:visibility="gone" />
<ImageButton <ImageButton
@@ -15,7 +15,6 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:visibility="gone" android:visibility="gone"
android:importantForAccessibility="no"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle"
xmlns:app="http://schemas.android.com/apk/res-auto" /> xmlns:app="http://schemas.android.com/apk/res-auto" />
@@ -50,8 +49,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodySmall" android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -7,8 +7,8 @@
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:cardCornerRadius="@dimen/card_radius_lg" app:cardCornerRadius="16dp"
app:cardElevation="@dimen/card_elevation_flat" app:cardElevation="0dp"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutlineVariant"> app:strokeColor="?attr/colorOutlineVariant">
@@ -114,8 +114,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:layout_marginTop="2dp" android:layout_marginTop="2dp" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -138,8 +137,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:layout_marginTop="2dp" android:layout_marginTop="2dp" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
@@ -164,8 +162,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorError" android:textColor="?attr/colorError"
android:layout_marginTop="2dp" android:layout_marginTop="2dp" />
android:fontFeatureSettings="tnum" />
</LinearLayout> </LinearLayout>
+7 -5
View File
@@ -5,10 +5,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:clickable="true" app:cardCornerRadius="16dp"
android:focusable="true" app:cardElevation="0dp"
app:cardCornerRadius="@dimen/card_radius_lg"
app:cardElevation="@dimen/card_elevation_flat"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutlineVariant"> app:strokeColor="?attr/colorOutlineVariant">
@@ -53,7 +51,11 @@
android:id="@+id/tvLoanStatus" android:id="@+id/tvLoanStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.App.StatusChip" /> android:paddingHorizontal="10dp"
android:paddingVertical="4dp"
android:background="@drawable/chip_background"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSecondaryContainer" />
</LinearLayout> </LinearLayout>
+3 -4
View File
@@ -9,8 +9,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingHorizontal="@dimen/list_item_padding_h" android:paddingHorizontal="16dp"
android:paddingVertical="@dimen/list_item_padding_v" android:paddingVertical="14dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:foreground="?attr/selectableItemBackground"> android:foreground="?attr/selectableItemBackground">
@@ -83,8 +83,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleSmall" android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:layout_marginTop="6dp" android:layout_marginTop="6dp" />
android:fontFeatureSettings="tnum" />
<ImageButton <ImageButton
android:id="@+id/btnTransfer" android:id="@+id/btnTransfer"
@@ -5,10 +5,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:clickable="true" app:cardCornerRadius="16dp"
android:focusable="true" app:cardElevation="0dp"
app:cardCornerRadius="@dimen/card_radius_lg"
app:cardElevation="@dimen/card_elevation_flat"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutlineVariant"> app:strokeColor="?attr/colorOutlineVariant">
@@ -53,7 +51,11 @@
android:id="@+id/tvStatus" android:id="@+id/tvStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.App.StatusChip" /> android:paddingHorizontal="10dp"
android:paddingVertical="4dp"
android:background="@drawable/chip_background"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSecondaryContainer" />
</LinearLayout> </LinearLayout>
@@ -5,10 +5,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="true" app:cardElevation="1dp"
android:focusable="true" app:cardCornerRadius="12dp">
app:cardElevation="@dimen/card_elevation_raised"
app:cardCornerRadius="@dimen/card_radius_md">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
+3 -4
View File
@@ -5,16 +5,15 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingHorizontal="@dimen/list_item_padding_h" android:paddingHorizontal="16dp"
android:paddingVertical="@dimen/list_item_padding_v" android:paddingVertical="14dp"
android:background="?attr/selectableItemBackground"> android:background="?attr/selectableItemBackground">
<ImageView <ImageView
android:id="@+id/ivIcon" android:id="@+id/ivIcon"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp" />
android:importantForAccessibility="no" />
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
+1 -2
View File
@@ -13,8 +13,7 @@
android:id="@+id/ivNavIcon" android:id="@+id/ivNavIcon"
android:layout_width="28dp" android:layout_width="28dp"
android:layout_height="28dp" android:layout_height="28dp"
android:layout_marginBottom="6dp" android:layout_marginBottom="6dp" />
android:importantForAccessibility="no" />
<TextView <TextView
android:id="@+id/tvNavLabel" android:id="@+id/tvNavLabel"
+2 -2
View File
@@ -6,8 +6,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
app:cardCornerRadius="@dimen/card_radius_lg" app:cardCornerRadius="16dp"
app:cardElevation="@dimen/card_elevation_raised"> app:cardElevation="2dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -16,7 +16,6 @@
android:layout_width="44dp" android:layout_width="44dp"
android:layout_height="44dp" android:layout_height="44dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:importantForAccessibility="no"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" /> app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" />
<LinearLayout <LinearLayout
@@ -54,7 +53,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleMedium" android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:fontFeatureSettings="tnum"
android:paddingHorizontal="8dp" android:paddingHorizontal="8dp"
android:visibility="gone" /> android:visibility="gone" />
@@ -20,6 +20,10 @@
android:id="@+id/tvProfileType" android:id="@+id/tvProfileType"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.App.StatusChip" /> android:paddingHorizontal="10dp"
android:paddingVertical="4dp"
android:background="@drawable/chip_background"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSecondaryContainer" />
</LinearLayout> </LinearLayout>
@@ -86,7 +86,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleSmall" android:textAppearance="?attr/textAppearanceTitleSmall"
android:textStyle="bold" android:textStyle="bold"
android:fontFeatureSettings="tnum"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/fvAvatar" app:layout_constraintTop_toTopOf="@id/fvAvatar"
app:layout_constraintBottom_toBottomOf="@id/fvAvatar" /> app:layout_constraintBottom_toBottomOf="@id/fvAvatar" />
@@ -65,8 +65,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:visibility="gone" android:visibility="gone"
app:cardCornerRadius="@dimen/card_radius_md" app:cardCornerRadius="12dp"
app:cardElevation="@dimen/card_elevation_raised"> app:cardElevation="2dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
+1 -33
View File
@@ -5,36 +5,4 @@
<dimen name="nav_header_vertical_spacing">8dp</dimen> <dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen> <dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen>
</resources>
<!-- ====== Design tokens ======================================== -->
<!-- Card surfaces.
Two-tier radius:
lg → hero/standalone cards (bank tiles, history header, finance deals, OTP cards)
md → repeating row/summary cards (dashboard cards, foreign limit, bottom sheets)
Card-visual representations (item_card_*) keep their own larger radius/elevation
to read as real credit cards — they are not tokenized. -->
<dimen name="card_radius_lg">16dp</dimen>
<dimen name="card_radius_md">12dp</dimen>
<!-- Card elevation. Two tiers:
flat → outlined cards (paired with a colorOutlineVariant stroke)
raised → subtle Material 3 default raise
Larger elevations belong to specific card-visual surfaces only. -->
<dimen name="card_elevation_flat">0dp</dimen>
<dimen name="card_elevation_raised">1dp</dimen>
<!-- Screen-level spacing -->
<dimen name="screen_margin">16dp</dimen>
<dimen name="section_gap">16dp</dimen>
<!-- Standard list/row padding -->
<dimen name="list_item_padding_h">16dp</dimen>
<dimen name="list_item_padding_v">14dp</dimen>
<!-- Gutters: small inline spacing units -->
<dimen name="gutter_xs">2dp</dimen>
<dimen name="gutter_sm">4dp</dimen>
<dimen name="gutter_md">8dp</dimen>
<dimen name="gutter_lg">12dp</dimen>
</resources>
+9 -2
View File
@@ -148,6 +148,15 @@
<string name="theme_system">System</string> <string name="theme_system">System</string>
<string name="theme_light">Light</string> <string name="theme_light">Light</string>
<string name="theme_dark">Dark</string> <string name="theme_dark">Dark</string>
<string name="settings_pitch_black">Pitch Black</string>
<string name="settings_accent_color">Accent Color</string>
<string name="accent_blue">Blue</string>
<string name="accent_orange">Red</string>
<string name="accent_green">Green</string>
<string name="accent_custom">Custom</string>
<string name="accent_custom_pick">Custom Accent Color</string>
<string name="accent_custom_hint">#RRGGBB hex color</string>
<string name="accent_invalid_color">Invalid color — enter a valid hex code</string>
<string name="language">Language</string> <string name="language">Language</string>
<string name="lang_english">English</string> <string name="lang_english">English</string>
<string name="lang_dhivehi">ދިވެހި</string> <string name="lang_dhivehi">ދިވެހި</string>
@@ -255,8 +264,6 @@
<!-- Accounts --> <!-- Accounts -->
<string name="accounts_empty">No accounts found</string> <string name="accounts_empty">No accounts found</string>
<string name="activities_empty">No recent transfers</string>
<string name="transactions_empty">No transactions found</string>
<!-- Contacts --> <!-- Contacts -->
<string name="contacts_empty">No contacts found</string> <string name="contacts_empty">No contacts found</string>
+28 -9
View File
@@ -1,18 +1,37 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="ShapeAppearance.Circle" parent="ShapeAppearance.Material3.Corner.Full" /> <style name="ShapeAppearance.Circle" parent="ShapeAppearance.Material3.Corner.Full" />
<!-- Reusable status chip (filled, secondary container). Apply to a TextView via style. -->
<style name="Widget.App.StatusChip" parent="">
<item name="android:paddingHorizontal">10dp</item>
<item name="android:paddingVertical">4dp</item>
<item name="android:background">@drawable/chip_background</item>
<item name="android:textAppearance">?attr/textAppearanceLabelSmall</item>
<item name="android:textColor">?attr/colorOnSecondaryContainer</item>
</style>
<style name="Theme.BasedBank" parent="Theme.Material3.DayNight.NoActionBar"> <style name="Theme.BasedBank" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/seed_primary</item> <item name="colorPrimary">@color/seed_primary</item>
<item name="colorSecondary">@color/seed_secondary</item> <item name="colorSecondary">@color/seed_secondary</item>
<item name="android:windowSoftInputMode">adjustResize</item> <item name="android:windowSoftInputMode">adjustResize</item>
</style> </style>
<!-- Accent overlays — applied on API < 31 as fallback when content-based dynamic colors unavailable -->
<style name="ThemeOverlay_Accent_Blue" parent="">
<item name="colorPrimary">#3F65AD</item>
<item name="colorSecondary">#9AD141</item>
</style>
<style name="ThemeOverlay_Accent_Orange" parent="">
<item name="colorPrimary">#D32F2F</item>
<item name="colorSecondary">#EF9A9A</item>
</style>
<style name="ThemeOverlay_Accent_Green" parent="">
<item name="colorPrimary">#4CAF50</item>
<item name="colorSecondary">#80CBC4</item>
</style>
<!-- Pitch black overlay — forces pure black surfaces for OLED displays -->
<style name="ThemeOverlay_PitchBlack" parent="">
<item name="android:colorBackground">#000000</item>
<item name="colorSurface">#000000</item>
<item name="colorSurfaceVariant">#0d0d0d</item>
<item name="colorSurfaceContainer">#0d0d0d</item>
<item name="colorSurfaceContainerLow">#000000</item>
<item name="colorSurfaceContainerLowest">#000000</item>
<item name="colorSurfaceContainerHigh">#1a1a1a</item>
<item name="colorSurfaceContainerHighest">#1a1a1a</item>
</style>
</resources> </resources>