Backend (Spring Boot 3.2 / Java 21 / PostgreSQL): - JWT auth with BCrypt password hashing - User profile + Mifflin-St Jeor BMR calculator - Food search + barcode via OpenFoodFacts API with local cache - Meal CRUD with user data isolation and ownership checks - AI photo analysis (OpenAI Vision) with confidence intervals - AI correction feedback loop for personalisation - Flyway DB migrations + RFC-7807 error responses Mobile (React Native / TypeScript): - Full navigation stack (Auth → Tabs → Home stack) - Design tokens (WCAG 2.2 AA colours, 8px grid, 48px touch targets) - 10 screens: Login, Register, Home, Search, Camera, AI Result, Edit Meal, Daily Details, History, Profile - Confidence-aware calorie display (kcal ± range) - Repeat last meal shortcut + macro tracking Docs: - docs/PLAN-AND-REQUIREMENTS.md - docs/traceability.csv (35 requirements, all Implemented)
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
// Generated by GitHub Copilot
|
|
import React from 'react';
|
|
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
import { FoodItem } from '../services/api';
|
|
import { Colors } from '../theme/colors';
|
|
import { Spacing } from '../theme/spacing';
|
|
|
|
interface FoodRowProps {
|
|
item: FoodItem;
|
|
onSelect: (item: FoodItem) => void;
|
|
}
|
|
|
|
/**
|
|
* Single food result row in the search screen.
|
|
* REQ-MOB-006
|
|
*/
|
|
export default function FoodRow({ item, onSelect }: FoodRowProps) {
|
|
return (
|
|
<TouchableOpacity
|
|
style={styles.row}
|
|
onPress={() => onSelect(item)}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={`${item.name}, ${item.caloriesPer100g} calories per 100 grams`}
|
|
>
|
|
<Text style={styles.name}>{item.name}</Text>
|
|
<Text style={styles.kcal}>{item.caloriesPer100g} kcal / 100g</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
row: {
|
|
minHeight: Spacing.touchTarget,
|
|
paddingHorizontal: Spacing.md,
|
|
paddingVertical: Spacing.sm,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: Colors.gray100,
|
|
justifyContent: 'center',
|
|
},
|
|
name: { fontSize: 16, fontWeight: '500', color: Colors.gray900 },
|
|
kcal: { fontSize: 13, color: Colors.gray500, marginTop: 2 },
|
|
});
|