Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
## [Unreleased]

### Added
- **Mobile Accessibility:** Completed accessibility audit for all mobile screens.
- **Features:**
- Added `accessibilityLabel` to all interactive elements (buttons, inputs, list items).
- Added `accessibilityRole` to ensure screen readers identify element types correctly.
- Added `accessibilityHint` for clearer context on destructive actions or complex interactions.
- Covered Auth, Dashboard, Groups, and Utility screens.
- **Technical:** Updated all files in `mobile/screens/` to compliant with React Native accessibility standards.

- **Mobile Pull-to-Refresh:** Implemented native pull-to-refresh interactions with haptic feedback for key lists.
- **Features:**
- Integrated `RefreshControl` into `HomeScreen`, `FriendsScreen`, and `GroupDetailsScreen`.
Expand Down
11 changes: 11 additions & 0 deletions .Jules/knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,17 @@ Commonly used components:

Most screens use `<View style={{ flex: 1 }}>` - consider wrapping in `SafeAreaView` for notched devices.

### Accessibility Patterns

**Date:** 2026-01-29
**Context:** Auditing and fixing mobile accessibility

When building mobile screens with React Native Paper:
1. **Explicit Labels:** Always add `accessibilityLabel` to `IconButton`, `FAB`, and `Card` components that act as buttons.
2. **Roles:** Use `accessibilityRole="button"` for pressable elements, `accessibilityRole="header"` for titles.
3. **Hints:** Use `accessibilityHint` for non-obvious actions (e.g., "Double tap to delete").
4. **State:** For custom checkboxes or toggles, use `accessibilityState={{ checked: boolean }}`.

---

## API Response Patterns
Expand Down
3 changes: 2 additions & 1 deletion .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
- Size: ~40 lines
- Added: 2026-01-01

- [ ] **[a11y]** Complete accessibility labels for all screens
- [x] **[a11y]** Complete accessibility labels for all screens
- Completed: 2026-01-29
- Files: All screens in `mobile/screens/`
- Context: Add accessibilityLabel, accessibilityHint, accessibilityRole throughout
- Impact: Screen reader users can use app fully
Expand Down
11 changes: 11 additions & 0 deletions mobile/screens/AccountScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,41 @@ const AccountScreen = ({ navigation }) => {
title="Edit Profile"
left={() => <List.Icon icon="account-edit" />}
onPress={() => navigation.navigate("EditProfile")}
accessibilityLabel="Edit Profile"
accessibilityRole="button"
/>
<Divider />
<List.Item
title="Email Settings"
left={() => <List.Icon icon="email-edit-outline" />}
onPress={handleComingSoon}
accessibilityLabel="Email Settings"
accessibilityRole="button"
/>
<Divider />
<List.Item
title="Send Feedback"
left={() => <List.Icon icon="message-alert-outline" />}
onPress={handleComingSoon}
accessibilityLabel="Send Feedback"
accessibilityRole="button"
/>
<Divider />
<List.Item
title="Import from Splitwise"
left={() => <List.Icon icon="import" />}
onPress={() => navigation.navigate("SplitwiseImport")}
accessibilityLabel="Import from Splitwise"
accessibilityRole="button"
/>
<Divider />
<List.Item
title="Logout"
left={() => <List.Icon icon="logout" />}
onPress={handleLogout}
accessibilityLabel="Logout"
accessibilityRole="button"
accessibilityHint="Logs you out of the application"
/>
</List.Section>
</View>
Expand Down
19 changes: 18 additions & 1 deletion mobile/screens/AddExpenseScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ const AddExpenseScreen = ({ route, navigation }) => {
label={member.user.name}
status={selectedMembers[member.userId] ? "checked" : "unchecked"}
onPress={() => handleMemberSelect(member.userId)}
accessibilityLabel={`Select ${member.user.name}`}
accessibilityRole="checkbox"
accessibilityState={{
checked: !!selectedMembers[member.userId],
}}
/>
));
case "exact":
Expand All @@ -295,6 +300,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
}
keyboardType="numeric"
style={styles.splitInput}
accessibilityLabel={`${member.user.name}'s exact amount`}
/>
));
case "percentage":
Expand All @@ -308,6 +314,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
}
keyboardType="numeric"
style={styles.splitInput}
accessibilityLabel={`${member.user.name}'s percentage`}
/>
));
case "shares":
Expand All @@ -321,6 +328,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
}
keyboardType="numeric"
style={styles.splitInput}
accessibilityLabel={`${member.user.name}'s shares`}
/>
));
default:
Expand Down Expand Up @@ -351,20 +359,27 @@ const AddExpenseScreen = ({ route, navigation }) => {
value={description}
onChangeText={setDescription}
style={styles.input}
accessibilityLabel="Expense Description"
/>
<TextInput
label="Amount"
value={amount}
onChangeText={setAmount}
style={styles.input}
keyboardType="numeric"
accessibilityLabel="Expense Amount"
/>

<Menu
visible={menuVisible}
onDismiss={() => setMenuVisible(false)}
anchor={
<Button onPress={() => setMenuVisible(true)}>
<Button
onPress={() => setMenuVisible(true)}
accessibilityLabel={`Paid by ${selectedPayerName}`}
accessibilityRole="button"
accessibilityHint="Double tap to change payer"
>
Paid by: {selectedPayerName}
</Button>
}
Expand Down Expand Up @@ -442,6 +457,8 @@ const AddExpenseScreen = ({ route, navigation }) => {
style={styles.button}
loading={isSubmitting}
disabled={isSubmitting}
accessibilityLabel="Add Expense"
accessibilityRole="button"
>
Add Expense
</Button>
Expand Down
6 changes: 6 additions & 0 deletions mobile/screens/EditProfileScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ const EditProfileScreen = ({ navigation }) => {
onPress={pickImage}
icon="camera"
style={styles.imageButton}
accessibilityLabel="Change profile picture"
accessibilityRole="button"
accessibilityHint="Opens your media library to select a new photo"
>
{pickedImage ? "Change Photo" : "Add Photo"}
</Button>
Expand All @@ -113,13 +116,16 @@ const EditProfileScreen = ({ navigation }) => {
value={name}
onChangeText={setName}
style={styles.input}
accessibilityLabel="Full Name"
/>
<Button
mode="contained"
onPress={handleUpdateProfile}
loading={isSubmitting}
disabled={isSubmitting}
style={styles.button}
accessibilityLabel="Save Changes"
accessibilityRole="button"
>
Save Changes
</Button>
Expand Down
13 changes: 12 additions & 1 deletion mobile/screens/FriendsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ const FriendsScreen = () => {
descriptionStyle={{
color: item.netBalance !== 0 ? balanceColor : "gray",
}}
accessibilityRole="button"
accessibilityLabel={`Friend ${item.name}. ${
item.netBalance !== 0 ? balanceText : "Settled up"
}`}
accessibilityHint="Double tap to see balance breakdown"
left={(props) =>
imageUri ? (
<Avatar.Image {...props} size={40} source={{ uri: imageUri }} />
Expand Down Expand Up @@ -207,7 +212,11 @@ const FriendsScreen = () => {
<Appbar.Header>
<Appbar.Content title="Friends" />
</Appbar.Header>
<View style={styles.skeletonContainer}>
<View
style={styles.skeletonContainer}
accessibilityLabel="Loading friends list"
accessibilityRole="progressbar"
>
{Array.from({ length: 5 }).map((_, i) => (
<SkeletonRow key={i} />
))}
Expand All @@ -234,6 +243,8 @@ const FriendsScreen = () => {
size={16}
onPress={() => setShowTooltip(false)}
style={styles.closeButton}
accessibilityLabel="Close tooltip"
accessibilityRole="button"
/>
</View>
</View>
Expand Down
12 changes: 11 additions & 1 deletion mobile/screens/GroupDetailsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ const GroupDetailsScreen = ({ route, navigation }) => {
<IconButton
icon="cog"
onPress={() => navigation.navigate("GroupSettings", { groupId })}
accessibilityLabel="Group settings"
accessibilityRole="button"
/>
),
});
Expand Down Expand Up @@ -101,7 +103,13 @@ const GroupDetailsScreen = ({ route, navigation }) => {
}

return (
<Card style={styles.card}>
<Card
style={styles.card}
accessibilityRole="button"
accessibilityLabel={`Expense: ${item.description}, Amount: ${formatCurrency(
item.amount
)}. Paid by ${getMemberName(item.paidBy || item.createdBy)}. ${balanceText}`}
>
<Card.Content>
<Title>{item.description}</Title>
<Paragraph>Amount: {formatCurrency(item.amount)}</Paragraph>
Expand Down Expand Up @@ -227,6 +235,8 @@ const GroupDetailsScreen = ({ route, navigation }) => {
style={styles.fab}
icon="plus"
onPress={() => navigation.navigate("AddExpense", { groupId: groupId })}
accessibilityLabel="Add expense"
accessibilityRole="button"
/>
</View>
);
Expand Down
18 changes: 18 additions & 0 deletions mobile/screens/GroupSettingsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ const GroupSettingsScreen = ({ route, navigation }) => {
<IconButton
icon="account-remove"
onPress={() => onKick(m.userId, displayName)}
accessibilityLabel={`Remove ${displayName} from group`}
accessibilityRole="button"
accessibilityHint="Removes this member from the group"
/>
) : null
}
Expand Down Expand Up @@ -307,6 +310,7 @@ const GroupSettingsScreen = ({ route, navigation }) => {
onChangeText={setName}
editable={!!isAdmin}
style={{ marginBottom: 12 }}
accessibilityLabel="Group Name"
/>
<Text style={{ marginBottom: 8 }}>Icon</Text>
<View style={styles.iconRow}>
Expand All @@ -317,6 +321,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
style={styles.iconBtn}
onPress={() => setIcon(i)}
disabled={!isAdmin}
accessibilityLabel={`Select icon ${i}`}
accessibilityRole="button"
>
{i}
</Button>
Expand All @@ -329,6 +335,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
disabled={!isAdmin}
icon="image"
style={{ marginRight: 12 }}
accessibilityLabel="Change group image"
accessibilityRole="button"
>
{pickedImage ? "Change Image" : "Upload Image"}
</Button>
Expand All @@ -354,6 +362,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
loading={saving}
disabled={saving}
onPress={onSave}
accessibilityLabel="Save Changes"
accessibilityRole="button"
>
Save Changes
</Button>
Expand All @@ -376,6 +386,8 @@ const GroupSettingsScreen = ({ route, navigation }) => {
mode="outlined"
onPress={onShareInvite}
icon="share-variant"
accessibilityLabel="Share invite code"
accessibilityRole="button"
>
Share invite
</Button>
Expand All @@ -392,6 +404,9 @@ const GroupSettingsScreen = ({ route, navigation }) => {
textColor="#d32f2f"
onPress={onLeave}
icon="logout-variant"
accessibilityLabel="Leave Group"
accessibilityRole="button"
accessibilityHint="You must settle balances before leaving"
>
Leave Group
</Button>
Expand All @@ -402,6 +417,9 @@ const GroupSettingsScreen = ({ route, navigation }) => {
onPress={onDeleteGroup}
icon="delete"
style={{ marginTop: 8 }}
accessibilityLabel="Delete Group"
accessibilityRole="button"
accessibilityHint="Permanently deletes the group and all data"
>
Delete Group
</Button>
Expand Down
15 changes: 14 additions & 1 deletion mobile/screens/HomeScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ const HomeScreen = ({ navigation }) => {
groupIcon,
})
}
accessibilityRole="button"
accessibilityLabel={`Group ${item.name}. ${getSettlementStatusText()}`}
accessibilityHint="Double tap to view group details"
>
<Card.Title
title={item.name}
Expand Down Expand Up @@ -219,12 +222,15 @@ const HomeScreen = ({ navigation }) => {
value={newGroupName}
onChangeText={setNewGroupName}
style={styles.input}
accessibilityLabel="New group name"
/>
<Button
mode="contained"
onPress={handleCreateGroup}
loading={isCreatingGroup}
disabled={isCreatingGroup}
accessibilityLabel="Create Group"
accessibilityRole="button"
>
Create
</Button>
Expand All @@ -233,12 +239,19 @@ const HomeScreen = ({ navigation }) => {

<Appbar.Header>
<Appbar.Content title="Your Groups" />
<Appbar.Action icon="plus" onPress={showModal} />
<Appbar.Action
icon="plus"
onPress={showModal}
accessibilityLabel="Create new group"
accessibilityRole="button"
/>
<Appbar.Action
icon="account-plus"
onPress={() =>
navigation.navigate("JoinGroup", { onGroupJoined: fetchGroups })
}
accessibilityLabel="Join a group"
accessibilityRole="button"
/>
</Appbar.Header>

Expand Down
3 changes: 3 additions & 0 deletions mobile/screens/JoinGroupScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ const JoinGroupScreen = ({ navigation, route }) => {
onChangeText={setJoinCode}
style={styles.input}
autoCapitalize="characters"
accessibilityLabel="Group Join Code"
/>
<Button
mode="contained"
onPress={handleJoinGroup}
loading={isJoining}
disabled={isJoining}
style={styles.button}
accessibilityLabel="Join Group"
accessibilityRole="button"
>
Join Group
</Button>
Expand Down
Loading
Loading