implement viewing contacts
This commit is contained in:
155
app/src/main/java/sh/sar/basedbank/api/mib/MibContactsClient.kt
Normal file
155
app/src/main/java/sh/sar/basedbank/api/mib/MibContactsClient.kt
Normal file
@@ -0,0 +1,155 @@
|
||||
package sh.sar.basedbank.api.mib
|
||||
|
||||
import android.util.Log
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONObject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MibContactsClient {
|
||||
|
||||
private val TAG = "MibContactsClient"
|
||||
private val BASE_WV_URL = "https://faisamobilex-wv.mib.com.mv"
|
||||
|
||||
private val client = OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
private fun cookieHeader(session: MibSession) =
|
||||
"mbmodel=IOS-1.0; xxid=${session.xxid}; IBSID=${session.xxid}; " +
|
||||
"mbnonce=${session.nonceGenerator}; time-tracker=597"
|
||||
|
||||
private fun Request.Builder.withSessionHeaders(session: MibSession): Request.Builder = this
|
||||
.header("Cookie", cookieHeader(session))
|
||||
.header(
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (Linux; Android 14; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36"
|
||||
)
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.header("Accept", "*/*")
|
||||
.header("Origin", BASE_WV_URL)
|
||||
.header("Referer", "$BASE_WV_URL/beneficiary?dashurl=1")
|
||||
|
||||
fun fetchCategories(session: MibSession): List<MibBeneficiaryCategory> {
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_WV_URL/ajaxBeneficiary/getCategories")
|
||||
.post(FormBody.Builder().build())
|
||||
.withSessionHeaders(session)
|
||||
.build()
|
||||
|
||||
return client.newCall(request).execute().use { response ->
|
||||
Log.d(TAG, "getCategories: HTTP ${response.code}")
|
||||
if (!response.isSuccessful) return emptyList()
|
||||
parseCategories(response.body?.string() ?: return emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseCategories(json: String): List<MibBeneficiaryCategory> {
|
||||
return try {
|
||||
val arr = JSONObject(json).getJSONArray("data")
|
||||
(0 until arr.length()).map { i ->
|
||||
val o = arr.getJSONObject(i)
|
||||
MibBeneficiaryCategory(
|
||||
id = o.getString("id"),
|
||||
categoryName = o.getString("categoryName"),
|
||||
numBenef = o.optString("numBenef", "0").toIntOrNull() ?: 0
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "parseCategories error: $e")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchContacts(session: MibSession): List<MibBeneficiary> {
|
||||
val all = mutableListOf<MibBeneficiary>()
|
||||
var page = 1
|
||||
val pageSize = 100
|
||||
while (true) {
|
||||
val start = (page - 1) * pageSize + 1
|
||||
val end = page * pageSize
|
||||
val body = FormBody.Builder()
|
||||
.add("page", page.toString())
|
||||
.add("search", "")
|
||||
.add("searchCategoryId", "0")
|
||||
.add("benefType", "A")
|
||||
.add("sortBenef", "name")
|
||||
.add("sortDir", "asc")
|
||||
.add("start", start.toString())
|
||||
.add("end", end.toString())
|
||||
.add("includeCount", "1")
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_WV_URL/ajaxBeneficiary/main")
|
||||
.post(body)
|
||||
.withSessionHeaders(session)
|
||||
.build()
|
||||
|
||||
val (contacts, totalCount) = client.newCall(request).execute().use { response ->
|
||||
Log.d(TAG, "fetchContacts page $page: HTTP ${response.code}")
|
||||
if (!response.isSuccessful) return all
|
||||
parseContacts(response.body?.string() ?: return all)
|
||||
}
|
||||
all.addAll(contacts)
|
||||
if (all.size >= totalCount || contacts.isEmpty()) break
|
||||
page++
|
||||
}
|
||||
Log.d(TAG, "fetchContacts: loaded ${all.size} contacts")
|
||||
return all
|
||||
}
|
||||
|
||||
private fun parseContacts(json: String): Pair<List<MibBeneficiary>, Int> {
|
||||
return try {
|
||||
val obj = JSONObject(json)
|
||||
val totalCount = obj.optString("total_count", "0").toIntOrNull() ?: 0
|
||||
val arr = obj.getJSONArray("data")
|
||||
val contacts = (0 until arr.length()).map { i ->
|
||||
val o = arr.getJSONObject(i)
|
||||
val hash = o.optString("customerImgHash")
|
||||
MibBeneficiary(
|
||||
benefNo = o.optString("benefNo"),
|
||||
benefName = o.optString("benefName"),
|
||||
benefNickName = o.optString("benefNickName")
|
||||
.ifBlank { o.optString("benefName") },
|
||||
benefAccount = o.optString("benefAccount"),
|
||||
benefType = o.optString("benefType"),
|
||||
bankColor = o.optString("bankColor", "#888888"),
|
||||
benefBankName = o.optString("benefBankName"),
|
||||
bankCode = o.optString("bankCode"),
|
||||
benefStatus = o.optString("benefStatus"),
|
||||
transferCyDesc = o.optString("transferCyDesc", "MVR"),
|
||||
customerImgHash = hash.takeIf { it.isNotBlank() && it != "null" },
|
||||
benefCategoryId = o.optString("benefCategoryID", "0")
|
||||
)
|
||||
}
|
||||
Pair(contacts, totalCount)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "parseContacts error: $e")
|
||||
Pair(emptyList(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchProfileImageBase64(session: MibSession, imageHash: String): String? {
|
||||
val body = FormBody.Builder()
|
||||
.add("imageHash", imageHash)
|
||||
.build()
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_WV_URL/ajaxBeneficiary/getProfileImage")
|
||||
.post(body)
|
||||
.withSessionHeaders(session)
|
||||
.build()
|
||||
|
||||
return client.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) return null
|
||||
try {
|
||||
val json = response.body?.string() ?: return null
|
||||
JSONObject(json).optString("profileImage").takeIf { it.isNotBlank() }
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,27 @@ data class MibAccount(
|
||||
val statusDesc: String
|
||||
)
|
||||
|
||||
data class MibBeneficiaryCategory(
|
||||
val id: String,
|
||||
val categoryName: String,
|
||||
val numBenef: Int
|
||||
)
|
||||
|
||||
data class MibBeneficiary(
|
||||
val benefNo: String,
|
||||
val benefName: String,
|
||||
val benefNickName: String,
|
||||
val benefAccount: String,
|
||||
val benefType: String, // L=Local, I=Internal(MIB), S=Swift
|
||||
val bankColor: String,
|
||||
val benefBankName: String,
|
||||
val bankCode: String,
|
||||
val benefStatus: String,
|
||||
val transferCyDesc: String,
|
||||
val customerImgHash: String?,
|
||||
val benefCategoryId: String // "0" = uncategorized
|
||||
)
|
||||
|
||||
data class MibFinanceDeal(
|
||||
val dealNo: String,
|
||||
val productDesc: String,
|
||||
|
||||
110
app/src/main/java/sh/sar/basedbank/ui/home/ContactsAdapter.kt
Normal file
110
app/src/main/java/sh/sar/basedbank/ui/home/ContactsAdapter.kt
Normal file
@@ -0,0 +1,110 @@
|
||||
package sh.sar.basedbank.ui.home
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiary
|
||||
import sh.sar.basedbank.databinding.ItemContactBinding
|
||||
|
||||
class ContactsAdapter(
|
||||
private val onImageNeeded: (hash: String) -> Unit
|
||||
) : RecyclerView.Adapter<ContactsAdapter.ViewHolder>() {
|
||||
|
||||
private var allContacts: List<MibBeneficiary> = emptyList()
|
||||
private var displayed: List<MibBeneficiary> = emptyList()
|
||||
private val imageCache = mutableMapOf<String, Bitmap>()
|
||||
|
||||
private var activeCategoryId: String? = null
|
||||
private var searchQuery: String = ""
|
||||
|
||||
fun updateContacts(contacts: List<MibBeneficiary>) {
|
||||
allContacts = contacts
|
||||
applyFilter()
|
||||
}
|
||||
|
||||
fun updateImage(hash: String, bitmap: Bitmap) {
|
||||
imageCache[hash] = bitmap
|
||||
displayed.forEachIndexed { index, contact ->
|
||||
if (contact.customerImgHash == hash) notifyItemChanged(index)
|
||||
}
|
||||
}
|
||||
|
||||
fun setFilter(categoryId: String?, query: String) {
|
||||
activeCategoryId = categoryId
|
||||
searchQuery = query
|
||||
applyFilter()
|
||||
}
|
||||
|
||||
private fun applyFilter() {
|
||||
displayed = allContacts.filter { contact ->
|
||||
val matchesCategory = activeCategoryId == null || contact.benefCategoryId == activeCategoryId
|
||||
val matchesSearch = searchQuery.isBlank() ||
|
||||
contact.benefNickName.contains(searchQuery, ignoreCase = true) ||
|
||||
contact.benefName.contains(searchQuery, ignoreCase = true) ||
|
||||
contact.benefAccount.contains(searchQuery)
|
||||
matchesCategory && matchesSearch
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = ItemContactBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val contact = displayed[position]
|
||||
val cachedImage = contact.customerImgHash?.let { hash ->
|
||||
imageCache[hash] ?: run { onImageNeeded(hash); null }
|
||||
}
|
||||
holder.bind(contact, cachedImage)
|
||||
}
|
||||
|
||||
override fun getItemCount() = displayed.size
|
||||
|
||||
inner class ViewHolder(private val binding: ItemContactBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(contact: MibBeneficiary, photo: Bitmap?) {
|
||||
binding.tvContactName.text = contact.benefNickName
|
||||
binding.tvContactBank.text = contact.benefBankName
|
||||
binding.tvContactAccount.text = "${contact.benefAccount} · ${contact.transferCyDesc}"
|
||||
|
||||
if (photo != null) {
|
||||
binding.ivContactPhoto.setImageBitmap(photo)
|
||||
} else {
|
||||
binding.ivContactPhoto.setImageBitmap(
|
||||
makeInitialsBitmap(contact.benefNickName, contact.bankColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeInitialsBitmap(name: String, colorHex: String): Bitmap {
|
||||
val sizePx = binding.ivContactPhoto.context.resources
|
||||
.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
||||
.coerceAtLeast(96)
|
||||
val bgColor = try { Color.parseColor(colorHex) } catch (e: Exception) { Color.GRAY }
|
||||
|
||||
val bm = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bm)
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
|
||||
paint.color = bgColor
|
||||
canvas.drawCircle(sizePx / 2f, sizePx / 2f, sizePx / 2f, paint)
|
||||
|
||||
paint.color = Color.WHITE
|
||||
paint.textSize = sizePx * 0.42f
|
||||
paint.textAlign = Paint.Align.CENTER
|
||||
val letter = name.firstOrNull()?.uppercaseChar()?.toString() ?: "?"
|
||||
val metrics = paint.fontMetrics
|
||||
val textY = sizePx / 2f - (metrics.ascent + metrics.descent) / 2f
|
||||
canvas.drawText(letter, sizePx / 2f, textY, paint)
|
||||
|
||||
return bm
|
||||
}
|
||||
}
|
||||
}
|
||||
123
app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt
Normal file
123
app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt
Normal file
@@ -0,0 +1,123 @@
|
||||
package sh.sar.basedbank.ui.home
|
||||
|
||||
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 androidx.core.widget.addTextChangedListener
|
||||
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 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.MibBeneficiaryCategory
|
||||
import sh.sar.basedbank.api.mib.MibContactsClient
|
||||
import sh.sar.basedbank.databinding.FragmentContactsBinding
|
||||
|
||||
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<String>()
|
||||
private val session get() = (requireActivity().application as BasedBankApp).mibSession
|
||||
|
||||
private var categories: List<MibBeneficiaryCategory> = emptyList()
|
||||
private var activeCategoryId: String? = null // null = All
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentContactsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
adapter = ContactsAdapter { hash -> fetchImage(hash) }
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
binding.etSearch.addTextChangedListener { text ->
|
||||
adapter.setFilter(activeCategoryId, text?.toString() ?: "")
|
||||
}
|
||||
|
||||
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) {}
|
||||
})
|
||||
|
||||
viewModel.contactCategories.observe(viewLifecycleOwner) { cats ->
|
||||
categories = cats
|
||||
rebuildTabs(cats)
|
||||
}
|
||||
|
||||
viewModel.contacts.observe(viewLifecycleOwner) { contacts ->
|
||||
adapter.updateContacts(contacts)
|
||||
binding.recyclerView.visibility = if (contacts.isEmpty()) View.GONE else View.VISIBLE
|
||||
binding.emptyView.visibility = if (contacts.isEmpty()) View.VISIBLE else View.GONE
|
||||
binding.loadingView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun rebuildTabs(cats: List<MibBeneficiaryCategory>) {
|
||||
binding.tabLayout.clearOnTabSelectedListeners()
|
||||
binding.tabLayout.removeAllTabs()
|
||||
|
||||
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 }
|
||||
)
|
||||
}
|
||||
|
||||
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 fetchImage(hash: String) {
|
||||
if (!pendingHashes.add(hash)) return
|
||||
val sess = session ?: return
|
||||
val client = MibContactsClient()
|
||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
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)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
pendingHashes.remove(hash) // allow retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
requireActivity().title = getString(R.string.nav_contacts)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,15 @@ import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.mib.MibLoginFlow
|
||||
import sh.sar.basedbank.databinding.ActivityHomeBinding
|
||||
import sh.sar.basedbank.ui.login.LoginActivity
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiary
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiaryCategory
|
||||
import sh.sar.basedbank.api.mib.MibContactsClient
|
||||
import sh.sar.basedbank.api.mib.MibFinancingClient
|
||||
import sh.sar.basedbank.api.mib.MibProfile
|
||||
import sh.sar.basedbank.api.mib.MibSession
|
||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
import sh.sar.basedbank.util.AccountCache
|
||||
import sh.sar.basedbank.util.ContactsCache
|
||||
import sh.sar.basedbank.util.CredentialStore
|
||||
import sh.sar.basedbank.util.FinancingCache
|
||||
|
||||
@@ -49,6 +53,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
R.id.nav_dashboard -> show(DashboardFragment())
|
||||
R.id.nav_add_account -> startActivity(Intent(this, LoginActivity::class.java))
|
||||
R.id.nav_accounts -> show(AccountsFragment())
|
||||
R.id.nav_contacts -> show(ContactsFragment())
|
||||
R.id.nav_finances -> show(FinancingFragment())
|
||||
R.id.nav_settings -> show(SettingsFragment())
|
||||
else -> Toast.makeText(this, R.string.work_in_progress, Toast.LENGTH_SHORT).show()
|
||||
@@ -59,18 +64,27 @@ class HomeActivity : AppCompatActivity() {
|
||||
// Load data
|
||||
val app = application as BasedBankApp
|
||||
if (app.accounts.isNotEmpty()) {
|
||||
// Came from fresh manual login — accounts ready, financing fetched in background
|
||||
// Came from fresh manual login — accounts ready, rest fetched in background
|
||||
viewModel.accounts.value = app.accounts
|
||||
AccountCache.save(this, app.accounts)
|
||||
val cached = FinancingCache.load(this)
|
||||
if (cached.isNotEmpty()) viewModel.financing.value = cached
|
||||
val cachedContacts = ContactsCache.loadContacts(this)
|
||||
if (cachedContacts.isNotEmpty()) viewModel.contacts.value = cachedContacts
|
||||
val cachedCats = ContactsCache.loadCategories(this)
|
||||
if (cachedCats.isNotEmpty()) viewModel.contactCategories.value = cachedCats
|
||||
refreshFinancing(app.mibSession, app.mibProfiles)
|
||||
refreshContacts(app.mibSession, app.mibProfiles)
|
||||
} else {
|
||||
// Came from lock screen — show caches immediately, refresh everything in background
|
||||
val cached = AccountCache.load(this)
|
||||
if (cached.isNotEmpty()) viewModel.accounts.value = cached
|
||||
val cachedFinancing = FinancingCache.load(this)
|
||||
if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing
|
||||
val cachedContacts = ContactsCache.loadContacts(this)
|
||||
if (cachedContacts.isNotEmpty()) viewModel.contacts.value = cachedContacts
|
||||
val cachedCats = ContactsCache.loadCategories(this)
|
||||
if (cachedCats.isNotEmpty()) viewModel.contactCategories.value = cachedCats
|
||||
val creds = CredentialStore(this).loadMibCredentials()
|
||||
if (creds != null) autoRefresh(creds)
|
||||
}
|
||||
@@ -117,6 +131,41 @@ class HomeActivity : AppCompatActivity() {
|
||||
}
|
||||
val app = application as BasedBankApp
|
||||
refreshFinancing(app.mibSession, app.mibProfiles)
|
||||
refreshContacts(app.mibSession, app.mibProfiles)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshContacts(session: MibSession?, profiles: List<MibProfile>) {
|
||||
if (session == null || profiles.isEmpty()) return
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
val contactsClient = MibContactsClient()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val (allContacts, allCategories) = withContext(Dispatchers.IO) {
|
||||
val seenContacts = mutableSetOf<String>()
|
||||
val seenCategories = mutableSetOf<String>()
|
||||
val contacts = mutableListOf<MibBeneficiary>()
|
||||
val categories = mutableListOf<MibBeneficiaryCategory>()
|
||||
for (profile in profiles) {
|
||||
try {
|
||||
flow.switchProfile(session, profile)
|
||||
for (cat in contactsClient.fetchCategories(session)) {
|
||||
if (seenCategories.add(cat.id)) categories.add(cat)
|
||||
}
|
||||
for (contact in contactsClient.fetchContacts(session)) {
|
||||
if (seenContacts.add(contact.benefNo)) contacts.add(contact)
|
||||
}
|
||||
} catch (_: Exception) { /* profile has no contacts access */ }
|
||||
}
|
||||
Pair(contacts, categories)
|
||||
}
|
||||
if (allContacts.isNotEmpty()) {
|
||||
ContactsCache.save(this@HomeActivity, allContacts, allCategories)
|
||||
viewModel.contacts.postValue(allContacts)
|
||||
viewModel.contactCategories.postValue(allCategories)
|
||||
}
|
||||
} catch (_: Exception) { /* keep cached data */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,13 @@ package sh.sar.basedbank.ui.home
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import sh.sar.basedbank.api.mib.MibAccount
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiary
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiaryCategory
|
||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
val accounts = MutableLiveData<List<MibAccount>>(emptyList())
|
||||
val financing = MutableLiveData<List<MibFinanceDeal>>(emptyList())
|
||||
val contacts = MutableLiveData<List<MibBeneficiary>>(emptyList())
|
||||
val contactCategories = MutableLiveData<List<MibBeneficiaryCategory>>(emptyList())
|
||||
}
|
||||
|
||||
97
app/src/main/java/sh/sar/basedbank/util/ContactsCache.kt
Normal file
97
app/src/main/java/sh/sar/basedbank/util/ContactsCache.kt
Normal file
@@ -0,0 +1,97 @@
|
||||
package sh.sar.basedbank.util
|
||||
|
||||
import android.content.Context
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiary
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiaryCategory
|
||||
|
||||
object ContactsCache {
|
||||
|
||||
private const val PREFS = "contacts_cache"
|
||||
private const val KEY_CONTACTS = "mib_contacts"
|
||||
private const val KEY_CATEGORIES = "mib_categories"
|
||||
|
||||
fun save(
|
||||
context: Context,
|
||||
contacts: List<MibBeneficiary>,
|
||||
categories: List<MibBeneficiaryCategory>
|
||||
) {
|
||||
val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
|
||||
val contactsArr = JSONArray()
|
||||
for (c in contacts) {
|
||||
contactsArr.put(JSONObject().apply {
|
||||
put("benefNo", c.benefNo)
|
||||
put("benefName", c.benefName)
|
||||
put("benefNickName", c.benefNickName)
|
||||
put("benefAccount", c.benefAccount)
|
||||
put("benefType", c.benefType)
|
||||
put("bankColor", c.bankColor)
|
||||
put("benefBankName", c.benefBankName)
|
||||
put("bankCode", c.bankCode)
|
||||
put("benefStatus", c.benefStatus)
|
||||
put("transferCyDesc", c.transferCyDesc)
|
||||
put("customerImgHash", c.customerImgHash ?: "")
|
||||
put("benefCategoryId", c.benefCategoryId)
|
||||
})
|
||||
}
|
||||
prefs.putString(KEY_CONTACTS, contactsArr.toString())
|
||||
|
||||
val catArr = JSONArray()
|
||||
for (cat in categories) {
|
||||
catArr.put(JSONObject().apply {
|
||||
put("id", cat.id)
|
||||
put("categoryName", cat.categoryName)
|
||||
put("numBenef", cat.numBenef)
|
||||
})
|
||||
}
|
||||
prefs.putString(KEY_CATEGORIES, catArr.toString())
|
||||
prefs.apply()
|
||||
}
|
||||
|
||||
fun loadContacts(context: Context): List<MibBeneficiary> {
|
||||
val json = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.getString(KEY_CONTACTS, null) ?: return emptyList()
|
||||
return try {
|
||||
val arr = JSONArray(json)
|
||||
(0 until arr.length()).map { i ->
|
||||
val o = arr.getJSONObject(i)
|
||||
MibBeneficiary(
|
||||
benefNo = o.optString("benefNo"),
|
||||
benefName = o.optString("benefName"),
|
||||
benefNickName = o.optString("benefNickName"),
|
||||
benefAccount = o.optString("benefAccount"),
|
||||
benefType = o.optString("benefType"),
|
||||
bankColor = o.optString("bankColor", "#888888"),
|
||||
benefBankName = o.optString("benefBankName"),
|
||||
bankCode = o.optString("bankCode"),
|
||||
benefStatus = o.optString("benefStatus"),
|
||||
transferCyDesc = o.optString("transferCyDesc", "MVR"),
|
||||
customerImgHash = o.optString("customerImgHash").takeIf { it.isNotBlank() },
|
||||
benefCategoryId = o.optString("benefCategoryId", "0")
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadCategories(context: Context): List<MibBeneficiaryCategory> {
|
||||
val json = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.getString(KEY_CATEGORIES, null) ?: return emptyList()
|
||||
return try {
|
||||
val arr = JSONArray(json)
|
||||
(0 until arr.length()).map { i ->
|
||||
val o = arr.getJSONObject(i)
|
||||
MibBeneficiaryCategory(
|
||||
id = o.optString("id"),
|
||||
categoryName = o.optString("categoryName"),
|
||||
numBenef = o.optInt("numBenef", 0)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user