Compare commits
2 Commits
luggage-li
...
luggage-li
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e45261354 | |||
| bd500674a0 |
1
TODO.md
1
TODO.md
@@ -38,3 +38,4 @@ Improving & Fixing Bugs (V3)
|
||||
- [x] Added check-up stats (correct/bad/pending) and persisted per snapshot
|
||||
- [x] Extra UI polish pass (spacing, cards, hierarchy)
|
||||
- [x] Centered and enlarged edit/check-up modals to fully overlay nav
|
||||
- [x] Fixed modal keyboard glitching (stable centered keyboard-aware layout)
|
||||
|
||||
11
app.json
11
app.json
@@ -34,6 +34,15 @@
|
||||
"eas": {
|
||||
"projectId": "1275f90e-33c6-4af1-942e-ca29a309f8c8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"expo-image-picker",
|
||||
{
|
||||
"photosPermission": "Allow Luggage List to access your photos for trip and item images.",
|
||||
"cameraPermission": "Allow Luggage List to use your camera to take trip and item photos."
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,23 @@ export default function AppRoot() {
|
||||
}
|
||||
}
|
||||
|
||||
async function takeImage(onPicked) {
|
||||
const perm = await ImagePicker.requestCameraPermissionsAsync();
|
||||
if (!perm.granted) {
|
||||
Alert.alert('Permission needed', 'Allow camera access to take photos.');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: false,
|
||||
quality: 0.85,
|
||||
});
|
||||
|
||||
if (!result.canceled && result.assets?.[0]?.uri) {
|
||||
onPicked(result.assets[0].uri);
|
||||
}
|
||||
}
|
||||
|
||||
function createTrip() {
|
||||
if (!tripForm.name.trim()) {
|
||||
Alert.alert('Missing name', 'Trip name is required.');
|
||||
@@ -526,6 +543,7 @@ export default function AppRoot() {
|
||||
tripForm={tripForm}
|
||||
updateTripForm={updateTripForm}
|
||||
pickTripImage={() => pickImage((uri) => updateTripForm('imageUri', uri))}
|
||||
takeTripImage={() => takeImage((uri) => updateTripForm('imageUri', uri))}
|
||||
templateTrip={templateTrip}
|
||||
createTrip={createTrip}
|
||||
trips={data.trips}
|
||||
@@ -587,6 +605,7 @@ export default function AppRoot() {
|
||||
setItemModalVisible={setItemModalVisible}
|
||||
updateItemForm={updateItemForm}
|
||||
pickItemImage={() => pickImage((uri) => updateItemForm('imageUri', uri))}
|
||||
takeItemImage={() => takeImage((uri) => updateItemForm('imageUri', uri))}
|
||||
saveItemFromModal={saveItemFromModal}
|
||||
/>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function CheckupFixModal({
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide" transparent>
|
||||
<View style={styles.modalBackdrop}>
|
||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.modalKeyboardWrap}>
|
||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} style={styles.modalKeyboardWrap}>
|
||||
<View style={styles.modalCard}>
|
||||
<View style={styles.sectionRow}>
|
||||
<Text style={styles.sectionTitle}>Update for this Check-Up</Text>
|
||||
@@ -24,7 +24,12 @@ export default function CheckupFixModal({
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ScrollView keyboardShouldPersistTaps="handled" showsVerticalScrollIndicator={false}>
|
||||
<ScrollView
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="interactive"
|
||||
contentContainerStyle={{ paddingBottom: 12 }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Field label="Status">
|
||||
<ChipGroup
|
||||
options={ITEM_STATUSES}
|
||||
|
||||
@@ -11,12 +11,13 @@ export default function ItemModal({
|
||||
setItemModalVisible,
|
||||
updateItemForm,
|
||||
pickItemImage,
|
||||
takeItemImage,
|
||||
saveItemFromModal,
|
||||
}) {
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide" transparent>
|
||||
<View style={styles.modalBackdrop}>
|
||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.modalKeyboardWrap}>
|
||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} style={styles.modalKeyboardWrap}>
|
||||
<View style={styles.modalCard}>
|
||||
<View style={styles.sectionRow}>
|
||||
<Text style={styles.sectionTitle}>{itemForm.id ? 'Update Item' : 'Add Item'}</Text>
|
||||
@@ -25,7 +26,12 @@ export default function ItemModal({
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ScrollView keyboardShouldPersistTaps="handled" showsVerticalScrollIndicator={false}>
|
||||
<ScrollView
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="interactive"
|
||||
contentContainerStyle={{ paddingBottom: 12 }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Field label="Name">
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
@@ -76,9 +82,14 @@ export default function ItemModal({
|
||||
</Field>
|
||||
) : null}
|
||||
|
||||
<Pressable style={styles.secondaryBtn} onPress={pickItemImage}>
|
||||
<Text style={styles.secondaryBtnText}>{itemForm.imageUri ? 'Change image' : 'Add image'}</Text>
|
||||
</Pressable>
|
||||
<View style={styles.actionRow}>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={takeItemImage}>
|
||||
<Text style={styles.secondaryBtnText}>Take photo</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={pickItemImage}>
|
||||
<Text style={styles.secondaryBtnText}>{itemForm.imageUri ? 'From gallery (change)' : 'From gallery'}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
{!!itemForm.imageUri && <Image source={{ uri: itemForm.imageUri }} style={styles.previewImageSmall} />}
|
||||
|
||||
<Pressable style={styles.primaryBtn} onPress={saveItemFromModal}>
|
||||
|
||||
@@ -211,6 +211,11 @@ export const styles = StyleSheet.create({
|
||||
color: '#dbeafe',
|
||||
fontWeight: '700',
|
||||
},
|
||||
actionRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
marginTop: 4,
|
||||
},
|
||||
|
||||
inlineToggle: {
|
||||
marginTop: 2,
|
||||
@@ -400,17 +405,17 @@ export const styles = StyleSheet.create({
|
||||
modalBackdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(2,6,23,0.72)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
modalKeyboardWrap: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
modalCard: {
|
||||
width: '96%',
|
||||
maxHeight: '92%',
|
||||
maxHeight: '90%',
|
||||
backgroundColor: '#0f172a',
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
|
||||
@@ -17,6 +17,7 @@ export default function TripsTab({
|
||||
tripForm,
|
||||
updateTripForm,
|
||||
pickTripImage,
|
||||
takeTripImage,
|
||||
templateTrip,
|
||||
createTrip,
|
||||
trips,
|
||||
@@ -58,9 +59,14 @@ export default function TripsTab({
|
||||
<DateField label="Start Date" value={tripForm.startDate} onPress={() => openDatePicker('startDate')} />
|
||||
<DateField label="End Date" value={tripForm.endDate} onPress={() => openDatePicker('endDate')} />
|
||||
|
||||
<Pressable style={styles.secondaryBtn} onPress={pickTripImage}>
|
||||
<Text style={styles.secondaryBtnText}>{tripForm.imageUri ? 'Change trip image' : 'Add trip image'}</Text>
|
||||
</Pressable>
|
||||
<View style={styles.actionRow}>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={takeTripImage}>
|
||||
<Text style={styles.secondaryBtnText}>Take photo</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={pickTripImage}>
|
||||
<Text style={styles.secondaryBtnText}>{tripForm.imageUri ? 'From gallery (change)' : 'From gallery'}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{tripForm.imageUri ? <Image source={{ uri: tripForm.imageUri }} style={styles.previewImage} /> : null}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user