847 lines
24 KiB
TypeScript
847 lines
24 KiB
TypeScript
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
Alert,
|
|
Image,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TouchableOpacity,
|
|
View,
|
|
Dimensions,
|
|
} from 'react-native';
|
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { useRouter } from 'expo-router';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { useApp } from '../../context/AppContext';
|
|
import { useColors } from '../../constants/Colors';
|
|
import { ThemeBackdrop } from '../../components/ThemeBackdrop';
|
|
import { SafeImage } from '../../components/SafeImage';
|
|
import { Plant } from '../../types';
|
|
import { useCoachMarks } from '../../context/CoachMarksContext';
|
|
|
|
const { width: SCREEN_W, height: SCREEN_H } = Dimensions.get('window');
|
|
|
|
type FilterKey = 'all' | 'today' | 'week' | 'healthy' | 'dormant';
|
|
|
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
const CONTENT_BOTTOM_PADDING = 12;
|
|
const FAB_BOTTOM_OFFSET = 16;
|
|
|
|
function OnboardingChecklist({ plantsCount, colors, router, t }: { plantsCount: number; colors: any; router: any; t: any }) {
|
|
const checklist = [
|
|
{ id: 'scan', label: t.stepScan, completed: plantsCount > 0, icon: 'camera-outline', route: '/scanner' },
|
|
{ id: 'lexicon', label: t.stepLexicon, completed: false, icon: 'search-outline', route: '/lexicon' },
|
|
{ id: 'theme', label: t.stepTheme, completed: false, icon: 'color-palette-outline', route: '/profile/preferences' },
|
|
];
|
|
|
|
return (
|
|
<View style={[styles.checklistCard, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
|
<Text style={[styles.checklistTitle, { color: colors.text }]}>{t.nextStepsTitle}</Text>
|
|
<View style={styles.checklistGrid}>
|
|
{checklist.map((item) => (
|
|
<TouchableOpacity
|
|
key={item.id}
|
|
style={styles.checklistItem}
|
|
onPress={() => {
|
|
if (item.id === 'theme') {
|
|
router.push('/profile/preferences');
|
|
} else if (item.id === 'scan') {
|
|
router.push('/scanner');
|
|
} else if (item.id === 'lexicon') {
|
|
router.push('/lexicon');
|
|
} else {
|
|
router.push(item.route);
|
|
}
|
|
}}
|
|
disabled={item.completed}
|
|
>
|
|
<View style={[styles.checkIcon, { backgroundColor: item.completed ? colors.successSoft : colors.surfaceMuted }]}>
|
|
<Ionicons
|
|
name={item.completed ? 'checkmark-circle' : item.icon as any}
|
|
size={18}
|
|
color={item.completed ? colors.success : colors.textMuted}
|
|
/>
|
|
</View>
|
|
<Text
|
|
style={[
|
|
styles.checklistText,
|
|
{
|
|
color: item.completed ? colors.textMuted : colors.text,
|
|
textDecorationLine: item.completed ? 'line-through' : 'none',
|
|
},
|
|
]}
|
|
numberOfLines={1}
|
|
>
|
|
{item.label}
|
|
</Text>
|
|
{!item.completed && <Ionicons name="chevron-forward" size={12} color={colors.textMuted} />}
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const getDaysUntilWatering = (plant: Plant): number => {
|
|
const lastWateredTs = new Date(plant.lastWatered).getTime();
|
|
if (Number.isNaN(lastWateredTs)) return 0;
|
|
|
|
const dueTs = lastWateredTs + (plant.careInfo.waterIntervalDays * DAY_MS);
|
|
const remainingMs = dueTs - Date.now();
|
|
if (remainingMs <= 0) return 0;
|
|
|
|
return Math.ceil(remainingMs / DAY_MS);
|
|
};
|
|
|
|
export default function HomeScreen() {
|
|
const {
|
|
plants,
|
|
isLoadingPlants,
|
|
profileImageUri,
|
|
profileName,
|
|
billingSummary,
|
|
isLoadingBilling,
|
|
language,
|
|
t,
|
|
isDarkMode,
|
|
colorPalette,
|
|
} = useApp();
|
|
const colors = useColors(isDarkMode, colorPalette);
|
|
const router = useRouter();
|
|
const insets = useSafeAreaInsets();
|
|
const [activeFilter, setActiveFilter] = useState<FilterKey>('all');
|
|
const { registerLayout, startTour } = useCoachMarks();
|
|
const fabRef = useRef<View>(null);
|
|
|
|
// Tour nach Registrierung starten
|
|
useEffect(() => {
|
|
const checkTour = async () => {
|
|
const flag = await AsyncStorage.getItem('greenlens_show_tour');
|
|
if (flag !== 'true') return;
|
|
await AsyncStorage.removeItem('greenlens_show_tour');
|
|
|
|
// 1 Sekunde warten, dann Tour starten
|
|
setTimeout(() => {
|
|
// Tab-Positionen approximieren (gleichmäßig verteilt)
|
|
const tabBarBottom = SCREEN_H - 85;
|
|
const tabW = SCREEN_W / 3;
|
|
registerLayout('tab_search', { x: tabW, y: tabBarBottom + 8, width: tabW, height: 52 });
|
|
registerLayout('tab_profile', { x: tabW * 2, y: tabBarBottom + 8, width: tabW, height: 52 });
|
|
|
|
startTour([
|
|
{
|
|
elementKey: 'fab',
|
|
title: t.tourFabTitle,
|
|
description: t.tourFabDesc,
|
|
tooltipSide: 'above',
|
|
},
|
|
{
|
|
elementKey: 'tab_search',
|
|
title: t.tourSearchTitle,
|
|
description: t.tourSearchDesc,
|
|
tooltipSide: 'above',
|
|
},
|
|
{
|
|
elementKey: 'tab_profile',
|
|
title: t.tourProfileTitle,
|
|
description: t.tourProfileDesc,
|
|
tooltipSide: 'above',
|
|
},
|
|
]);
|
|
}, 1000);
|
|
};
|
|
checkTour();
|
|
}, []);
|
|
|
|
const copy = t;
|
|
const greetingText = useMemo(() => {
|
|
const hour = new Date().getHours();
|
|
if (hour < 12) return copy.greetingMorning;
|
|
if (hour < 18) return copy.greetingAfternoon;
|
|
return copy.greetingEvening;
|
|
}, [copy.greetingAfternoon, copy.greetingEvening, copy.greetingMorning]);
|
|
const creditsText = useMemo(() => {
|
|
if (isLoadingBilling && !billingSummary) {
|
|
return '...';
|
|
}
|
|
if (!billingSummary) {
|
|
return `-- ${copy.creditsLabel}`;
|
|
}
|
|
return `${billingSummary.credits.available} ${copy.creditsLabel}`;
|
|
}, [billingSummary, copy.creditsLabel, isLoadingBilling]);
|
|
|
|
const thirstyCount = useMemo(
|
|
() => plants.filter(plant => getDaysUntilWatering(plant) === 0).length,
|
|
[plants]
|
|
);
|
|
const dueTodayPlants = useMemo(
|
|
() => plants.filter(plant => getDaysUntilWatering(plant) === 0),
|
|
[plants]
|
|
);
|
|
|
|
const filteredPlants = useMemo(() => {
|
|
return plants.filter((plant) => {
|
|
if (activeFilter === 'all') return true;
|
|
|
|
const daysUntil = getDaysUntilWatering(plant);
|
|
|
|
if (activeFilter === 'today') return daysUntil === 0;
|
|
if (activeFilter === 'week') return daysUntil <= 7;
|
|
if (activeFilter === 'healthy') return daysUntil >= 2;
|
|
return plant.careInfo.waterIntervalDays >= 14;
|
|
});
|
|
}, [plants, activeFilter]);
|
|
|
|
const chips: Array<{ key: FilterKey; label: string }> = [
|
|
{ key: 'all', label: copy.all },
|
|
{ key: 'today', label: copy.today },
|
|
{ key: 'week', label: copy.week },
|
|
{ key: 'healthy', label: copy.healthy },
|
|
{ key: 'dormant', label: copy.dormant },
|
|
];
|
|
|
|
const handleBellPress = () => {
|
|
setActiveFilter('today');
|
|
|
|
if (dueTodayPlants.length === 0) {
|
|
Alert.alert(copy.reminderTitle, copy.reminderNone);
|
|
return;
|
|
}
|
|
|
|
const previewNames = dueTodayPlants
|
|
.slice(0, 6)
|
|
.map((plant) => `- ${plant.name}`)
|
|
.join('\n');
|
|
const remainingCount = dueTodayPlants.length - 6;
|
|
const remainingText = remainingCount > 0
|
|
? `\n+ ${remainingCount} ${copy.more}`
|
|
: '';
|
|
|
|
Alert.alert(
|
|
copy.reminderTitle,
|
|
`${copy.reminderDue.replace('{0}', dueTodayPlants.length.toString())}\n\n${previewNames}${remainingText}`
|
|
);
|
|
};
|
|
|
|
if (isLoadingPlants) {
|
|
return (
|
|
<SafeAreaView style={[styles.loadingContainer, { backgroundColor: colors.background }]} edges={['top', 'left', 'right']}>
|
|
<ThemeBackdrop colors={colors} />
|
|
<ActivityIndicator size="large" color={colors.primary} />
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]} edges={['top', 'left', 'right']}>
|
|
<ThemeBackdrop colors={colors} />
|
|
|
|
<ScrollView
|
|
contentContainerStyle={[
|
|
styles.content,
|
|
{ paddingBottom: Math.max(CONTENT_BOTTOM_PADDING, insets.bottom + CONTENT_BOTTOM_PADDING) },
|
|
]}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
<View style={styles.header}>
|
|
<View style={styles.headerLeft}>
|
|
<View style={[styles.avatarWrap, { backgroundColor: colors.primaryDark }]}>
|
|
{profileImageUri ? (
|
|
<Image source={{ uri: profileImageUri }} style={styles.avatarImage} />
|
|
) : (
|
|
<Ionicons name="leaf" size={20} color={colors.iconOnImage} />
|
|
)}
|
|
</View>
|
|
<View style={styles.headerTextBlock}>
|
|
<Text style={[styles.greetingText, { color: colors.textSecondary }]}>{greetingText}</Text>
|
|
<View style={styles.nameRow}>
|
|
<Text style={[styles.nameText, { color: colors.text }]} numberOfLines={1}>
|
|
{profileName || ''}
|
|
</Text>
|
|
<View
|
|
style={[
|
|
styles.creditsPill,
|
|
{
|
|
backgroundColor: colors.cardBg,
|
|
borderColor: colors.cardBorder,
|
|
},
|
|
]}
|
|
>
|
|
<Text style={[styles.creditsText, { color: colors.textSecondary }]} numberOfLines={1}>
|
|
{creditsText}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.bellBtn,
|
|
{
|
|
backgroundColor: colors.cardBg,
|
|
borderColor: colors.cardBorder,
|
|
shadowColor: colors.cardShadow,
|
|
},
|
|
]}
|
|
onPress={handleBellPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<Ionicons name="notifications-outline" size={20} color={colors.text} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={[styles.priorityCard, { backgroundColor: colors.primaryDark }]}>
|
|
<View style={styles.priorityLabelRow}>
|
|
<Ionicons name="water-outline" size={14} color={colors.heroButton} />
|
|
<Text style={[styles.priorityLabel, { color: colors.heroButton }]}>{copy.needsWaterToday}</Text>
|
|
</View>
|
|
|
|
<Text style={[styles.priorityTitle, { color: colors.iconOnImage }]}>
|
|
{copy.plantsThirsty.replace('{0}', thirstyCount.toString())}
|
|
</Text>
|
|
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.priorityButton,
|
|
{
|
|
backgroundColor: colors.heroButton,
|
|
borderColor: colors.heroButtonBorder,
|
|
},
|
|
]}
|
|
onPress={handleBellPress}
|
|
activeOpacity={0.85}
|
|
>
|
|
<Text style={[styles.priorityButtonText, { color: colors.text }]}>{copy.viewSchedule}</Text>
|
|
<Ionicons name="arrow-forward" size={14} color={colors.text} />
|
|
</TouchableOpacity>
|
|
|
|
<Ionicons
|
|
name="water"
|
|
size={124}
|
|
color={colors.overlay}
|
|
style={styles.priorityBgIcon}
|
|
/>
|
|
</View>
|
|
|
|
{plants.length === 0 && (
|
|
<OnboardingChecklist plantsCount={plants.length} colors={colors} router={router} t={t} />
|
|
)}
|
|
|
|
<ScrollView
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
contentContainerStyle={styles.filterRow}
|
|
>
|
|
{chips.map(chip => (
|
|
<TouchableOpacity
|
|
key={chip.key}
|
|
style={[
|
|
activeFilter === chip.key ? styles.filterChipActive : styles.filterChip,
|
|
activeFilter === chip.key
|
|
? { backgroundColor: colors.primary, shadowColor: colors.fabShadow }
|
|
: { backgroundColor: colors.cardBg, borderColor: colors.cardBorder },
|
|
]}
|
|
onPress={() => setActiveFilter(chip.key)}
|
|
activeOpacity={0.85}
|
|
>
|
|
<Text
|
|
style={[
|
|
activeFilter === chip.key ? styles.filterTextActive : styles.filterText,
|
|
{ color: activeFilter === chip.key ? colors.onPrimary : colors.textSecondary },
|
|
]}
|
|
>
|
|
{chip.label}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</ScrollView>
|
|
|
|
<View style={styles.collectionHeader}>
|
|
<Text style={[styles.collectionTitle, { color: colors.text }]}>{copy.collectionTitle}</Text>
|
|
<View style={[styles.collectionCountPill, { backgroundColor: colors.cardBg, borderColor: colors.cardBorder }]}>
|
|
<Text style={[styles.collectionCountText, { color: colors.textSecondary }]}>
|
|
{copy.collectionCount.replace('{0}', filteredPlants.length.toString())}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{filteredPlants.length === 0 ? (
|
|
<View style={[styles.emptyState, { backgroundColor: colors.cardBg, borderColor: colors.cardBorder }]}>
|
|
<View style={[styles.emptyIconWrap, { backgroundColor: colors.surfaceMuted }]}>
|
|
<Ionicons name="leaf-outline" size={28} color={colors.textMuted} />
|
|
</View>
|
|
<Text style={[styles.emptyTitle, { color: colors.text }]}>
|
|
{plants.length === 0 ? copy.emptyCollectionTitle : copy.noneInFilter}
|
|
</Text>
|
|
<Text style={[styles.emptyText, { color: colors.textSecondary }]}>
|
|
{plants.length === 0 ? copy.emptyCollectionHint : copy.noneInFilter}
|
|
</Text>
|
|
{plants.length === 0 && (
|
|
<TouchableOpacity
|
|
style={[styles.emptyCta, { backgroundColor: colors.primary, shadowColor: colors.fabShadow }]}
|
|
onPress={() => router.push('/scanner')}
|
|
activeOpacity={0.86}
|
|
>
|
|
<Ionicons name="scan-outline" size={15} color={colors.onPrimary} />
|
|
<Text style={[styles.emptyCtaText, { color: colors.onPrimary }]}>{copy.scanFirstPlant}</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
) : (
|
|
filteredPlants.map((plant) => {
|
|
const daysUntil = getDaysUntilWatering(plant);
|
|
const thirsty = daysUntil === 0;
|
|
const nextWaterText = thirsty
|
|
? copy.today
|
|
: t.inXDays.replace('{0}', daysUntil.toString());
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
key={plant.id}
|
|
style={[
|
|
styles.plantCard,
|
|
{
|
|
backgroundColor: colors.cardBg,
|
|
borderColor: colors.cardBorder,
|
|
shadowColor: colors.cardShadow,
|
|
},
|
|
]}
|
|
activeOpacity={0.9}
|
|
onPress={() => router.push(`/plant/${plant.id}`)}
|
|
>
|
|
<View style={[styles.plantImageWrap, { borderColor: colors.border }]}>
|
|
<SafeImage uri={plant.imageUri} style={[styles.plantImage, { backgroundColor: colors.surfaceStrong }]} />
|
|
</View>
|
|
|
|
<View style={styles.plantBody}>
|
|
<View style={styles.plantHeadRow}>
|
|
<View style={styles.plantTitleCol}>
|
|
<Text style={[styles.plantName, { color: colors.text }]} numberOfLines={1}>
|
|
{plant.name}
|
|
</Text>
|
|
<Text style={[styles.botanicalName, { color: colors.textSecondary }]} numberOfLines={1}>
|
|
{plant.botanicalName}
|
|
</Text>
|
|
</View>
|
|
<Ionicons name="chevron-forward" size={16} color={colors.textMuted} />
|
|
</View>
|
|
|
|
<View style={styles.metaRow}>
|
|
<View
|
|
style={[
|
|
thirsty ? styles.statusThirsty : styles.statusHealthy,
|
|
{ backgroundColor: thirsty ? colors.dangerSoft : colors.successSoft },
|
|
]}
|
|
>
|
|
<Text
|
|
style={[
|
|
thirsty ? styles.statusThirstyText : styles.statusHealthyText,
|
|
{ color: thirsty ? colors.danger : colors.success },
|
|
]}
|
|
>
|
|
{thirsty ? copy.thirsty : copy.healthyStatus}
|
|
</Text>
|
|
</View>
|
|
<View style={[styles.nextWaterPill, { backgroundColor: colors.surfaceMuted, borderColor: colors.border }]}>
|
|
<Ionicons name="water-outline" size={12} color={colors.textMuted} />
|
|
<Text style={[styles.waterMeta, { color: colors.textMuted }]}>
|
|
{copy.nextWaterLabel}: {nextWaterText}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
})
|
|
)}
|
|
</ScrollView>
|
|
|
|
<TouchableOpacity
|
|
ref={fabRef}
|
|
style={[
|
|
styles.fab,
|
|
{ bottom: Math.max(FAB_BOTTOM_OFFSET, insets.bottom + FAB_BOTTOM_OFFSET) },
|
|
{
|
|
backgroundColor: colors.fabBg,
|
|
borderColor: colors.surface,
|
|
shadowColor: colors.fabShadow,
|
|
},
|
|
]}
|
|
activeOpacity={0.8}
|
|
onPress={() => router.push('/scanner')}
|
|
onLayout={() => {
|
|
fabRef.current?.measureInWindow((x, y, width, height) => {
|
|
registerLayout('fab', { x, y, width, height });
|
|
});
|
|
}}
|
|
>
|
|
<Ionicons name="add" size={30} color={colors.onPrimary} />
|
|
</TouchableOpacity>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
loadingContainer: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
content: {
|
|
paddingHorizontal: 24,
|
|
paddingTop: 14,
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 26,
|
|
},
|
|
headerLeft: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
flex: 1,
|
|
},
|
|
headerTextBlock: {
|
|
flex: 1,
|
|
minWidth: 0,
|
|
},
|
|
avatarWrap: {
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 16,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
overflow: 'hidden',
|
|
},
|
|
avatarImage: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
greetingText: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
},
|
|
nameRow: {
|
|
marginTop: 1,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
minWidth: 0,
|
|
},
|
|
nameText: {
|
|
fontSize: 21,
|
|
fontWeight: '700',
|
|
letterSpacing: 0.1,
|
|
flexShrink: 1,
|
|
},
|
|
creditsPill: {
|
|
borderRadius: 999,
|
|
borderWidth: 1,
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 3,
|
|
},
|
|
creditsText: {
|
|
fontSize: 10,
|
|
fontWeight: '700',
|
|
letterSpacing: 0.3,
|
|
textTransform: 'uppercase',
|
|
},
|
|
bellBtn: {
|
|
width: 46,
|
|
height: 46,
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
shadowOpacity: 0.08,
|
|
shadowRadius: 8,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
elevation: 2,
|
|
},
|
|
priorityCard: {
|
|
borderRadius: 32,
|
|
padding: 24,
|
|
marginBottom: 20,
|
|
overflow: 'hidden',
|
|
},
|
|
priorityLabelRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 6,
|
|
marginBottom: 8,
|
|
},
|
|
priorityLabel: {
|
|
fontSize: 10,
|
|
fontWeight: '700',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.9,
|
|
},
|
|
priorityTitle: {
|
|
fontSize: 29,
|
|
fontWeight: '700',
|
|
lineHeight: 36,
|
|
maxWidth: 260,
|
|
marginBottom: 14,
|
|
},
|
|
priorityButton: {
|
|
alignSelf: 'flex-start',
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 10,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 6,
|
|
},
|
|
priorityButtonText: {
|
|
fontSize: 13,
|
|
fontWeight: '700',
|
|
},
|
|
priorityBgIcon: {
|
|
position: 'absolute',
|
|
right: -18,
|
|
bottom: -22,
|
|
},
|
|
filterRow: {
|
|
gap: 10,
|
|
paddingVertical: 2,
|
|
paddingRight: 4,
|
|
marginBottom: 16,
|
|
},
|
|
collectionHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
collectionTitle: {
|
|
fontSize: 19,
|
|
fontWeight: '700',
|
|
letterSpacing: 0.2,
|
|
},
|
|
collectionCountPill: {
|
|
borderRadius: 999,
|
|
borderWidth: 1,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
},
|
|
collectionCountText: {
|
|
fontSize: 11,
|
|
fontWeight: '700',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.4,
|
|
},
|
|
filterChip: {
|
|
paddingHorizontal: 22,
|
|
paddingVertical: 10,
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
},
|
|
filterChipActive: {
|
|
paddingHorizontal: 22,
|
|
paddingVertical: 10,
|
|
borderRadius: 16,
|
|
shadowOpacity: 0.24,
|
|
shadowRadius: 10,
|
|
shadowOffset: { width: 0, height: 3 },
|
|
elevation: 3,
|
|
},
|
|
filterText: {
|
|
fontSize: 13,
|
|
fontWeight: '700',
|
|
},
|
|
filterTextActive: {
|
|
fontSize: 13,
|
|
fontWeight: '700',
|
|
},
|
|
plantCard: {
|
|
borderRadius: 30,
|
|
borderWidth: 1,
|
|
padding: 15,
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
gap: 14,
|
|
marginBottom: 14,
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 12,
|
|
shadowOffset: { width: 0, height: 4 },
|
|
elevation: 3,
|
|
},
|
|
plantImageWrap: {
|
|
borderWidth: 1,
|
|
borderRadius: 20,
|
|
overflow: 'hidden',
|
|
},
|
|
plantImage: {
|
|
width: 98,
|
|
height: 98,
|
|
borderRadius: 20,
|
|
},
|
|
plantBody: {
|
|
flex: 1,
|
|
paddingTop: 2,
|
|
},
|
|
plantHeadRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 8,
|
|
gap: 8,
|
|
},
|
|
plantTitleCol: {
|
|
flex: 1,
|
|
minWidth: 0,
|
|
},
|
|
plantName: {
|
|
fontWeight: '700',
|
|
fontSize: 19,
|
|
letterSpacing: 0.15,
|
|
},
|
|
botanicalName: {
|
|
fontSize: 12.5,
|
|
fontStyle: 'italic',
|
|
fontWeight: '500',
|
|
marginTop: 2,
|
|
opacity: 0.95,
|
|
},
|
|
metaRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
flexWrap: 'wrap',
|
|
},
|
|
statusThirsty: {
|
|
borderRadius: 20,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
},
|
|
statusHealthy: {
|
|
borderRadius: 20,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
},
|
|
statusThirstyText: {
|
|
fontSize: 10,
|
|
fontWeight: '800',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.4,
|
|
},
|
|
statusHealthyText: {
|
|
fontSize: 10,
|
|
fontWeight: '800',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.4,
|
|
},
|
|
waterMeta: {
|
|
fontSize: 10,
|
|
fontWeight: '700',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.4,
|
|
},
|
|
nextWaterPill: {
|
|
borderRadius: 999,
|
|
borderWidth: 1,
|
|
paddingHorizontal: 9,
|
|
paddingVertical: 4,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
},
|
|
emptyState: {
|
|
borderRadius: 24,
|
|
borderWidth: 1,
|
|
paddingVertical: 32,
|
|
paddingHorizontal: 22,
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
},
|
|
emptyIconWrap: {
|
|
width: 56,
|
|
height: 56,
|
|
borderRadius: 18,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
emptyTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
textAlign: 'center',
|
|
},
|
|
emptyText: {
|
|
fontSize: 13,
|
|
fontWeight: '500',
|
|
textAlign: 'center',
|
|
lineHeight: 20,
|
|
},
|
|
emptyCta: {
|
|
marginTop: 8,
|
|
borderRadius: 999,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 10,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 10,
|
|
shadowOffset: { width: 0, height: 4 },
|
|
elevation: 4,
|
|
},
|
|
emptyCtaText: {
|
|
fontSize: 13,
|
|
fontWeight: '700',
|
|
},
|
|
fab: {
|
|
position: 'absolute',
|
|
right: 24,
|
|
width: 66,
|
|
height: 66,
|
|
borderRadius: 33,
|
|
borderWidth: 4,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
shadowOpacity: 0.38,
|
|
shadowRadius: 14,
|
|
shadowOffset: { width: 0, height: 5 },
|
|
elevation: 9,
|
|
},
|
|
checklistCard: {
|
|
borderRadius: 24,
|
|
borderWidth: 1,
|
|
padding: 20,
|
|
marginBottom: 20,
|
|
},
|
|
checklistTitle: {
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
marginBottom: 16,
|
|
},
|
|
checklistGrid: {
|
|
gap: 12,
|
|
},
|
|
checklistItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
},
|
|
checkIcon: {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 16,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
checklistText: {
|
|
flex: 1,
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
},
|
|
});
|