ui: pass 3 — skeleton loading, a11y, snackbars, dark mode
Skeleton loading state:
* Wire item_skeleton_transaction (previously dormant in res) into
AccountHistoryFragment and TransferHistoryFragment. When a load
starts with no cached transactions, 8 skeleton rows appear under
the search bar instead of a blank screen. Skeleton hides as soon
as transactions arrive or the load completes.
* Decision: ActivitiesFragment loads from local storage synchronously,
so no skeleton there — would only flash for a frame.
Accessibility:
* Decorative icons in item_more_nav, item_nav_slot, item_picker_row,
and item_account_dropdown now declare importantForAccessibility="no"
so TalkBack skips them (the adjacent text label conveys meaning).
* Also added tnum to item_picker_row.tvBalance and
item_account_dropdown.tvDropdownBalance — missed amounts from Pass 2.
Snackbar:
* ContactsFragment.deleteContact's success/failure feedback is now
a Snackbar instead of Toast — the fragment stays visible, so a
bottom-anchored Snackbar fits better than a floating Toast.
* Other Toasts are either followed by navigation away (Snackbar
couldn't display), or purely informational ("copied", "press back
to exit") where Toast is the right primitive. Left as-is.
Dark mode:
* Connectivity banner in activity_home no longer hardcodes
#C62828 / #FFFFFF — uses colorErrorContainer / colorOnErrorContainer
so it adapts to the user's theme.
* Other hex literals (card-artwork text, receipt screens that
intentionally emulate paper) are intentional and stay.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,8 @@ class AccountHistoryFragment : Fragment() {
|
||||
}
|
||||
adapter.setTransactions(filtered)
|
||||
binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE
|
||||
binding.skeletonContainer.visibility =
|
||||
if (allTransactions.isEmpty() && isLoading) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun resetAndReload() {
|
||||
@@ -168,6 +170,9 @@ class AccountHistoryFragment : Fragment() {
|
||||
|
||||
if (firstPageDone && allTransactions.isNotEmpty()) {
|
||||
adapter.showLoadingFooter = true
|
||||
} else if (allTransactions.isEmpty()) {
|
||||
binding.skeletonContainer.visibility = View.VISIBLE
|
||||
binding.emptyView.visibility = View.GONE
|
||||
}
|
||||
|
||||
val app = requireActivity().application as BasedBankApp
|
||||
@@ -187,6 +192,7 @@ class AccountHistoryFragment : Fragment() {
|
||||
|
||||
isLoading = false
|
||||
if (_binding == null) return@launch
|
||||
binding.skeletonContainer.visibility = View.GONE
|
||||
|
||||
if (!firstPageDone) {
|
||||
firstPageDone = true
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
@@ -194,10 +195,10 @@ class ContactsFragment : Fragment() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
val success = withContext(Dispatchers.IO) { ContactManager.delete(contact, app) }
|
||||
if (success) {
|
||||
Toast.makeText(requireContext(), R.string.contact_deleted, Toast.LENGTH_SHORT).show()
|
||||
removeFromViewModel(contact)
|
||||
Snackbar.make(binding.root, R.string.contact_deleted, Snackbar.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(requireContext(), R.string.contact_delete_failed, Toast.LENGTH_SHORT).show()
|
||||
Snackbar.make(binding.root, R.string.contact_delete_failed, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,9 @@ class TransferHistoryFragment : Fragment() {
|
||||
|
||||
if (firstBatchDone && allTransactions.isNotEmpty()) {
|
||||
adapter.showLoadingFooter = true
|
||||
} else if (allTransactions.isEmpty()) {
|
||||
binding.skeletonContainer.visibility = View.VISIBLE
|
||||
binding.emptyView.visibility = View.GONE
|
||||
}
|
||||
|
||||
val app = requireActivity().application as BasedBankApp
|
||||
@@ -286,6 +289,7 @@ class TransferHistoryFragment : Fragment() {
|
||||
|
||||
isLoading = false
|
||||
if (_binding == null) return@launch
|
||||
binding.skeletonContainer.visibility = View.GONE
|
||||
|
||||
val raw = bannerMsg.get()
|
||||
when {
|
||||
@@ -329,6 +333,8 @@ class TransferHistoryFragment : Fragment() {
|
||||
}
|
||||
adapter.setTransactions(filtered)
|
||||
binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE
|
||||
binding.skeletonContainer.visibility =
|
||||
if (allTransactions.isEmpty() && isLoading) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun loadContactImage(name: String) {
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
android:id="@+id/connectivityBanner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#C62828"
|
||||
android:textColor="#FFFFFF"
|
||||
android:background="?attr/colorErrorContainer"
|
||||
android:textColor="?attr/colorOnErrorContainer"
|
||||
android:gravity="center"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp"
|
||||
|
||||
@@ -48,6 +48,22 @@
|
||||
android:paddingBottom="16dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/skeletonContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyView"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -49,6 +49,22 @@
|
||||
android:paddingBottom="16dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/skeletonContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
<include layout="@layout/item_skeleton_transaction" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyView"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:visibility="gone"
|
||||
android:importantForAccessibility="no"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" />
|
||||
|
||||
@@ -49,7 +50,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:fontFeatureSettings="tnum" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
android:id="@+id/ivIcon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp" />
|
||||
android:layout_marginEnd="16dp"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
android:id="@+id/ivNavIcon"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginBottom="6dp" />
|
||||
android:layout_marginBottom="6dp"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNavLabel"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" />
|
||||
|
||||
<LinearLayout
|
||||
@@ -53,6 +54,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:fontFeatureSettings="tnum"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user