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)
|
adapter.setTransactions(filtered)
|
||||||
binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE
|
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() {
|
private fun resetAndReload() {
|
||||||
@@ -168,6 +170,9 @@ class AccountHistoryFragment : Fragment() {
|
|||||||
|
|
||||||
if (firstPageDone && allTransactions.isNotEmpty()) {
|
if (firstPageDone && allTransactions.isNotEmpty()) {
|
||||||
adapter.showLoadingFooter = true
|
adapter.showLoadingFooter = true
|
||||||
|
} else if (allTransactions.isEmpty()) {
|
||||||
|
binding.skeletonContainer.visibility = View.VISIBLE
|
||||||
|
binding.emptyView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
val app = requireActivity().application as BasedBankApp
|
val app = requireActivity().application as BasedBankApp
|
||||||
@@ -187,6 +192,7 @@ class AccountHistoryFragment : Fragment() {
|
|||||||
|
|
||||||
isLoading = false
|
isLoading = false
|
||||||
if (_binding == null) return@launch
|
if (_binding == null) return@launch
|
||||||
|
binding.skeletonContainer.visibility = View.GONE
|
||||||
|
|
||||||
if (!firstPageDone) {
|
if (!firstPageDone) {
|
||||||
firstPageDone = true
|
firstPageDone = true
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
@@ -194,10 +195,10 @@ class ContactsFragment : Fragment() {
|
|||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
val success = withContext(Dispatchers.IO) { ContactManager.delete(contact, app) }
|
val success = withContext(Dispatchers.IO) { ContactManager.delete(contact, app) }
|
||||||
if (success) {
|
if (success) {
|
||||||
Toast.makeText(requireContext(), R.string.contact_deleted, Toast.LENGTH_SHORT).show()
|
|
||||||
removeFromViewModel(contact)
|
removeFromViewModel(contact)
|
||||||
|
Snackbar.make(binding.root, R.string.contact_deleted, Snackbar.LENGTH_SHORT).show()
|
||||||
} else {
|
} 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()) {
|
if (firstBatchDone && allTransactions.isNotEmpty()) {
|
||||||
adapter.showLoadingFooter = true
|
adapter.showLoadingFooter = true
|
||||||
|
} else if (allTransactions.isEmpty()) {
|
||||||
|
binding.skeletonContainer.visibility = View.VISIBLE
|
||||||
|
binding.emptyView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
val app = requireActivity().application as BasedBankApp
|
val app = requireActivity().application as BasedBankApp
|
||||||
@@ -286,6 +289,7 @@ class TransferHistoryFragment : Fragment() {
|
|||||||
|
|
||||||
isLoading = false
|
isLoading = false
|
||||||
if (_binding == null) return@launch
|
if (_binding == null) return@launch
|
||||||
|
binding.skeletonContainer.visibility = View.GONE
|
||||||
|
|
||||||
val raw = bannerMsg.get()
|
val raw = bannerMsg.get()
|
||||||
when {
|
when {
|
||||||
@@ -329,6 +333,8 @@ class TransferHistoryFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
adapter.setTransactions(filtered)
|
adapter.setTransactions(filtered)
|
||||||
binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE
|
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) {
|
private fun loadContactImage(name: String) {
|
||||||
|
|||||||
@@ -44,8 +44,8 @@
|
|||||||
android:id="@+id/connectivityBanner"
|
android:id="@+id/connectivityBanner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="#C62828"
|
android:background="?attr/colorErrorContainer"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="?attr/colorOnErrorContainer"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:paddingTop="6dp"
|
android:paddingTop="6dp"
|
||||||
android:paddingBottom="6dp"
|
android:paddingBottom="6dp"
|
||||||
|
|||||||
@@ -48,6 +48,22 @@
|
|||||||
android:paddingBottom="16dp"
|
android:paddingBottom="16dp"
|
||||||
android:clipToPadding="false" />
|
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
|
<TextView
|
||||||
android:id="@+id/emptyView"
|
android:id="@+id/emptyView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -49,6 +49,22 @@
|
|||||||
android:paddingBottom="16dp"
|
android:paddingBottom="16dp"
|
||||||
android:clipToPadding="false" />
|
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
|
<TextView
|
||||||
android:id="@+id/emptyView"
|
android:id="@+id/emptyView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle"
|
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto" />
|
xmlns:app="http://schemas.android.com/apk/res-auto" />
|
||||||
|
|
||||||
@@ -49,7 +50,8 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||||
android:textColor="?attr/colorOnSurface" />
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:fontFeatureSettings="tnum" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
android:id="@+id/ivIcon"
|
android:id="@+id/ivIcon"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:layout_marginEnd="16dp" />
|
android:layout_marginEnd="16dp"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
android:id="@+id/ivNavIcon"
|
android:id="@+id/ivNavIcon"
|
||||||
android:layout_width="28dp"
|
android:layout_width="28dp"
|
||||||
android:layout_height="28dp"
|
android:layout_height="28dp"
|
||||||
android:layout_marginBottom="6dp" />
|
android:layout_marginBottom="6dp"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvNavLabel"
|
android:id="@+id/tvNavLabel"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
android:layout_width="44dp"
|
android:layout_width="44dp"
|
||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" />
|
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
android:textAppearance="?attr/textAppearanceTitleMedium"
|
||||||
android:textColor="?attr/colorOnSurface"
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:fontFeatureSettings="tnum"
|
||||||
android:paddingHorizontal="8dp"
|
android:paddingHorizontal="8dp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user