forked from shihaam/thijooree
add support to view previous transfer recipts
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
package sh.sar.basedbank.ui.home
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import sh.sar.basedbank.databinding.ItemDateHeaderBinding
|
||||||
|
import sh.sar.basedbank.databinding.ItemTransactionBinding
|
||||||
|
import sh.sar.basedbank.util.ReceiptStore
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class ActivitiesAdapter(
|
||||||
|
private val onItemClick: (ReceiptStore.Entry) -> Unit
|
||||||
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
private sealed class Item {
|
||||||
|
data class DateHeader(val label: String) : Item()
|
||||||
|
data class ReceiptItem(val entry: ReceiptStore.Entry) : Item()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val displayItems = mutableListOf<Item>()
|
||||||
|
|
||||||
|
fun setEntries(entries: List<ReceiptStore.Entry>) {
|
||||||
|
displayItems.clear()
|
||||||
|
var lastDateKey = ""
|
||||||
|
for (entry in entries) {
|
||||||
|
val dateKey = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date(entry.savedAt))
|
||||||
|
if (dateKey != lastDateKey) {
|
||||||
|
displayItems.add(Item.DateHeader(formatDateHeader(entry.savedAt)))
|
||||||
|
lastDateKey = dateKey
|
||||||
|
}
|
||||||
|
displayItems.add(Item.ReceiptItem(entry))
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = displayItems.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) =
|
||||||
|
if (displayItems[position] is Item.DateHeader) TYPE_DATE_HEADER else TYPE_RECEIPT
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
return if (viewType == TYPE_DATE_HEADER)
|
||||||
|
DateHeaderVH(ItemDateHeaderBinding.inflate(inflater, parent, false))
|
||||||
|
else
|
||||||
|
ReceiptVH(ItemTransactionBinding.inflate(inflater, parent, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is DateHeaderVH -> holder.bind((displayItems[position] as Item.DateHeader).label)
|
||||||
|
is ReceiptVH -> holder.bind((displayItems[position] as Item.ReceiptItem).entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class DateHeaderVH(private val b: ItemDateHeaderBinding) :
|
||||||
|
RecyclerView.ViewHolder(b.root) {
|
||||||
|
fun bind(label: String) { b.tvDateHeader.text = label }
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ReceiptVH(private val b: ItemTransactionBinding) :
|
||||||
|
RecyclerView.ViewHolder(b.root) {
|
||||||
|
fun bind(entry: ReceiptStore.Entry) {
|
||||||
|
val d = entry.data
|
||||||
|
val colorHex = d.fromColorHex.takeIf { it.isNotBlank() } ?: "#607D8B"
|
||||||
|
val initial = d.toLabel.firstOrNull()?.uppercaseChar()?.toString() ?: "?"
|
||||||
|
|
||||||
|
b.fvAvatar.background = GradientDrawable().apply {
|
||||||
|
shape = GradientDrawable.OVAL
|
||||||
|
setColor(try { Color.parseColor(colorHex) } catch (_: Exception) { Color.GRAY })
|
||||||
|
}
|
||||||
|
b.tvInitial.visibility = android.view.View.VISIBLE
|
||||||
|
b.tvInitial.text = initial
|
||||||
|
|
||||||
|
b.tvCounterparty.text = d.toLabel
|
||||||
|
b.tvCounterparty.visibility = android.view.View.VISIBLE
|
||||||
|
b.tvDescription.text = buildString {
|
||||||
|
append(d.fromLabel)
|
||||||
|
if (d.toBank.isNotBlank()) append(" · ${d.toBank}")
|
||||||
|
}
|
||||||
|
b.tvDate.text = formatTime(entry.savedAt)
|
||||||
|
|
||||||
|
b.tvAmount.text = "- ${d.currency} ${d.amount}"
|
||||||
|
b.tvAmount.setTextColor(Color.parseColor("#FF7043"))
|
||||||
|
|
||||||
|
b.root.setOnClickListener { onItemClick(entry) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDateHeader(millis: Long): String {
|
||||||
|
val sdf = SimpleDateFormat("EEEE, d MMMM yyyy", Locale.US)
|
||||||
|
return sdf.format(Date(millis))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTime(millis: Long): String {
|
||||||
|
val sdf = SimpleDateFormat("HH:mm", Locale.US)
|
||||||
|
return sdf.format(Date(millis))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TYPE_DATE_HEADER = 0
|
||||||
|
private const val TYPE_RECEIPT = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package sh.sar.basedbank.ui.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import sh.sar.basedbank.R
|
||||||
|
import sh.sar.basedbank.databinding.FragmentActivitiesBinding
|
||||||
|
import sh.sar.basedbank.util.ReceiptStore
|
||||||
|
|
||||||
|
class ActivitiesFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentActivitiesBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var adapter: ActivitiesAdapter
|
||||||
|
private val allEntries = mutableListOf<ReceiptStore.Entry>()
|
||||||
|
private var searchQuery = ""
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
_binding = FragmentActivitiesBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
adapter = ActivitiesAdapter { entry ->
|
||||||
|
(activity as? HomeActivity)?.showWithBackStack(
|
||||||
|
TransferReceiptFragment.newInstance(entry.data, null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
val bottomPaddingBase = (16 * resources.displayMetrics.density).toInt()
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { v, insets ->
|
||||||
|
val isBottomNav = requireContext()
|
||||||
|
.getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("bottom_nav", false)
|
||||||
|
val navBar = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
val extraBottom = if (isBottomNav) 0 else navBar.bottom
|
||||||
|
v.setPadding(v.paddingLeft, v.paddingTop, v.paddingRight, bottomPaddingBase + extraBottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.etSearch.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
searchQuery = s?.toString()?.trim() ?: ""
|
||||||
|
filterAndDisplay()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
requireActivity().title = getString(R.string.nav_activities)
|
||||||
|
// Reload in case a new receipt was added while we were away
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadEntries() {
|
||||||
|
allEntries.clear()
|
||||||
|
allEntries.addAll(ReceiptStore.loadAll(requireContext()))
|
||||||
|
filterAndDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterAndDisplay() {
|
||||||
|
val filtered = if (searchQuery.isBlank()) allEntries
|
||||||
|
else allEntries.filter { entry ->
|
||||||
|
entry.data.toLabel.contains(searchQuery, ignoreCase = true) ||
|
||||||
|
entry.data.fromLabel.contains(searchQuery, ignoreCase = true) ||
|
||||||
|
entry.data.toAccount.contains(searchQuery, ignoreCase = true) ||
|
||||||
|
entry.data.toBank.contains(searchQuery, ignoreCase = true) ||
|
||||||
|
entry.data.mibReferenceNo.contains(searchQuery, ignoreCase = true) ||
|
||||||
|
entry.data.bmlReference.contains(searchQuery, ignoreCase = true)
|
||||||
|
}
|
||||||
|
adapter.setEntries(filtered)
|
||||||
|
binding.emptyView.visibility = if (filtered.isEmpty()) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,6 +116,7 @@ class HomeActivity : AppCompatActivity() {
|
|||||||
R.id.nav_contacts -> ContactsFragment()
|
R.id.nav_contacts -> ContactsFragment()
|
||||||
R.id.nav_transfer -> TransferFragment()
|
R.id.nav_transfer -> TransferFragment()
|
||||||
R.id.nav_more -> MoreFragment()
|
R.id.nav_more -> MoreFragment()
|
||||||
|
R.id.nav_activities -> ActivitiesFragment()
|
||||||
R.id.nav_transfer_history -> TransferHistoryFragment()
|
R.id.nav_transfer_history -> TransferHistoryFragment()
|
||||||
R.id.nav_finances -> FinancingFragment()
|
R.id.nav_finances -> FinancingFragment()
|
||||||
R.id.nav_otp -> OtpFragment()
|
R.id.nav_otp -> OtpFragment()
|
||||||
@@ -276,6 +277,7 @@ class HomeActivity : AppCompatActivity() {
|
|||||||
R.id.nav_accounts -> AccountsFragment()
|
R.id.nav_accounts -> AccountsFragment()
|
||||||
R.id.nav_contacts -> ContactsFragment()
|
R.id.nav_contacts -> ContactsFragment()
|
||||||
R.id.nav_transfer -> TransferFragment()
|
R.id.nav_transfer -> TransferFragment()
|
||||||
|
R.id.nav_activities -> ActivitiesFragment()
|
||||||
R.id.nav_transfer_history -> TransferHistoryFragment()
|
R.id.nav_transfer_history -> TransferHistoryFragment()
|
||||||
R.id.nav_finances -> FinancingFragment()
|
R.id.nav_finances -> FinancingFragment()
|
||||||
R.id.nav_otp -> OtpFragment()
|
R.id.nav_otp -> OtpFragment()
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import sh.sar.basedbank.util.AccountInputParser
|
|||||||
import sh.sar.basedbank.util.PaymvQrParser
|
import sh.sar.basedbank.util.PaymvQrParser
|
||||||
import sh.sar.basedbank.util.RecentPick
|
import sh.sar.basedbank.util.RecentPick
|
||||||
import sh.sar.basedbank.util.RecentsCache
|
import sh.sar.basedbank.util.RecentsCache
|
||||||
|
import sh.sar.basedbank.util.ReceiptStore
|
||||||
import sh.sar.basedbank.util.Totp
|
import sh.sar.basedbank.util.Totp
|
||||||
|
|
||||||
class TransferFragment : Fragment() {
|
class TransferFragment : Fragment() {
|
||||||
@@ -628,6 +629,7 @@ class TransferFragment : Fragment() {
|
|||||||
binding.btnTransfer.isEnabled = true
|
binding.btnTransfer.isEnabled = true
|
||||||
(activity as? HomeActivity)?.setRefreshing(false)
|
(activity as? HomeActivity)?.setRefreshing(false)
|
||||||
if (ok && receipt != null) {
|
if (ok && receipt != null) {
|
||||||
|
ReceiptStore.save(requireContext(), receipt)
|
||||||
clearForm()
|
clearForm()
|
||||||
val activity = requireActivity() as HomeActivity
|
val activity = requireActivity() as HomeActivity
|
||||||
activity.refreshBalances(src)
|
activity.refreshBalances(src)
|
||||||
@@ -755,7 +757,7 @@ class TransferFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
val receipt = TransferReceiptData(
|
val receipt = TransferReceiptData(
|
||||||
isMib = true,
|
bank = "MIB",
|
||||||
amount = "%.2f".format(amount.toDoubleOrNull() ?: 0.0),
|
amount = "%.2f".format(amount.toDoubleOrNull() ?: 0.0),
|
||||||
currency = currency,
|
currency = currency,
|
||||||
fromLabel = src.accountBriefName,
|
fromLabel = src.accountBriefName,
|
||||||
@@ -839,7 +841,7 @@ class TransferFragment : Fragment() {
|
|||||||
val result = bmlFlow.confirmTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, confirmOtp, remarks, bank)
|
val result = bmlFlow.confirmTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, confirmOtp, remarks, bank)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
val receipt = TransferReceiptData(
|
val receipt = TransferReceiptData(
|
||||||
isMib = false,
|
bank = "BML",
|
||||||
amount = "%.2f".format(amount),
|
amount = "%.2f".format(amount),
|
||||||
currency = currency,
|
currency = currency,
|
||||||
fromLabel = src.accountBriefName,
|
fromLabel = src.accountBriefName,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package sh.sar.basedbank.ui.home
|
package sh.sar.basedbank.ui.home
|
||||||
|
|
||||||
data class TransferReceiptData(
|
data class TransferReceiptData(
|
||||||
val isMib: Boolean,
|
val bank: String, // "MIB", "BML", etc.
|
||||||
val amount: String,
|
val amount: String,
|
||||||
val currency: String,
|
val currency: String,
|
||||||
val fromLabel: String,
|
val fromLabel: String,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class TransferReceiptFragment : Fragment() {
|
|||||||
private val receiptCard get() = _receiptCard!!
|
private val receiptCard get() = _receiptCard!!
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ARG_IS_MIB = "is_mib"
|
private const val ARG_BANK = "bank"
|
||||||
private const val ARG_AMOUNT = "amount"
|
private const val ARG_AMOUNT = "amount"
|
||||||
private const val ARG_CURRENCY = "currency"
|
private const val ARG_CURRENCY = "currency"
|
||||||
private const val ARG_FROM_LABEL = "from_label"
|
private const val ARG_FROM_LABEL = "from_label"
|
||||||
@@ -69,7 +69,7 @@ class TransferReceiptFragment : Fragment() {
|
|||||||
fun newInstance(data: TransferReceiptData, toAvatarBitmap: Bitmap?) = TransferReceiptFragment().apply {
|
fun newInstance(data: TransferReceiptData, toAvatarBitmap: Bitmap?) = TransferReceiptFragment().apply {
|
||||||
pendingToAvatarBitmap = toAvatarBitmap
|
pendingToAvatarBitmap = toAvatarBitmap
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putBoolean(ARG_IS_MIB, data.isMib)
|
putString(ARG_BANK, data.bank)
|
||||||
putString(ARG_AMOUNT, data.amount)
|
putString(ARG_AMOUNT, data.amount)
|
||||||
putString(ARG_CURRENCY, data.currency)
|
putString(ARG_CURRENCY, data.currency)
|
||||||
putString(ARG_FROM_LABEL, data.fromLabel)
|
putString(ARG_FROM_LABEL, data.fromLabel)
|
||||||
@@ -90,8 +90,8 @@ class TransferReceiptFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
val isMib = arguments?.getBoolean(ARG_IS_MIB, true) ?: true
|
val bank = arguments?.getString(ARG_BANK, "MIB") ?: "MIB"
|
||||||
return if (isMib) {
|
return if (bank == "MIB") {
|
||||||
val binding = FragmentReceiptMibBinding.inflate(inflater, container, false)
|
val binding = FragmentReceiptMibBinding.inflate(inflater, container, false)
|
||||||
bindMib(binding)
|
bindMib(binding)
|
||||||
_receiptCard = binding.receiptCard
|
_receiptCard = binding.receiptCard
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package sh.sar.basedbank.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import sh.sar.basedbank.ui.home.TransferReceiptData
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/** Persistent (non-cache) store for completed transfer receipts shown in Activities. */
|
||||||
|
object ReceiptStore {
|
||||||
|
|
||||||
|
private const val FILE_NAME = "activities.json"
|
||||||
|
|
||||||
|
data class Entry(val data: TransferReceiptData, val savedAt: Long)
|
||||||
|
|
||||||
|
fun save(context: Context, receipt: TransferReceiptData) {
|
||||||
|
val existing = loadAll(context).toMutableList()
|
||||||
|
existing.add(0, Entry(receipt, System.currentTimeMillis()))
|
||||||
|
writeAll(context, existing)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAll(context: Context): List<Entry> {
|
||||||
|
val file = File(context.filesDir, FILE_NAME)
|
||||||
|
if (!file.exists()) return emptyList()
|
||||||
|
return try {
|
||||||
|
val arr = JSONArray(CacheEncryption.decrypt(file.readText()))
|
||||||
|
(0 until arr.length()).map { i ->
|
||||||
|
val o = arr.getJSONObject(i)
|
||||||
|
Entry(
|
||||||
|
data = TransferReceiptData(
|
||||||
|
bank = o.optString("bank", "MIB"),
|
||||||
|
amount = o.optString("amount"),
|
||||||
|
currency = o.optString("currency"),
|
||||||
|
fromLabel = o.optString("fromLabel"),
|
||||||
|
fromColorHex = o.optString("fromColorHex"),
|
||||||
|
fromProfileImageHash = o.optString("fromProfileImageHash").takeIf { it.isNotBlank() },
|
||||||
|
toLabel = o.optString("toLabel"),
|
||||||
|
toAccount = o.optString("toAccount"),
|
||||||
|
toBank = o.optString("toBank"),
|
||||||
|
remarks = o.optString("remarks"),
|
||||||
|
mibReferenceNo = o.optString("mibReferenceNo"),
|
||||||
|
mibTransactionDate = o.optString("mibTransactionDate"),
|
||||||
|
bmlFromName = o.optString("bmlFromName"),
|
||||||
|
bmlReference = o.optString("bmlReference"),
|
||||||
|
bmlTimestamp = o.optString("bmlTimestamp"),
|
||||||
|
bmlMessage = o.optString("bmlMessage")
|
||||||
|
),
|
||||||
|
savedAt = o.optLong("savedAt", 0L)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) { emptyList() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAll(context: Context) {
|
||||||
|
File(context.filesDir, FILE_NAME).delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeAll(context: Context, items: List<Entry>) {
|
||||||
|
try {
|
||||||
|
val arr = JSONArray()
|
||||||
|
for ((d, ts) in items) arr.put(JSONObject().apply {
|
||||||
|
put("bank", d.bank)
|
||||||
|
put("amount", d.amount)
|
||||||
|
put("currency", d.currency)
|
||||||
|
put("fromLabel", d.fromLabel)
|
||||||
|
put("fromColorHex", d.fromColorHex)
|
||||||
|
put("fromProfileImageHash", d.fromProfileImageHash ?: "")
|
||||||
|
put("toLabel", d.toLabel)
|
||||||
|
put("toAccount", d.toAccount)
|
||||||
|
put("toBank", d.toBank)
|
||||||
|
put("remarks", d.remarks)
|
||||||
|
put("mibReferenceNo", d.mibReferenceNo)
|
||||||
|
put("mibTransactionDate", d.mibTransactionDate)
|
||||||
|
put("bmlFromName", d.bmlFromName)
|
||||||
|
put("bmlReference", d.bmlReference)
|
||||||
|
put("bmlTimestamp", d.bmlTimestamp)
|
||||||
|
put("bmlMessage", d.bmlMessage)
|
||||||
|
put("savedAt", ts)
|
||||||
|
})
|
||||||
|
File(context.filesDir, FILE_NAME).writeText(CacheEncryption.encrypt(arr.toString()))
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="?attr/colorSurface">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="12dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
app:startIconDrawable="@android:drawable/ic_menu_search"
|
||||||
|
app:boxCornerRadiusTopStart="24dp"
|
||||||
|
app:boxCornerRadiusTopEnd="24dp"
|
||||||
|
app:boxCornerRadiusBottomStart="24dp"
|
||||||
|
app:boxCornerRadiusBottomEnd="24dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/etSearch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Search"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:imeOptions="actionSearch" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:clipToPadding="false" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emptyView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="No activities yet"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user