diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt index 2109297..14e118b 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt @@ -359,55 +359,87 @@ class SettingsLoginsFragment : Fragment() { _binding = null } + private data class LoginEntry(val logoRes: Int, val displayName: String, val onClick: () -> Unit) + private fun buildLoginsSection() { val ctx = requireContext() val store = CredentialStore(ctx) val container = binding.loginsContainer container.removeAllViews() - val mibLoginIds = store.getMibLoginIds() - val bmlLoginIds = store.getBmlLoginIds() - val fahipayLoginIds = store.getFahipayLoginIds() - val mfaisaLoginIds = store.getMfaisaLoginIds() + var anyLogins = false - binding.tvLoginsTitle.visibility = if (mibLoginIds.isNotEmpty() || bmlLoginIds.isNotEmpty() || fahipayLoginIds.isNotEmpty() || mfaisaLoginIds.isNotEmpty()) View.VISIBLE else View.GONE + fun addGroup(loginIds: List, entryFor: (String) -> LoginEntry, persistOrder: (List) -> Unit) { + if (loginIds.isEmpty()) return + anyLogins = true + val groupContainer = LinearLayout(ctx).apply { orientation = LinearLayout.VERTICAL } + loginIds.forEachIndexed { index, loginId -> + val entry = entryFor(loginId) + fun swapAndPersist(otherIndex: Int) { + val newOrder = loginIds.toMutableList().apply { + val tmp = this[index]; this[index] = this[otherIndex]; this[otherIndex] = tmp + } + persistOrder(newOrder) + buildLoginsSection() + } + addLoginRow( + groupContainer, entry.logoRes, entry.displayName, entry.onClick, + canMoveUp = index > 0, + canMoveDown = index < loginIds.size - 1, + onMoveUp = { swapAndPersist(index - 1) }, + onMoveDown = { swapAndPersist(index + 1) } + ) + } + container.addView(groupContainer) + } - for (loginId in mibLoginIds) { + addGroup(store.getMibLoginIds(), { loginId -> val profile = store.loadMibUserProfile(loginId) val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.mib_name) val mibProfiles = store.loadMibProfiles(loginId) - addLoginRow(container, R.drawable.mib_logo, displayName) { + LoginEntry(R.drawable.mib_logo, displayName) { showMibLoginDetails(store, loginId, profile, mibProfiles) } - } + }, store::setMibLoginIds) - for (loginId in bmlLoginIds) { + addGroup(store.getBmlLoginIds(), { loginId -> val profile = store.loadBmlUserProfile(loginId) val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.bml_name) val bmlProfiles = store.loadBmlProfiles(loginId) - addLoginRow(container, R.drawable.bml_logo_vector, displayName) { + LoginEntry(R.drawable.bml_logo_vector, displayName) { showBmlLoginDetails(store, loginId, profile, bmlProfiles) } - } + }, store::setBmlLoginIds) - for (loginId in fahipayLoginIds) { + addGroup(store.getFahipayLoginIds(), { loginId -> val profile = store.loadFahipayUserProfile(loginId) val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.fahipay_name) - addLoginRow(container, R.drawable.fahipay_logo, displayName) { + LoginEntry(R.drawable.fahipay_logo, displayName) { showFahipayLoginDetails(store, loginId, profile) } - } + }, store::setFahipayLoginIds) - for (loginId in mfaisaLoginIds) { + addGroup(store.getMfaisaLoginIds(), { loginId -> val profile = store.loadMfaisaUserProfile(loginId) val displayName = profile?.name?.takeIf { it.isNotBlank() } ?: getString(R.string.ooredoo_name) - addLoginRow(container, R.drawable.ooredoo_logo, displayName) { + LoginEntry(R.drawable.ooredoo_logo, displayName) { showMfaisaLoginDetails(store, loginId, profile) } - } + }, store::setMfaisaLoginIds) + + binding.tvLoginsTitle.visibility = if (anyLogins) View.VISIBLE else View.GONE } - private fun addLoginRow(container: LinearLayout, logoRes: Int, displayName: String, onClick: () -> Unit) { + private fun addLoginRow( + container: LinearLayout, + logoRes: Int, + displayName: String, + onClick: () -> Unit, + canMoveUp: Boolean, + canMoveDown: Boolean, + onMoveUp: () -> Unit, + onMoveDown: () -> Unit + ) { val ctx = requireContext() val dp = ctx.resources.displayMetrics.density val row = LinearLayout(ctx).apply { @@ -429,10 +461,31 @@ class SettingsLoginsFragment : Fragment() { setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) } - row.addView(logo); row.addView(tvName) + val btnUp = makeMoveButton(ctx, rotationDeg = -90f, enabled = canMoveUp, onClick = onMoveUp) + val btnDown = makeMoveButton(ctx, rotationDeg = 90f, enabled = canMoveDown, onClick = onMoveDown) + row.addView(logo); row.addView(tvName); row.addView(btnUp); row.addView(btnDown) container.addView(row) } + private fun makeMoveButton(ctx: Context, rotationDeg: Float, enabled: Boolean, onClick: () -> Unit): ImageView { + val dp = ctx.resources.displayMetrics.density + val size = (32 * dp).toInt() + return ImageView(ctx).apply { + setImageResource(R.drawable.ic_arrow_right) + rotation = rotationDeg + scaleType = ImageView.ScaleType.CENTER_INSIDE + val ta = ctx.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackgroundBorderless)) + background = ta.getDrawable(0); ta.recycle() + val iconColor = com.google.android.material.color.MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorOnSurfaceVariant, android.graphics.Color.GRAY) + imageTintList = android.content.res.ColorStateList.valueOf(iconColor) + alpha = if (enabled) 1f else 0.3f + isClickable = enabled; isFocusable = enabled + layoutParams = LinearLayout.LayoutParams(size, size).apply { marginStart = (4 * dp).toInt() } + if (enabled) setOnClickListener { onClick() } + } + } + private fun showMibLoginDetails( store: CredentialStore, loginId: String, diff --git a/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt b/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt index b5fcce8..c06b4e5 100644 --- a/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt +++ b/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt @@ -36,6 +36,11 @@ class CredentialStore(context: Context) { fun hasMibCredentials(): Boolean = getMibLoginIds().isNotEmpty() + fun setMibLoginIds(order: List) { + if (order.toSet() != getMibLoginIds().toSet()) return + prefs.edit().putString("mib_login_ids", org.json.JSONArray(order).toString()).apply() + } + private fun addMibLoginId(loginId: String) { val ids = getMibLoginIds().toMutableList() if (loginId !in ids) { @@ -160,6 +165,11 @@ class CredentialStore(context: Context) { fun hasBmlCredentials(): Boolean = getBmlLoginIds().isNotEmpty() + fun setBmlLoginIds(order: List) { + if (order.toSet() != getBmlLoginIds().toSet()) return + prefs.edit().putString("bml_login_ids", org.json.JSONArray(order).toString()).apply() + } + private fun addBmlLoginId(loginId: String) { val ids = getBmlLoginIds().toMutableList() if (loginId !in ids) { @@ -316,6 +326,11 @@ class CredentialStore(context: Context) { fun hasFahipayCredentials(): Boolean = getFahipayLoginIds().isNotEmpty() + fun setFahipayLoginIds(order: List) { + if (order.toSet() != getFahipayLoginIds().toSet()) return + prefs.edit().putString("fahipay_login_ids", org.json.JSONArray(order).toString()).apply() + } + private fun addFahipayLoginId(loginId: String) { val ids = getFahipayLoginIds().toMutableList() if (loginId !in ids) { @@ -473,6 +488,11 @@ class CredentialStore(context: Context) { fun hasMfaisaCredentials(): Boolean = getMfaisaLoginIds().isNotEmpty() + fun setMfaisaLoginIds(order: List) { + if (order.toSet() != getMfaisaLoginIds().toSet()) return + prefs.edit().putString("mfaisa_login_ids", org.json.JSONArray(order).toString()).apply() + } + private fun addMfaisaLoginId(loginId: String) { val ids = getMfaisaLoginIds().toMutableList() if (loginId !in ids) {