fixxed color selector in the category settings and fixxed drag and drop
Build APK / build (push) Canceled after 2m0s
Build APK / build (push) Canceled after 2m0s
subtask
This commit is contained in:
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { TabBarIcon } from '@/components/TabBarIcon';
|
||||
import { useSettings } from '@/theme';
|
||||
import { setLastVisitedTab } from '@/utils/tabHistory';
|
||||
|
||||
export default function TabLayout() {
|
||||
const { theme } = useSettings();
|
||||
@@ -12,27 +13,44 @@ export default function TabLayout() {
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
tabBarActiveTintColor: theme.accent,
|
||||
tabBarInactiveTintColor: '#8E8E8E',
|
||||
tabBarInactiveTintColor: theme.text,
|
||||
tabBarStyle: {
|
||||
backgroundColor: theme.tabBarBg,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.border,
|
||||
height: 64 + insets.bottom,
|
||||
height: 68 + insets.bottom,
|
||||
paddingBottom: insets.bottom,
|
||||
paddingTop: 6,
|
||||
elevation: 8,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 8,
|
||||
},
|
||||
tabBarItemStyle: {
|
||||
paddingVertical: 0,
|
||||
},
|
||||
tabBarLabelStyle: {
|
||||
fontSize: 11,
|
||||
fontWeight: '500',
|
||||
fontWeight: '700',
|
||||
},
|
||||
headerShown: false,
|
||||
sceneStyle: { backgroundColor: theme.background },
|
||||
}}
|
||||
screenListeners={({ route }) => ({
|
||||
focus: () => {
|
||||
if (route.name !== 'settings') {
|
||||
setLastVisitedTab(route.name);
|
||||
}
|
||||
},
|
||||
})}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: 'Tasks',
|
||||
tabBarIcon: ({ focused, color }) => (
|
||||
<TabBarIcon name="checklist" focused={focused} color={color} />
|
||||
title: 'ToDo',
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name="checklist" focused={focused} color={color} size={26} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
@@ -40,8 +58,8 @@ export default function TabLayout() {
|
||||
name="calendar"
|
||||
options={{
|
||||
title: 'Calendar',
|
||||
tabBarIcon: ({ focused, color }) => (
|
||||
<TabBarIcon name="calendar" focused={focused} color={color} />
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name="calendar" focused={focused} color={color} size={26} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
@@ -49,8 +67,8 @@ export default function TabLayout() {
|
||||
name="stats"
|
||||
options={{
|
||||
title: 'Stats',
|
||||
tabBarIcon: ({ focused, color }) => (
|
||||
<TabBarIcon name="stats" focused={focused} color={color} />
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name="stats" focused={focused} color={color} size={26} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
@@ -58,11 +76,11 @@ export default function TabLayout() {
|
||||
name="settings"
|
||||
options={{
|
||||
title: 'Settings',
|
||||
tabBarIcon: ({ focused, color }) => (
|
||||
<TabBarIcon name="gear" focused={focused} color={color} />
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name="gear" focused={focused} color={color} size={26} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useRef, useState, useCallback, useEffect } from 'react';
|
||||
import { View, Text, StyleSheet, SafeAreaView, ScrollView, TouchableOpacity, Animated, Dimensions } from 'react-native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { View, Text, StyleSheet, SafeAreaView, ScrollView, TouchableOpacity, Animated, Dimensions, BackHandler, KeyboardAvoidingView } from 'react-native';
|
||||
import { useRouter, useFocusEffect } from 'expo-router';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import { Header } from '@/components/Header';
|
||||
import { useTasksByDate, useTasksInMonth } from '@/hooks/useTasks';
|
||||
@@ -15,10 +15,10 @@ import Task from '@/models/Task';
|
||||
import { format, addMonths, addDays, startOfMonth, isSameDay, isSameMonth, isToday } from 'date-fns';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import Svg, { Path, Circle } from 'react-native-svg';
|
||||
import { desaturate } from '@/theme';
|
||||
import type { ThemeColors } from '@/theme';
|
||||
|
||||
const WIDTH = Dimensions.get('window').width;
|
||||
const ORANGE = '#FF7043';
|
||||
const GERMAN_WEEKDAYS = ['mo', 'di', 'mi', 'do', 'fr', 'sa', 'so'];
|
||||
const MONTH_NAMES = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
@@ -43,6 +43,13 @@ export default function CalendarScreen() {
|
||||
const selectedDateRef = useRef(selectedDate);
|
||||
selectedDateRef.current = selectedDate;
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const sub = BackHandler.addEventListener('hardwareBackPress', () => true);
|
||||
return () => sub.remove();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const translateX = useRef(new Animated.Value(0)).current;
|
||||
const animatingRef = useRef(false);
|
||||
const gridWidthRef = useRef<number>(WIDTH);
|
||||
@@ -181,17 +188,17 @@ export default function CalendarScreen() {
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<GestureDetector gesture={pan}>
|
||||
<View style={styles.flex}>
|
||||
<Header title="Calendar" showLogo={false} />
|
||||
<Header title="Calendar" showLogo={false} />
|
||||
|
||||
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.scrollContent}>
|
||||
<Animated.View
|
||||
onLayout={(e) => {
|
||||
gridWidthRef.current = e.nativeEvent.layout.width;
|
||||
}}
|
||||
style={[styles.calendarArea, { transform: [{ translateX }] }]}
|
||||
>
|
||||
<KeyboardAvoidingView style={styles.kbAvoid} behavior="padding">
|
||||
<ScrollView style={styles.scrollBody} showsVerticalScrollIndicator={false} contentContainerStyle={styles.scrollContent}>
|
||||
<GestureDetector gesture={pan}>
|
||||
<Animated.View
|
||||
onLayout={(e) => {
|
||||
gridWidthRef.current = e.nativeEvent.layout.width;
|
||||
}}
|
||||
style={[styles.calendarArea, { transform: [{ translateX }] }]}
|
||||
>
|
||||
<View style={styles.monthRow}>
|
||||
<TouchableOpacity
|
||||
onPress={() => transitionTo(-1)}
|
||||
@@ -199,7 +206,7 @@ export default function CalendarScreen() {
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Svg width={20} height={20} viewBox="0 0 24 24">
|
||||
<Path d="M15 18l-6-6 6-6" stroke={theme.textFaint} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<Path d="M15 18l-6-6 6-6" stroke={theme.textSecondary} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -207,7 +214,7 @@ export default function CalendarScreen() {
|
||||
<TouchableOpacity onPress={() => setMonthPickerVisible(true)} activeOpacity={0.7} style={styles.monthButton}>
|
||||
<Text style={[styles.monthLabel, { color: theme.text }]}>{format(visibleMonth, 'MMMM')}</Text>
|
||||
<Svg width={14} height={14} viewBox="0 0 24 24">
|
||||
<Path d="M6 9l6 6 6-6" stroke={theme.textMuted} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<Path d="M6 9l6 6 6-6" stroke={theme.textSecondary} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setYearPickerVisible(true)} activeOpacity={0.7} style={styles.yearButton}>
|
||||
@@ -221,7 +228,7 @@ export default function CalendarScreen() {
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Svg width={20} height={20} viewBox="0 0 24 24">
|
||||
<Path d="M9 6l6 6-6 6" stroke={theme.textFaint} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<Path d="M9 6l6 6-6 6" stroke={theme.textSecondary} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -252,8 +259,9 @@ export default function CalendarScreen() {
|
||||
</View>
|
||||
))}
|
||||
</Animated.View>
|
||||
</GestureDetector>
|
||||
|
||||
<View style={styles.panelHeader}>
|
||||
<View style={styles.panelHeader}>
|
||||
<Text style={[styles.panelDate, { color: theme.text }]}>{format(selectedDate, 'EEEE, MMMM d')}</Text>
|
||||
{selectedDayTasks.length > 0 && (
|
||||
<Text style={[styles.panelCount, { color: theme.textFaint }]}>
|
||||
@@ -276,14 +284,14 @@ export default function CalendarScreen() {
|
||||
onPress={() => handleToggleComplete(task.id)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Svg width={22} height={22} viewBox="0 0 24 24">
|
||||
<Svg width={30} height={30} viewBox="0 0 24 24">
|
||||
{task.completed ? (
|
||||
<>
|
||||
<Circle cx={12} cy={12} r={10} fill={theme.accent} />
|
||||
<Path d="M7 12l3 3 7-7" stroke="#FFFFFF" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</>
|
||||
) : (
|
||||
<Circle cx={12} cy={12} r={10} stroke={theme.textMuted} strokeWidth={2} fill="none" />
|
||||
<Circle cx={12} cy={12} r={10} stroke={theme.textSecondary} strokeWidth={2.5} fill="none" />
|
||||
)}
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
@@ -303,9 +311,7 @@ export default function CalendarScreen() {
|
||||
|
||||
<TouchableOpacity style={styles.menuButton} onPress={() => openTaskEdit(task.id)} activeOpacity={0.7} accessibilityRole="button" accessibilityLabel={`Edit ${task.title}`}>
|
||||
<Svg width={20} height={20} viewBox="0 0 24 24">
|
||||
<Circle cx={12} cy={5} r={1.5} fill={theme.textMuted} />
|
||||
<Circle cx={12} cy={12} r={1.5} fill={theme.textMuted} />
|
||||
<Circle cx={12} cy={19} r={1.5} fill={theme.textMuted} />
|
||||
<Path d="M9 6l6 6-6 6" stroke={theme.textSecondary} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -337,28 +343,27 @@ export default function CalendarScreen() {
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<QuickAddBar dueDate={selectedDate.getTime()} placeholder={`Add event for ${format(selectedDate, 'MMM d')}`} />
|
||||
<QuickAddBar dueDate={selectedDate.getTime()} placeholder={`Add event for ${format(selectedDate, 'MMM d')}`} />
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
{modals(() => {})}
|
||||
{modals(() => {})}
|
||||
|
||||
<OptionPickerModal
|
||||
visible={monthPickerVisible}
|
||||
title="Select Month"
|
||||
options={monthOptions}
|
||||
selectedValue={String(visibleMonth.getMonth())}
|
||||
onSelect={handleSelectMonth}
|
||||
onClose={() => setMonthPickerVisible(false)}
|
||||
/>
|
||||
<OptionPickerModal
|
||||
visible={yearPickerVisible}
|
||||
title="Select Year"
|
||||
options={yearOptions}
|
||||
selectedValue={String(visibleMonth.getFullYear())}
|
||||
onSelect={handleSelectYear}
|
||||
onClose={() => setYearPickerVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
</GestureDetector>
|
||||
<OptionPickerModal
|
||||
visible={monthPickerVisible}
|
||||
title="Select Month"
|
||||
options={monthOptions}
|
||||
selectedValue={String(visibleMonth.getMonth())}
|
||||
onSelect={handleSelectMonth}
|
||||
onClose={() => setMonthPickerVisible(false)}
|
||||
/>
|
||||
<OptionPickerModal
|
||||
visible={yearPickerVisible}
|
||||
title="Select Year"
|
||||
options={yearOptions}
|
||||
selectedValue={String(visibleMonth.getFullYear())}
|
||||
onSelect={handleSelectYear}
|
||||
onClose={() => setYearPickerVisible(false)}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -376,7 +381,7 @@ function cellTitle(tasks: Task[]): string | null {
|
||||
function cellColor(tasks: Task[], categories: { id: string; color: string }[]): string {
|
||||
if (tasks.length === 0) return '#8E8E8E';
|
||||
const cat = categories.find((c) => c.id === tasks[0].categoryId);
|
||||
return cat?.color ?? '#8E8E8E';
|
||||
return cat ? desaturate(cat.color, 0.3) : '#8E8E8E';
|
||||
}
|
||||
|
||||
interface DayCellProps {
|
||||
@@ -393,7 +398,7 @@ interface DayCellProps {
|
||||
const DayCell = React.memo(function DayCell({ day, label, dotColor, selected, today, inMonth, theme, onPress }: DayCellProps) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.dayCell, selected && { backgroundColor: ORANGE }]}
|
||||
style={[styles.dayCell, selected && { backgroundColor: theme.accentSoft, borderColor: theme.accentBorder }]}
|
||||
onPress={() => onPress(day)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
@@ -401,8 +406,8 @@ const DayCell = React.memo(function DayCell({ day, label, dotColor, selected, to
|
||||
style={[
|
||||
styles.dayNumber,
|
||||
{ color: theme.text },
|
||||
!inMonth && { color: theme.borderStrong },
|
||||
today && !selected && { color: ORANGE },
|
||||
!inMonth && { color: theme.textMuted },
|
||||
today && !selected && { color: theme.accent },
|
||||
selected && styles.dayNumberSelected,
|
||||
]}
|
||||
>
|
||||
@@ -425,12 +430,15 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
flex: {
|
||||
scrollContent: {
|
||||
paddingBottom: 16,
|
||||
flexGrow: 1,
|
||||
},
|
||||
scrollBody: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 96,
|
||||
flexGrow: 1,
|
||||
kbAvoid: {
|
||||
flex: 1,
|
||||
},
|
||||
calendarArea: {
|
||||
paddingHorizontal: 12,
|
||||
@@ -565,8 +573,8 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
checkCircle: {
|
||||
width: 24,
|
||||
marginRight: 10,
|
||||
width: 30,
|
||||
marginRight: 12,
|
||||
},
|
||||
eventTitleTouch: {
|
||||
flex: 1,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, SafeAreaView } from 'react-native';
|
||||
import React, { useCallback } from 'react';
|
||||
import { View, Text, StyleSheet, SafeAreaView, BackHandler, KeyboardAvoidingView } from 'react-native';
|
||||
import { useFocusEffect } from 'expo-router';
|
||||
import { Header } from '@/components/Header';
|
||||
import { CategoryFilter } from '@/components/CategoryFilter';
|
||||
import { TaskList } from '@/components/TaskList';
|
||||
@@ -12,6 +13,13 @@ export default function TasksScreen() {
|
||||
const { theme } = useSettings();
|
||||
const [selectedCategory, setSelectedCategory] = React.useState<string>('all');
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const sub = BackHandler.addEventListener('hardwareBackPress', () => true);
|
||||
return () => sub.remove();
|
||||
}, [])
|
||||
);
|
||||
|
||||
if (!isReady) {
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
@@ -22,12 +30,17 @@ export default function TasksScreen() {
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<Header title="TODO" showLogo={false} />
|
||||
<Header title="ToDo" showLogo={false} />
|
||||
<View style={styles.categoryFilterWrapper}>
|
||||
<CategoryFilter selected={selectedCategory} onSelect={setSelectedCategory} />
|
||||
</View>
|
||||
<TaskList categoryId={selectedCategory} />
|
||||
<QuickAddBar />
|
||||
<KeyboardAvoidingView
|
||||
style={styles.kbAvoid}
|
||||
behavior="padding"
|
||||
>
|
||||
<TaskList categoryId={selectedCategory} />
|
||||
<QuickAddBar />
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -37,11 +50,14 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
categoryFilterWrapper: {
|
||||
height: 36,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
kbAvoid: {
|
||||
flex: 1,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, SafeAreaView, ScrollView, Switch, TouchableOpacity, Alert, Linking, Modal, Pressable } from 'react-native';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text, StyleSheet, SafeAreaView, ScrollView, Switch, TouchableOpacity, Alert, Linking, Modal, Pressable, BackHandler } from 'react-native';
|
||||
import { useFocusEffect, useRouter } from 'expo-router';
|
||||
import { Header } from '@/components/Header';
|
||||
import { ListItem } from '@/components/ListItem';
|
||||
import { OptionPickerModal } from '@/components/OptionPickerModal';
|
||||
@@ -17,8 +18,10 @@ import { getLastSyncTime } from '@/database/sync';
|
||||
import Category from '@/models/Category';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
import ColorWheel from '@/components/ColorWheel';
|
||||
import { getLastVisitedTab, tabHref } from '@/utils/tabHistory';
|
||||
|
||||
export default function SettingsScreen() {
|
||||
const router = useRouter();
|
||||
const { theme, notifications, setNotifications, defaultCategoryId, setDefaultCategoryId, sortBy, setSortBy, reminderPreference, setReminderPreference, apiUrl, accentColor, setAccentColor, todoAheadDays, setTodoAheadDays } = useSettings();
|
||||
const categories = useCategories();
|
||||
const [picker, setPicker] = useState<null | 'category' | 'sort' | 'reminder' | 'todoAhead'>(null);
|
||||
@@ -75,8 +78,21 @@ export default function SettingsScreen() {
|
||||
refreshSyncStatus();
|
||||
}, []);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const sub = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
const target = getLastVisitedTab();
|
||||
if (target && target !== 'settings') {
|
||||
router.navigate(tabHref(target));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return () => sub.remove();
|
||||
}, [router])
|
||||
);
|
||||
|
||||
const defaultCategory = categories.find((c) => c.id === defaultCategoryId);
|
||||
const defaultCategoryLabel = defaultCategory?.name ?? (categories.length > 0 ? categories[0].name : 'None');
|
||||
const defaultCategoryLabel = defaultCategory?.name ?? 'None';
|
||||
const reminderLabel = REMINDER_OPTIONS.find((o) => o.value === reminderPreference)?.label ?? 'No reminder';
|
||||
|
||||
const openCustomAccent = () => {
|
||||
@@ -123,7 +139,7 @@ export default function SettingsScreen() {
|
||||
>
|
||||
<View style={[styles.addIcon, { backgroundColor: theme.accentSoft }]}>
|
||||
<Svg width={16} height={16} viewBox="0 0 24 24">
|
||||
<Path d="M12 5v14M5 12h14" stroke={theme.accent} strokeWidth={2} strokeLinecap="round" />
|
||||
<Path d="M12 5v14M5 12h14" stroke={theme.accentText} strokeWidth={2} strokeLinecap="round" />
|
||||
</Svg>
|
||||
</View>
|
||||
<Text style={[styles.addCategoryText, { color: theme.accent }]}>Add Category</Text>
|
||||
@@ -242,8 +258,8 @@ export default function SettingsScreen() {
|
||||
<OptionPickerModal
|
||||
visible={picker === 'category'}
|
||||
title="Default Category"
|
||||
options={categories.map((c) => ({ value: c.id, label: c.name, color: c.color }))}
|
||||
selectedValue={defaultCategoryId || categories[0]?.id}
|
||||
options={[{ value: '', label: 'None', color: '#9E9E9E' }, ...categories.map((c) => ({ value: c.id, label: c.name, color: c.color }))]}
|
||||
selectedValue={defaultCategoryId}
|
||||
onSelect={handleDefaultCategory}
|
||||
onClose={() => setPicker(null)}
|
||||
/>
|
||||
@@ -298,7 +314,7 @@ export default function SettingsScreen() {
|
||||
<Text style={[styles.accentModalTitle, { color: theme.text }]}>Custom Accent Color</Text>
|
||||
<TouchableOpacity onPress={() => setCustomAccentVisible(false)} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
||||
<Svg width={18} height={18} viewBox="0 0 24 24">
|
||||
<Path d="M18 6L6 18M6 6l12 12" stroke={theme.textMuted} strokeWidth={2} strokeLinecap="round" />
|
||||
<Path d="M18 6L6 18M6 6l12 12" stroke={theme.textSecondary} strokeWidth={2.2} strokeLinecap="round" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, SafeAreaView, ScrollView } from 'react-native';
|
||||
import React, { useCallback } from 'react';
|
||||
import { View, Text, StyleSheet, SafeAreaView, ScrollView, BackHandler } from 'react-native';
|
||||
import { useFocusEffect } from 'expo-router';
|
||||
import { Header } from '@/components/Header';
|
||||
import { useStats } from '@/hooks/useStats';
|
||||
import { useSettings } from '@/theme';
|
||||
@@ -8,6 +9,13 @@ export default function StatsScreen() {
|
||||
const { theme } = useSettings();
|
||||
const stats = useStats();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const sub = BackHandler.addEventListener('hardwareBackPress', () => true);
|
||||
return () => sub.remove();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const maxDaily = Math.max(1, ...stats.daily.map((d) => d.count));
|
||||
|
||||
return (
|
||||
|
||||
@@ -160,7 +160,7 @@ export default function SubtaskDetailScreen() {
|
||||
rightAction={
|
||||
<TouchableOpacity onPress={confirmDelete} activeOpacity={0.7} style={[styles.deleteButton, { borderColor: theme.accentBorder, backgroundColor: theme.accentSoft }]}>
|
||||
<Svg width={22} height={22} viewBox="0 0 24 24">
|
||||
<Path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" stroke={theme.accent} strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<Path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" stroke={theme.accent} strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ function CollapsibleSection({ title, children, defaultExpanded = false, icon }:
|
||||
<Svg width={20} height={20} viewBox="0 0 24 24">
|
||||
<Path
|
||||
d="M6 9l6 6 6-6"
|
||||
stroke={theme.textMuted}
|
||||
strokeWidth={2}
|
||||
stroke={theme.textSecondary}
|
||||
strokeWidth={2.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
@@ -245,7 +245,7 @@ export default function TaskDetailScreen() {
|
||||
rightAction={
|
||||
<TouchableOpacity onPress={handleDelete} activeOpacity={0.7} style={[styles.deleteButton, { borderColor: theme.accentBorder, backgroundColor: theme.accentSoft }]}>
|
||||
<Svg width={22} height={22} viewBox="0 0 24 24">
|
||||
<Path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" stroke={theme.accent} strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<Path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" stroke={theme.accent} strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
@@ -285,8 +285,8 @@ export default function TaskDetailScreen() {
|
||||
|
||||
<CollapsibleSection title="Date & Time" icon={
|
||||
<Svg width={20} height={20} viewBox="0 0 24 24">
|
||||
<Circle cx={12} cy={12} r={10} stroke={theme.accent} strokeWidth={1.5} fill="none" />
|
||||
<Path d="M12 6v6l4 2" stroke={theme.accent} strokeWidth={1.5} strokeLinecap="round" />
|
||||
<Circle cx={12} cy={12} r={10} stroke={theme.accent} strokeWidth={1.8} fill="none" />
|
||||
<Path d="M12 6v6l4 2" stroke={theme.accent} strokeWidth={1.8} strokeLinecap="round" />
|
||||
</Svg>
|
||||
} defaultExpanded={!!dueDate}>
|
||||
<DateTimePickerComponent control={control} />
|
||||
@@ -297,7 +297,7 @@ export default function TaskDetailScreen() {
|
||||
<Path
|
||||
d="M17 2l4 4-4 4M3 11v-1a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 13v1a4 4 0 0 1-4 4H3"
|
||||
stroke={theme.accent}
|
||||
strokeWidth={1.5}
|
||||
strokeWidth={1.8}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
@@ -321,7 +321,7 @@ export default function TaskDetailScreen() {
|
||||
<Path
|
||||
d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10.3 21a1.94 1.94 0 0 0 3.4 0"
|
||||
stroke={theme.accent}
|
||||
strokeWidth={1.5}
|
||||
strokeWidth={1.8}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
@@ -337,8 +337,8 @@ export default function TaskDetailScreen() {
|
||||
|
||||
<CollapsibleSection title="Assignee" icon={
|
||||
<Svg width={20} height={20} viewBox="0 0 24 24">
|
||||
<Circle cx={12} cy={12} r={10} stroke={theme.accent} strokeWidth={1.5} fill="none" />
|
||||
<Path d="M12 10v6M12 19v1" stroke={theme.accent} strokeWidth={1.5} strokeLinecap="round" />
|
||||
<Circle cx={12} cy={12} r={10} stroke={theme.accent} strokeWidth={1.8} fill="none" />
|
||||
<Path d="M12 10v6M12 19v1" stroke={theme.accent} strokeWidth={1.8} strokeLinecap="round" />
|
||||
</Svg>
|
||||
} defaultExpanded={!!assigneeId}>
|
||||
<AssigneeSelector
|
||||
@@ -350,7 +350,7 @@ export default function TaskDetailScreen() {
|
||||
|
||||
<CollapsibleSection title="Description" icon={
|
||||
<Svg width={20} height={20} viewBox="0 0 24 24">
|
||||
<Path d="M4 6h16M4 12h16M4 18h10" stroke={theme.accent} strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<Path d="M4 6h16M4 12h16M4 18h10" stroke={theme.accent} strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
</Svg>
|
||||
} defaultExpanded={!!methods.getValues('description')}>
|
||||
<Controller
|
||||
|
||||
Reference in New Issue
Block a user