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" >