reworked the timer flow, set a default timer duration, updated help button modal and more

This commit is contained in:
Chris Sanden
2026-05-04 17:19:59 +02:00
parent 907fa18841
commit 245b6db3fd
8 changed files with 503 additions and 107 deletions

View File

@@ -7,6 +7,7 @@ import type { SessionType } from '@/lib/types';
import { formatDate, formatDateTime } from '@/lib/date';
import { RegisterForLocalNotificationsAsync } from '@/lib/notifications';
import { CheckAssignmentCompletion } from '@/lib/progress';
import { DEFAULT_FOCUS_DURATION_MINUTES } from '@/lib/sessionDefaults';
import { supabase } from "@/lib/supabase";
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { Session } from '@supabase/supabase-js';
@@ -73,7 +74,7 @@ const FLOW_STEPS = [
{
label: '4',
title: 'Sprint',
description: 'A sprint is one focused work session tied to a single task and tracked by the timer.',
description: 'A sprint is one focused work session tied to a single task. After it ends, you can take a break, continue the same task, or return to the dashboard.',
},
] as const;
@@ -545,6 +546,71 @@ export default function HomeScreen() {
setCompletingTaskId(null);
}, [completingTaskId]);
const handleStartSprint = useCallback(async (task: UpcomingDeadlineTask) => {
const storedSession = await GetActiveSession();
if (!storedSession) {
router.push({
pathname: '/task/timer',
params: {
tId: task.tId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
},
});
return;
}
const secondsLeft = Math.ceil((storedSession.endTime - Date.now()) / 1000);
if (secondsLeft <= 0) {
await RemoveActiveSession();
router.push({
pathname: '/task/timer',
params: {
tId: task.tId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
},
});
return;
}
if (storedSession.taskId === task.tId) {
router.push({
pathname: '/task/timer',
params: {
tId: task.tId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
},
});
return;
}
Alert.alert(
'Active session in progress',
`End the current session and start a new ${DEFAULT_FOCUS_DURATION_MINUTES} minute sprint on "${task.title}"?`,
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Start sprint',
style: 'destructive',
onPress: async () => {
await RemoveActiveSession();
router.push({
pathname: '/task/timer',
params: {
tId: task.tId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
},
});
},
},
]
);
}, []);
return (
<View className="flex-1 bg-app-bg">
<Stack.Screen
@@ -601,7 +667,7 @@ export default function HomeScreen() {
<View className="flex-row items-start justify-between gap-3">
<View>
<Text className="text-xs font-bold uppercase tracking-[0.8px] text-[#7B8794]">
How the app is structured
How work is organized
</Text>
<Text className="mt-1 text-[28px] font-extrabold text-[#1F2933]">
Study flow
@@ -617,7 +683,7 @@ export default function HomeScreen() {
</View>
<Text className="text-[15px] leading-[22px] text-[#52606D]">
Build your work from the big container down to the focused work session.
Build your work from the big container down to one concrete task, then use sprints and breaks to move that work forward.
</Text>
<ScrollView
@@ -653,6 +719,9 @@ export default function HomeScreen() {
<Text className="mt-[6px] text-base font-bold text-[#1F2933]">
{'Subject -> Assignment -> Task -> Sprint'}
</Text>
<Text className="mt-2 text-sm leading-[20px] text-[#52606D]">
The dashboard then helps you resume an active session, start the next sprint, or review recent study progress.
</Text>
</View>
<Pressable
@@ -662,7 +731,7 @@ export default function HomeScreen() {
router.push('/subjects');
}}
>
<Text className="text-[15px] font-bold text-white">Start with Subjects</Text>
<Text className="text-[15px] font-bold text-white">Open Subjects</Text>
</Pressable>
</View>
</View>
@@ -723,7 +792,9 @@ export default function HomeScreen() {
})
}
>
<Text className="text-[15px] font-bold text-white">Open Session</Text>
<Text className="text-[15px] font-bold text-white">
{activeSprint.sessionType === 'focus' ? 'Resume Sprint' : 'Resume Break'}
</Text>
</Pressable>
</View>
) : (
@@ -800,6 +871,18 @@ export default function HomeScreen() {
{task.subjectTitle} {task.assignmentTitle} {formatDate(task.deadline)}
</Text>
<Pressable
className="mt-2 min-h-10 items-center justify-center rounded-xl bg-[#DCE8F7] px-4"
onPress={(event) => {
event.stopPropagation();
void handleStartSprint(task);
}}
>
<Text className="text-sm font-bold text-[#1F2933]">
Start Sprint
</Text>
</Pressable>
<Pressable
className={`mt-2 min-h-10 items-center justify-center rounded-xl px-4 ${
completingTaskId === task.tId ? 'bg-[#9AA5B1]' : 'bg-[#323F4E]'

View File

@@ -28,7 +28,7 @@ const FLOW_STEPS = [
{
label: '4',
title: 'Sprint',
description: 'A sprint is one focused work session tied to a single task and tracked by the timer.',
description: 'A sprint is one focused work session tied to a single task. After it ends, you can take a break, continue the same task, or return to the dashboard.',
},
] as const;
@@ -123,7 +123,7 @@ export default function Subjects() {
<View className="flex-row items-start justify-between gap-3">
<View>
<Text className="text-xs font-bold uppercase tracking-[0.8px] text-[#7B8794]">
How the app is structured
How work is organized
</Text>
<Text className="mt-1 text-[28px] font-extrabold text-[#1F2933]">
Study flow
@@ -139,7 +139,7 @@ export default function Subjects() {
</View>
<Text className="text-[15px] leading-[22px] text-[#52606D]">
Build your work from the big container down to the focused work session.
Build your work from the big container down to one concrete task, then use sprints and breaks to move that work forward.
</Text>
<ScrollView
@@ -179,16 +179,16 @@ export default function Subjects() {
<Text className="mt-[6px] text-base font-bold text-[#1F2933]">
{'Subject -> Assignment -> Task -> Sprint'}
</Text>
<Text className="mt-2 text-sm leading-[20px] text-[#52606D]">
Subjects hold the study structure. The dashboard is where you resume active sessions, start the next sprint, and check recent progress.
</Text>
</View>
<Pressable
className="min-h-12 items-center justify-center rounded-2xl bg-[#323F4E] px-4"
onPress={() => {
setIsFlowInfoVisible(false);
router.push('/subjects');
}}
onPress={() => setIsFlowInfoVisible(false)}
>
<Text className="text-[15px] font-bold text-white">Start with Subjects</Text>
<Text className="text-[15px] font-bold text-white">Close Guide</Text>
</Pressable>
</View>
</View>

View File

@@ -3,6 +3,10 @@ import {
RemoveActiveSession,
SaveActiveSession,
} from '@/lib/asyncStorage';
import {
DEFAULT_FOCUS_DURATION_MINUTES,
DEFAULT_SHORT_BREAK_DURATION_MINUTES,
} from '@/lib/sessionDefaults';
import { supabase } from '@/lib/supabase';
import type { SessionType, Task } from '@/lib/types';
import * as Haptics from 'expo-haptics';
@@ -39,7 +43,6 @@ const colors = {
Might have to save duration as well in DB to preserve timer animation persistance
*/
const TIMER_OPTIONS = [...Array(13).keys()].map((index) => (index === 0 ? 1 : index * 5));
const ITEM_SIZE = width * 0.38;
const ITEM_SPACING = (width - ITEM_SIZE) / 2;
@@ -48,8 +51,6 @@ const HOLD_TO_CANCEL_MS = 2000;
const CANCEL_ANIMATION_DELAY_MS = 250;
const BUTTON_PRESS_IN_MS = 80;
const BUTTON_PRESS_OUT_MS = 140;
const SHORT_BREAK_DURATION_MINUTES = 5;
type PostSessionPrompt = {
completedSessionType: SessionType;
returnTaskId: string | null;
@@ -94,15 +95,17 @@ type StartSessionInput = {
export default function TimerScreen() {
const [containerHeight, setContainerHeight] = React.useState(0);
const [duration, setDuration] = React.useState(TIMER_OPTIONS[0]);
const duration = DEFAULT_FOCUS_DURATION_MINUTES;
const [timerIsRunning, setIsRunning] = React.useState(false);
const [timerOverlayVisible, setTimerOverlayVisible] = React.useState(false);
const [timeRemaining, setTimeRemaining] = React.useState(0);
const [task, setTask] = React.useState<Task | null>(null);
const [currentSessionType, setCurrentSessionType] = React.useState<SessionType>('focus');
const [postSessionPrompt, setPostSessionPrompt] = React.useState<PostSessionPrompt | null>(null);
const [pickerDuration, setPickerDuration] = React.useState(DEFAULT_FOCUS_DURATION_MINUTES);
const scrollX = React.useRef(new Animated.Value(0)).current;
const pickerListRef = React.useRef<Animated.FlatList<number> | null>(null);
const timerAnimation = React.useRef(new Animated.Value(0)).current;
const buttonAnimation = React.useRef(new Animated.Value(0)).current;
const taskDetailsAnimation = React.useRef(new Animated.Value(0)).current;
@@ -125,18 +128,21 @@ export default function TimerScreen() {
const cancelHoldIdRef = React.useRef(0);
const cancelHoldStartedAtRef = React.useRef(0);
const { tId, sessionType: sessionTypeParam, durationMinutes, durationSeconds, returnTaskId } = useLocalSearchParams<{
const { tId, sessionType: sessionTypeParam, durationMinutes, durationSeconds, returnTaskId, chooseDuration } = useLocalSearchParams<{
tId?: string;
sessionType?: SessionType;
durationMinutes?: string;
durationSeconds?: string;
returnTaskId?: string;
chooseDuration?: string;
}>();
const timerOverlayHeight = Math.max(containerHeight, 1);
const timerOverlayOffscreenY = timerOverlayHeight + 1000;
const selectedSessionType: SessionType = sessionTypeParam ?? 'focus';
const showDurationPicker =
selectedSessionType === 'focus' && durationMinutes == null && durationSeconds == null;
selectedSessionType === 'focus' &&
chooseDuration === 'true' &&
durationSeconds == null;
const selectedDurationMinutes = React.useMemo(() => {
if (!durationMinutes) {
return null;
@@ -163,6 +169,45 @@ export default function TimerScreen() {
return parsedDuration;
}, [durationSeconds]);
const displayDurationMinutes = React.useMemo(() => {
if (selectedDurationSeconds != null) {
return null;
}
if (selectedDurationMinutes != null) {
return selectedDurationMinutes;
}
if (selectedSessionType === 'focus') {
return DEFAULT_FOCUS_DURATION_MINUTES;
}
return DEFAULT_SHORT_BREAK_DURATION_MINUTES;
}, [selectedDurationMinutes, selectedDurationSeconds, selectedSessionType]);
React.useEffect(() => {
if (showDurationPicker) {
setPickerDuration(displayDurationMinutes ?? duration);
}
}, [displayDurationMinutes, duration, showDurationPicker]);
React.useEffect(() => {
if (!showDurationPicker) {
return;
}
const selectedIndex = Math.max(0, TIMER_OPTIONS.indexOf(pickerDuration));
const nextOffset = selectedIndex * ITEM_SIZE;
scrollX.setValue(nextOffset);
requestAnimationFrame(() => {
pickerListRef.current?.scrollToOffset({
offset: nextOffset,
animated: false,
});
});
}, [pickerDuration, scrollX, showDurationPicker]);
React.useEffect(() => {
if (containerHeight > 0 && !timerIsRunning) {
@@ -234,11 +279,6 @@ export default function TimerScreen() {
outputRange: [0, 200],
});
const pickerOpacity = buttonAnimation.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
});
const taskDetailsOpacity = taskDetailsAnimation.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
@@ -615,7 +655,6 @@ export default function TimerScreen() {
cancelOverlayAnimation,
containerHeight,
countdownAnimation,
duration,
runStartSequence,
startCountdown,
taskDetailsAnimation,
@@ -628,14 +667,24 @@ export default function TimerScreen() {
}
const totalSeconds =
selectedDurationSeconds ?? (selectedDurationMinutes ?? duration) * TIMER_UNIT_IN_SECONDS;
selectedDurationSeconds ??
(showDurationPicker ? pickerDuration : (displayDurationMinutes ?? duration)) * TIMER_UNIT_IN_SECONDS;
await startSession({
sessionType: selectedSessionType,
taskId: selectedSessionType === 'focus' ? (tId ?? null) : null,
durationSeconds: totalSeconds,
});
}, [duration, selectedDurationMinutes, selectedDurationSeconds, selectedSessionType, startSession, tId]);
}, [
displayDurationMinutes,
duration,
pickerDuration,
selectedDurationSeconds,
selectedSessionType,
showDurationPicker,
startSession,
tId,
]);
const handleStartShortBreak = React.useCallback(() => {
setPostSessionPrompt(null);
@@ -643,7 +692,7 @@ export default function TimerScreen() {
pathname: '/task/timer',
params: {
sessionType: 'short_break',
durationMinutes: String(SHORT_BREAK_DURATION_MINUTES),
durationMinutes: String(DEFAULT_SHORT_BREAK_DURATION_MINUTES),
returnTaskId: tId ?? undefined,
},
});
@@ -658,7 +707,10 @@ export default function TimerScreen() {
setPostSessionPrompt(null);
router.replace({
pathname: '/task/timer',
params: { tId: postSessionPrompt.returnTaskId },
params: {
tId: postSessionPrompt.returnTaskId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
},
});
}, [postSessionPrompt]);
@@ -667,6 +719,22 @@ export default function TimerScreen() {
router.replace('/');
}, []);
const handleChooseCustomDuration = React.useCallback(() => {
if (timerIsRunning || selectedSessionType !== 'focus') {
return;
}
router.replace({
pathname: '/task/timer',
params: {
tId: tId ?? undefined,
sessionType: 'focus',
chooseDuration: 'true',
durationMinutes: String(displayDurationMinutes ?? duration),
},
});
}, [displayDurationMinutes, duration, selectedSessionType, tId, timerIsRunning]);
const cancelTimer = React.useCallback(() => {
if (!timerIsRunning) {
return;
@@ -736,6 +804,58 @@ export default function TimerScreen() {
timerIsRunning,
]);
const handleTimerPickerMomentumEnd = React.useCallback(
(event: { nativeEvent: { contentOffset: { x: number } } }) => {
if (timerIsRunning) {
return;
}
const index = Math.round(event.nativeEvent.contentOffset.x / ITEM_SIZE);
const clampedIndex = Math.max(0, Math.min(index, TIMER_OPTIONS.length - 1));
const nextDuration = TIMER_OPTIONS[clampedIndex];
setPickerDuration(nextDuration);
},
[timerIsRunning]
);
const renderTimerItem = React.useCallback(
({ item, index }: { item: number; index: number }) => {
const inputRange = [
(index - 1) * ITEM_SIZE,
index * ITEM_SIZE,
(index + 1) * ITEM_SIZE,
];
const opacity = scrollX.interpolate({
inputRange,
outputRange: [0.38, 1, 0.38],
});
const scale = scrollX.interpolate({
inputRange,
outputRange: [0.72, 1, 0.72],
});
return (
<View style={styles.timerOptionItem}>
<Animated.Text
style={[
styles.timerOptionText,
{
opacity,
transform: [{ scale }],
},
]}
>
{item}
</Animated.Text>
</View>
);
},
[scrollX]
);
const handleCancelHoldStart = React.useCallback(() => {
animateButtonPress(true);
cancelHoldIdRef.current += 1;
@@ -815,57 +935,6 @@ export default function TimerScreen() {
});
}, [animateButtonPress, cancelOverlayAnimation, clearCancelHoldTimeouts]);
const handleTimerPickerMomentumEnd = React.useCallback(
(event: { nativeEvent: { contentOffset: { x: number } } }) => {
if (timerIsRunning) {
return;
}
const index = Math.round(event.nativeEvent.contentOffset.x / ITEM_SIZE);
const clampedIndex = Math.max(0, Math.min(index, TIMER_OPTIONS.length - 1));
setDuration(TIMER_OPTIONS[clampedIndex]);
},
[timerIsRunning]
);
const renderTimerItem = React.useCallback(
({ item, index }: { item: number; index: number }) => {
const inputRange = [
(index - 1) * ITEM_SIZE,
index * ITEM_SIZE,
(index + 1) * ITEM_SIZE,
];
const baseOpacity = scrollX.interpolate({
inputRange,
outputRange: [0.4, 1, 0.4],
});
const opacity = Animated.multiply(baseOpacity, pickerOpacity);
const scale = scrollX.interpolate({
inputRange,
outputRange: [0.7, 1, 0.7],
});
return (
<View style={styles.timerOptionItem}>
<Animated.Text
style={[
styles.text,
{
opacity,
transform: [{ scale }],
},
]}
>
{item}
</Animated.Text>
</View>
);
},
[pickerOpacity, scrollX]
);
return (
<View
style={styles.container}
@@ -980,21 +1049,27 @@ export default function TimerScreen() {
]}
>
<Animated.FlatList
ref={pickerListRef}
data={TIMER_OPTIONS}
scrollEnabled={!timerIsRunning}
keyExtractor={(item) => item.toString()}
horizontal
bounces={false}
keyExtractor={(item) => item.toString()}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }], {
useNativeDriver: true,
})}
showsHorizontalScrollIndicator={false}
onMomentumScrollEnd={handleTimerPickerMomentumEnd}
showsHorizontalScrollIndicator={false}
snapToInterval={ITEM_SIZE}
decelerationRate="fast"
style={styles.timerPickerList}
contentContainerStyle={styles.timerPickerContent}
renderItem={renderTimerItem}
getItemLayout={(_, index) => ({
length: ITEM_SIZE,
offset: ITEM_SIZE * index,
index,
})}
initialNumToRender={TIMER_OPTIONS.length}
/>
</View>
) : !timerIsRunning ? (
@@ -1002,11 +1077,18 @@ export default function TimerScreen() {
<Text style={styles.fixedDurationLabel}>
{selectedDurationSeconds != null
? `${selectedDurationSeconds} sec`
: `${selectedDurationMinutes ?? SHORT_BREAK_DURATION_MINUTES} min`}
: `${displayDurationMinutes ?? duration} min`}
</Text>
<Text style={styles.fixedDurationDescription}>
This session uses a fixed duration so you can move straight into the next step.
{selectedSessionType === 'focus'
? 'This sprint uses the default focus duration so you can begin immediately.'
: 'This session uses a fixed duration so you can move straight into the next step.'}
</Text>
{selectedSessionType === 'focus' ? (
<TouchableOpacity onPress={handleChooseCustomDuration} style={styles.durationPickerLink}>
<Text style={styles.durationPickerLinkText}>Choose a different duration</Text>
</TouchableOpacity>
) : null}
</View>
) : null}
@@ -1041,7 +1123,7 @@ export default function TimerScreen() {
</Text>
<Text style={styles.postSessionBody}>
{postSessionPrompt.completedSessionType === 'focus'
? 'Start a short break now or skip it and return to your dashboard.'
? 'Take a short break, jump straight into another sprint on the same task, or head back to the dashboard.'
: 'Jump back into the same task or head back to the dashboard.'}
</Text>
@@ -1050,8 +1132,11 @@ export default function TimerScreen() {
<TouchableOpacity onPress={handleStartShortBreak} style={styles.postSessionPrimaryButton}>
<Text style={styles.postSessionPrimaryButtonText}>Start short break</Text>
</TouchableOpacity>
<TouchableOpacity onPress={handleBackToDashboard} style={styles.postSessionSecondaryButton}>
<Text style={styles.postSessionSecondaryButtonText}>Skip break</Text>
<TouchableOpacity onPress={handleContinueSameTask} style={styles.postSessionSecondaryButton}>
<Text style={styles.postSessionSecondaryButtonText}>Continue same task</Text>
</TouchableOpacity>
<TouchableOpacity onPress={handleBackToDashboard} style={styles.postSessionTertiaryButton}>
<Text style={styles.postSessionTertiaryButtonText}>Back to dashboard</Text>
</TouchableOpacity>
</>
) : (
@@ -1102,6 +1187,7 @@ const styles = StyleSheet.create({
left: 0,
right: 0,
flex: 1,
alignItems: 'center',
},
timerPickerList: {
flexGrow: 0,
@@ -1127,6 +1213,23 @@ const styles = StyleSheet.create({
marginTop: 16,
textAlign: 'center',
},
durationPickerLink: {
marginTop: 18,
minHeight: 42,
paddingHorizontal: 18,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 999,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.24)',
backgroundColor: 'rgba(255,255,255,0.08)',
},
durationPickerLinkText: {
color: '#F3EBDD',
fontSize: 15,
fontWeight: '700',
textAlign: 'center',
},
timerPickerContent: {
paddingHorizontal: ITEM_SPACING,
},
@@ -1135,7 +1238,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
text: {
timerOptionText: {
fontSize: ITEM_SIZE * 0.8,
fontFamily: 'Menlo',
color: colors.text,
@@ -1258,4 +1361,15 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '700',
},
postSessionTertiaryButton: {
minHeight: 48,
alignItems: 'center',
justifyContent: 'center',
marginTop: 10,
},
postSessionTertiaryButtonText: {
color: '#52606D',
fontSize: 15,
fontWeight: '700',
},
});

View File

@@ -1,6 +1,7 @@
import { GetActiveSession, RemoveActiveSession } from '@/lib/asyncStorage';
import { formatDateTime } from '@/lib/date';
import { CheckAssignmentCompletion } from '@/lib/progress';
import { DEFAULT_FOCUS_DURATION_MINUTES } from '@/lib/sessionDefaults';
import { getSubjectColorSet, type SubjectColor } from '@/lib/subjectColors';
import { supabase } from '@/lib/supabase';
import type { Task } from '@/lib/types';
@@ -137,7 +138,10 @@ const handleSprintStart = async () => {
if (!activeSession) {
router.push({
pathname: '/task/timer',
params: { tId: task?.tId},
params: {
tId: task?.tId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
},
});
return;
}
@@ -150,7 +154,10 @@ const handleSprintStart = async () => {
await RemoveActiveSession();
router.push({
pathname: '/task/timer',
params: { tId: task?.tId}
params: {
tId: task?.tId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
}
});
return;
}
@@ -159,13 +166,16 @@ const handleSprintStart = async () => {
if (activeSession.taskId === task?.tId) {
router.push({
pathname: '/task/timer',
params: { tId: activeSession.taskId ?? undefined }});
params: {
tId: activeSession.taskId ?? undefined,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
}});
return;
}
Alert.alert(
'Active session in progress',
'Starting a new sprint will end the current active session',
`End the current session and start a new ${DEFAULT_FOCUS_DURATION_MINUTES} minute sprint on this task?`,
[
{ text: 'Cancel', style: 'cancel', },
{
@@ -175,7 +185,10 @@ const handleSprintStart = async () => {
await RemoveActiveSession();
router.push({
pathname: '/task/timer',
params: { tId: task?.tId },
params: {
tId: task?.tId,
durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
},
});
},
},
@@ -395,7 +408,21 @@ return (
</View>
{isOwner && (
<View className="mt-5 flex-row border-t border-app-border pt-5">
<View className="mt-5 border-t border-app-border pt-5">
<Pressable
className="h-14 items-center justify-center rounded-2xl bg-accent"
onPress={() => handleSprintStart()}
>
<Text className="text-base font-bold text-text-inverse">
Start Sprint
</Text>
</Pressable>
<Text className="mt-3 text-sm text-text-muted">
Starts a {DEFAULT_FOCUS_DURATION_MINUTES} minute focus sprint for this task.
</Text>
<View className="mt-4 flex-row">
<Pressable
className="mr-3 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-subtle py-3"
onPress={() =>
@@ -409,15 +436,6 @@ return (
Edit
</Text>
</Pressable>
<Pressable
className='mr-3 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-subtle py-3'
onPress={() =>
handleSprintStart()
}>
<Text className='text.sm font-bold text-text-secondary'>
Start Sprint
</Text>
</Pressable>
<Pressable
className="flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-surface py-3"
onPress={() => DeleteTask(task.tId)}
@@ -426,6 +444,7 @@ return (
Delete
</Text>
</Pressable>
</View>
</View>
)}
</View>