From 0e0ab4a059a50dc4507f1d6f1faf5757871e05d7 Mon Sep 17 00:00:00 2001 From: Luna Date: Sat, 18 Apr 2026 18:45:21 +0200 Subject: [PATCH] feat(#1,#2,#5): add checkup nav/progress, image optimization controls, and raise tab bar --- src/AppRoot.js | 46 ++++++++++++++++++++++++++++------ src/modals/CheckupFlowModal.js | 35 +++++++++++++++++++++----- src/modals/ItemModal.js | 32 +++++++++++++++++++++-- src/styles.js | 15 ++++++++++- 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/src/AppRoot.js b/src/AppRoot.js index 90456ba..b81c9bf 100644 --- a/src/AppRoot.js +++ b/src/AppRoot.js @@ -35,6 +35,8 @@ const emptyItemForm = () => ({ placement: 'suitcase', lentTo: '', imageUri: '', + imageQuality: 'balanced', + imageAllowCrop: false, }); const emptyCheckupNoForm = () => ({ @@ -234,7 +236,7 @@ export default function AppRoot() { setDatePicker((prev) => ({ ...prev, visible: false })); } - async function pickImage(onPicked) { + async function pickImage(onPicked, options = {}) { const perm = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!perm.granted) { showAlert('Permission needed', 'Allow gallery access to select images.'); @@ -243,8 +245,8 @@ export default function AppRoot() { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, - allowsEditing: false, - quality: 0.85, + allowsEditing: !!options.allowCrop, + quality: typeof options.quality === 'number' ? options.quality : 0.85, }); if (!result.canceled && result.assets?.[0]?.uri) { @@ -252,7 +254,7 @@ export default function AppRoot() { } } - async function takeImage(onPicked) { + async function takeImage(onPicked, options = {}) { const perm = await ImagePicker.requestCameraPermissionsAsync(); if (!perm.granted) { showAlert('Permission needed', 'Allow camera access to take photos.'); @@ -260,8 +262,8 @@ export default function AppRoot() { } const result = await ImagePicker.launchCameraAsync({ - allowsEditing: false, - quality: 0.85, + allowsEditing: !!options.allowCrop, + quality: typeof options.quality === 'number' ? options.quality : 0.85, }); if (!result.canceled && result.assets?.[0]?.uri) { @@ -434,6 +436,8 @@ export default function AppRoot() { placement: item.placement || 'suitcase', lentTo: item.lentTo || '', imageUri: item.imageUri || '', + imageQuality: item.imageQuality || 'balanced', + imageAllowCrop: !!item.imageAllowCrop, }); setItemModalVisible(true); } @@ -463,6 +467,8 @@ export default function AppRoot() { placement: itemForm.placement, lentTo: itemForm.status === 'lent-to' ? itemForm.lentTo.trim() : '', imageUri: itemForm.imageUri, + imageQuality: itemForm.imageQuality, + imageAllowCrop: itemForm.imageAllowCrop, createdAt: existingCreatedAt, updatedAt: now, }; @@ -606,6 +612,28 @@ export default function AppRoot() { setCheckupNoForm(emptyCheckupNoForm()); } + function goBackInCheckup() { + setCheckupFlowIndex((prev) => Math.max(0, prev - 1)); + setCheckupFlowMode('question'); + setCheckupNoForm(emptyCheckupNoForm()); + } + + function skipCurrentCheckupItem() { + if (!checkupCurrentEntry) return; + setCheckupSession((prev) => + prev.map((x) => + x.itemId === checkupCurrentEntry.itemId + ? { + ...x, + confirmed: false, + result: 'pending', + } + : x + ) + ); + goNextInCheckup(); + } + function answerCurrentCheckupYes() { const entry = checkupCurrentEntry; if (!entry) return; @@ -855,8 +883,8 @@ export default function AppRoot() { itemForm={itemForm} setItemModalVisible={setItemModalVisible} updateItemForm={updateItemForm} - pickItemImage={() => pickImage((uri) => updateItemForm('imageUri', uri))} - takeItemImage={() => takeImage((uri) => updateItemForm('imageUri', uri))} + pickItemImage={(options) => pickImage((uri) => updateItemForm('imageUri', uri), options)} + takeItemImage={(options) => takeImage((uri) => updateItemForm('imageUri', uri), options)} saveItemFromModal={saveItemFromModal} /> @@ -872,6 +900,8 @@ export default function AppRoot() { onYes={answerCurrentCheckupYes} onNo={openCurrentCheckupNo} onSaveNo={saveCurrentCheckupNo} + onSkip={skipCurrentCheckupItem} + onBack={goBackInCheckup} onFinish={finishCheckupFlow} /> diff --git a/src/modals/CheckupFlowModal.js b/src/modals/CheckupFlowModal.js index 7254551..9770043 100644 --- a/src/modals/CheckupFlowModal.js +++ b/src/modals/CheckupFlowModal.js @@ -17,6 +17,8 @@ export default function CheckupFlowModal({ onYes, onNo, onSaveNo, + onSkip, + onBack, onFinish, }) { const finished = !entry; @@ -37,9 +39,14 @@ export default function CheckupFlowModal({ Done. Save this snapshot? All {total} items were checked. - - Save Check-Up Snapshot - + + + Back + + + Save Snapshot + + ) : ( Item {stepIndex + 1} / {total} + + + {entry.name} {entry.category || 'uncategorized'} @@ -60,6 +70,14 @@ export default function CheckupFlowModal({ {mode === 'question' ? ( + + + Back + + + Skip + + Yes, correct @@ -106,9 +124,14 @@ export default function CheckupFlowModal({ - - Save update + next - + + + Back + + + Save + Next + + )} diff --git a/src/modals/ItemModal.js b/src/modals/ItemModal.js index d1efbb0..eed1e83 100644 --- a/src/modals/ItemModal.js +++ b/src/modals/ItemModal.js @@ -5,6 +5,12 @@ import ChipGroup from '../components/ChipGroup'; import Field from '../components/Field'; import { styles } from '../styles'; +function qualityValue(level) { + if (level === 'high') return 0.95; + if (level === 'low') return 0.45; + return 0.75; +} + export default function ItemModal({ visible, itemForm, @@ -14,6 +20,11 @@ export default function ItemModal({ takeItemImage, saveItemFromModal, }) { + const mediaOptions = { + quality: qualityValue(itemForm.imageQuality), + allowCrop: !!itemForm.imageAllowCrop, + }; + return ( @@ -82,11 +93,28 @@ export default function ItemModal({ ) : null} + + + {['low', 'balanced', 'high'].map((level) => { + const active = itemForm.imageQuality === level; + return ( + updateItemForm('imageQuality', level)}> + {level} + + ); + })} + + + + updateItemForm('imageAllowCrop', !itemForm.imageAllowCrop)}> + {itemForm.imageAllowCrop ? '☑' : '☐'} Enable optional crop before save + + - + takeItemImage(mediaOptions)}> Take photo - + pickItemImage(mediaOptions)}> {itemForm.imageUri ? 'From gallery (change)' : 'From gallery'} diff --git a/src/styles.js b/src/styles.js index a4f4e42..10ea627 100644 --- a/src/styles.js +++ b/src/styles.js @@ -404,6 +404,19 @@ export const styles = StyleSheet.create({ marginTop: 14, gap: 10, }, + checkupProgressTrack: { + width: '100%', + height: 8, + borderRadius: 999, + backgroundColor: '#1e293b', + overflow: 'hidden', + marginTop: 4, + marginBottom: 8, + }, + checkupProgressFill: { + height: '100%', + backgroundColor: '#2563eb', + }, answerYesWide: { backgroundColor: '#163223', borderWidth: 1, @@ -494,7 +507,7 @@ export const styles = StyleSheet.create({ tabBarWrap: { position: 'absolute', - bottom: 0, + bottom: 6, left: 0, right: 0, paddingHorizontal: 10,