From 1e3cf3420be00adba3688241a35d8742a331759e Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Fri, 25 Sep 2026 03:43:58 +0500 Subject: [PATCH] login sorting fixed --- .../basedbank/ui/home/DashboardFragment.kt | 3 +- .../sar/basedbank/ui/home/FinancingAdapter.kt | 6 +- .../basedbank/ui/home/FinancingFragment.kt | 7 +- .../sh/sar/basedbank/ui/home/HomeViewModel.kt | 32 +++- .../sh/sar/basedbank/ui/home/OtpFragment.kt | 8 +- .../basedbank/ui/home/PayWithCardFragment.kt | 4 +- .../ui/home/SettingsLoginsFragment.kt | 168 +++++++++++------- .../sh/sar/basedbank/util/CredentialStore.kt | 36 ++++ .../main/res/drawable/ic_reorder_lines.xml | 10 ++ app/src/main/res/values-b+dv/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 11 files changed, 200 insertions(+), 76 deletions(-) create mode 100644 app/src/main/res/drawable/ic_reorder_lines.xml 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 index 948c5a6..57d446a 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt @@ -121,7 +121,8 @@ class DashboardFragment : Fragment() { val bmlItems = (viewModel.accounts.value ?: emptyList()) .filter { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.statusDesc.equals("Active", ignoreCase = true) && !hidden.contains(it.accountNumber) } .map { CardItem.Bml(it) } - val all = bmlItems + mibItems + val rank = credStore.loginRank() + val all = (bmlItems + mibItems).sortedBy { rank(it.loginTag) } val defaultNum = credStore.getDefaultCardAccountNumber() val ordered = if (defaultNum != null) { val def = all.filterIsInstance().firstOrNull { it.account.accountNumber == defaultNum } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/FinancingAdapter.kt b/app/src/main/java/sh/sar/basedbank/ui/home/FinancingAdapter.kt index 08d130c..35ada5d 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/FinancingAdapter.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/FinancingAdapter.kt @@ -44,9 +44,11 @@ class FinancingAdapter(mibDeals: List) : notifyDataSetChanged() } - fun update(mibDeals: List, bmlLoans: List>) { + fun update(mibDeals: List, bmlLoans: List>, mibFirst: Boolean = true) { expandedPositions.clear() - items = mibDeals.map { Item.Mib(it) } + bmlLoans.map { (acc, detail) -> Item.Bml(acc, detail) } + val mibItems = mibDeals.map { Item.Mib(it) } + val bmlItems = bmlLoans.map { (acc, detail) -> Item.Bml(acc, detail) } + items = if (mibFirst) mibItems + bmlItems else bmlItems + mibItems notifyDataSetChanged() } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt index fbda63d..5ceb7ce 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt @@ -14,6 +14,7 @@ import sh.sar.basedbank.api.bml.BmlLoanDetail import sh.sar.basedbank.api.mib.MibFinanceDeal import sh.sar.basedbank.api.models.BankAccount import sh.sar.basedbank.databinding.FragmentFinancingBinding +import sh.sar.basedbank.util.CredentialStore class FinancingFragment : Fragment() { @@ -67,7 +68,11 @@ class FinancingFragment : Fragment() { val bmlLoans: List> = loanAccounts.map { acc -> acc to latestBmlLoanDetails[acc.internalId] } - adapter.update(latestMibDeals, bmlLoans) + // MIB deals carry no login tag, so order the MIB and BML blocks by each bank's first login + val order = CredentialStore(requireContext()).getLoginOrder() + val firstMib = order.indexOfFirst { it.startsWith("mib_") } + val firstBml = order.indexOfFirst { it.startsWith("bml_") } + adapter.update(latestMibDeals, bmlLoans, mibFirst = firstBml < 0 || (firstMib in 0 until firstBml)) val isEmpty = latestMibDeals.isEmpty() && bmlLoans.isEmpty() binding.recyclerView.visibility = if (isEmpty) View.GONE else View.VISIBLE 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 index 298eebc..ffac7a9 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeViewModel.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeViewModel.kt @@ -1,7 +1,8 @@ package sh.sar.basedbank.ui.home +import android.app.Application +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import sh.sar.basedbank.api.bml.BmlForeignLimit import sh.sar.basedbank.api.bml.BmlLoanDetail import sh.sar.basedbank.api.models.BankAccount @@ -9,14 +10,21 @@ import sh.sar.basedbank.api.models.BankContact import sh.sar.basedbank.api.models.BankContactCategory import sh.sar.basedbank.api.mib.MibCard import sh.sar.basedbank.api.mib.MibFinanceDeal +import sh.sar.basedbank.util.CredentialStore sealed class CardItem { - data class Mib(val card: MibCard) : CardItem() - data class Bml(val account: BankAccount) : CardItem() + abstract val loginTag: String + data class Mib(val card: MibCard) : CardItem() { override val loginTag get() = card.loginTag } + data class Bml(val account: BankAccount) : CardItem() { override val loginTag get() = account.loginTag } } -class HomeViewModel : ViewModel() { - val accounts = MutableLiveData>(emptyList()) +class HomeViewModel(application: Application) : AndroidViewModel(application) { + /** Always kept in the user's login order (Settings > Logins), so every screen lists accounts the same way. */ + val accounts: MutableLiveData> = object : MutableLiveData>(emptyList()) { + override fun setValue(value: List?) { + super.setValue(value?.let { list -> loginRank().let { rank -> list.sortedBy { rank(it.loginTag) } } }) + } + } val financing = MutableLiveData>(emptyList()) /** BML loan details keyed by account internalId. */ val bmlLoanDetails = MutableLiveData>(emptyMap()) @@ -26,7 +34,19 @@ class HomeViewModel : ViewModel() { data class BmlLimitsData(val userName: String, val limits: List) val bmlLimits = MutableLiveData>(emptyList()) - val mibCards = MutableLiveData?>(null) + val mibCards: MutableLiveData?> = object : MutableLiveData?>(null) { + override fun setValue(value: List?) { + super.setValue(value?.let { list -> loginRank().let { rank -> list.sortedBy { rank(it.loginTag) } } }) + } + } + + fun loginRank(): (String) -> Int = CredentialStore(getApplication()).loginRank() + + /** Re-applies the login order after the user changes it. */ + fun resortByLoginOrder() { + accounts.value = accounts.value + mibCards.value = mibCards.value + } val hideAmounts = MutableLiveData(false) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/OtpFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/OtpFragment.kt index 532cf50..27205c5 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/OtpFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/OtpFragment.kt @@ -86,17 +86,19 @@ class OtpFragment : Fragment() { val store = CredentialStore(requireContext()) val app = requireActivity().application as BasedBankApp - val entries = mutableListOf() + val rank = store.loginRank() + val tagged = mutableListOf>() for (loginId in store.getMibLoginIds()) { val creds = store.loadMibCredentials(loginId) ?: continue val name = store.loadMibFullName(loginId) - entries.add(OtpEntry(if (name != null) "MIB · $name" else "MIB", creds.otpSeed)) + tagged.add(CredentialStore.loginKey("mib", loginId) to OtpEntry(if (name != null) "MIB · $name" else "MIB", creds.otpSeed)) } for (loginId in store.getBmlLoginIds()) { val creds = store.loadBmlCredentials(loginId) ?: continue val name = store.loadBmlUserProfile(loginId)?.fullName - entries.add(OtpEntry(if (!name.isNullOrBlank()) "BML · $name" else "BML", creds.otpSeed)) + tagged.add(CredentialStore.loginKey("bml", loginId) to OtpEntry(if (!name.isNullOrBlank()) "BML · $name" else "BML", creds.otpSeed)) } + val entries = tagged.sortedBy { rank(it.first) }.map { it.second }.toMutableList() val adapter = OtpAdapter(entries) binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt index 2ca307c..83ae66c 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt @@ -904,7 +904,9 @@ class CardsFragment : Fragment() { val bmlInactive = bmlItems.filter { !it.account.statusDesc.equals("Active", ignoreCase = true) } val mibActive = mibItems.filter { isMibCardActive(it.card.cardStatus) } val mibInactive = mibItems.filter { !isMibCardActive(it.card.cardStatus) } - val all: List = bmlActive + mibActive + bmlInactive + mibInactive + val rank = store.loginRank() + val all: List = (bmlActive + mibActive).sortedBy { rank(it.loginTag) } + + (bmlInactive + mibInactive).sortedBy { rank(it.loginTag) } // Move default BML card to front cards = if (defaultNum != null) { val def = all.filterIsInstance().firstOrNull { it.account.accountNumber == defaultNum } 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 14e118b..3bc2bf7 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 @@ -10,8 +10,11 @@ import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle import android.util.Base64 +import android.annotation.SuppressLint import android.view.Gravity +import android.view.HapticFeedbackConstants import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -359,7 +362,7 @@ class SettingsLoginsFragment : Fragment() { _binding = null } - private data class LoginEntry(val logoRes: Int, val displayName: String, val onClick: () -> Unit) + private data class LoginEntry(val key: String, val logoRes: Int, val displayName: String, val onClick: () -> Unit) private fun buildLoginsSection() { val ctx = requireContext() @@ -367,67 +370,57 @@ class SettingsLoginsFragment : Fragment() { val container = binding.loginsContainer container.removeAllViews() - var anyLogins = false + val entries = mutableListOf() - 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) - } - - addGroup(store.getMibLoginIds(), { loginId -> + for (loginId in store.getMibLoginIds()) { val profile = store.loadMibUserProfile(loginId) val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.mib_name) val mibProfiles = store.loadMibProfiles(loginId) - LoginEntry(R.drawable.mib_logo, displayName) { + entries += LoginEntry(CredentialStore.loginKey("mib", loginId), R.drawable.mib_logo, displayName) { showMibLoginDetails(store, loginId, profile, mibProfiles) } - }, store::setMibLoginIds) + } - addGroup(store.getBmlLoginIds(), { loginId -> + for (loginId in store.getBmlLoginIds()) { val profile = store.loadBmlUserProfile(loginId) val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.bml_name) val bmlProfiles = store.loadBmlProfiles(loginId) - LoginEntry(R.drawable.bml_logo_vector, displayName) { + entries += LoginEntry(CredentialStore.loginKey("bml", loginId), R.drawable.bml_logo_vector, displayName) { showBmlLoginDetails(store, loginId, profile, bmlProfiles) } - }, store::setBmlLoginIds) + } - addGroup(store.getFahipayLoginIds(), { loginId -> + for (loginId in store.getFahipayLoginIds()) { val profile = store.loadFahipayUserProfile(loginId) val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.fahipay_name) - LoginEntry(R.drawable.fahipay_logo, displayName) { + entries += LoginEntry(CredentialStore.loginKey("fahipay", loginId), R.drawable.fahipay_logo, displayName) { showFahipayLoginDetails(store, loginId, profile) } - }, store::setFahipayLoginIds) + } - addGroup(store.getMfaisaLoginIds(), { loginId -> + for (loginId in store.getMfaisaLoginIds()) { val profile = store.loadMfaisaUserProfile(loginId) val displayName = profile?.name?.takeIf { it.isNotBlank() } ?: getString(R.string.ooredoo_name) - LoginEntry(R.drawable.ooredoo_logo, displayName) { + entries += LoginEntry(CredentialStore.loginKey("mfaisa", loginId), R.drawable.ooredoo_logo, displayName) { showMfaisaLoginDetails(store, loginId, profile) } - }, store::setMfaisaLoginIds) + } - binding.tvLoginsTitle.visibility = if (anyLogins) View.VISIBLE else View.GONE + val rank = store.loginRank() + val sorted = entries.sortedBy { rank(it.key) } + val draggable = sorted.size > 1 + for (entry in sorted) { + addLoginRow( + container, entry.logoRes, entry.displayName, entry.onClick, + dragTag = entry.key.takeIf { draggable }, + onReordered = { order -> + store.setLoginOrder(order) + viewModel.resortByLoginOrder() + } + ) + } + + binding.tvLoginsTitle.visibility = if (entries.isNotEmpty()) View.VISIBLE else View.GONE } private fun addLoginRow( @@ -435,10 +428,8 @@ class SettingsLoginsFragment : Fragment() { logoRes: Int, displayName: String, onClick: () -> Unit, - canMoveUp: Boolean, - canMoveDown: Boolean, - onMoveUp: () -> Unit, - onMoveDown: () -> Unit + dragTag: String? = null, + onReordered: (List) -> Unit = {} ) { val ctx = requireContext() val dp = ctx.resources.displayMetrics.density @@ -450,6 +441,7 @@ class SettingsLoginsFragment : Fragment() { val ta = ctx.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground)) background = ta.getDrawable(0); ta.recycle() setOnClickListener { onClick() } + tag = dragTag } val logo = ImageView(ctx).apply { setImageResource(logoRes) @@ -461,28 +453,80 @@ class SettingsLoginsFragment : Fragment() { setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) } - 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) + row.addView(logo); row.addView(tvName) + if (dragTag != null) { + val handle = ImageView(ctx).apply { + setImageResource(R.drawable.ic_reorder_lines) + scaleType = ImageView.ScaleType.CENTER_INSIDE + contentDescription = getString(R.string.drag_to_reorder) + layoutParams = LinearLayout.LayoutParams((40 * dp).toInt(), (40 * dp).toInt()).apply { marginStart = (4 * dp).toInt() } + } + row.addView(handle) + attachDragHandle(handle, row, container, onReordered) + } 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() } + /** Drag [row] vertically within [group] by its [handle]; rows are identified by their String tag. */ + @SuppressLint("ClickableViewAccessibility") + private fun attachDragHandle(handle: View, row: View, group: LinearLayout, onReordered: (List) -> Unit) { + val ctx = handle.context + val lift = 8 * ctx.resources.displayMetrics.density + val dragBg = android.graphics.drawable.ColorDrawable( + com.google.android.material.color.MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorSurfaceContainerHigh, android.graphics.Color.LTGRAY)) + var lastY = 0f + var startIndex = 0 + var savedBg: android.graphics.drawable.Drawable? = null + + handle.setOnTouchListener { v, ev -> + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + v.parent.requestDisallowInterceptTouchEvent(true) + lastY = ev.rawY + startIndex = group.indexOfChild(row) + row.animate().cancel() + savedBg = row.background + row.background = dragBg + row.translationZ = lift + v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + MotionEvent.ACTION_MOVE -> { + row.translationY += ev.rawY - lastY + lastY = ev.rawY + var index = group.indexOfChild(row) + while (index < group.childCount - 1) { + val next = group.getChildAt(index + 1) + if (row.translationY <= next.height / 2f) break + group.removeView(next); group.addView(next, index) + row.translationY -= next.height + next.translationY = row.height.toFloat() + next.animate().translationY(0f).setDuration(150).start() + index++ + } + while (index > 0) { + val prev = group.getChildAt(index - 1) + if (row.translationY >= -prev.height / 2f) break + group.removeView(prev); group.addView(prev, index) + row.translationY += prev.height + prev.translationY = -row.height.toFloat() + prev.animate().translationY(0f).setDuration(150).start() + index-- + } + if (index == 0) row.translationY = row.translationY.coerceAtLeast(0f) + if (index == group.childCount - 1) row.translationY = row.translationY.coerceAtMost(0f) + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + v.parent.requestDisallowInterceptTouchEvent(false) + row.animate().translationY(0f).translationZ(0f).setDuration(150) + .withEndAction { row.background = savedBg } + .start() + if (group.indexOfChild(row) != startIndex) { + onReordered((0 until group.childCount).mapNotNull { group.getChildAt(it).tag as? String }) + } + } + } + true } } 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 c06b4e5..ddad526 100644 --- a/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt +++ b/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt @@ -14,6 +14,11 @@ import javax.crypto.spec.GCMParameterSpec class CredentialStore(context: Context) { + companion object { + /** Same format as BankAccount.loginTag / MibCard.loginTag, e.g. "bml_". */ + fun loginKey(bank: String, loginId: String) = "${bank}_$loginId" + } + private val prefs = context.getSharedPreferences("credential_store", Context.MODE_PRIVATE) private val keyAlias = "basedbank_credential_key" private val transformation = "AES/GCM/NoPadding" @@ -23,6 +28,37 @@ class CredentialStore(context: Context) { data class FahipayCredentials(val idCard: String, val password: String) data class MfaisaCredentials(val msisdn: String, val pin: String) + // ── Cross-bank login order (keys are login tags, see loginKey) ────────── + + /** User-chosen order of all logins; logins not yet ordered (e.g. newly added) follow, bank by bank. */ + fun getLoginOrder(): List { + val all = getMibLoginIds().map { loginKey("mib", it) } + + getBmlLoginIds().map { loginKey("bml", it) } + + getFahipayLoginIds().map { loginKey("fahipay", it) } + + getMfaisaLoginIds().map { loginKey("mfaisa", it) } + val saved = try { + val arr = org.json.JSONArray(prefs.getString("login_order", null) ?: "[]") + (0 until arr.length()).map { arr.getString(it) } + } catch (_: Exception) { emptyList() } + return saved.filter { it in all } + all.filter { it !in saved } + } + + /** Position of a login tag in [getLoginOrder]; unknown tags sort last. */ + fun loginRank(): (String) -> Int { + val order = getLoginOrder() + return { tag -> order.indexOf(tag).let { if (it < 0) Int.MAX_VALUE else it } } + } + + /** Saves the combined order and keeps each bank's own login list in the same relative order. */ + fun setLoginOrder(keys: List) { + prefs.edit().putString("login_order", org.json.JSONArray(keys).toString()).apply() + fun idsFor(bank: String) = keys.filter { it.startsWith("${bank}_") }.map { it.removePrefix("${bank}_") } + setMibLoginIds(idsFor("mib")) + setBmlLoginIds(idsFor("bml")) + setFahipayLoginIds(idsFor("fahipay")) + setMfaisaLoginIds(idsFor("mfaisa")) + } + // ── MIB login credentials (multi-login, keyed by loginId = username) ───── fun getMibLoginIds(): List { diff --git a/app/src/main/res/drawable/ic_reorder_lines.xml b/app/src/main/res/drawable/ic_reorder_lines.xml new file mode 100644 index 0000000..a42abb7 --- /dev/null +++ b/app/src/main/res/drawable/ic_reorder_lines.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/values-b+dv/strings.xml b/app/src/main/res/values-b+dv/strings.xml index 112eae3..ae4ff7a 100644 --- a/app/src/main/res/values-b+dv/strings.xml +++ b/app/src/main/res/values-b+dv/strings.xml @@ -145,4 +145,5 @@ އިންޓަނެޓް ބައްލަވާ، ދެން ތިޖޫރީ ލޯޑް ކުރޭ %s އާ ގުޅުމުގައި މައްސަލައެއް + ދަމާ ތަރުތީބު ބަދަލުކުރޭ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1d2177..96ab07e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -391,4 +391,5 @@ Please check your connection and reload Thijooree Connectivity issue with %s + Drag to reorder