diff --git a/js/mobile/src/features/stream-view/TaskParticleView.tsx b/js/mobile/src/features/stream-view/TaskParticleView.tsx index c14ceb4..649e08c 100644 --- a/js/mobile/src/features/stream-view/TaskParticleView.tsx +++ b/js/mobile/src/features/stream-view/TaskParticleView.tsx @@ -1,5 +1,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import { Pressable, ScrollView, Text, TextInput, View } from 'react-native'; +import { + Keyboard, + Platform, + Pressable, + ScrollView, + Text, + TextInput, + View, +} from 'react-native'; import { deleteField } from 'firebase/firestore'; import { Check, Plus, X } from 'lucide-react-native'; import type { ChecklistItem, Particle } from '@/api/types'; @@ -158,119 +166,146 @@ export function TaskParticleView({ className="flex-1 items-center justify-center px-6" style={{ paddingTop: safe.top + 16, paddingBottom: safe.bottom + 16 }} > - - {/* Title + done */} - - + + {/* Title + done */} + - {done ? : null} - + + {done ? : null} + + setEditing(true)} + onBlur={() => setEditing(false)} + onEndEditing={(e) => { + const value = e.nativeEvent.text; + if (value !== title) { + void updateParticleProperties<'task'>(docPath, { + title: value, + }); + } + }} + placeholder="Task title" + placeholderTextColor="rgba(255,255,255,0.3)" + multiline + className={cn( + 'flex-1 text-white text-2xl font-semibold', + done && 'text-white/50 line-through', + )} + /> + + + {/* Notes */} setEditing(true)} onBlur={() => setEditing(false)} onEndEditing={(e) => { const value = e.nativeEvent.text; - if (value !== title) { + if (value !== (notes ?? '')) { void updateParticleProperties<'task'>(docPath, { - title: value, + notes: value, }); } }} - placeholder="Task title" + placeholder="Add notes…" placeholderTextColor="rgba(255,255,255,0.3)" multiline - className={cn( - 'flex-1 text-white text-2xl font-semibold', - done && 'text-white/50 line-through', - )} + className="text-white/80 text-base" /> - - {/* Notes */} - setEditing(true)} - onBlur={() => setEditing(false)} - onEndEditing={(e) => { - const value = e.nativeEvent.text; - if (value !== (notes ?? '')) { - void updateParticleProperties<'task'>(docPath, { notes: value }); - } - }} - placeholder="Add notes…" - placeholderTextColor="rgba(255,255,255,0.3)" - multiline - className="text-white/80 text-base" - /> - - {/* Checklist */} - - {checklist.length > 0 ? ( - - {doneCount} / {checklist.length} done - - ) : null} - {checklist.map((item, index) => ( - handleToggleItem(index)} - onCommitText={(text) => handleCommitItemText(index, text)} - onRemove={() => handleRemoveItem(index)} + {/* Checklist */} + + {checklist.length > 0 ? ( + + {doneCount} / {checklist.length} done + + ) : null} + {checklist.map((item, index) => ( + handleToggleItem(index)} + onCommitText={(text) => handleCommitItemText(index, text)} + onRemove={() => handleRemoveItem(index)} + onFocusChange={setEditing} + /> + ))} + - ))} - - + - {/* Assignee */} - - Assignee - - handleAssign(null)} - /> - {network?.humans?.map((human) => { - const display = resolveHumanDisplay(human.id, network?.humans); - return ( - handleAssign(human.id)} - avatarHumanId={human.id} - humans={network?.humans} - /> - ); - })} - - - + {/* Assignee */} + + Assignee + + handleAssign(null)} + /> + {network?.humans?.map((human) => { + const display = resolveHumanDisplay(human.id, network?.humans); + return ( + handleAssign(human.id)} + avatarHumanId={human.id} + humans={network?.humans} + /> + ); + })} + + + + + {/* While a field is focused the keyboard hides the stream's tap-zones, + so offer an explicit way out. Dismissing blurs the active field, + which flips `editing` off and resumes the dwell + tap navigation. */} + {editing ? ( + + Keyboard.dismiss()} + hitSlop={8} + accessibilityLabel="Done editing" + className="rounded-full bg-white/15 px-3 py-1.5" + > + Done + + + ) : null} + ); }