diff --git a/App.js b/App.js
index 41ba7a2..dc27c69 100644
--- a/App.js
+++ b/App.js
@@ -1,6 +1,9 @@
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Alert,
+ KeyboardAvoidingView,
+ Modal,
+ Platform,
Pressable,
SafeAreaView,
ScrollView,
@@ -12,15 +15,22 @@ import {
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as ImagePicker from 'expo-image-picker';
-import * as FileSystem from 'expo-file-system';
-import * as Sharing from 'expo-sharing';
import { StatusBar } from 'expo-status-bar';
-const STORAGE_KEY = 'luggage-list:v1';
+const STORAGE_KEY = 'luggage-list:v2';
+const TAB_BAR_HEIGHT = 72;
const ITEM_STATUSES = ['packed', 'unpacked', 'lost', 'left-behind', 'lent-to'];
const ITEM_PLACEMENTS = ['suitcase', 'backpack', 'with-user', 'other'];
+const STATUS_COLORS = {
+ packed: '#22c55e',
+ unpacked: '#64748b',
+ lost: '#ef4444',
+ 'left-behind': '#f59e0b',
+ 'lent-to': '#8b5cf6',
+};
+
const emptyData = {
trips: [],
itemsByTrip: {},
@@ -28,16 +38,34 @@ const emptyData = {
defaultTemplateTripId: null,
};
+const emptyTripForm = () => ({
+ name: '',
+ location: '',
+ startDate: todayYMD(),
+ endDate: todayYMD(),
+ imageUri: '',
+ copyDefaultTemplate: true,
+ setAsDefaultTemplate: false,
+});
+
+const emptyItemForm = () => ({
+ id: null,
+ name: '',
+ description: '',
+ category: '',
+ status: 'unpacked',
+ placement: 'suitcase',
+ lentTo: '',
+ imageUri: '',
+});
+
function makeId(prefix) {
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}
function todayYMD() {
const now = new Date();
- const y = now.getFullYear();
- const m = `${now.getMonth() + 1}`.padStart(2, '0');
- const d = `${now.getDate()}`.padStart(2, '0');
- return `${y}-${m}-${d}`;
+ return `${now.getFullYear()}-${`${now.getMonth() + 1}`.padStart(2, '0')}-${`${now.getDate()}`.padStart(2, '0')}`;
}
function parseYMD(value) {
@@ -46,37 +74,15 @@ function parseYMD(value) {
return Number.isNaN(d.getTime()) ? null : d;
}
-function findAutoActiveTrip(trips) {
+function findActiveTripId(trips) {
const today = parseYMD(todayYMD());
if (!today) return null;
-
const active = trips.find((trip) => {
const start = parseYMD(trip.startDate);
const end = parseYMD(trip.endDate);
- if (!start || !end) return false;
- return today >= start && today <= end;
+ return !!start && !!end && today >= start && today <= end;
});
-
- return active || null;
-}
-
-function ChipGroup({ options, value, onChange }) {
- return (
-
- {options.map((option) => {
- const active = value === option;
- return (
- onChange(option)}
- >
- {option}
-
- );
- })}
-
- );
+ return active?.id || null;
}
function Field({ label, children }) {
@@ -88,94 +94,72 @@ function Field({ label, children }) {
);
}
-function Card({ title, children, right }) {
+function ChipGroup({ options, value, onChange }) {
return (
-
-
- {title}
- {right}
+
+ {options.map((option) => {
+ const active = value === option;
+ return (
+ onChange(option)}>
+ {option}
+
+ );
+ })}
+
+ );
+}
+
+function BottomTab({ current, onChange }) {
+ const tabs = [
+ { key: 'trips', label: 'Trips' },
+ { key: 'items', label: 'Items' },
+ { key: 'checkup', label: 'Check-Up' },
+ { key: 'history', label: 'History' },
+ ];
+
+ return (
+
+
+ {tabs.map((tab) => {
+ const active = current === tab.key;
+ return (
+ onChange(tab.key)} style={styles.tabItem}>
+
+ {tab.label}
+
+ );
+ })}
- {children}
);
}
export default function App() {
- const [data, setData] = useState(emptyData);
+ const scrollRef = useRef(null);
+
const [loaded, setLoaded] = useState(false);
const [tab, setTab] = useState('trips');
+ const [data, setData] = useState(emptyData);
+
const [selectedTripId, setSelectedTripId] = useState(null);
+ const [tripForm, setTripForm] = useState(emptyTripForm());
- const [tripForm, setTripForm] = useState({
- name: '',
- location: '',
- startDate: todayYMD(),
- endDate: todayYMD(),
- imageUri: '',
- copyDefaultTemplate: true,
- setAsDefaultTemplate: false,
- });
+ const [itemModalVisible, setItemModalVisible] = useState(false);
+ const [itemForm, setItemForm] = useState(emptyItemForm());
- const [itemForm, setItemForm] = useState({
- id: null,
- name: '',
- description: '',
- category: '',
+ const [checkupSession, setCheckupSession] = useState([]);
+ const [checkupFixModalVisible, setCheckupFixModalVisible] = useState(false);
+ const [checkupFixTargetId, setCheckupFixTargetId] = useState(null);
+ const [checkupFixForm, setCheckupFixForm] = useState({
status: 'unpacked',
placement: 'suitcase',
lentTo: '',
- imageUri: '',
+ updateMasterList: false,
});
- const [checkupDraft, setCheckupDraft] = useState({});
const [selectedCheckupId, setSelectedCheckupId] = useState(null);
- useEffect(() => {
- (async () => {
- try {
- const raw = await AsyncStorage.getItem(STORAGE_KEY);
- if (raw) {
- const parsed = JSON.parse(raw);
- setData({ ...emptyData, ...parsed });
- }
- } catch (error) {
- Alert.alert('Load error', 'Could not load local data.');
- } finally {
- setLoaded(true);
- }
- })();
- }, []);
-
- useEffect(() => {
- if (!loaded) return;
- AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(data)).catch(() => {
- Alert.alert('Save error', 'Could not save local data.');
- });
- }, [data, loaded]);
-
- useEffect(() => {
- if (!loaded) return;
- const autoTrip = findAutoActiveTrip(data.trips);
-
- if (autoTrip?.id && selectedTripId !== autoTrip.id) {
- setSelectedTripId(autoTrip.id);
- return;
- }
-
- if (!selectedTripId && data.trips[0]?.id) {
- setSelectedTripId(data.trips[0].id);
- return;
- }
-
- if (selectedTripId && !data.trips.some((trip) => trip.id === selectedTripId)) {
- setSelectedTripId(data.trips[0]?.id || null);
- }
- }, [data.trips, selectedTripId, loaded]);
-
- const selectedTrip = useMemo(
- () => data.trips.find((trip) => trip.id === selectedTripId) || null,
- [data.trips, selectedTripId]
- );
+ const selectedTrip = useMemo(() => data.trips.find((trip) => trip.id === selectedTripId) || null, [data.trips, selectedTripId]);
const selectedTripItems = useMemo(() => {
if (!selectedTripId) return [];
@@ -192,22 +176,52 @@ export default function App() {
[data.trips, data.defaultTemplateTripId]
);
- async function pickImage(onPick) {
- const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
- if (!permission.granted) {
- Alert.alert('Permission needed', 'Please allow gallery access to pick an image.');
+ useEffect(() => {
+ (async () => {
+ try {
+ const raw = await AsyncStorage.getItem(STORAGE_KEY);
+ if (raw) {
+ const parsed = JSON.parse(raw);
+ setData({ ...emptyData, ...parsed });
+ }
+ } catch {
+ Alert.alert('Error', 'Could not load local data.');
+ } finally {
+ setLoaded(true);
+ }
+ })();
+ }, []);
+
+ useEffect(() => {
+ if (!loaded) return;
+ AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(data)).catch(() => {
+ Alert.alert('Error', 'Could not save local data.');
+ });
+ }, [data, loaded]);
+
+ useEffect(() => {
+ if (!loaded) return;
+ if (!data.trips.length) {
+ setSelectedTripId(null);
return;
}
- const result = await ImagePicker.launchImageLibraryAsync({
- mediaTypes: ['images'],
- allowsEditing: true,
- quality: 0.8,
- });
-
- if (!result.canceled && result.assets?.[0]?.uri) {
- onPick(result.assets[0].uri);
+ if (selectedTripId && data.trips.some((trip) => trip.id === selectedTripId)) {
+ return;
}
+
+ const activeTripId = findActiveTripId(data.trips);
+ setSelectedTripId(activeTripId || data.trips[0].id);
+ }, [data.trips, selectedTripId, loaded]);
+
+ useEffect(() => {
+ if (tab !== 'checkup') return;
+ createFreshCheckupSession();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selectedTripId, selectedTripItems.length]);
+
+ function chooseTrip(tripId) {
+ setSelectedTripId(tripId);
}
function updateTripForm(field, value) {
@@ -218,50 +232,88 @@ export default function App() {
setItemForm((prev) => ({ ...prev, [field]: value }));
}
+ function openAddItemModal() {
+ setItemForm(emptyItemForm());
+ setItemModalVisible(true);
+ }
+
+ function openEditItemModal(item) {
+ setItemForm({
+ id: item.id,
+ name: item.name || '',
+ description: item.description || '',
+ category: item.category || '',
+ status: item.status || 'unpacked',
+ placement: item.placement || 'suitcase',
+ lentTo: item.lentTo || '',
+ imageUri: item.imageUri || '',
+ });
+ setItemModalVisible(true);
+ }
+
+ async function pickImage(setter) {
+ const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
+ if (!perm.granted) {
+ Alert.alert('Permission needed', 'Allow gallery access to select images.');
+ return;
+ }
+
+ const result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
+ allowsEditing: false,
+ quality: 0.85,
+ });
+
+ if (!result.canceled && result.assets?.[0]?.uri) {
+ setter(result.assets[0].uri);
+ }
+ }
+
function createTrip() {
if (!tripForm.name.trim()) {
Alert.alert('Missing name', 'Trip name is required.');
return;
}
- const startDate = parseYMD(tripForm.startDate);
- const endDate = parseYMD(tripForm.endDate);
+ const start = parseYMD(tripForm.startDate);
+ const end = parseYMD(tripForm.endDate);
- if (!startDate || !endDate) {
- Alert.alert('Date format', 'Please use YYYY-MM-DD for dates.');
+ if (!start || !end) {
+ Alert.alert('Invalid dates', 'Use YYYY-MM-DD format.');
return;
}
- if (startDate > endDate) {
- Alert.alert('Dates invalid', 'Start date cannot be after end date.');
+ if (start > end) {
+ Alert.alert('Invalid dates', 'Start date cannot be after end date.');
return;
}
const now = Date.now();
- const newTripId = makeId('trip');
-
- const newTrip = {
- id: newTripId,
- name: tripForm.name.trim(),
- location: tripForm.location.trim(),
- startDate: tripForm.startDate,
- endDate: tripForm.endDate,
- imageUri: tripForm.imageUri,
- createdAt: now,
- updatedAt: now,
- };
+ const tripId = makeId('trip');
setData((prev) => {
const next = {
...prev,
- trips: [...prev.trips, newTrip],
- itemsByTrip: { ...prev.itemsByTrip, [newTripId]: [] },
- checkupsByTrip: { ...prev.checkupsByTrip, [newTripId]: [] },
+ trips: [
+ ...prev.trips,
+ {
+ id: tripId,
+ name: tripForm.name.trim(),
+ location: tripForm.location.trim(),
+ startDate: tripForm.startDate,
+ endDate: tripForm.endDate,
+ imageUri: tripForm.imageUri,
+ createdAt: now,
+ updatedAt: now,
+ },
+ ],
+ itemsByTrip: { ...prev.itemsByTrip, [tripId]: [] },
+ checkupsByTrip: { ...prev.checkupsByTrip, [tripId]: [] },
};
- if (tripForm.copyDefaultTemplate && prev.defaultTemplateTripId && prev.defaultTemplateTripId !== newTripId) {
- const source = prev.itemsByTrip[prev.defaultTemplateTripId] || [];
- next.itemsByTrip[newTripId] = source.map((item) => ({
+ if (tripForm.copyDefaultTemplate && prev.defaultTemplateTripId) {
+ const templateItems = prev.itemsByTrip[prev.defaultTemplateTripId] || [];
+ next.itemsByTrip[tripId] = templateItems.map((item) => ({
...item,
id: makeId('item'),
createdAt: now,
@@ -270,22 +322,14 @@ export default function App() {
}
if (tripForm.setAsDefaultTemplate) {
- next.defaultTemplateTripId = newTripId;
+ next.defaultTemplateTripId = tripId;
}
return next;
});
- setSelectedTripId(newTripId);
- setTripForm({
- name: '',
- location: '',
- startDate: todayYMD(),
- endDate: todayYMD(),
- imageUri: '',
- copyDefaultTemplate: true,
- setAsDefaultTemplate: false,
- });
+ setSelectedTripId(tripId);
+ setTripForm(emptyTripForm());
}
function setTripAsTemplate(tripId) {
@@ -293,24 +337,24 @@ export default function App() {
}
function deleteTrip(tripId) {
- Alert.alert('Delete trip?', 'This removes the trip, its items, and its check-up history.', [
+ Alert.alert('Delete trip?', 'Trip items and check-up history will also be deleted.', [
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete',
style: 'destructive',
onPress: () => {
setData((prev) => {
- const nextTrips = prev.trips.filter((trip) => trip.id !== tripId);
- const nextItemsByTrip = { ...prev.itemsByTrip };
- const nextCheckupsByTrip = { ...prev.checkupsByTrip };
- delete nextItemsByTrip[tripId];
- delete nextCheckupsByTrip[tripId];
+ const trips = prev.trips.filter((trip) => trip.id !== tripId);
+ const itemsByTrip = { ...prev.itemsByTrip };
+ const checkupsByTrip = { ...prev.checkupsByTrip };
+ delete itemsByTrip[tripId];
+ delete checkupsByTrip[tripId];
return {
...prev,
- trips: nextTrips,
- itemsByTrip: nextItemsByTrip,
- checkupsByTrip: nextCheckupsByTrip,
+ trips,
+ itemsByTrip,
+ checkupsByTrip,
defaultTemplateTripId: prev.defaultTemplateTripId === tripId ? null : prev.defaultTemplateTripId,
};
});
@@ -319,9 +363,9 @@ export default function App() {
]);
}
- function saveItem() {
+ function saveItemFromModal() {
if (!selectedTripId) {
- Alert.alert('No trip', 'Create or select a trip first.');
+ Alert.alert('No trip selected', 'Please select or create a trip first.');
return;
}
@@ -333,8 +377,9 @@ export default function App() {
const now = Date.now();
setData((prev) => {
- const currentItems = prev.itemsByTrip[selectedTripId] || [];
- const normalized = {
+ const items = prev.itemsByTrip[selectedTripId] || [];
+ const existingCreatedAt = items.find((item) => item.id === itemForm.id)?.createdAt || now;
+ const nextItem = {
id: itemForm.id || makeId('item'),
name: itemForm.name.trim(),
description: itemForm.description.trim(),
@@ -343,13 +388,13 @@ export default function App() {
placement: itemForm.placement,
lentTo: itemForm.status === 'lent-to' ? itemForm.lentTo.trim() : '',
imageUri: itemForm.imageUri,
- createdAt: itemForm.id ? (currentItems.find((x) => x.id === itemForm.id)?.createdAt || now) : now,
+ createdAt: existingCreatedAt,
updatedAt: now,
};
const nextItems = itemForm.id
- ? currentItems.map((item) => (item.id === itemForm.id ? normalized : item))
- : [...currentItems, normalized];
+ ? items.map((item) => (item.id === itemForm.id ? nextItem : item))
+ : [...items, nextItem];
return {
...prev,
@@ -360,127 +405,148 @@ export default function App() {
};
});
- setItemForm({
- id: null,
- name: '',
- description: '',
- category: '',
- status: 'unpacked',
- placement: 'suitcase',
- lentTo: '',
- imageUri: '',
- });
- }
-
- function editItem(item) {
- setItemForm({
- id: item.id,
- name: item.name || '',
- description: item.description || '',
- category: item.category || '',
- status: item.status || 'unpacked',
- placement: item.placement || 'suitcase',
- lentTo: item.lentTo || '',
- imageUri: item.imageUri || '',
- });
- setTab('items');
+ setItemModalVisible(false);
+ setItemForm(emptyItemForm());
}
function deleteItem(itemId) {
setData((prev) => {
- const currentItems = prev.itemsByTrip[selectedTripId] || [];
+ const items = prev.itemsByTrip[selectedTripId] || [];
return {
...prev,
itemsByTrip: {
...prev.itemsByTrip,
- [selectedTripId]: currentItems.filter((item) => item.id !== itemId),
+ [selectedTripId]: items.filter((item) => item.id !== itemId),
},
};
});
}
- function initCheckupDraft() {
- const draft = {};
- selectedTripItems.forEach((item) => {
- draft[item.id] = {
- status: item.status || 'unpacked',
- placement: item.placement || 'suitcase',
- lentTo: item.lentTo || '',
- };
- });
- setCheckupDraft(draft);
- }
-
- useEffect(() => {
- initCheckupDraft();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [selectedTripId, selectedTripItems.length]);
-
- function updateCheckupItem(itemId, field, value) {
- setCheckupDraft((prev) => ({
- ...prev,
- [itemId]: {
- status: prev[itemId]?.status || 'unpacked',
- placement: prev[itemId]?.placement || 'suitcase',
- lentTo: prev[itemId]?.lentTo || '',
- [field]: value,
- },
- }));
- }
-
- function createCheckup() {
- if (!selectedTripId) {
- Alert.alert('No trip', 'Create or select a trip first.');
- return;
- }
-
+ function createFreshCheckupSession() {
if (!selectedTripItems.length) {
- Alert.alert('No items', 'Add at least one luggage item first.');
+ setCheckupSession([]);
return;
}
- const now = Date.now();
- const snapshot = selectedTripItems.map((item) => {
- const draft = checkupDraft[item.id] || {};
- return {
- itemId: item.id,
- name: item.name,
- category: item.category,
- status: draft.status || item.status || 'unpacked',
- placement: draft.placement || item.placement || 'suitcase',
- lentTo: (draft.status || item.status) === 'lent-to' ? (draft.lentTo || item.lentTo || '') : '',
- };
+ const fresh = selectedTripItems.map((item) => ({
+ itemId: item.id,
+ name: item.name,
+ category: item.category,
+ current: {
+ status: item.status,
+ placement: item.placement,
+ lentTo: item.lentTo || '',
+ },
+ confirmed: false,
+ }));
+
+ setCheckupSession(fresh);
+ }
+
+ function answerCheckupYes(itemId) {
+ setCheckupSession((prev) => prev.map((entry) => (entry.itemId === itemId ? { ...entry, confirmed: true } : entry)));
+ }
+
+ function openFixModal(itemId) {
+ const entry = checkupSession.find((x) => x.itemId === itemId);
+ if (!entry) return;
+
+ setCheckupFixTargetId(itemId);
+ setCheckupFixForm({
+ status: entry.current.status || 'unpacked',
+ placement: entry.current.placement || 'suitcase',
+ lentTo: entry.current.lentTo || '',
+ updateMasterList: false,
});
+ setCheckupFixModalVisible(true);
+ }
- setData((prev) => {
- const currentCheckups = prev.checkupsByTrip[selectedTripId] || [];
- const currentItems = prev.itemsByTrip[selectedTripId] || [];
+ function saveFixModal() {
+ if (!checkupFixTargetId) return;
- const nextItems = currentItems.map((item) => {
- const snap = snapshot.find((x) => x.itemId === item.id);
- if (!snap) return item;
+ const targetId = checkupFixTargetId;
+ const patch = {
+ status: checkupFixForm.status,
+ placement: checkupFixForm.placement,
+ lentTo: checkupFixForm.status === 'lent-to' ? checkupFixForm.lentTo.trim() : '',
+ };
+
+ setCheckupSession((prev) =>
+ prev.map((entry) =>
+ entry.itemId === targetId
+ ? {
+ ...entry,
+ current: patch,
+ confirmed: true,
+ }
+ : entry
+ )
+ );
+
+ if (checkupFixForm.updateMasterList && selectedTripId) {
+ setData((prev) => {
+ const items = prev.itemsByTrip[selectedTripId] || [];
return {
- ...item,
- status: snap.status,
- placement: snap.placement,
- lentTo: snap.lentTo,
- updatedAt: now,
+ ...prev,
+ itemsByTrip: {
+ ...prev.itemsByTrip,
+ [selectedTripId]: items.map((item) =>
+ item.id === targetId
+ ? {
+ ...item,
+ status: patch.status,
+ placement: patch.placement,
+ lentTo: patch.lentTo,
+ updatedAt: Date.now(),
+ }
+ : item
+ ),
+ },
};
});
+ }
+ setCheckupFixModalVisible(false);
+ setCheckupFixTargetId(null);
+ }
+
+ function saveCheckup() {
+ if (!selectedTripId) {
+ Alert.alert('No trip selected', 'Please select a trip first.');
+ return;
+ }
+
+ if (!checkupSession.length) {
+ Alert.alert('No items', 'Add items before creating a check-up.');
+ return;
+ }
+
+ const pending = checkupSession.filter((entry) => !entry.confirmed).length;
+ if (pending > 0) {
+ Alert.alert('Incomplete', `Please confirm all items first (${pending} remaining).`);
+ return;
+ }
+
+ const snapshot = checkupSession.map((entry) => ({
+ itemId: entry.itemId,
+ name: entry.name,
+ category: entry.category,
+ status: entry.current.status,
+ placement: entry.current.placement,
+ lentTo: entry.current.status === 'lent-to' ? entry.current.lentTo : '',
+ }));
+
+ setData((prev) => {
+ const existing = prev.checkupsByTrip[selectedTripId] || [];
return {
...prev,
- itemsByTrip: {
- ...prev.itemsByTrip,
- [selectedTripId]: nextItems,
- },
checkupsByTrip: {
...prev.checkupsByTrip,
[selectedTripId]: [
- ...currentCheckups,
+ ...existing,
{
id: makeId('checkup'),
- createdAt: now,
+ createdAt: Date.now(),
snapshot,
},
],
@@ -488,48 +554,26 @@ export default function App() {
};
});
- Alert.alert('Saved', 'Check-up snapshot stored.');
+ Alert.alert('Saved', 'Check-up snapshot saved.');
+ createFreshCheckupSession();
}
- async function exportJson() {
- try {
- const payload = {
- exportedAt: new Date().toISOString(),
- app: 'Luggage List',
- ...data,
- };
-
- const fileName = `luggage-list-export-${Date.now()}.json`;
- const path = `${FileSystem.documentDirectory}${fileName}`;
- await FileSystem.writeAsStringAsync(path, JSON.stringify(payload, null, 2), {
- encoding: FileSystem.EncodingType.UTF8,
- });
-
- const canShare = await Sharing.isAvailableAsync();
- if (canShare) {
- await Sharing.shareAsync(path, {
- mimeType: 'application/json',
- dialogTitle: 'Export luggage data',
- UTI: 'public.json',
- });
- } else {
- Alert.alert('Exported', `Saved to: ${path}`);
- }
- } catch (error) {
- Alert.alert('Export failed', 'Could not export JSON file.');
- }
+ function statusAccent(status) {
+ return STATUS_COLORS[status] || '#64748b';
}
- function formatDateTime(ts) {
- return new Date(ts).toLocaleString();
+ function focusToEnd() {
+ setTimeout(() => {
+ scrollRef.current?.scrollToEnd?.({ animated: true });
+ }, 80);
}
if (!loaded) {
return (
-
-
- Loading local data...
+
+
+ Loading local data...
);
@@ -537,352 +581,390 @@ export default function App() {
return (
-
-
- Luggage List
- Simple local luggage tracking
-
+
-
- {['trips', 'items', 'checkup', 'history', 'export'].map((name) => (
- setTab(name)}
- >
- {name}
-
- ))}
-
-
-
- {selectedTrip ? `${selectedTrip.startDate} → ${selectedTrip.endDate}` : 'None'}
- }
+
+
- {selectedTrip ? (
-
- {selectedTrip.name}
- {selectedTrip.location || 'No location set'}
- {!!selectedTrip.imageUri && }
-
- ) : (
- Create your first trip to start.
- )}
-
+
+
+ {data.trips.length ? (
+ data.trips
+ .slice()
+ .sort((a, b) => b.startDate.localeCompare(a.startDate))
+ .map((trip) => {
+ const active = selectedTripId === trip.id;
+ return (
+ chooseTrip(trip.id)}>
+ {trip.name}
+ {trip.startDate}
+
+ );
+ })
+ ) : (
+ Create your first trip to start.
+ )}
+
+
- {tab === 'trips' && (
- <>
-
-
- updateTripForm('name', v)}
- style={styles.input}
- placeholder="Weekend in Berlin"
- />
-
-
- updateTripForm('location', v)}
- style={styles.input}
- placeholder="Berlin"
- />
-
-
- updateTripForm('startDate', v)}
- style={styles.input}
- placeholder="2026-04-18"
- />
-
-
- updateTripForm('endDate', v)}
- style={styles.input}
- placeholder="2026-04-21"
- />
-
+ {tab === 'trips' && (
+
+ Trips
+
+
+
+ updateTripForm('name', v)}
+ placeholder="Summer Weekend"
+ placeholderTextColor="#6b7280"
+ onFocus={focusToEnd}
+ />
+
+
+
+ updateTripForm('location', v)}
+ placeholder="Berlin"
+ placeholderTextColor="#6b7280"
+ onFocus={focusToEnd}
+ />
+
+
+
+ updateTripForm('startDate', v)}
+ placeholderTextColor="#6b7280"
+ onFocus={focusToEnd}
+ />
+
+
+
+ updateTripForm('endDate', v)}
+ placeholderTextColor="#6b7280"
+ onFocus={focusToEnd}
+ />
+
-
pickImage((uri) => updateTripForm('imageUri', uri))}>
- {tripForm.imageUri ? 'Change Trip Image' : 'Add Trip Image'}
+ {tripForm.imageUri ? 'Change trip image' : 'Add trip image'}
- {tripForm.imageUri ? : null}
-
- {templateTrip ? (
- updateTripForm('copyDefaultTemplate', !tripForm.copyDefaultTemplate)}
- >
+ {tripForm.imageUri ? : null}
+
+ {templateTrip ? (
+ updateTripForm('copyDefaultTemplate', !tripForm.copyDefaultTemplate)}>
+
+ {tripForm.copyDefaultTemplate ? '☑' : '☐'} Copy items from template ({templateTrip.name})
+
+
+ ) : null}
+
+ updateTripForm('setAsDefaultTemplate', !tripForm.setAsDefaultTemplate)}>
- {tripForm.copyDefaultTemplate ? '☑' : '☐'} Copy items from template trip: {templateTrip.name}
+ {tripForm.setAsDefaultTemplate ? '☑' : '☐'} Set as default template
- ) : null}
- updateTripForm('setAsDefaultTemplate', !tripForm.setAsDefaultTemplate)}
- >
-
- {tripForm.setAsDefaultTemplate ? '☑' : '☐'} Set this trip as default template
-
-
+
+ Create Trip
+
+
-
- Create Trip
-
-
-
-
- {!data.trips.length ? No trips yet. : null}
{data.trips
.slice()
.sort((a, b) => b.startDate.localeCompare(a.startDate))
.map((trip) => (
-
- setSelectedTripId(trip.id)} style={styles.grow}>
- {trip.name}
- {trip.location || 'No location'} • {trip.startDate} → {trip.endDate}
-
- {selectedTripId === trip.id ? 'Active' : 'Tap to select'}
- {data.defaultTemplateTripId === trip.id ? ' • Template' : ''}
-
-
-
- setTripAsTemplate(trip.id)}>
- Template
-
- deleteTrip(trip.id)}>
- Delete
-
+
+
+
+ {trip.name}
+ {trip.location || 'No location'} · {trip.startDate} → {trip.endDate}
+
+ {data.defaultTemplateTripId === trip.id ? 'Default template' : ' '}
+
+
+
+ chooseTrip(trip.id)}>
+ Select
+
+ setTripAsTemplate(trip.id)}>
+ Template
+
+ deleteTrip(trip.id)}>
+ Delete
+
+
+ {trip.imageUri ? : null}
))}
-
- >
- )}
+
+ )}
- {tab === 'items' && (
- <>
-
- {!selectedTripId ? Create/select a trip first. : null}
-
- updateItemForm('name', v)}
- style={styles.input}
- placeholder="Toothbrush"
- />
-
-
- updateItemForm('description', v)}
- style={styles.input}
- placeholder="Electric toothbrush"
- />
-
-
- updateItemForm('category', v)}
- style={styles.input}
- placeholder="toiletries"
- />
-
-
- updateItemForm('status', v)} />
-
-
- updateItemForm('placement', v)} />
-
- {itemForm.status === 'lent-to' ? (
-
- updateItemForm('lentTo', v)}
- style={styles.input}
- placeholder="Person name"
- />
-
+ {tab === 'items' && (
+
+
+ Luggage Items
+
+ + Add
+
+
+
+ {!selectedTrip ? (
+ Select a trip first.
) : null}
-
- pickImage((uri) => updateItemForm('imageUri', uri))}>
- {itemForm.imageUri ? 'Change Item Image' : 'Add Item Image'}
-
- {itemForm.imageUri ? : null}
-
+ {selectedTripItems.length === 0 && selectedTrip ? No items yet. : null}
-
-
- {itemForm.id ? 'Save Item' : 'Add Item'}
-
- {itemForm.id ? (
-
- setItemForm({
- id: null,
- name: '',
- description: '',
- category: '',
- status: 'unpacked',
- placement: 'suitcase',
- lentTo: '',
- imageUri: '',
- })
- }
- >
- Cancel
-
- ) : null}
-
-
-
-
- {!selectedTripItems.length ? No items yet for this trip. : null}
{selectedTripItems.map((item) => (
-
-
-
- {item.name}
-
- {(item.category || 'uncategorized')} • {item.status} • {item.placement}
-
- {!!item.description && {item.description}}
- {item.status === 'lent-to' && !!item.lentTo && Lent to: {item.lentTo}}
-
-
- editItem(item)}>
- Edit
-
- deleteItem(item.id)}>
- Delete
-
+
+
+
+
+
+ {item.name}
+ {item.category || 'uncategorized'} · {item.status}
+ Location: {item.placement}
+ {item.status === 'lent-to' && !!item.lentTo ? Lent to: {item.lentTo} : null}
+ {!!item.description ? {item.description} : null}
+
+
+ openEditItemModal(item)}>
+ Edit
+
+ deleteItem(item.id)}>
+ Delete
+
+
+ {item.imageUri ? : null}
- {!!item.imageUri && }
))}
-
- >
- )}
+
+ )}
- {tab === 'checkup' && (
- <>
-
- {!selectedTripItems.length ? (
- Add items first, then do a check-up.
- ) : (
- <>
- {selectedTripItems.map((item) => {
- const draft = checkupDraft[item.id] || {};
- const statusValue = draft.status || item.status || 'unpacked';
- const placementValue = draft.placement || item.placement || 'suitcase';
- const lentValue = draft.lentTo || '';
+ {tab === 'checkup' && (
+
+
+ Check-Up
+
+ Restart
+
+
- return (
-
- {item.name}
- {item.category || 'uncategorized'}
-
- updateCheckupItem(item.id, 'status', v)}
- />
-
-
- updateCheckupItem(item.id, 'placement', v)}
- />
-
- {statusValue === 'lent-to' ? (
-
- updateCheckupItem(item.id, 'lentTo', v)}
- style={styles.input}
- placeholder="Person name"
- />
-
- ) : null}
-
- );
- })}
+ {checkupSession.length === 0 ? No items for this trip yet. : null}
-
-
- Save Check-Up
+ {checkupSession.map((entry) => (
+
+ {entry.name}
+ {entry.category || 'uncategorized'}
+
+ {entry.current.status} · {entry.current.placement}
+ {entry.current.status === 'lent-to' && entry.current.lentTo ? ` · ${entry.current.lentTo}` : ''}
+
+
+
+ answerCheckupYes(entry.itemId)}>
+ Yes
-
- Reset
+ openFixModal(entry.itemId)}>
+ No
+
- >
+
+ ))}
+
+ {!!checkupSession.length && (
+
+ Save Check-Up Snapshot
+
)}
-
- >
- )}
+
+ )}
- {tab === 'history' && (
-
- {!selectedTripCheckups.length ? No check-ups saved yet. : null}
- {selectedTripCheckups.map((checkup) => {
- const lostCount = checkup.snapshot.filter((x) => x.status === 'lost').length;
- const leftBehindCount = checkup.snapshot.filter((x) => x.status === 'left-behind').length;
+ {tab === 'history' && (
+
+ History
+ {selectedTripCheckups.length === 0 ? No check-ups saved yet. : null}
- return (
-
+ {selectedTripCheckups.map((checkup) => (
+
setSelectedCheckupId((prev) => (prev === checkup.id ? null : checkup.id))}>
- {formatDateTime(checkup.createdAt)}
-
- {checkup.snapshot.length} items • lost: {lostCount} • left-behind: {leftBehindCount}
-
- {selectedCheckupId === checkup.id ? 'Tap to collapse' : 'Tap to view snapshot'}
+ {new Date(checkup.createdAt).toLocaleString()}
+ {checkup.snapshot.length} items
+ {selectedCheckupId === checkup.id ? 'Tap to collapse' : 'Tap to open'}
{selectedCheckupId === checkup.id ? (
-
+
{checkup.snapshot.map((entry) => (
- {entry.name}
-
- {entry.status} • {entry.placement}
- {entry.status === 'lent-to' && entry.lentTo ? ` • ${entry.lentTo}` : ''}
+ {entry.name}
+
+ {entry.status} · {entry.placement}
+ {entry.status === 'lent-to' && entry.lentTo ? ` · ${entry.lentTo}` : ''}
))}
) : null}
- );
- })}
-
- )}
+ ))}
+
+ )}
+
+
- {tab === 'export' && (
-
-
- Export trips, items, and check-up history as JSON for backup or sharing.
-
-
- Export JSON
-
-
- )}
-
+
+
+
+
+
+
+
+ {itemForm.id ? 'Update Item' : 'Add Item'}
+ setItemModalVisible(false)}>
+ Close
+
+
+
+
+
+ updateItemForm('name', v)}
+ placeholder="Toothbrush"
+ placeholderTextColor="#6b7280"
+ />
+
+
+
+ updateItemForm('description', v)}
+ placeholder="Optional"
+ placeholderTextColor="#6b7280"
+ />
+
+
+
+ updateItemForm('category', v)}
+ placeholder="toiletries"
+ placeholderTextColor="#6b7280"
+ />
+
+
+
+ updateItemForm('status', v)} />
+
+
+
+ updateItemForm('placement', v)} />
+
+
+ {itemForm.status === 'lent-to' ? (
+
+ updateItemForm('lentTo', v)}
+ placeholder="Person name"
+ placeholderTextColor="#6b7280"
+ />
+
+ ) : null}
+
+ pickImage((uri) => updateItemForm('imageUri', uri))}>
+ {itemForm.imageUri ? 'Change image' : 'Add image'}
+
+ {!!itemForm.imageUri && }
+
+
+ {itemForm.id ? 'Save Changes' : 'Add Item'}
+
+
+
+
+
+
+
+
+
+
+
+
+ Update for this Check-Up
+ setCheckupFixModalVisible(false)}>
+ Close
+
+
+
+
+
+ setCheckupFixForm((prev) => ({ ...prev, status: v }))}
+ />
+
+
+
+ setCheckupFixForm((prev) => ({ ...prev, placement: v }))}
+ />
+
+
+ {checkupFixForm.status === 'lent-to' ? (
+
+ setCheckupFixForm((prev) => ({ ...prev, lentTo: v }))}
+ placeholder="Person name"
+ placeholderTextColor="#6b7280"
+ />
+
+ ) : null}
+
+ setCheckupFixForm((prev) => ({ ...prev, updateMasterList: !prev.updateMasterList }))}
+ >
+
+ {checkupFixForm.updateMasterList ? '☑' : '☐'} Also update item in trip list
+
+
+
+
+ Save
+
+
+
+
+
+
);
}
@@ -890,234 +972,373 @@ export default function App() {
const styles = StyleSheet.create({
safe: {
flex: 1,
- backgroundColor: '#f5f5f7',
+ backgroundColor: '#090d12',
},
- header: {
- paddingHorizontal: 16,
- paddingTop: 14,
- paddingBottom: 8,
- },
- title: {
- fontSize: 26,
- fontWeight: '700',
- color: '#0f172a',
- },
- subtitle: {
- marginTop: 2,
- color: '#475569',
- },
- tabRow: {
- flexDirection: 'row',
- flexWrap: 'wrap',
- paddingHorizontal: 10,
- gap: 6,
- },
- tabBtn: {
- backgroundColor: '#e2e8f0',
- borderRadius: 8,
- paddingHorizontal: 10,
- paddingVertical: 6,
- },
- tabBtnActive: {
- backgroundColor: '#0f172a',
- },
- tabText: {
- color: '#1e293b',
- textTransform: 'capitalize',
- fontWeight: '600',
- },
- tabTextActive: {
- color: '#fff',
+ flex: {
+ flex: 1,
},
content: {
- padding: 12,
- paddingBottom: 24,
+ paddingHorizontal: 14,
+ paddingTop: 12,
+ paddingBottom: TAB_BAR_HEIGHT + 18,
gap: 12,
},
- card: {
- backgroundColor: '#fff',
- borderRadius: 12,
- padding: 12,
- borderWidth: 1,
- borderColor: '#e2e8f0',
+ center: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
},
- cardHeader: {
+ muted: {
+ color: '#8793a5',
+ },
+
+ tripPickerWrap: {
+ marginBottom: 6,
+ },
+ tripChipScroll: {
+ gap: 8,
+ paddingRight: 12,
+ },
+ tripChip: {
+ paddingHorizontal: 12,
+ paddingVertical: 9,
+ borderRadius: 12,
+ backgroundColor: '#121923',
+ borderWidth: 1,
+ borderColor: '#1f2937',
+ minWidth: 120,
+ },
+ tripChipActive: {
+ backgroundColor: '#1d2a3a',
+ borderColor: '#60a5fa',
+ },
+ tripChipText: {
+ color: '#e2e8f0',
+ fontWeight: '700',
+ },
+ tripChipTextActive: {
+ color: '#bfdbfe',
+ },
+ tripChipSub: {
+ color: '#64748b',
+ fontSize: 12,
+ marginTop: 2,
+ },
+ tripChipSubActive: {
+ color: '#93c5fd',
+ },
+
+ section: {
+ gap: 10,
+ },
+ sectionTitle: {
+ color: '#f1f5f9',
+ fontSize: 18,
+ fontWeight: '700',
+ },
+ sectionRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
- marginBottom: 8,
+ gap: 10,
+ },
+
+ card: {
+ backgroundColor: '#111827',
+ borderRadius: 14,
+ borderWidth: 1,
+ borderColor: '#1f2937',
+ padding: 12,
gap: 8,
},
- cardTitle: {
- fontSize: 17,
- fontWeight: '700',
- color: '#0f172a',
+ cardActive: {
+ borderColor: '#60a5fa',
},
+ cardSoft: {
+ backgroundColor: '#0f172a',
+ borderRadius: 14,
+ borderWidth: 1,
+ borderColor: '#1e293b',
+ padding: 12,
+ gap: 10,
+ },
+ cardRow: {
+ flexDirection: 'row',
+ gap: 10,
+ },
+ cardTitle: {
+ color: '#f8fafc',
+ fontWeight: '700',
+ fontSize: 15,
+ },
+ cardMeta: {
+ color: '#94a3b8',
+ marginTop: 3,
+ fontSize: 13,
+ },
+
fieldWrap: {
- marginTop: 8,
+ gap: 6,
},
label: {
- marginBottom: 6,
- color: '#334155',
+ color: '#cbd5e1',
fontWeight: '600',
},
input: {
borderWidth: 1,
- borderColor: '#cbd5e1',
- backgroundColor: '#fff',
- paddingHorizontal: 10,
- paddingVertical: 10,
+ borderColor: '#243244',
borderRadius: 10,
+ backgroundColor: '#0b1220',
+ color: '#e5e7eb',
+ paddingHorizontal: 10,
+ paddingVertical: 11,
},
+
chipGroup: {
flexDirection: 'row',
flexWrap: 'wrap',
- gap: 6,
+ gap: 7,
},
chip: {
- borderWidth: 1,
- borderColor: '#cbd5e1',
borderRadius: 999,
- paddingHorizontal: 10,
- paddingVertical: 6,
- backgroundColor: '#fff',
+ paddingHorizontal: 11,
+ paddingVertical: 7,
+ borderWidth: 1,
+ borderColor: '#273449',
+ backgroundColor: '#0b1220',
},
chipActive: {
- backgroundColor: '#0f172a',
- borderColor: '#0f172a',
+ borderColor: '#60a5fa',
+ backgroundColor: '#172338',
},
chipText: {
- color: '#334155',
- fontSize: 12,
+ color: '#cbd5e1',
fontWeight: '600',
+ fontSize: 12,
},
chipTextActive: {
- color: '#fff',
+ color: '#bfdbfe',
},
+
primaryBtn: {
- marginTop: 12,
- backgroundColor: '#0f172a',
+ marginTop: 4,
borderRadius: 10,
- paddingVertical: 10,
- paddingHorizontal: 12,
+ backgroundColor: '#2563eb',
alignItems: 'center',
+ paddingVertical: 11,
+ paddingHorizontal: 12,
+ },
+ primaryBtnTight: {
+ borderRadius: 10,
+ backgroundColor: '#2563eb',
+ alignItems: 'center',
+ paddingVertical: 8,
+ paddingHorizontal: 12,
},
primaryBtnText: {
color: '#fff',
fontWeight: '700',
},
secondaryBtn: {
- marginTop: 12,
- backgroundColor: '#e2e8f0',
+ marginTop: 4,
borderRadius: 10,
- paddingVertical: 10,
- paddingHorizontal: 12,
+ backgroundColor: '#1f2937',
alignItems: 'center',
+ paddingVertical: 11,
+ paddingHorizontal: 12,
+ },
+ secondaryBtnTight: {
+ borderRadius: 10,
+ backgroundColor: '#1f2937',
+ alignItems: 'center',
+ paddingVertical: 8,
+ paddingHorizontal: 12,
},
secondaryBtnText: {
- color: '#1e293b',
+ color: '#dbeafe',
fontWeight: '700',
},
- actionRow: {
- flexDirection: 'row',
- gap: 8,
- flexWrap: 'wrap',
+
+ inlineToggle: {
+ marginTop: 2,
},
- listItem: {
- marginTop: 8,
- borderWidth: 1,
- borderColor: '#e2e8f0',
- borderRadius: 10,
- padding: 10,
- flexDirection: 'row',
- gap: 8,
- alignItems: 'center',
+ inlineToggleText: {
+ color: '#cbd5e1',
},
- listItemStack: {
- marginTop: 8,
- borderWidth: 1,
- borderColor: '#e2e8f0',
- borderRadius: 10,
- padding: 10,
- gap: 8,
+
+ stackButtons: {
+ gap: 7,
},
- listItemTop: {
- flexDirection: 'row',
- gap: 8,
- },
- itemTitle: {
- fontWeight: '700',
- color: '#0f172a',
- fontSize: 15,
- },
- activeTripName: {
- fontSize: 16,
- fontWeight: '700',
- color: '#0f172a',
- },
- metaText: {
- color: '#64748b',
- fontSize: 13,
- },
- grow: {
- flex: 1,
- },
- rowGap: {
- gap: 8,
- marginTop: 6,
- },
- previewImage: {
- width: '100%',
- height: 140,
- borderRadius: 10,
- marginTop: 8,
- backgroundColor: '#e2e8f0',
- },
- smallActionBtn: {
- backgroundColor: '#e2e8f0',
+ miniBtn: {
+ backgroundColor: '#1e293b',
borderRadius: 8,
- paddingVertical: 6,
+ paddingVertical: 7,
paddingHorizontal: 10,
},
- smallActionBtnText: {
- color: '#1e293b',
+ miniBtnDanger: {
+ backgroundColor: '#3b1d22',
+ borderRadius: 8,
+ paddingVertical: 7,
+ paddingHorizontal: 10,
+ },
+ miniBtnText: {
+ color: '#e2e8f0',
fontWeight: '700',
fontSize: 12,
},
- itemActionsColumn: {
- gap: 6,
+
+ itemCard: {
+ borderRadius: 14,
+ backgroundColor: '#111827',
+ borderWidth: 1,
+ borderColor: '#1f2937',
+ overflow: 'hidden',
+ flexDirection: 'row',
},
- inlineToggle: {
- marginTop: 10,
+ itemAccent: {
+ width: 5,
},
- inlineToggleText: {
- color: '#1e293b',
+ itemMain: {
+ flex: 1,
+ padding: 12,
+ gap: 8,
},
- checkupItem: {
- marginTop: 12,
+ itemTitle: {
+ color: '#f8fafc',
+ fontWeight: '700',
+ fontSize: 15,
+ },
+ itemMeta: {
+ color: '#94a3b8',
+ marginTop: 2,
+ fontSize: 13,
+ },
+
+ answerRow: {
+ marginTop: 8,
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ answerYes: {
+ backgroundColor: '#163223',
+ borderWidth: 1,
+ borderColor: '#1f7a4e',
+ borderRadius: 9,
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ },
+ answerNo: {
+ backgroundColor: '#3b1d22',
+ borderWidth: 1,
+ borderColor: '#7f1d1d',
+ borderRadius: 9,
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ },
+ answerText: {
+ color: '#f8fafc',
+ fontWeight: '700',
+ },
+ answerStateDot: {
+ width: 10,
+ height: 10,
+ borderRadius: 99,
+ backgroundColor: '#475569',
+ },
+ answerStateDotOn: {
+ backgroundColor: '#22c55e',
+ },
+
+ snapshotWrap: {
+ marginTop: 8,
borderTopWidth: 1,
- borderTopColor: '#e2e8f0',
- paddingTop: 10,
- },
- snapshotList: {
- borderTopWidth: 1,
- borderTopColor: '#e2e8f0',
+ borderTopColor: '#1e293b',
paddingTop: 8,
- gap: 6,
+ gap: 7,
},
snapshotRow: {
- paddingVertical: 4,
+ paddingVertical: 3,
},
- snapshotName: {
- color: '#0f172a',
+ snapshotTitle: {
+ color: '#e2e8f0',
fontWeight: '600',
},
- loadingWrap: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
+
+ previewImage: {
+ width: '100%',
+ height: 150,
+ borderRadius: 10,
+ backgroundColor: '#111827',
},
- loadingText: {
- color: '#334155',
+ previewImageSmall: {
+ width: '100%',
+ height: 120,
+ borderRadius: 10,
+ backgroundColor: '#111827',
+ },
+
+ tabBarWrap: {
+ position: 'absolute',
+ bottom: 0,
+ left: 0,
+ right: 0,
+ paddingHorizontal: 10,
+ paddingBottom: Platform.OS === 'ios' ? 14 : 8,
+ backgroundColor: 'transparent',
+ },
+ tabBar: {
+ height: TAB_BAR_HEIGHT,
+ borderRadius: 16,
+ backgroundColor: '#0b1220',
+ borderWidth: 1,
+ borderColor: '#1f2937',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ },
+ tabItem: {
+ alignItems: 'center',
+ gap: 4,
+ },
+ tabDot: {
+ width: 6,
+ height: 6,
+ borderRadius: 99,
+ backgroundColor: '#334155',
+ },
+ tabDotActive: {
+ backgroundColor: '#60a5fa',
+ },
+ tabLabel: {
+ color: '#94a3b8',
+ fontSize: 12,
+ fontWeight: '600',
+ },
+ tabLabelActive: {
+ color: '#dbeafe',
+ },
+
+ modalBackdrop: {
+ flex: 1,
+ backgroundColor: 'rgba(2,6,23,0.72)',
+ justifyContent: 'flex-end',
+ },
+ modalKeyboardWrap: {
+ width: '100%',
+ },
+ modalCard: {
+ maxHeight: '87%',
+ backgroundColor: '#0f172a',
+ borderTopLeftRadius: 18,
+ borderTopRightRadius: 18,
+ borderWidth: 1,
+ borderColor: '#1e293b',
+ padding: 14,
+ gap: 8,
+ },
+ closeText: {
+ color: '#93c5fd',
+ fontWeight: '700',
},
});
diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png
index 0db9e07..decb7d7 100644
Binary files a/assets/adaptive-icon.png and b/assets/adaptive-icon.png differ
diff --git a/assets/icon.png b/assets/icon.png
index b6a2553..decb7d7 100644
Binary files a/assets/icon.png and b/assets/icon.png differ
diff --git a/assets/splash-icon.png b/assets/splash-icon.png
index ba5f039..decb7d7 100644
Binary files a/assets/splash-icon.png and b/assets/splash-icon.png differ
diff --git a/package-lock.json b/package-lock.json
index ecbcb25..9029412 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,9 +10,7 @@
"dependencies": {
"@react-native-async-storage/async-storage": "2.2.0",
"expo": "~54.0.33",
- "expo-file-system": "~19.0.17",
"expo-image-picker": "~17.0.8",
- "expo-sharing": "~14.0.7",
"expo-status-bar": "~3.0.9",
"react": "19.1.0",
"react-dom": "19.1.0",
@@ -4441,15 +4439,6 @@
"node": ">=20.16.0"
}
},
- "node_modules/expo-sharing": {
- "version": "14.0.8",
- "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-14.0.8.tgz",
- "integrity": "sha512-A1pPr2iBrxypFDCWVAESk532HK+db7MFXbvO2sCV9ienaFXAk7lIBm6bkqgE6vzRd9O3RGdEGzYx80cYlc089Q==",
- "license": "MIT",
- "peerDependencies": {
- "expo": "*"
- }
- },
"node_modules/expo-status-bar": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-3.0.9.tgz",
diff --git a/package.json b/package.json
index d08675d..6d14160 100644
--- a/package.json
+++ b/package.json
@@ -11,9 +11,7 @@
"dependencies": {
"@react-native-async-storage/async-storage": "2.2.0",
"expo": "~54.0.33",
- "expo-file-system": "~19.0.17",
"expo-image-picker": "~17.0.8",
- "expo-sharing": "~14.0.7",
"expo-status-bar": "~3.0.9",
"react": "19.1.0",
"react-dom": "19.1.0",