From 982963cc2f6c216f8d179e4851fa7407a720384f Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Wed, 15 Apr 2026 20:14:32 +0000
Subject: [PATCH] [jules] enhance: Add global Toast notification system for
mobile
- Created ToastContext using react-native-paper Snackbar
- Replaced intrusive Alert.alert calls with non-blocking showToast in Auth and Home screens
- Updated .Jules tracking files
Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com>
---
.Jules/changelog.md | 7 ++++
.Jules/todo.md | 5 +++
mobile/App.js | 5 ++-
mobile/context/ToastContext.js | 73 ++++++++++++++++++++++++++++++++++
mobile/screens/HomeScreen.js | 11 +++--
mobile/screens/LoginScreen.js | 10 +++--
mobile/screens/SignupScreen.js | 17 ++++----
7 files changed, 111 insertions(+), 17 deletions(-)
create mode 100644 mobile/context/ToastContext.js
diff --git a/.Jules/changelog.md b/.Jules/changelog.md
index 11fc864e..05e9ecae 100644
--- a/.Jules/changelog.md
+++ b/.Jules/changelog.md
@@ -7,6 +7,13 @@
## [Unreleased]
### Added
+- **Mobile Toast Notifications:** Added a global Toast context for non-blocking feedback messages.
+ - **Features:**
+ - Uses React Native Paper `Snackbar` component.
+ - Replaces intrusive `Alert.alert` calls.
+ - Themed according to message type ('success', 'error', 'info').
+ - **Technical:** Created `mobile/context/ToastContext.js` and wrapped App. Integrated into Auth and Home screens.
+
- **Password Strength Meter:** Added a visual password strength indicator to the signup form.
- **Features:**
- Real-time strength calculation (Length, Uppercase, Lowercase, Number, Symbol).
diff --git a/.Jules/todo.md b/.Jules/todo.md
index ebb0c7a5..123e184f 100644
--- a/.Jules/todo.md
+++ b/.Jules/todo.md
@@ -143,6 +143,11 @@
## ✅ Completed Tasks
+- [x] **[ux]** Global Toast Notification System for Mobile
+ - Completed: 2026-04-15
+ - Files modified: `mobile/context/ToastContext.js`, `mobile/App.js`, `mobile/screens/LoginScreen.js`, `mobile/screens/SignupScreen.js`, `mobile/screens/HomeScreen.js`
+ - Impact: Replaces intrusive Alert.alert with non-blocking modern snackbar notifications for form validation and success/error messages.
+
- [x] **[ux]** Comprehensive empty states with illustrations
- Completed: 2026-01-01
- Files modified: `web/components/ui/EmptyState.tsx`, `web/pages/Groups.tsx`, `web/pages/Friends.tsx`
diff --git a/mobile/App.js b/mobile/App.js
index f5496adf..acbb8612 100644
--- a/mobile/App.js
+++ b/mobile/App.js
@@ -2,12 +2,15 @@ import React from 'react';
import AppNavigator from './navigation/AppNavigator';
import { PaperProvider } from 'react-native-paper';
import { AuthProvider } from './context/AuthContext';
+import { ToastProvider } from './context/ToastContext';
export default function App() {
return (
-
+
+
+
);
diff --git a/mobile/context/ToastContext.js b/mobile/context/ToastContext.js
new file mode 100644
index 00000000..38936123
--- /dev/null
+++ b/mobile/context/ToastContext.js
@@ -0,0 +1,73 @@
+import React, { createContext, useState, useContext, useCallback } from 'react';
+import { StyleSheet } from 'react-native';
+import { Snackbar, useTheme } from 'react-native-paper';
+
+export const ToastContext = createContext();
+
+export const ToastProvider = ({ children }) => {
+ const [visible, setVisible] = useState(false);
+ const [message, setMessage] = useState('');
+ const [type, setType] = useState('info'); // 'info', 'success', 'error'
+ const theme = useTheme();
+
+ const showToast = useCallback((msg, toastType = 'info') => {
+ setMessage(msg);
+ setType(toastType);
+ setVisible(true);
+ }, []);
+
+ const hideToast = useCallback(() => {
+ setVisible(false);
+ }, []);
+
+ const getBackgroundColor = () => {
+ switch (type) {
+ case 'success':
+ return '#4CAF50';
+ case 'error':
+ return '#F44336';
+ case 'info':
+ default:
+ return theme.colors.elevation.level3;
+ }
+ };
+
+ const getTextColor = () => {
+ switch (type) {
+ case 'success':
+ case 'error':
+ return '#FFFFFF';
+ case 'info':
+ default:
+ return theme.colors.onSurface;
+ }
+ };
+
+ return (
+
+ {children}
+
+ {message}
+
+
+ );
+};
+
+export const useToast = () => useContext(ToastContext);
+
+const styles = StyleSheet.create({
+ snackbar: {
+ marginBottom: 80, // Avoid bottom nav bar
+ },
+});
diff --git a/mobile/screens/HomeScreen.js b/mobile/screens/HomeScreen.js
index d2f3c383..32c4bbbb 100644
--- a/mobile/screens/HomeScreen.js
+++ b/mobile/screens/HomeScreen.js
@@ -1,5 +1,5 @@
import { useContext, useEffect, useState } from "react";
-import { Alert, FlatList, RefreshControl, StyleSheet, View } from "react-native";
+import { FlatList, RefreshControl, StyleSheet, View } from "react-native";
import {
ActivityIndicator,
Appbar,
@@ -17,10 +17,12 @@ import * as Haptics from "expo-haptics";
import { createGroup, getGroups, getOptimizedSettlements } from "../api/groups";
import { AuthContext } from "../context/AuthContext";
import { formatCurrency, getCurrencySymbol } from "../utils/currency";
+import { useToast } from "../context/ToastContext";
const HomeScreen = ({ navigation }) => {
const { token, logout, user } = useContext(AuthContext);
const theme = useTheme();
+ const { showToast } = useToast();
const [groups, setGroups] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false);
@@ -94,7 +96,7 @@ const HomeScreen = ({ navigation }) => {
}
} catch (error) {
console.error("Failed to fetch groups:", error);
- Alert.alert("Error", "Failed to fetch groups.");
+ showToast("Failed to fetch groups.", "error");
} finally {
if (showLoading) setIsLoading(false);
}
@@ -115,7 +117,7 @@ const HomeScreen = ({ navigation }) => {
const handleCreateGroup = async () => {
if (!newGroupName) {
- Alert.alert("Error", "Please enter a group name.");
+ showToast("Please enter a group name.", "error");
return;
}
setIsCreatingGroup(true);
@@ -123,10 +125,11 @@ const HomeScreen = ({ navigation }) => {
await createGroup(newGroupName);
hideModal();
setNewGroupName("");
+ showToast("Group created successfully!", "success");
await fetchGroups(); // Refresh the groups list
} catch (error) {
console.error("Failed to create group:", error);
- Alert.alert("Error", "Failed to create group.");
+ showToast("Failed to create group.", "error");
} finally {
setIsCreatingGroup(false);
}
diff --git a/mobile/screens/LoginScreen.js b/mobile/screens/LoginScreen.js
index 194db5bb..594ed264 100644
--- a/mobile/screens/LoginScreen.js
+++ b/mobile/screens/LoginScreen.js
@@ -1,25 +1,29 @@
import React, { useState, useContext } from 'react';
-import { View, StyleSheet, Alert } from 'react-native';
+import { View, StyleSheet } from 'react-native';
import { Text, TextInput } from 'react-native-paper';
import HapticButton from '../components/ui/HapticButton';
import { AuthContext } from '../context/AuthContext';
+import { useToast } from '../context/ToastContext';
const LoginScreen = ({ navigation }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { login } = useContext(AuthContext);
+ const { showToast } = useToast();
const handleLogin = async () => {
if (!email || !password) {
- Alert.alert('Error', 'Please enter both email and password.');
+ showToast('Please enter both email and password.', 'error');
return;
}
setIsLoading(true);
const success = await login(email, password);
setIsLoading(false);
if (!success) {
- Alert.alert('Login Failed', 'Invalid email or password. Please try again.');
+ showToast('Invalid email or password. Please try again.', 'error');
+ } else {
+ showToast('Logged in successfully!', 'success');
}
};
diff --git a/mobile/screens/SignupScreen.js b/mobile/screens/SignupScreen.js
index d40f3627..c0619766 100644
--- a/mobile/screens/SignupScreen.js
+++ b/mobile/screens/SignupScreen.js
@@ -1,8 +1,9 @@
import React, { useState, useContext } from 'react';
-import { View, StyleSheet, Alert } from 'react-native';
+import { View, StyleSheet, ScrollView } from 'react-native';
import { Text, TextInput } from 'react-native-paper';
import HapticButton from '../components/ui/HapticButton';
import { AuthContext } from '../context/AuthContext';
+import { useToast } from '../context/ToastContext';
const SignupScreen = ({ navigation }) => {
const [name, setName] = useState('');
@@ -11,27 +12,25 @@ const SignupScreen = ({ navigation }) => {
const [confirmPassword, setConfirmPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { signup } = useContext(AuthContext);
+ const { showToast } = useToast();
const handleSignup = async () => {
if (!name || !email || !password || !confirmPassword) {
- Alert.alert('Error', 'Please fill in all fields.');
+ showToast('Please fill in all fields.', 'error');
return;
}
if (password !== confirmPassword) {
- Alert.alert('Error', "Passwords don't match!");
+ showToast("Passwords don't match!", 'error');
return;
}
setIsLoading(true);
const success = await signup(name, email, password);
setIsLoading(false);
if (success) {
- Alert.alert(
- 'Success',
- 'Your account has been created successfully. Please log in.',
- [{ text: 'OK', onPress: () => navigation.navigate('Login') }]
- );
+ showToast('Account created successfully! Please login.', 'success');
+ navigation.navigate('Login');
} else {
- Alert.alert('Signup Failed', 'An error occurred. Please try again.');
+ showToast('Signup failed. An error occurred. Please try again.', 'error');
}
};