Implement scan MVP flow
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
import { Pressable, StyleSheet, Text } from 'react-native';
|
||||
|
||||
export default function PrimaryButton({ label, onPress }) {
|
||||
import { colors } from '../utils/constants';
|
||||
|
||||
export default function PrimaryButton({ label, onPress, disabled = false }) {
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
style={({ pressed }) => [
|
||||
styles.button,
|
||||
pressed ? styles.buttonPressed : null
|
||||
pressed ? styles.buttonPressed : null,
|
||||
disabled ? styles.disabled : null
|
||||
]}
|
||||
>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<Text style={[styles.label, disabled ? styles.disabledLabel : null]}>{label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
@@ -18,7 +22,7 @@ export default function PrimaryButton({ label, onPress }) {
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#256f5b',
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 14
|
||||
@@ -27,8 +31,14 @@ const styles = StyleSheet.create({
|
||||
opacity: 0.82
|
||||
},
|
||||
label: {
|
||||
color: '#ffffff',
|
||||
color: colors.surface,
|
||||
fontSize: 16,
|
||||
fontWeight: '700'
|
||||
},
|
||||
disabled: {
|
||||
backgroundColor: colors.border
|
||||
},
|
||||
disabledLabel: {
|
||||
color: colors.muted
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Pressable, StyleSheet, Text } from 'react-native';
|
||||
|
||||
import { colors } from '../utils/constants';
|
||||
|
||||
export default function SecondaryButton({ label, onPress, disabled = false }) {
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
style={({ pressed }) => [
|
||||
styles.button,
|
||||
pressed ? styles.buttonPressed : null,
|
||||
disabled ? styles.disabled : null
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.label, disabled ? styles.disabledLabel : null]}>{label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
alignItems: 'center',
|
||||
borderColor: colors.primary,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 12
|
||||
},
|
||||
buttonPressed: {
|
||||
opacity: 0.75
|
||||
},
|
||||
disabled: {
|
||||
borderColor: colors.border
|
||||
},
|
||||
label: {
|
||||
color: colors.primary,
|
||||
fontSize: 15,
|
||||
fontWeight: '700'
|
||||
},
|
||||
disabledLabel: {
|
||||
color: colors.muted
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function BudgetInput({ value, currency, onChangeValue }) {
|
||||
return (
|
||||
<View style={styles.wrap}>
|
||||
<Text style={styles.label}>Maximales Einkaufsbudget</Text>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.currency}>{currency}</Text>
|
||||
<TextInput
|
||||
keyboardType="decimal-pad"
|
||||
onChangeText={(text) => onChangeValue(Number(text.replace(',', '.')) || 0)}
|
||||
style={styles.input}
|
||||
value={String(value ?? '')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrap: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
gap: 8,
|
||||
padding: 14
|
||||
},
|
||||
label: {
|
||||
color: colors.text,
|
||||
fontSize: 15,
|
||||
fontWeight: '800'
|
||||
},
|
||||
row: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
gap: 10
|
||||
},
|
||||
currency: {
|
||||
color: colors.muted,
|
||||
fontSize: 16,
|
||||
fontWeight: '800'
|
||||
},
|
||||
input: {
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
color: colors.text,
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { StyleSheet, TextInput, View } from 'react-native';
|
||||
|
||||
import PrimaryButton from '../PrimaryButton';
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function ManualSearchInput({ value, onChangeText, onSubmit, placeholder = 'Produktname oder Barcode' }) {
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmit}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={colors.muted}
|
||||
returnKeyType="search"
|
||||
style={styles.input}
|
||||
value={value}
|
||||
/>
|
||||
<PrimaryButton label="Suchen" onPress={onSubmit} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
gap: 10
|
||||
},
|
||||
input: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
color: colors.text,
|
||||
fontSize: 16,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { StyleSheet, Switch, Text, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function SettingsSwitchRow({ label, description, value, onValueChange }) {
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<View style={styles.copy}>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<Text style={styles.description}>{description}</Text>
|
||||
</View>
|
||||
<Switch
|
||||
onValueChange={onValueChange}
|
||||
thumbColor={value ? colors.primary : '#f4f3f4'}
|
||||
trackColor={{ false: colors.border, true: '#b7dbcf' }}
|
||||
value={value}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
padding: 14
|
||||
},
|
||||
copy: {
|
||||
flex: 1
|
||||
},
|
||||
label: {
|
||||
color: colors.text,
|
||||
fontSize: 15,
|
||||
fontWeight: '800'
|
||||
},
|
||||
description: {
|
||||
color: colors.muted,
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
marginTop: 3
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function AppHeader({ title, subtitle, onBack, rightAction }) {
|
||||
return (
|
||||
<View style={styles.header}>
|
||||
<View style={styles.row}>
|
||||
{onBack ? (
|
||||
<Pressable accessibilityRole="button" onPress={onBack} style={styles.backButton}>
|
||||
<Text style={styles.backText}>‹</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
<View style={styles.titleBlock}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : null}
|
||||
</View>
|
||||
{rightAction ? <View style={styles.rightAction}>{rightAction}</View> : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
paddingTop: 6
|
||||
},
|
||||
row: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
gap: 12
|
||||
},
|
||||
backButton: {
|
||||
alignItems: 'center',
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
width: 40
|
||||
},
|
||||
backText: {
|
||||
color: colors.primary,
|
||||
fontSize: 30,
|
||||
lineHeight: 34
|
||||
},
|
||||
titleBlock: {
|
||||
flex: 1
|
||||
},
|
||||
title: {
|
||||
color: colors.text,
|
||||
fontSize: 28,
|
||||
fontWeight: '800'
|
||||
},
|
||||
subtitle: {
|
||||
color: colors.muted,
|
||||
fontSize: 15,
|
||||
lineHeight: 21,
|
||||
marginTop: 4
|
||||
},
|
||||
rightAction: {
|
||||
alignItems: 'flex-end'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
const tabs = [
|
||||
{ id: 'home', label: 'Home', icon: '⌂' },
|
||||
{ id: 'scan', label: 'Scan', icon: '□' },
|
||||
{ id: 'list', label: 'Liste', icon: '✓' },
|
||||
{ id: 'profile', label: 'Profil', icon: '◦' }
|
||||
];
|
||||
|
||||
export default function BottomTabBar({ activeTab, onChange }) {
|
||||
return (
|
||||
<View style={styles.wrap}>
|
||||
{tabs.map((tab) => {
|
||||
const isActive = activeTab === tab.id;
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
key={tab.id}
|
||||
onPress={() => onChange(tab.id)}
|
||||
style={[styles.tab, isActive ? styles.activeTab : null]}
|
||||
>
|
||||
<Text style={[styles.icon, isActive ? styles.activeText : null]}>{tab.icon}</Text>
|
||||
<Text style={[styles.label, isActive ? styles.activeText : null]}>{tab.label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrap: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderTopWidth: 1,
|
||||
bottom: 0,
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
left: 0,
|
||||
padding: 10,
|
||||
position: 'absolute',
|
||||
right: 0
|
||||
},
|
||||
tab: {
|
||||
alignItems: 'center',
|
||||
borderRadius: 8,
|
||||
flex: 1,
|
||||
gap: 2,
|
||||
paddingVertical: 8
|
||||
},
|
||||
activeTab: {
|
||||
backgroundColor: '#e5f1ec'
|
||||
},
|
||||
icon: {
|
||||
color: colors.muted,
|
||||
fontSize: 18,
|
||||
fontWeight: '800'
|
||||
},
|
||||
label: {
|
||||
color: colors.muted,
|
||||
fontSize: 12,
|
||||
fontWeight: '700'
|
||||
},
|
||||
activeText: {
|
||||
color: colors.primary
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function ScreenContainer({ children, scroll = true }) {
|
||||
if (!scroll) {
|
||||
return <View style={styles.container}>{children}</View>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={styles.content} style={styles.container}>
|
||||
{children}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: colors.background,
|
||||
flex: 1
|
||||
},
|
||||
content: {
|
||||
gap: 18,
|
||||
padding: 20,
|
||||
paddingBottom: 110
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function SectionTitle({ children }) {
|
||||
return <Text style={styles.title}>{children}</Text>;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
color: colors.text,
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
marginTop: 4
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
import { formatPrice } from '../../utils/formatPrice';
|
||||
|
||||
export default function ShoppingListItem({ item, onToggle, onDelete }) {
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<Pressable accessibilityRole="checkbox" onPress={onToggle} style={[styles.checkbox, item.checked ? styles.checked : null]}>
|
||||
<Text style={styles.checkText}>{item.checked ? '✓' : ''}</Text>
|
||||
</Pressable>
|
||||
<View style={styles.copy}>
|
||||
<Text style={[styles.name, item.checked ? styles.done : null]}>{item.productName}</Text>
|
||||
<Text style={styles.meta}>Menge {item.quantity} · {formatPrice(item.estimatedPrice, 'CHF')}</Text>
|
||||
</View>
|
||||
<Pressable accessibilityRole="button" onPress={onDelete} style={styles.deleteButton}>
|
||||
<Text style={styles.deleteText}>Löschen</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
padding: 14
|
||||
},
|
||||
checkbox: {
|
||||
alignItems: 'center',
|
||||
borderColor: colors.primary,
|
||||
borderRadius: 6,
|
||||
borderWidth: 2,
|
||||
height: 28,
|
||||
justifyContent: 'center',
|
||||
width: 28
|
||||
},
|
||||
checked: {
|
||||
backgroundColor: colors.primary
|
||||
},
|
||||
checkText: {
|
||||
color: colors.surface,
|
||||
fontWeight: '800'
|
||||
},
|
||||
copy: {
|
||||
flex: 1
|
||||
},
|
||||
name: {
|
||||
color: colors.text,
|
||||
fontSize: 16,
|
||||
fontWeight: '800'
|
||||
},
|
||||
done: {
|
||||
textDecorationLine: 'line-through'
|
||||
},
|
||||
meta: {
|
||||
color: colors.muted,
|
||||
fontSize: 13,
|
||||
marginTop: 3
|
||||
},
|
||||
deleteButton: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 6
|
||||
},
|
||||
deleteText: {
|
||||
color: colors.danger,
|
||||
fontSize: 13,
|
||||
fontWeight: '800'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import PrimaryButton from '../PrimaryButton';
|
||||
import { colors } from '../../utils/constants';
|
||||
import ProductCard from './ProductCard';
|
||||
|
||||
export default function AlternativeProductCard({ item, onChoose, onOpen }) {
|
||||
return (
|
||||
<View style={styles.wrap}>
|
||||
<ProductCard product={item.product} rating={item.rating} onPress={onOpen} />
|
||||
<Text style={styles.reason}>{item.reason}</Text>
|
||||
<PrimaryButton label="Beste Alternative wählen" onPress={onChoose} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrap: {
|
||||
gap: 10
|
||||
},
|
||||
reason: {
|
||||
color: colors.muted,
|
||||
fontSize: 14,
|
||||
lineHeight: 20
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
import { formatPrice } from '../../utils/formatPrice';
|
||||
import ProductScoreBadge from './ProductScoreBadge';
|
||||
|
||||
export default function ProductCard({ product, rating, onPress }) {
|
||||
return (
|
||||
<Pressable accessibilityRole="button" onPress={onPress} style={styles.card}>
|
||||
<View style={styles.topRow}>
|
||||
<View style={styles.copy}>
|
||||
<Text style={styles.name}>{product.name}</Text>
|
||||
<Text style={styles.meta}>{product.brand || 'Ohne Marke'} · {formatPrice(product.price, product.currency)}</Text>
|
||||
</View>
|
||||
{rating ? <ProductScoreBadge status={rating.overallStatus} score={rating.overallScore} /> : null}
|
||||
</View>
|
||||
<Text style={styles.note}>{product.category || 'Produkt'} · Barcode {product.barcode}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
gap: 10,
|
||||
padding: 16
|
||||
},
|
||||
topRow: {
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
gap: 12
|
||||
},
|
||||
copy: {
|
||||
flex: 1
|
||||
},
|
||||
name: {
|
||||
color: colors.text,
|
||||
fontSize: 17,
|
||||
fontWeight: '800'
|
||||
},
|
||||
meta: {
|
||||
color: colors.muted,
|
||||
fontSize: 14,
|
||||
marginTop: 4
|
||||
},
|
||||
note: {
|
||||
color: colors.muted,
|
||||
fontSize: 13
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
const labels = {
|
||||
good: 'Gut',
|
||||
medium: 'Prüfen',
|
||||
critical: 'Meiden'
|
||||
};
|
||||
|
||||
const statusStyles = {
|
||||
good: {
|
||||
backgroundColor: '#dff4e8',
|
||||
color: '#1f7a4d'
|
||||
},
|
||||
medium: {
|
||||
backgroundColor: '#fff4d8',
|
||||
color: '#9a650f'
|
||||
},
|
||||
critical: {
|
||||
backgroundColor: '#fee4e2',
|
||||
color: '#b42318'
|
||||
}
|
||||
};
|
||||
|
||||
export default function ProductScoreBadge({ status, score }) {
|
||||
const style = statusStyles[status] || statusStyles.medium;
|
||||
|
||||
return (
|
||||
<View style={[styles.badge, { backgroundColor: style.backgroundColor }]}>
|
||||
<Text style={[styles.text, { color: style.color }]}>
|
||||
{labels[status] || 'Prüfen'} · {score}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
badge: {
|
||||
alignSelf: 'flex-start',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6
|
||||
},
|
||||
text: {
|
||||
fontSize: 13,
|
||||
fontWeight: '800'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
import ProductScoreBadge from './ProductScoreBadge';
|
||||
|
||||
const labels = {
|
||||
allergies: 'Allergien',
|
||||
budget: 'Budget',
|
||||
nutrition: 'Ernährung',
|
||||
sustainability: 'Nachhaltigkeit'
|
||||
};
|
||||
|
||||
export default function RatingCategoryRow({ category, rating }) {
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<View style={styles.copy}>
|
||||
<Text style={styles.title}>{labels[category] || category}</Text>
|
||||
<Text style={styles.message}>{rating.message}</Text>
|
||||
</View>
|
||||
<ProductScoreBadge status={rating.status} score={rating.status === 'good' ? 25 : rating.status === 'medium' ? 14 : 0} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
padding: 14
|
||||
},
|
||||
copy: {
|
||||
flex: 1
|
||||
},
|
||||
title: {
|
||||
color: colors.text,
|
||||
fontSize: 15,
|
||||
fontWeight: '800',
|
||||
marginBottom: 4
|
||||
},
|
||||
message: {
|
||||
color: colors.muted,
|
||||
fontSize: 14,
|
||||
lineHeight: 20
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { CameraView } from 'expo-camera';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function BarcodeScannerView({ isScanning, onBarcodeScanned }) {
|
||||
return (
|
||||
<View style={styles.wrap}>
|
||||
<CameraView
|
||||
barcodeScannerSettings={{
|
||||
barcodeTypes: ['ean13', 'ean8', 'upc_a', 'upc_e', 'code128', 'qr']
|
||||
}}
|
||||
onBarcodeScanned={isScanning ? onBarcodeScanned : undefined}
|
||||
style={styles.camera}
|
||||
>
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.frame} />
|
||||
<Text style={styles.hint}>
|
||||
Barcode in den Rahmen halten. Bei Problemen manuell suchen.
|
||||
</Text>
|
||||
</View>
|
||||
</CameraView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrap: {
|
||||
backgroundColor: '#000000',
|
||||
borderRadius: 8,
|
||||
height: 320,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
camera: {
|
||||
flex: 1
|
||||
},
|
||||
overlay: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
padding: 24
|
||||
},
|
||||
frame: {
|
||||
borderColor: colors.surface,
|
||||
borderRadius: 8,
|
||||
borderWidth: 3,
|
||||
height: 150,
|
||||
width: '82%'
|
||||
},
|
||||
hint: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.55)',
|
||||
borderRadius: 8,
|
||||
color: colors.surface,
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
marginTop: 18,
|
||||
padding: 10,
|
||||
textAlign: 'center'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Modal, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import PrimaryButton from '../PrimaryButton';
|
||||
import SecondaryButton from '../SecondaryButton';
|
||||
import { colors } from '../../utils/constants';
|
||||
|
||||
export default function ScanErrorModal({ visible, message, onRetry, onManualSearch, onClose }) {
|
||||
return (
|
||||
<Modal animationType="fade" onRequestClose={onClose} transparent visible={visible}>
|
||||
<View style={styles.backdrop}>
|
||||
<View style={styles.modal}>
|
||||
<Text style={styles.title}>Scan nicht erfolgreich</Text>
|
||||
<Text style={styles.message}>{message}</Text>
|
||||
<PrimaryButton label="Erneut scannen" onPress={onRetry} />
|
||||
<SecondaryButton label="Manuell eingeben" onPress={onManualSearch} />
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
backdrop: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.45)',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
padding: 24
|
||||
},
|
||||
modal: {
|
||||
backgroundColor: colors.surface,
|
||||
borderRadius: 8,
|
||||
gap: 14,
|
||||
padding: 20,
|
||||
width: '100%'
|
||||
},
|
||||
title: {
|
||||
color: colors.text,
|
||||
fontSize: 20,
|
||||
fontWeight: '800'
|
||||
},
|
||||
message: {
|
||||
color: colors.muted,
|
||||
fontSize: 15,
|
||||
lineHeight: 21
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user