From 57d264e27eb6e3ec5e4e0d1ac5318bab7907a273 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 17 Apr 2026 00:28:35 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Enhance=20FlightScree?=
=?UTF-8?q?n=20accessibility=20and=20feedback?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This update improves the FlightScreen by adding tactile feedback and
comprehensive accessibility support to the flight cards:
- Integrated `expo-haptics` for threshold crossing and success feedback
during flight pinning gestures.
- Added detailed `accessibilityLabel` to flight cards, providing
Flight Number, Airline, Origin/Destination, Time, and Status.
- Implemented `accessibilityActions` ('togglePin') and
`onAccessibilityAction` for pinning/unpinning flights without gestures.
- Replaced hardcoded Italian strings in the notification toggle with
localized translation keys.
- Added `flightAccessibilityPinHint` and `flightAccessibilityUnpinHint`
to the translation system.
- Refactored `SwipeableFlightCard` to support prop spreading for a11y.
Co-authored-by: TargetMisser <52361977+TargetMisser@users.noreply.github.com>
---
src/i18n/translations.ts | 4 ++++
src/screens/FlightScreen.tsx | 43 ++++++++++++++++++++++++++++++++----
2 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/src/i18n/translations.ts b/src/i18n/translations.ts
index 8ccd1e2..8989360 100644
--- a/src/i18n/translations.ts
+++ b/src/i18n/translations.ts
@@ -106,6 +106,8 @@ const it = {
flightNotifMsg1: 'Programmate {count} notifiche: arrivi voli (15 min prima) + fine turno.',
flightNotifMsg0: 'Nessun volo futuro trovato, ma riceverai la notifica di fine turno.',
flightNotifAccessEnable: 'Attiva notifiche voli', flightNotifAccessDisable: 'Disattiva notifiche voli',
+ flightAccessibilityPinHint: 'Tocca due volte per pinnare il volo e ricevere notifiche specifiche.',
+ flightAccessibilityUnpinHint: 'Tocca due volte per rimuovere il pin da questo volo.',
// Phonebook
phonebookTitle: 'Rubrica', contactAdd: 'Aggiungi',
contactSearch: 'Cerca nome o numero...', contactAll: 'Tutti',
@@ -264,6 +266,8 @@ const en: typeof it = {
flightNotifMsg1: '{count} notifications scheduled: flight arrivals (15 min before) + end of shift.',
flightNotifMsg0: 'No future flights found, but you will receive the end-of-shift notification.',
flightNotifAccessEnable: 'Enable flight notifications', flightNotifAccessDisable: 'Disable flight notifications',
+ flightAccessibilityPinHint: 'Double tap to pin this flight and receive specific notifications.',
+ flightAccessibilityUnpinHint: 'Double tap to unpin this flight.',
// Phonebook
phonebookTitle: 'Phonebook', contactAdd: 'Add',
contactSearch: 'Search name or number...', contactAll: 'All',
diff --git a/src/screens/FlightScreen.tsx b/src/screens/FlightScreen.tsx
index 7b37ca6..da0f6ac 100644
--- a/src/screens/FlightScreen.tsx
+++ b/src/screens/FlightScreen.tsx
@@ -3,7 +3,9 @@ import {
View, Text, StyleSheet, ActivityIndicator, Modal,
FlatList, TouchableOpacity, RefreshControl, Image, Alert,
Animated, PanResponder, NativeModules, Platform,
+ type AccessibilityActionEvent,
} from 'react-native';
+import * as Haptics from 'expo-haptics';
import * as Calendar from 'expo-calendar';
import * as Notifications from 'expo-notifications';
import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -61,24 +63,37 @@ function LogoPill({ iataCode, airlineName, color }: { iataCode: string; airlineN
const SWIPE_THRESHOLD = 80;
function SwipeableFlightCardComponent({
- children, isPinned, onToggle,
+ children, isPinned, onToggle, ...rest
}: {
children: React.ReactNode;
isPinned: boolean;
onToggle: () => void;
+ [key: string]: any;
}) {
const translateX = useRef(new Animated.Value(0)).current;
const onToggleRef = useRef(onToggle);
onToggleRef.current = onToggle;
+ const hasTriggeredHaptic = useRef(false);
+
const panResponder = useMemo(() => PanResponder.create({
onMoveShouldSetPanResponder: (_, g) =>
Math.abs(g.dx) > 15 && Math.abs(g.dx) > Math.abs(g.dy) * 1.5,
onPanResponderMove: (_, g) => {
- if (g.dx < 0) translateX.setValue(g.dx);
+ if (g.dx < 0) {
+ translateX.setValue(g.dx);
+ if (Math.abs(g.dx) >= SWIPE_THRESHOLD && !hasTriggeredHaptic.current) {
+ Haptics.selectionAsync();
+ hasTriggeredHaptic.current = true;
+ } else if (Math.abs(g.dx) < SWIPE_THRESHOLD && hasTriggeredHaptic.current) {
+ hasTriggeredHaptic.current = false;
+ }
+ }
},
onPanResponderRelease: (_, g) => {
+ hasTriggeredHaptic.current = false;
if (g.dx < -SWIPE_THRESHOLD) {
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
Animated.timing(translateX, { toValue: -SWIPE_THRESHOLD, duration: 100, useNativeDriver: true }).start(() => {
onToggleRef.current();
Animated.spring(translateX, { toValue: 0, useNativeDriver: true, tension: 120, friction: 10 }).start();
@@ -94,7 +109,11 @@ function SwipeableFlightCardComponent({
return (
-
+
{children}
@@ -594,6 +613,18 @@ export default function FlightScreen() {
const flightId = item.flight?.identification?.number?.default || null;
const isPinned = flightId !== null && flightId === pinnedFlightId;
+ // Accessibility label
+ const direction = activeTab === 'arrivals' ? t('homeArrival') : t('homeDeparture');
+ const pinStatus = isPinned ? `${t('homePinned')}, ` : '';
+ const accessibilityLabel = `${pinStatus}${flightNumber}, ${airline}, ${direction} ${originDest}, ${time}, ${statusText}`;
+
+ const onAccessibilityAction = (event: AccessibilityActionEvent) => {
+ if (event.nativeEvent.actionName === 'togglePin') {
+ if (isPinned) unpinFlight();
+ else pinFlight(item);
+ }
+ };
+
const normFn = normalizeFlightNumber(flightNumber);
const normalizeForMatching = (s: string) => s.replace(/[\s\-_]/g, '').toUpperCase();
const normFnStripped = normalizeForMatching(normFn);
@@ -609,6 +640,10 @@ export default function FlightScreen() {
isPinned ? unpinFlight() : pinFlight(item)}
+ accessible
+ accessibilityLabel={accessibilityLabel}
+ accessibilityActions={[{ name: 'togglePin', label: isPinned ? t('flightAccessibilityUnpinHint') : t('flightAccessibilityPinHint') }]}
+ onAccessibilityAction={onAccessibilityAction}
>
{isPinned && {t('flightPinned')}}
@@ -769,7 +804,7 @@ export default function FlightScreen() {
onPress={toggleNotifications}
activeOpacity={0.8}
accessible
- accessibilityLabel={notifsEnabled ? 'Disattiva notifiche voli' : 'Attiva notifiche voli'}
+ accessibilityLabel={notifsEnabled ? t('flightNotifAccessDisable') : t('flightNotifAccessEnable')}
accessibilityRole="button"
>