From b460bed4297b5a2428ec8f876bc119a61c0b6086 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:02:42 +0000 Subject: [PATCH 1/4] [jules] style: Add haptic feedback to all mobile buttons Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com> --- .Jules/changelog.md | 7 ++++ .Jules/todo.md | 11 +++--- mobile/components/ui/HapticAppbar.js | 21 ++++++++++++ mobile/components/ui/HapticButton.js | 14 ++++++++ mobile/components/ui/HapticCard.js | 22 ++++++++++++ mobile/components/ui/HapticCheckbox.js | 13 +++++++ mobile/components/ui/HapticFAB.js | 14 ++++++++ mobile/components/ui/HapticIconButton.js | 14 ++++++++ mobile/components/ui/HapticList.js | 24 +++++++++++++ mobile/components/ui/HapticMenu.js | 19 +++++++++++ .../components/ui/HapticSegmentedButtons.js | 13 +++++++ mobile/screens/AccountScreen.js | 11 +++--- mobile/screens/AddExpenseScreen.js | 26 +++++++------- mobile/screens/EditProfileScreen.js | 14 ++++---- mobile/screens/FriendsScreen.js | 7 ++-- mobile/screens/GroupDetailsScreen.js | 26 +++++++------- mobile/screens/GroupSettingsScreen.js | 34 +++++++++---------- mobile/screens/HomeScreen.js | 23 +++++++------ mobile/screens/JoinGroupScreen.js | 10 +++--- mobile/screens/LoginScreen.js | 11 +++--- mobile/screens/SignupScreen.js | 11 +++--- mobile/screens/SplitwiseImportScreen.js | 9 ++--- 22 files changed, 263 insertions(+), 91 deletions(-) create mode 100644 mobile/components/ui/HapticAppbar.js create mode 100644 mobile/components/ui/HapticButton.js create mode 100644 mobile/components/ui/HapticCard.js create mode 100644 mobile/components/ui/HapticCheckbox.js create mode 100644 mobile/components/ui/HapticFAB.js create mode 100644 mobile/components/ui/HapticIconButton.js create mode 100644 mobile/components/ui/HapticList.js create mode 100644 mobile/components/ui/HapticMenu.js create mode 100644 mobile/components/ui/HapticSegmentedButtons.js diff --git a/.Jules/changelog.md b/.Jules/changelog.md index 007d653..7b3396c 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,13 @@ ## [Unreleased] ### Added +- **Mobile Haptics:** Implemented system-wide haptic feedback for all interactive elements. + - **Features:** + - Created `HapticButton`, `HapticIconButton`, `HapticFAB`, `HapticCard`, `HapticList`, `HapticCheckbox`, `HapticMenu`, `HapticSegmentedButtons` wrappers. + - Integrated into all screens (`Home`, `GroupDetails`, `AddExpense`, `Friends`, `Account`, `EditProfile`, `Login`, `Signup`, `JoinGroup`, `GroupSettings`, `SplitwiseImport`). + - Uses `expo-haptics` with `Light` impact style for subtle feedback. + - **Technical:** Centralized haptic logic in `mobile/components/ui/` to ensure consistency and maintainability. + - **Mobile Accessibility:** Completed accessibility audit for all mobile screens. - **Features:** - Added `accessibilityLabel` to all interactive elements (buttons, inputs, list items). diff --git a/.Jules/todo.md b/.Jules/todo.md index de49cb8..ccd795e 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -94,11 +94,12 @@ - Size: ~55 lines - Added: 2026-01-01 -- [ ] **[style]** Haptic feedback on all button presses - - Files: All button interactions across mobile - - Context: Add Expo.Haptics.impactAsync(Light) to buttons - - Impact: Tactile feedback makes app feel responsive - - Size: ~40 lines +- [x] **[style]** Haptic feedback on all button presses + - Completed: 2026-02-07 + - Files: `mobile/components/ui/Haptic*.js`, `mobile/screens/*.js` + - Context: Created comprehensive Haptic UI system wrapping React Native Paper components + - Impact: Tactile feedback makes app feel responsive and native + - Size: ~400 lines - Added: 2026-01-01 --- diff --git a/mobile/components/ui/HapticAppbar.js b/mobile/components/ui/HapticAppbar.js new file mode 100644 index 0000000..87fd8df --- /dev/null +++ b/mobile/components/ui/HapticAppbar.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { Appbar } from 'react-native-paper'; +import * as Haptics from 'expo-haptics'; + +const HapticAppbarAction = ({ onPress, ...props }) => { + const handlePress = (e) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + if (onPress) onPress(e); + }; + return ; +}; + +const HapticAppbarBackAction = ({ onPress, ...props }) => { + const handlePress = (e) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + if (onPress) onPress(e); + }; + return ; +}; + +export { HapticAppbarAction, HapticAppbarBackAction }; diff --git a/mobile/components/ui/HapticButton.js b/mobile/components/ui/HapticButton.js new file mode 100644 index 0000000..0776c19 --- /dev/null +++ b/mobile/components/ui/HapticButton.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { Button } from 'react-native-paper'; +import * as Haptics from 'expo-haptics'; + +const HapticButton = ({ onPress, ...props }) => { + const handlePress = (e) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + if (onPress) onPress(e); + }; + + return + } > {members.map((member) => ( - { setPayerId(member.userId); @@ -394,10 +394,10 @@ const AddExpenseScreen = ({ route, navigation }) => { title={member.user.name} /> ))} - + Split Method - { {renderSplitInputs()} - + ); diff --git a/mobile/screens/EditProfileScreen.js b/mobile/screens/EditProfileScreen.js index 33a8faf..990b8ae 100644 --- a/mobile/screens/EditProfileScreen.js +++ b/mobile/screens/EditProfileScreen.js @@ -1,7 +1,9 @@ import * as ImagePicker from "expo-image-picker"; import { useContext, useState } from "react"; import { Alert, StyleSheet, View } from "react-native"; -import { Appbar, Avatar, Button, TextInput, Title } from "react-native-paper"; +import { Appbar, Avatar, TextInput, Title } from "react-native-paper"; +import HapticButton from '../components/ui/HapticButton'; +import { HapticAppbarBackAction } from '../components/ui/HapticAppbar'; import { updateUser } from "../api/auth"; import { AuthContext } from "../context/AuthContext"; @@ -83,7 +85,7 @@ const EditProfileScreen = ({ navigation }) => { return ( - navigation.goBack()} /> + navigation.goBack()} /> @@ -98,7 +100,7 @@ const EditProfileScreen = ({ navigation }) => { ) : ( )} - + { style={styles.input} accessibilityLabel="Full Name" /> - + ); diff --git a/mobile/screens/FriendsScreen.js b/mobile/screens/FriendsScreen.js index ad9bea2..68ce726 100644 --- a/mobile/screens/FriendsScreen.js +++ b/mobile/screens/FriendsScreen.js @@ -5,11 +5,12 @@ import { Appbar, Avatar, Divider, - IconButton, List, Text, useTheme, } from "react-native-paper"; +import HapticIconButton from '../components/ui/HapticIconButton'; +import { HapticListAccordion } from '../components/ui/HapticList'; import * as Haptics from "expo-haptics"; import { getFriendsBalance, getGroups } from "../api/groups"; import { AuthContext } from "../context/AuthContext"; @@ -94,7 +95,7 @@ const FriendsScreen = () => { } return ( - { all shared groups. Check individual group details for optimized settlement suggestions. - setShowTooltip(false)} diff --git a/mobile/screens/GroupDetailsScreen.js b/mobile/screens/GroupDetailsScreen.js index bcb0018..7ac1ee8 100644 --- a/mobile/screens/GroupDetailsScreen.js +++ b/mobile/screens/GroupDetailsScreen.js @@ -2,13 +2,13 @@ import { useContext, useEffect, useState } from "react"; import { Alert, FlatList, RefreshControl, StyleSheet, Text, View } from "react-native"; import { ActivityIndicator, - Card, - FAB, - IconButton, Paragraph, Title, useTheme, } from "react-native-paper"; +import HapticCard from '../components/ui/HapticCard'; +import HapticFAB from '../components/ui/HapticFAB'; +import HapticIconButton from '../components/ui/HapticIconButton'; import * as Haptics from "expo-haptics"; import { getGroupExpenses, @@ -65,7 +65,7 @@ const GroupDetailsScreen = ({ route, navigation }) => { navigation.setOptions({ title: groupName, headerRight: () => ( - navigation.navigate("GroupSettings", { groupId })} accessibilityLabel="Group settings" @@ -103,22 +103,22 @@ const GroupDetailsScreen = ({ route, navigation }) => { } return ( - - + {item.description} Amount: {formatCurrency(item.amount)} Paid by: {getMemberName(item.paidBy || item.createdBy)} {balanceText} - - + + ); }; @@ -198,12 +198,12 @@ const GroupDetailsScreen = ({ route, navigation }) => { const renderHeader = () => ( <> - - + + Settlement Summary {renderSettlementSummary()} - - + + Expenses @@ -231,7 +231,7 @@ const GroupDetailsScreen = ({ route, navigation }) => { } /> - navigation.navigate("AddExpense", { groupId: groupId })} diff --git a/mobile/screens/GroupSettingsScreen.js b/mobile/screens/GroupSettingsScreen.js index f45c099..6dd6111 100644 --- a/mobile/screens/GroupSettingsScreen.js +++ b/mobile/screens/GroupSettingsScreen.js @@ -17,13 +17,13 @@ import { import { ActivityIndicator, Avatar, - Button, Card, - IconButton, - List, Text, TextInput, } from "react-native-paper"; +import HapticButton from '../components/ui/HapticButton'; +import HapticIconButton from '../components/ui/HapticIconButton'; +import { HapticListItem } from '../components/ui/HapticList'; import { deleteGroup as apiDeleteGroup, leaveGroup as apiLeaveGroup, @@ -264,7 +264,7 @@ const GroupSettingsScreen = ({ route, navigation }) => { const displayName = m.user?.name || "Unknown"; const imageUrl = m.user?.imageUrl; return ( - { } right={() => isAdmin && !isSelf ? ( - onKick(m.userId, displayName)} accessibilityLabel={`Remove ${displayName} from group`} @@ -315,7 +315,7 @@ const GroupSettingsScreen = ({ route, navigation }) => { Icon {ICON_CHOICES.map((i) => ( - + ))} - + {pickedImage?.uri ? ( { ) : null} {isAdmin && ( - + )} @@ -382,7 +382,7 @@ const GroupSettingsScreen = ({ route, navigation }) => { Join Code: {group?.joinCode} - + @@ -398,7 +398,7 @@ const GroupSettingsScreen = ({ route, navigation }) => { - + {isAdmin && ( - + )} diff --git a/mobile/screens/HomeScreen.js b/mobile/screens/HomeScreen.js index 373bb0a..d2f3c38 100644 --- a/mobile/screens/HomeScreen.js +++ b/mobile/screens/HomeScreen.js @@ -4,14 +4,15 @@ import { ActivityIndicator, Appbar, Avatar, - Button, - Card, Modal, Portal, Text, TextInput, useTheme, } from "react-native-paper"; +import HapticButton from '../components/ui/HapticButton'; +import HapticCard from '../components/ui/HapticCard'; +import { HapticAppbarAction } from '../components/ui/HapticAppbar'; import * as Haptics from "expo-haptics"; import { createGroup, getGroups, getOptimizedSettlements } from "../api/groups"; import { AuthContext } from "../context/AuthContext"; @@ -176,7 +177,7 @@ const HomeScreen = ({ navigation }) => { item.imageUrl && /^(https?:|data:image)/.test(item.imageUrl); const groupIcon = item.imageUrl || item.name?.charAt(0) || "?"; return ( - navigation.navigate("GroupDetails", { @@ -189,7 +190,7 @@ const HomeScreen = ({ navigation }) => { accessibilityLabel={`Group ${item.name}. ${getSettlementStatusText()}`} accessibilityHint="Double tap to view group details" > - isImage ? ( @@ -199,12 +200,12 @@ const HomeScreen = ({ navigation }) => { ) } /> - + {getSettlementStatusText()} - - + + ); }; @@ -224,7 +225,7 @@ const HomeScreen = ({ navigation }) => { style={styles.input} accessibilityLabel="New group name" /> - + - - navigation.navigate("JoinGroup", { onGroupJoined: fetchGroups }) diff --git a/mobile/screens/JoinGroupScreen.js b/mobile/screens/JoinGroupScreen.js index a1fc05b..122769d 100644 --- a/mobile/screens/JoinGroupScreen.js +++ b/mobile/screens/JoinGroupScreen.js @@ -1,6 +1,8 @@ import { useContext, useState } from "react"; import { Alert, StyleSheet, View } from "react-native"; -import { Appbar, Button, TextInput, Title } from "react-native-paper"; +import { Appbar, TextInput, Title } from "react-native-paper"; +import HapticButton from '../components/ui/HapticButton'; +import { HapticAppbarBackAction } from '../components/ui/HapticAppbar'; import { joinGroup } from "../api/groups"; import { AuthContext } from "../context/AuthContext"; @@ -35,7 +37,7 @@ const JoinGroupScreen = ({ navigation, route }) => { return ( - navigation.goBack()} /> + navigation.goBack()} /> @@ -48,7 +50,7 @@ const JoinGroupScreen = ({ navigation, route }) => { autoCapitalize="characters" accessibilityLabel="Group Join Code" /> - + ); diff --git a/mobile/screens/LoginScreen.js b/mobile/screens/LoginScreen.js index aa5e94a..194db5b 100644 --- a/mobile/screens/LoginScreen.js +++ b/mobile/screens/LoginScreen.js @@ -1,6 +1,7 @@ import React, { useState, useContext } from 'react'; import { View, StyleSheet, Alert } from 'react-native'; -import { Button, Text, TextInput } from 'react-native-paper'; +import { Text, TextInput } from 'react-native-paper'; +import HapticButton from '../components/ui/HapticButton'; import { AuthContext } from '../context/AuthContext'; const LoginScreen = ({ navigation }) => { @@ -42,7 +43,7 @@ const LoginScreen = ({ navigation }) => { secureTextEntry accessibilityLabel="Password" /> - - + ); }; diff --git a/mobile/screens/SignupScreen.js b/mobile/screens/SignupScreen.js index c3ba18f..d40f362 100644 --- a/mobile/screens/SignupScreen.js +++ b/mobile/screens/SignupScreen.js @@ -1,6 +1,7 @@ import React, { useState, useContext } from 'react'; import { View, StyleSheet, Alert } from 'react-native'; -import { Button, Text, TextInput } from 'react-native-paper'; +import { Text, TextInput } from 'react-native-paper'; +import HapticButton from '../components/ui/HapticButton'; import { AuthContext } from '../context/AuthContext'; const SignupScreen = ({ navigation }) => { @@ -70,7 +71,7 @@ const SignupScreen = ({ navigation }) => { secureTextEntry accessibilityLabel="Confirm Password" /> - - + ); }; diff --git a/mobile/screens/SplitwiseImportScreen.js b/mobile/screens/SplitwiseImportScreen.js index 8abf5cd..d6a65c1 100644 --- a/mobile/screens/SplitwiseImportScreen.js +++ b/mobile/screens/SplitwiseImportScreen.js @@ -2,12 +2,13 @@ import { useState } from "react"; import { Alert, Linking, ScrollView, StyleSheet, View } from "react-native"; import { Appbar, - Button, Card, IconButton, List, Text, } from "react-native-paper"; +import HapticButton from '../components/ui/HapticButton'; +import { HapticAppbarBackAction } from '../components/ui/HapticAppbar'; import { getSplitwiseAuthUrl } from "../api/client"; const SplitwiseImportScreen = ({ navigation }) => { @@ -45,7 +46,7 @@ const SplitwiseImportScreen = ({ navigation }) => { return ( - navigation.goBack()} /> + navigation.goBack()} /> @@ -59,7 +60,7 @@ const SplitwiseImportScreen = ({ navigation }) => { Import all your friends, groups, and expenses with one click - + You'll be redirected to Splitwise to authorize access From fb40f6476b8829ff7463d3bebc664fccb33ae537 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:07:33 +0000 Subject: [PATCH 2/4] [jules] fix: Fix closing tag syntax error in FriendsScreen.js Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com> --- mobile/screens/FriendsScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/screens/FriendsScreen.js b/mobile/screens/FriendsScreen.js index 68ce726..110fd0d 100644 --- a/mobile/screens/FriendsScreen.js +++ b/mobile/screens/FriendsScreen.js @@ -163,7 +163,7 @@ const FriendsScreen = () => { /> ); })} - + ); }; From bb920c5955d3654f2275edcc254c8eae06932b69 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:00:38 +0000 Subject: [PATCH 3/4] [jules] refactor: Use shared withHapticFeedback HOC Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com> --- .Jules/changelog.md | 2 +- mobile/components/ui/HapticAppbar.js | 20 +------ mobile/components/ui/HapticButton.js | 12 +--- mobile/components/ui/HapticCard.js | 14 +---- mobile/components/ui/HapticCheckbox.js | 11 +--- mobile/components/ui/HapticFAB.js | 12 +--- mobile/components/ui/HapticIconButton.js | 12 +--- mobile/components/ui/HapticList.js | 23 +------- mobile/components/ui/HapticMenu.js | 10 +--- .../components/ui/HapticSegmentedButtons.js | 13 ++--- mobile/components/ui/hapticUtils.js | 56 +++++++++++++++++++ mobile/screens/FriendsScreen.js | 4 +- 12 files changed, 81 insertions(+), 108 deletions(-) create mode 100644 mobile/components/ui/hapticUtils.js diff --git a/.Jules/changelog.md b/.Jules/changelog.md index 7b3396c..ecc0f8a 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -9,7 +9,7 @@ ### Added - **Mobile Haptics:** Implemented system-wide haptic feedback for all interactive elements. - **Features:** - - Created `HapticButton`, `HapticIconButton`, `HapticFAB`, `HapticCard`, `HapticList`, `HapticCheckbox`, `HapticMenu`, `HapticSegmentedButtons` wrappers. + - Created `HapticButton`, `HapticIconButton`, `HapticFAB`, `HapticCard`, `HapticList`, `HapticCheckbox`, `HapticMenu`, `HapticSegmentedButtons`, `HapticAppbar` (including `HapticAppbarAction`, `HapticAppbarBackAction`) wrappers. - Integrated into all screens (`Home`, `GroupDetails`, `AddExpense`, `Friends`, `Account`, `EditProfile`, `Login`, `Signup`, `JoinGroup`, `GroupSettings`, `SplitwiseImport`). - Uses `expo-haptics` with `Light` impact style for subtle feedback. - **Technical:** Centralized haptic logic in `mobile/components/ui/` to ensure consistency and maintainability. diff --git a/mobile/components/ui/HapticAppbar.js b/mobile/components/ui/HapticAppbar.js index 87fd8df..b51913b 100644 --- a/mobile/components/ui/HapticAppbar.js +++ b/mobile/components/ui/HapticAppbar.js @@ -1,21 +1,7 @@ -import React from 'react'; import { Appbar } from 'react-native-paper'; -import * as Haptics from 'expo-haptics'; +import { withHapticFeedback } from './hapticUtils'; -const HapticAppbarAction = ({ onPress, ...props }) => { - const handlePress = (e) => { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - if (onPress) onPress(e); - }; - return ; -}; - -const HapticAppbarBackAction = ({ onPress, ...props }) => { - const handlePress = (e) => { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - if (onPress) onPress(e); - }; - return ; -}; +const HapticAppbarAction = withHapticFeedback(Appbar.Action); +const HapticAppbarBackAction = withHapticFeedback(Appbar.BackAction); export { HapticAppbarAction, HapticAppbarBackAction }; diff --git a/mobile/components/ui/HapticButton.js b/mobile/components/ui/HapticButton.js index 0776c19..f8b821a 100644 --- a/mobile/components/ui/HapticButton.js +++ b/mobile/components/ui/HapticButton.js @@ -1,14 +1,6 @@ -import React from 'react'; import { Button } from 'react-native-paper'; -import * as Haptics from 'expo-haptics'; +import { withHapticFeedback } from './hapticUtils'; -const HapticButton = ({ onPress, ...props }) => { - const handlePress = (e) => { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - if (onPress) onPress(e); - }; - - return