diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt index b8a0d78..c96df39 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt @@ -1,26 +1,28 @@ package sh.sar.basedbank.ui.home +import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Bundle import android.util.Base64 import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.PopupMenu import androidx.core.os.bundleOf import androidx.core.widget.addTextChangedListener import androidx.fragment.app.activityViewModels import androidx.fragment.app.setFragmentResult import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.R import sh.sar.basedbank.api.mib.MibContactsClient -import android.widget.PopupMenu import sh.sar.basedbank.databinding.SheetContactPickerBinding import sh.sar.basedbank.util.RecentsCache @@ -30,106 +32,148 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { private val binding get() = _binding!! private val viewModel: HomeViewModel by activityViewModels() - private lateinit var adapter: ContactPickerAdapter - // null = All, RECENTS_TAG = Recents, else = category id - private var activeCategoryId: String? = RECENTS_TAG private val pendingHashes = mutableSetOf() private val profileImageHashes = mutableSetOf() private val app get() = requireActivity().application as BasedBankApp private val session get() = app.mibSession + private var fromAccountNumber: String = "" + private var mediator: TabLayoutMediator? = null + private lateinit var pagerAdapter: PickerPagerAdapter + + private data class TabDef(val tag: String?, val label: String) + + private inner class PickerPagerAdapter(val pages: List) : + RecyclerView.Adapter() { + + val pageAdapters: List = pages.mapIndexed { i, page -> + ContactPickerAdapter( + onItemClick = { accountNumber, label -> handlePickerSelection(accountNumber, label) }, + onSameAsFrom = {}, + onImageNeeded = { hash -> fetchImage(hash) }, + onItemLongClick = { accountNumber, anchor -> + if (page.tag == RECENTS_TAG) { + val menu = PopupMenu(requireContext(), anchor) + menu.menu.add(getString(R.string.recents_remove)) + menu.setOnMenuItemClickListener { + RecentsCache.remove(requireContext(), accountNumber) + rebuildPage(i) + true + } + menu.show() + true + } else false + } + ) + } + + fun rebuildAll() = pages.indices.forEach { rebuildPage(it) } + + fun rebuildPage(position: Int) { + pageAdapters[position].submitList(buildPageItems(pages[position].tag)) + } + + fun updateImage(hash: String, bitmap: Bitmap) = + pageAdapters.forEach { it.updateImage(hash, bitmap) } + + inner class PageHolder(val rv: RecyclerView) : RecyclerView.ViewHolder(rv) + + override fun getItemCount() = pages.size + override fun getItemViewType(position: Int) = position + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageHolder { + val rv = RecyclerView(parent.context).apply { + layoutManager = LinearLayoutManager(context) + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + clipToPadding = false + val p24 = (24 * resources.displayMetrics.density).toInt() + setPadding(0, 0, 0, p24) + adapter = pageAdapters[viewType] + } + return PageHolder(rv) + } + + override fun onBindViewHolder(holder: PageHolder, position: Int) { + rebuildPage(position) + } + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = SheetContactPickerBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val selectedAccountNumber = arguments?.getString(ARG_FROM_ACCOUNT) ?: "" + fromAccountNumber = arguments?.getString(ARG_FROM_ACCOUNT) ?: "" - adapter = ContactPickerAdapter( - onItemClick = { accountNumber, label -> - val contacts = viewModel.contacts.value ?: emptyList() - val accounts = viewModel.accounts.value ?: emptyList() - val contact = contacts.firstOrNull { it.benefAccount == accountNumber } - val account = accounts.firstOrNull { it.accountNumber == accountNumber } - val bundle = bundleOf(KEY_ACCOUNT_NUMBER to accountNumber, KEY_LABEL to label) - when { - account?.profileType == "BML_PREPAID" -> { - bundle.putBoolean(KEY_SKIP_LOOKUP, true) - bundle.putString(KEY_SUBTITLE, "${account.accountNumber} · ${account.currencyName} ${account.availableBalance}") - bundle.putString(KEY_COLOR, "#FE860E") - } - contact != null && !contact.transferCyDesc.equals("MVR", ignoreCase = true) -> { - bundle.putBoolean(KEY_SKIP_LOOKUP, true) - bundle.putString(KEY_SUBTITLE, "${contact.benefBankName} · ${contact.benefAccount}") - bundle.putString(KEY_COLOR, contact.bankColor) - contact.customerImgHash?.let { bundle.putString(KEY_IMAGE_HASH, it) } - } - } - setFragmentResult(REQUEST_KEY, bundle) - dismiss() - }, - onSameAsFrom = {}, - onImageNeeded = { hash -> fetchImage(hash) }, - onItemLongClick = { accountNumber, anchor -> - if (activeCategoryId == RECENTS_TAG) { - val menu = PopupMenu(requireContext(), anchor) - menu.menu.add(getString(R.string.recents_remove)) - menu.setOnMenuItemClickListener { - RecentsCache.remove(requireContext(), accountNumber) - rebuildList(selectedAccountNumber) - true - } - menu.show() - true - } else false - } + val initialPages = listOf( + TabDef(RECENTS_TAG, getString(R.string.contacts_tab_recents)), + TabDef(MY_ACCOUNTS_TAG, getString(R.string.transfer_my_accounts)), + TabDef(null, getString(R.string.contacts_tab_all)) ) + pagerAdapter = PickerPagerAdapter(initialPages) + binding.viewPager.adapter = pagerAdapter + binding.viewPager.offscreenPageLimit = 1 + attachMediator(initialPages) - binding.sheetRecyclerView.layoutManager = LinearLayoutManager(requireContext()) - binding.sheetRecyclerView.adapter = adapter - - binding.etSheetSearch.addTextChangedListener { rebuildList(selectedAccountNumber) } - - // Tabs: Recents | My Accounts | All | - binding.sheetCategoryTabs.addTab( - binding.sheetCategoryTabs.newTab().setText(R.string.contacts_tab_recents).apply { tag = RECENTS_TAG } - ) - binding.sheetCategoryTabs.addTab( - binding.sheetCategoryTabs.newTab().setText(R.string.transfer_my_accounts).apply { tag = MY_ACCOUNTS_TAG } - ) - binding.sheetCategoryTabs.addTab( - binding.sheetCategoryTabs.newTab().setText(R.string.contacts_tab_all).apply { tag = null } - ) - binding.sheetCategoryTabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - activeCategoryId = tab.tag as? String - rebuildList(selectedAccountNumber) - } - override fun onTabUnselected(tab: TabLayout.Tab) {} - override fun onTabReselected(tab: TabLayout.Tab) {} - }) + binding.etSheetSearch.addTextChangedListener { pagerAdapter.rebuildAll() } viewModel.contactCategories.observe(viewLifecycleOwner) { cats -> - // Remove all tabs after "All" (index 2) and re-add categories - while (binding.sheetCategoryTabs.tabCount > 3) binding.sheetCategoryTabs.removeTabAt(3) - for (cat in cats) { - binding.sheetCategoryTabs.addTab( - binding.sheetCategoryTabs.newTab().setText(cat.categoryName).apply { tag = cat.id } - ) + val pages = buildList { + add(TabDef(RECENTS_TAG, getString(R.string.contacts_tab_recents))) + add(TabDef(MY_ACCOUNTS_TAG, getString(R.string.transfer_my_accounts))) + add(TabDef(null, getString(R.string.contacts_tab_all))) + cats.forEach { add(TabDef(it.id, it.categoryName)) } } - rebuildList(selectedAccountNumber) + val savedPosition = binding.viewPager.currentItem + pagerAdapter = PickerPagerAdapter(pages) + binding.viewPager.adapter = pagerAdapter + attachMediator(pages) + binding.viewPager.setCurrentItem(savedPosition.coerceIn(0, pages.size - 1), false) } - viewModel.contacts.observe(viewLifecycleOwner) { rebuildList(selectedAccountNumber) } - viewModel.accounts.observe(viewLifecycleOwner) { rebuildList(selectedAccountNumber) } + viewModel.contacts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() } + viewModel.accounts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() } } - private fun rebuildList(fromNumber: String) { + private fun attachMediator(pages: List) { + mediator?.detach() + mediator = TabLayoutMediator(binding.sheetCategoryTabs, binding.viewPager) { tab, position -> + tab.text = pages[position].label + }.also { it.attach() } + } + + private fun handlePickerSelection(accountNumber: String, label: String) { + val contacts = viewModel.contacts.value ?: emptyList() + val accounts = viewModel.accounts.value ?: emptyList() + val contact = contacts.firstOrNull { it.benefAccount == accountNumber } + val account = accounts.firstOrNull { it.accountNumber == accountNumber } + val bundle = bundleOf(KEY_ACCOUNT_NUMBER to accountNumber, KEY_LABEL to label) + when { + account?.profileType == "BML_PREPAID" -> { + bundle.putBoolean(KEY_SKIP_LOOKUP, true) + bundle.putString(KEY_SUBTITLE, "${account.accountNumber} · ${account.currencyName} ${account.availableBalance}") + bundle.putString(KEY_COLOR, "#FE860E") + } + contact != null && !contact.transferCyDesc.equals("MVR", ignoreCase = true) -> { + bundle.putBoolean(KEY_SKIP_LOOKUP, true) + bundle.putString(KEY_SUBTITLE, "${contact.benefBankName} · ${contact.benefAccount}") + bundle.putString(KEY_COLOR, contact.bankColor) + contact.customerImgHash?.let { bundle.putString(KEY_IMAGE_HASH, it) } + } + } + setFragmentResult(REQUEST_KEY, bundle) + dismiss() + } + + private fun buildPageItems(tabTag: String?): List { val search = binding.etSheetSearch.text?.toString()?.trim() ?: "" val items = mutableListOf() - if (activeCategoryId == RECENTS_TAG) { + if (tabTag == RECENTS_TAG) { val recents = RecentsCache.load(requireContext()) val filtered = if (search.isBlank()) recents else recents.filter { it.displayName.contains(search, ignoreCase = true) || it.accountNumber.contains(search) @@ -138,25 +182,24 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { if (r.isProfileImage && r.imageHash != null) profileImageHashes.add(r.imageHash) items.add(ContactPickerAdapter.PickerItem.Row( accountNumber = r.accountNumber, - displayName = r.displayName, - subtitle = r.subtitle, - colorHex = r.colorHex, - isSameAsFrom = r.accountNumber == fromNumber, - imageHash = r.imageHash + displayName = r.displayName, + subtitle = r.subtitle, + colorHex = r.colorHex, + isSameAsFrom = r.accountNumber == fromAccountNumber, + imageHash = r.imageHash )) } - adapter.submitList(items) - return + return items } val accounts = viewModel.accounts.value ?: emptyList() val contacts = viewModel.contacts.value ?: emptyList() - val fromAccount = accounts.find { it.accountNumber == fromNumber } + val fromAccount = accounts.find { it.accountNumber == fromAccountNumber } val fromCurrency = fromAccount?.currencyName ?: "" val fromLoginTag = fromAccount?.loginTag ?: "" val fromIsCard = fromAccount?.profileType == "BML_PREPAID" - if (activeCategoryId == MY_ACCOUNTS_TAG) { + if (tabTag == MY_ACCOUNTS_TAG) { val regularAccounts = accounts.filter { it.profileType != "BML_PREPAID" } val cards = accounts.filter { it.profileType == "BML_PREPAID" } @@ -167,14 +210,14 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { items.add(ContactPickerAdapter.PickerItem.Header(getString(R.string.accounts))) for (acc in filteredRegular) { if (acc.profileImageHash != null) profileImageHashes.add(acc.profileImageHash) - val isSame = acc.accountNumber == fromNumber + val isSame = acc.accountNumber == fromAccountNumber items.add(ContactPickerAdapter.PickerItem.Row( accountNumber = acc.accountNumber, - displayName = acc.accountBriefName, - subtitle = "${acc.accountNumber} · ${acc.currencyName} ${acc.availableBalance}", - colorHex = "#FE860E", - isSameAsFrom = isSame, - imageHash = acc.profileImageHash, + displayName = acc.accountBriefName, + subtitle = "${acc.accountNumber} · ${acc.currencyName} ${acc.availableBalance}", + colorHex = "#FE860E", + isSameAsFrom = isSame, + imageHash = acc.profileImageHash, inactiveReason = if (isSame) null else if (fromIsCard && acc.loginTag != fromLoginTag) "Cards can only be used within the same BML account" else currencyMismatchReason(fromCurrency, acc.currencyName) @@ -189,15 +232,15 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { items.add(ContactPickerAdapter.PickerItem.Header(getString(R.string.cards))) for (acc in filteredCards) { if (acc.profileImageHash != null) profileImageHashes.add(acc.profileImageHash) - val isSame = acc.accountNumber == fromNumber + val isSame = acc.accountNumber == fromAccountNumber val isActive = acc.statusDesc.equals("Active", ignoreCase = true) items.add(ContactPickerAdapter.PickerItem.Row( accountNumber = acc.accountNumber, - displayName = acc.accountBriefName, - subtitle = "${acc.accountNumber} · ${acc.currencyName} ${acc.availableBalance}", - colorHex = "#FE860E", - isSameAsFrom = isSame, - imageHash = acc.profileImageHash, + displayName = acc.accountBriefName, + subtitle = "${acc.accountNumber} · ${acc.currencyName} ${acc.availableBalance}", + colorHex = "#FE860E", + isSameAsFrom = isSame, + imageHash = acc.profileImageHash, inactiveReason = if (isSame) null else if (!isActive) acc.statusDesc else if (acc.loginTag != fromLoginTag) "Cards can only be used within the same BML account" @@ -205,35 +248,29 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { )) } } - - adapter.submitList(items) - return + return items } val filteredContacts = contacts.filter { contact -> - val matchesCat = activeCategoryId == null || contact.benefCategoryId == activeCategoryId + val matchesCat = tabTag == null || contact.benefCategoryId == tabTag val matchesSearch = search.isBlank() || contact.benefNickName.contains(search, ignoreCase = true) || contact.benefName.contains(search, ignoreCase = true) || contact.benefAccount.contains(search) matchesCat && matchesSearch } - - if (filteredContacts.isNotEmpty()) { - for (contact in filteredContacts) { - items.add(ContactPickerAdapter.PickerItem.Row( - accountNumber = contact.benefAccount, - displayName = contact.benefNickName, - subtitle = "${contact.benefBankName} · ${contact.benefAccount}", - colorHex = contact.bankColor, - isSameAsFrom = contact.benefAccount == fromNumber, - imageHash = contact.customerImgHash, - inactiveReason = currencyMismatchReason(fromCurrency, contact.transferCyDesc) - )) - } + for (contact in filteredContacts) { + items.add(ContactPickerAdapter.PickerItem.Row( + accountNumber = contact.benefAccount, + displayName = contact.benefNickName, + subtitle = "${contact.benefBankName} · ${contact.benefAccount}", + colorHex = contact.bankColor, + isSameAsFrom = contact.benefAccount == fromAccountNumber, + imageHash = contact.customerImgHash, + inactiveReason = currencyMismatchReason(fromCurrency, contact.transferCyDesc) + )) } - - adapter.submitList(items) + return items } private fun fetchImage(hash: String) { @@ -249,7 +286,7 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { val bytes = Base64.decode(base64, Base64.DEFAULT) val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return@launch withContext(Dispatchers.Main) { - adapter.updateImage(hash, bitmap) + pagerAdapter.updateImage(hash, bitmap) } } catch (_: Exception) { pendingHashes.remove(hash) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/ContactsAdapter.kt b/app/src/main/java/sh/sar/basedbank/ui/home/ContactsAdapter.kt index fd8b0aa..dcf0eac 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/ContactsAdapter.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/ContactsAdapter.kt @@ -120,6 +120,8 @@ class ContactsAdapter( ) } + binding.tvRealName.text = contact.benefName + val vis = if (expanded) View.VISIBLE else View.GONE binding.dividerExpand.visibility = vis binding.expandedSection.visibility = vis diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt index edb8d24..9d0b671 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt @@ -1,5 +1,6 @@ package sh.sar.basedbank.ui.home +import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Bundle import android.util.Base64 @@ -13,7 +14,8 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.tabs.TabLayout +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.tabs.TabLayoutMediator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -31,14 +33,65 @@ class ContactsFragment : Fragment() { private var _binding: FragmentContactsBinding? = null private val binding get() = _binding!! private val viewModel: HomeViewModel by activityViewModels() - private lateinit var adapter: ContactsAdapter private val pendingHashes = mutableSetOf() private val app get() = requireActivity().application as BasedBankApp private val session get() = app.mibSession - private var categories: List = emptyList() - private var activeCategoryId: String? = null + private var allContacts: List = emptyList() + private var currentSearch: String = "" + private var mediator: TabLayoutMediator? = null + private lateinit var pagerAdapter: ContactsPagerAdapter + + private data class TabPage(val categoryId: String?, val label: String) + + private inner class ContactsPagerAdapter(val pages: List) : + RecyclerView.Adapter() { + + private val density get() = resources.displayMetrics.density + val contactAdapters: List = pages.map { page -> + ContactsAdapter( + onImageNeeded = { hash -> fetchImage(hash) }, + onDeleteClick = { contact -> confirmDelete(contact) }, + onTransferClick = { contact -> openTransfer(contact) } + ).also { a -> + a.setFilter(page.categoryId, currentSearch) + a.updateContacts(allContacts) + } + } + + fun updateContacts(contacts: List) = + contactAdapters.forEach { it.updateContacts(contacts) } + + fun updateSearch(query: String) = + pages.forEachIndexed { i, page -> contactAdapters[i].setFilter(page.categoryId, query) } + + fun updateImage(hash: String, bitmap: Bitmap) = + contactAdapters.forEach { it.updateImage(hash, bitmap) } + + inner class PageHolder(val rv: RecyclerView) : RecyclerView.ViewHolder(rv) + + override fun getItemCount() = pages.size + override fun getItemViewType(position: Int) = position + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageHolder { + val rv = RecyclerView(parent.context).apply { + layoutManager = LinearLayoutManager(context) + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + clipToPadding = false + val p4 = (4 * density).toInt() + val p80 = (80 * density).toInt() + setPadding(0, p4, 0, p80) + adapter = contactAdapters[viewType] + } + return PageHolder(rv) + } + + override fun onBindViewHolder(holder: PageHolder, position: Int) {} + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentContactsBinding.inflate(inflater, container, false) @@ -46,74 +99,61 @@ class ContactsFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - 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.adapter = adapter + pagerAdapter = ContactsPagerAdapter(listOf(TabPage(null, getString(R.string.contacts_tab_all)))) + binding.viewPager.adapter = pagerAdapter + binding.viewPager.offscreenPageLimit = 1 + + attachMediator(pagerAdapter.pages) binding.etSearch.addTextChangedListener { text -> - adapter.setFilter(activeCategoryId, text?.toString() ?: "") + currentSearch = text?.toString() ?: "" + pagerAdapter.updateSearch(currentSearch) } - binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - activeCategoryId = tab.tag as? String - adapter.setFilter(activeCategoryId, binding.etSearch.text?.toString() ?: "") - } - override fun onTabUnselected(tab: TabLayout.Tab) {} - override fun onTabReselected(tab: TabLayout.Tab) {} - }) - binding.fabAddContact.setOnClickListener { AddContactSheetFragment().show(childFragmentManager, "add_contact") } viewModel.contactCategories.observe(viewLifecycleOwner) { cats -> - categories = cats - rebuildTabs(cats) + rebuildPager(cats) } viewModel.contacts.observe(viewLifecycleOwner) { contacts -> - adapter.updateContacts(contacts) - binding.recyclerView.visibility = if (contacts.isEmpty()) View.GONE else View.VISIBLE + allContacts = contacts + pagerAdapter.updateContacts(contacts) binding.emptyView.visibility = if (contacts.isEmpty()) View.VISIBLE else View.GONE binding.loadingView.visibility = View.GONE } } - private fun rebuildTabs(cats: List) { - binding.tabLayout.clearOnTabSelectedListeners() - binding.tabLayout.removeAllTabs() + private fun attachMediator(pages: List) { + mediator?.detach() + mediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = pages[position].label + }.also { it.attach() } + } - binding.tabLayout.addTab( - binding.tabLayout.newTab().setText(R.string.contacts_tab_all).apply { tag = null } - ) - for (cat in cats) { - binding.tabLayout.addTab( - binding.tabLayout.newTab().setText(cat.categoryName).apply { tag = cat.id } - ) + private fun rebuildPager(cats: List) { + val pages = buildList { + add(TabPage(null, getString(R.string.contacts_tab_all))) + cats.forEach { add(TabPage(it.id, it.categoryName)) } } + val savedPosition = binding.viewPager.currentItem + pagerAdapter = ContactsPagerAdapter(pages) + binding.viewPager.adapter = pagerAdapter + attachMediator(pages) + binding.viewPager.setCurrentItem(savedPosition.coerceIn(0, pages.size - 1), false) + } - binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - activeCategoryId = tab.tag as? String - adapter.setFilter(activeCategoryId, binding.etSearch.text?.toString() ?: "") - } - override fun onTabUnselected(tab: TabLayout.Tab) {} - override fun onTabReselected(tab: TabLayout.Tab) {} - }) + private fun openTransfer(contact: MibBeneficiary) { + 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) } private fun confirmDelete(contact: MibBeneficiary) { @@ -179,7 +219,7 @@ class ContactsFragment : Fragment() { val base64 = client.fetchProfileImageBase64(sess, hash) ?: return@launch val bytes = Base64.decode(base64, Base64.DEFAULT) val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return@launch - withContext(Dispatchers.Main) { adapter.updateImage(hash, bitmap) } + withContext(Dispatchers.Main) { pagerAdapter.updateImage(hash, bitmap) } } catch (_: Exception) { pendingHashes.remove(hash) } diff --git a/app/src/main/res/layout/fragment_contacts.xml b/app/src/main/res/layout/fragment_contacts.xml index b200de3..47e29a9 100644 --- a/app/src/main/res/layout/fragment_contacts.xml +++ b/app/src/main/res/layout/fragment_contacts.xml @@ -46,13 +46,10 @@ android:layout_height="0dp" android:layout_weight="1"> - + android:layout_height="match_parent" /> + + - + android:layout_height="400dp" />