delete and transfer contacts

This commit is contained in:
2026-05-14 05:45:51 +05:00
parent 5805b4cb51
commit 4c91a1aa0e
10 changed files with 348 additions and 69 deletions
@@ -311,6 +311,22 @@ class BmlLoginFlow {
return parseContacts(json) return parseContacts(json)
} }
fun deleteContact(session: BmlSession, contactId: String): Boolean {
val body = """{"_method":"delete"}""".toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("$BASE_URL/api/mobile/contacts/$contactId")
.post(body)
.header("Authorization", "Bearer ${session.accessToken}")
.header("User-Agent", APP_USER_AGENT)
.header("x-app-version", APP_VERSION)
.header("accept", "application/json")
.build()
return apiClient.newCall(request).execute().use { response ->
val bodyStr = response.body?.string() ?: return@use false
try { JSONObject(bodyStr).optBoolean("success") } catch (_: Exception) { false }
}
}
private fun apiRequest(session: BmlSession, url: String) = private fun apiRequest(session: BmlSession, url: String) =
Request.Builder().url(url) Request.Builder().url(url)
.header("Authorization", "Bearer ${session.accessToken}") .header("Authorization", "Bearer ${session.accessToken}")
@@ -182,6 +182,19 @@ class MibContactsClient {
} }
} }
fun deleteContact(session: MibSession, benefNo: String): Boolean {
val body = FormBody.Builder().add("benefNo", benefNo).build()
val request = Request.Builder()
.url("$BASE_WV_URL/ajaxBeneficiary/deleteBeneficiary")
.post(body)
.withSessionHeaders(session)
.build()
return client.newCall(request).execute().use { response ->
val bodyStr = response.body?.string() ?: return@use false
try { JSONObject(bodyStr).optBoolean("success") } catch (_: Exception) { false }
}
}
fun fetchProfileImageBase64(session: MibSession, imageHash: String): String? { fun fetchProfileImageBase64(session: MibSession, imageHash: String): String? {
val body = FormBody.Builder() val body = FormBody.Builder()
.add("imageHash", imageHash) .add("imageHash", imageHash)
@@ -1,28 +1,38 @@
package sh.sar.basedbank.ui.home package sh.sar.basedbank.ui.home
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import sh.sar.basedbank.R
import sh.sar.basedbank.api.mib.MibBeneficiary import sh.sar.basedbank.api.mib.MibBeneficiary
import sh.sar.basedbank.databinding.ItemContactBinding import sh.sar.basedbank.databinding.ItemContactBinding
class ContactsAdapter( class ContactsAdapter(
private val onImageNeeded: (hash: String) -> Unit private val onImageNeeded: (hash: String) -> Unit,
private val onDeleteClick: (MibBeneficiary) -> Unit,
private val onTransferClick: (MibBeneficiary) -> Unit
) : RecyclerView.Adapter<ContactsAdapter.ViewHolder>() { ) : RecyclerView.Adapter<ContactsAdapter.ViewHolder>() {
private var allContacts: List<MibBeneficiary> = emptyList() private var allContacts: List<MibBeneficiary> = emptyList()
private var displayed: List<MibBeneficiary> = emptyList() private var displayed: List<MibBeneficiary> = emptyList()
private val imageCache = mutableMapOf<String, Bitmap>() private val imageCache = mutableMapOf<String, Bitmap>()
private val expandedPositions = mutableSetOf<Int>()
private var activeCategoryId: String? = null private var activeCategoryId: String? = null
private var searchQuery: String = "" private var searchQuery: String = ""
fun updateContacts(contacts: List<MibBeneficiary>) { fun updateContacts(contacts: List<MibBeneficiary>) {
allContacts = contacts allContacts = contacts
expandedPositions.clear()
applyFilter() applyFilter()
} }
@@ -36,6 +46,7 @@ class ContactsAdapter(
fun setFilter(categoryId: String?, query: String) { fun setFilter(categoryId: String?, query: String) {
activeCategoryId = categoryId activeCategoryId = categoryId
searchQuery = query searchQuery = query
expandedPositions.clear()
applyFilter() applyFilter()
} }
@@ -61,15 +72,42 @@ class ContactsAdapter(
val cachedImage = contact.customerImgHash?.let { hash -> val cachedImage = contact.customerImgHash?.let { hash ->
imageCache[hash] ?: run { onImageNeeded(hash); null } imageCache[hash] ?: run { onImageNeeded(hash); null }
} }
holder.bind(contact, cachedImage) holder.bind(contact, cachedImage, position in expandedPositions)
holder.binding.root.setOnClickListener {
val pos = holder.bindingAdapterPosition
if (pos == RecyclerView.NO_POSITION) return@setOnClickListener
if (pos in expandedPositions) expandedPositions.remove(pos) else expandedPositions.add(pos)
notifyItemChanged(pos)
}
holder.binding.root.setOnLongClickListener {
val ctx = it.context
val clipboard = ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText("account", contact.benefAccount))
Toast.makeText(ctx, contact.benefAccount, Toast.LENGTH_SHORT).show()
true
}
holder.binding.btnTransferContact.setOnClickListener {
onTransferClick(contact)
}
holder.binding.btnEditContact.setOnClickListener {
Toast.makeText(it.context, R.string.work_in_progress, Toast.LENGTH_SHORT).show()
}
holder.binding.btnDeleteContact.setOnClickListener {
onDeleteClick(contact)
}
} }
override fun getItemCount() = displayed.size override fun getItemCount() = displayed.size
inner class ViewHolder(private val binding: ItemContactBinding) : inner class ViewHolder(val binding: ItemContactBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bind(contact: MibBeneficiary, photo: Bitmap?) { fun bind(contact: MibBeneficiary, photo: Bitmap?, expanded: Boolean) {
binding.tvContactName.text = contact.benefNickName binding.tvContactName.text = contact.benefNickName
binding.tvContactBank.text = contact.benefBankName binding.tvContactBank.text = contact.benefBankName
binding.tvContactAccount.text = "${contact.benefAccount} · ${contact.transferCyDesc}" binding.tvContactAccount.text = "${contact.benefAccount} · ${contact.transferCyDesc}"
@@ -81,6 +119,10 @@ class ContactsAdapter(
makeInitialsBitmap(contact.benefNickName, contact.bankColor) makeInitialsBitmap(contact.benefNickName, contact.bankColor)
) )
} }
val vis = if (expanded) View.VISIBLE else View.GONE
binding.dividerExpand.visibility = vis
binding.expandedSection.visibility = vis
} }
private fun makeInitialsBitmap(name: String, colorHex: String): Bitmap { private fun makeInitialsBitmap(name: String, colorHex: String): Bitmap {
@@ -88,22 +130,17 @@ class ContactsAdapter(
.getDimensionPixelSize(android.R.dimen.app_icon_size) .getDimensionPixelSize(android.R.dimen.app_icon_size)
.coerceAtLeast(96) .coerceAtLeast(96)
val bgColor = try { Color.parseColor(colorHex) } catch (e: Exception) { Color.GRAY } val bgColor = try { Color.parseColor(colorHex) } catch (e: Exception) { Color.GRAY }
val bm = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888) val bm = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bm) val canvas = Canvas(bm)
val paint = Paint(Paint.ANTI_ALIAS_FLAG) val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = bgColor paint.color = bgColor
canvas.drawCircle(sizePx / 2f, sizePx / 2f, sizePx / 2f, paint) canvas.drawCircle(sizePx / 2f, sizePx / 2f, sizePx / 2f, paint)
paint.color = Color.WHITE paint.color = Color.WHITE
paint.textSize = sizePx * 0.42f paint.textSize = sizePx * 0.42f
paint.textAlign = Paint.Align.CENTER paint.textAlign = Paint.Align.CENTER
val letter = name.firstOrNull()?.uppercaseChar()?.toString() ?: "?" val letter = name.firstOrNull()?.uppercaseChar()?.toString() ?: "?"
val metrics = paint.fontMetrics val metrics = paint.fontMetrics
val textY = sizePx / 2f - (metrics.ascent + metrics.descent) / 2f canvas.drawText(letter, sizePx / 2f, sizePx / 2f - (metrics.ascent + metrics.descent) / 2f, paint)
canvas.drawText(letter, sizePx / 2f, textY, paint)
return bm return bm
} }
} }
@@ -6,6 +6,8 @@ import android.util.Base64
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@@ -17,9 +19,12 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.R import sh.sar.basedbank.R
import sh.sar.basedbank.api.bml.BmlLoginFlow
import sh.sar.basedbank.api.mib.MibBeneficiary
import sh.sar.basedbank.api.mib.MibBeneficiaryCategory import sh.sar.basedbank.api.mib.MibBeneficiaryCategory
import sh.sar.basedbank.api.mib.MibContactsClient import sh.sar.basedbank.api.mib.MibContactsClient
import sh.sar.basedbank.databinding.FragmentContactsBinding import sh.sar.basedbank.databinding.FragmentContactsBinding
import sh.sar.basedbank.util.ContactsCache
class ContactsFragment : Fragment() { class ContactsFragment : Fragment() {
@@ -29,10 +34,11 @@ class ContactsFragment : Fragment() {
private lateinit var adapter: ContactsAdapter private lateinit var adapter: ContactsAdapter
private val pendingHashes = mutableSetOf<String>() private val pendingHashes = mutableSetOf<String>()
private val session get() = (requireActivity().application as BasedBankApp).mibSession private val app get() = requireActivity().application as BasedBankApp
private val session get() = app.mibSession
private var categories: List<MibBeneficiaryCategory> = emptyList() private var categories: List<MibBeneficiaryCategory> = emptyList()
private var activeCategoryId: String? = null // null = All private var activeCategoryId: String? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentContactsBinding.inflate(inflater, container, false) _binding = FragmentContactsBinding.inflate(inflater, container, false)
@@ -40,7 +46,20 @@ class ContactsFragment : Fragment() {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = ContactsAdapter { hash -> fetchImage(hash) } adapter = ContactsAdapter(
onImageNeeded = { hash -> fetchImage(hash) },
onDeleteClick = { contact -> confirmDelete(contact) },
onTransferClick = { contact ->
val fragment = TransferFragment.newInstance(
accountNumber = contact.benefAccount,
displayName = contact.benefNickName,
subtitle = "${contact.benefBankName} · ${contact.benefAccount}",
colorHex = contact.bankColor,
imageHash = contact.customerImgHash
)
(requireActivity() as HomeActivity).showWithBackStack(fragment)
}
)
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
@@ -97,6 +116,60 @@ class ContactsFragment : Fragment() {
}) })
} }
private fun confirmDelete(contact: MibBeneficiary) {
AlertDialog.Builder(requireContext())
.setTitle(R.string.contact_delete_title)
.setMessage(getString(R.string.contact_delete_message, contact.benefNickName))
.setPositiveButton(R.string.contact_delete) { _, _ -> deleteContact(contact) }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun deleteContact(contact: MibBeneficiary) {
viewLifecycleOwner.lifecycleScope.launch {
val success = withContext(Dispatchers.IO) {
if (contact.benefCategoryId == "BML") deleteBml(contact) else deleteMib(contact)
}
if (success) {
Toast.makeText(requireContext(), R.string.contact_deleted, Toast.LENGTH_SHORT).show()
removeFromViewModel(contact)
} else {
Toast.makeText(requireContext(), R.string.contact_delete_failed, Toast.LENGTH_SHORT).show()
}
}
}
private fun deleteBml(contact: MibBeneficiary): Boolean {
val sess = app.bmlSession ?: return false
val contactId = contact.benefNo.removePrefix("bml_")
return try { BmlLoginFlow().deleteContact(sess, contactId) } catch (_: Exception) { false }
}
private fun deleteMib(contact: MibBeneficiary): Boolean {
val sess = session ?: return false
return try {
if (contact.profileId.isNotBlank()) {
val profile = app.mibProfiles.firstOrNull { it.profileId == contact.profileId }
if (profile != null) app.mibLoginFlow.switchProfile(sess, profile)
}
MibContactsClient().deleteContact(sess, contact.benefNo)
} catch (_: Exception) { false }
}
private fun removeFromViewModel(contact: MibBeneficiary) {
val updated = viewModel.contacts.value?.filter { it.benefNo != contact.benefNo } ?: return
viewModel.contacts.value = updated
if (contact.benefCategoryId == "BML") {
ContactsCache.saveBml(requireContext(), updated.filter { it.benefCategoryId == "BML" })
} else {
ContactsCache.save(
requireContext(),
updated.filter { it.benefCategoryId != "BML" },
viewModel.contactCategories.value ?: emptyList()
)
}
}
private fun fetchImage(hash: String) { private fun fetchImage(hash: String) {
if (!pendingHashes.add(hash)) return if (!pendingHashes.add(hash)) return
val sess = session ?: return val sess = session ?: return
@@ -106,11 +179,9 @@ class ContactsFragment : Fragment() {
val base64 = client.fetchProfileImageBase64(sess, hash) ?: return@launch val base64 = client.fetchProfileImageBase64(sess, hash) ?: return@launch
val bytes = Base64.decode(base64, Base64.DEFAULT) val bytes = Base64.decode(base64, Base64.DEFAULT)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return@launch val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return@launch
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) { adapter.updateImage(hash, bitmap) }
adapter.updateImage(hash, bitmap)
}
} catch (_: Exception) { } catch (_: Exception) {
pendingHashes.remove(hash) // allow retry pendingHashes.remove(hash)
} }
} }
} }
@@ -47,6 +47,30 @@ class TransferFragment : Fragment() {
private val session get() = (requireActivity().application as BasedBankApp).mibSession private val session get() = (requireActivity().application as BasedBankApp).mibSession
private val bmlSession get() = (requireActivity().application as BasedBankApp).bmlSession private val bmlSession get() = (requireActivity().application as BasedBankApp).bmlSession
companion object {
private const val ARG_ACCOUNT = "contact_account"
private const val ARG_NAME = "contact_name"
private const val ARG_SUBTITLE = "contact_subtitle"
private const val ARG_COLOR = "contact_color"
private const val ARG_IMAGE_HASH = "contact_image_hash"
fun newInstance(
accountNumber: String,
displayName: String,
subtitle: String,
colorHex: String,
imageHash: String?
) = TransferFragment().apply {
arguments = Bundle().apply {
putString(ARG_ACCOUNT, accountNumber)
putString(ARG_NAME, displayName)
putString(ARG_SUBTITLE, subtitle)
putString(ARG_COLOR, colorHex)
if (imageHash != null) putString(ARG_IMAGE_HASH, imageHash)
}
}
}
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
@@ -94,6 +118,17 @@ class TransferFragment : Fragment() {
binding.btnTransfer.setOnClickListener { binding.btnTransfer.setOnClickListener {
Toast.makeText(requireContext(), R.string.work_in_progress, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.work_in_progress, Toast.LENGTH_SHORT).show()
} }
// Pre-select contact if navigated from contacts page
arguments?.getString(ARG_ACCOUNT)?.let { account ->
prefillToDirectly(
accountNumber = account,
displayName = arguments?.getString(ARG_NAME) ?: account,
subtitle = arguments?.getString(ARG_SUBTITLE) ?: account,
colorHex = arguments?.getString(ARG_COLOR) ?: "#607D8B",
imageHash = arguments?.getString(ARG_IMAGE_HASH)
)
}
} }
private fun setupFromDropdown() { private fun setupFromDropdown() {
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M16,1H4C2.9,1 2,1.9 2,3v14h2V3h12V1zM19,5H8C6.9,5 6,5.9 6,7v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 19,5zM19,21H8V7h11V21z" />
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>
+121 -52
View File
@@ -1,63 +1,132 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="16dp" android:orientation="vertical">
android:paddingVertical="10dp">
<com.google.android.material.imageview.ShapeableImageView <!-- Header row — same look as before -->
android:id="@+id/ivContactPhoto" <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="48dp" android:layout_width="match_parent"
android:layout_height="48dp"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/tvContactName"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:paddingHorizontal="16dp"
android:textAppearance="?attr/textAppearanceBodyMedium" android:paddingVertical="10dp">
android:textStyle="bold"
android:textColor="?attr/colorOnSurface"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/ivContactPhoto"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/ivContactPhoto" />
<TextView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/tvContactBank" android:id="@+id/ivContactPhoto"
android:layout_width="0dp" android:layout_width="48dp"
android:layout_height="48dp"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/tvContactName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textStyle="bold"
android:textColor="?attr/colorOnSurface"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/ivContactPhoto"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/ivContactPhoto" />
<TextView
android:id="@+id/tvContactBank"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="2dp"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/ivContactPhoto"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvContactName" />
<TextView
android:id="@+id/tvContactAccount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="1dp"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/ivContactPhoto"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvContactBank"
app:layout_constraintBottom_toBottomOf="@id/ivContactPhoto" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Divider + expanded section, hidden by default -->
<View
android:id="@+id/dividerExpand"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="16dp"
android:background="?attr/colorOutlineVariant"
android:visibility="gone" />
<LinearLayout
android:id="@+id/expandedSection"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:orientation="vertical"
android:layout_marginTop="2dp" android:paddingHorizontal="16dp"
android:textAppearance="?attr/textAppearanceBodySmall" android:paddingTop="8dp"
android:textColor="?attr/colorOnSurfaceVariant" android:paddingBottom="8dp"
android:maxLines="1" android:visibility="gone">
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/ivContactPhoto"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvContactName" />
<TextView <!-- Action buttons -->
android:id="@+id/tvContactAccount" <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:orientation="horizontal">
android:layout_marginTop="1dp"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/ivContactPhoto"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvContactBank"
app:layout_constraintBottom_toBottomOf="@id/ivContactPhoto" />
</androidx.constraintlayout.widget.ConstraintLayout> <com.google.android.material.button.MaterialButton
android:id="@+id/btnTransferContact"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/transfer"
app:icon="@drawable/ic_nav_generic" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEditContact"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="6dp"
android:text="@string/contact_edit"
app:icon="@drawable/ic_edit" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDeleteContact"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="6dp"
android:text="@string/contact_delete"
android:textColor="?attr/colorError"
app:strokeColor="?attr/colorError"
app:icon="@drawable/ic_delete"
app:iconTint="?attr/colorError" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
+8
View File
@@ -140,6 +140,14 @@
<string name="contact_already_exists">Contact already exists: %s</string> <string name="contact_already_exists">Contact already exists: %s</string>
<string name="contact_own_account">Cannot save your own account as a contact</string> <string name="contact_own_account">Cannot save your own account as a contact</string>
<!-- Contact expand/delete -->
<string name="contact_edit">Edit</string>
<string name="contact_delete">Delete</string>
<string name="contact_delete_title">Delete Contact</string>
<string name="contact_delete_message">Remove %s from your contacts?</string>
<string name="contact_deleted">Contact deleted</string>
<string name="contact_delete_failed">Could not delete contact</string>
<!-- Financing --> <!-- Financing -->
<string name="financing_empty">No financing deals found</string> <string name="financing_empty">No financing deals found</string>
<string name="financing_total">Total</string> <string name="financing_total">Total</string>