314 lines
13 KiB
Markdown
314 lines
13 KiB
Markdown
# Technical Documentation
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Expo App Layer │
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Tasks │ │ Calendar │ │ Settings │ │
|
|
│ │ Screen │ │ Screen │ │ Screen │ │
|
|
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
|
│ │ │ │ │
|
|
│ └────────────────┼────────────────┘ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────┐ │
|
|
│ │ React Hook Form + Zod │ │
|
|
│ │ (Form Validation) │ │
|
|
│ └─────────────────────────────────────────────┘ │
|
|
└────────────────────────────────┬────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Database Layer │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ WatermelonDB Database │ │
|
|
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
|
│ │ │Categories│ │ Tasks │ │ Subtasks │ │ │
|
|
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ SQLite Adapter (expo-sqlite) │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
└────────────────────────────────┬────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Sync Layer │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ Custom Sync Adapter │ │
|
|
│ │ • pullChanges() - GET /sync?since={timestamp} │ │
|
|
│ │ • pushChanges() - POST /sync/push │ │
|
|
│ │ • Conflict resolution (last-write-wins) │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
└────────────────────────────────┬────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Backend API │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ Custom Postgres API │ │
|
|
│ │ • REST or GraphQL endpoints │ │
|
|
│ │ • Authentication (JWT/OAuth) │ │
|
|
│ │ • Conflict detection & resolution │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
### Tables
|
|
|
|
```sql
|
|
-- Categories table
|
|
CREATE TABLE categories (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
color TEXT NOT NULL,
|
|
"order" INTEGER NOT NULL DEFAULT 0,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
-- Tasks table
|
|
CREATE TABLE tasks (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
category_id TEXT NOT NULL,
|
|
priority TEXT NOT NULL DEFAULT 'none',
|
|
completed INTEGER NOT NULL DEFAULT 0,
|
|
due_date INTEGER NOT NULL DEFAULT 0,
|
|
due_time TEXT NOT NULL DEFAULT '',
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (category_id) REFERENCES categories(id)
|
|
);
|
|
|
|
CREATE INDEX idx_tasks_category ON tasks(category_id);
|
|
CREATE INDEX idx_tasks_due_date ON tasks(due_date);
|
|
CREATE INDEX idx_tasks_completed ON tasks(completed);
|
|
|
|
-- Subtasks table
|
|
CREATE TABLE subtasks (
|
|
id TEXT PRIMARY KEY,
|
|
task_id TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
completed INTEGER NOT NULL DEFAULT 0,
|
|
"order" INTEGER NOT NULL DEFAULT 0,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
|
);
|
|
|
|
CREATE INDEX idx_subtasks_task ON subtasks(task_id);
|
|
```
|
|
|
|
## Key Components
|
|
|
|
### DatabaseProvider (`src/hooks/useDatabase.tsx`)
|
|
|
|
Central context provider managing:
|
|
- Database initialization
|
|
- Default category seeding
|
|
- Collection access
|
|
- Ready state
|
|
|
|
```typescript
|
|
interface DatabaseContextType {
|
|
database: Database;
|
|
collections: {
|
|
categories: Collection<Category>;
|
|
tasks: Collection<Task>;
|
|
subtasks: Collection<Subtask>;
|
|
};
|
|
isReady: boolean;
|
|
initializeDatabase: () => Promise<void>;
|
|
}
|
|
```
|
|
|
|
### useTasks Hook (`src/hooks/useTasks.tsx`)
|
|
|
|
Reactive task queries with loading states:
|
|
|
|
```typescript
|
|
function useTasks(categoryId?: string, showCompleted = false): {
|
|
tasks: Task[];
|
|
loading: boolean;
|
|
}
|
|
```
|
|
|
|
### WatermelonDB Models
|
|
|
|
All models use decorators for field mapping:
|
|
|
|
```typescript
|
|
@field('column_name') propertyName!: Type;
|
|
@date('column_name') dateProperty!: Date;
|
|
@children('table_name') relationName!: Query<Model>;
|
|
```
|
|
|
|
## Sync Implementation
|
|
|
|
### Pull Strategy
|
|
1. Client sends `lastPulledAt` timestamp
|
|
2. Server returns all changes since timestamp
|
|
3. Client applies changes in transaction:
|
|
- Upsert categories (by id)
|
|
- Upsert tasks (by id)
|
|
- Upsert subtasks (by id)
|
|
|
|
### Push Strategy
|
|
1. Client collects local changes (created/updated/deleted)
|
|
2. Sends batched changes with `lastPulledAt`
|
|
3. Server applies and returns new timestamp
|
|
|
|
### Conflict Resolution
|
|
Default: Last-write-wins based on `updatedAt`
|
|
Customizable in `src/database/sync.ts`
|
|
|
|
## Navigation Structure
|
|
|
|
```
|
|
app/
|
|
├── _layout.tsx # Stack navigator + DatabaseProvider
|
|
│ ├── (tabs)/ # Tab group
|
|
│ │ ├── _layout.tsx # Tabs navigator
|
|
│ │ ├── index.tsx # / (Tasks)
|
|
│ │ ├── calendar.tsx # /calendar
|
|
│ │ └── settings.tsx # /settings
|
|
│ └── add-task.tsx # /add-task (modal push)
|
|
```
|
|
|
|
### Navigation Patterns
|
|
- **Tabs**: Persistent bottom navigation
|
|
- **Add Task**: Push onto stack (modal on iOS)
|
|
- **Edit Task**: Push onto stack (future)
|
|
|
|
## Form Handling
|
|
|
|
### Add Task Form (`app/add-task.tsx`)
|
|
|
|
Uses `react-hook-form` with `zod` resolver:
|
|
|
|
```typescript
|
|
const schema = z.object({
|
|
title: z.string().min(1).max(100),
|
|
description: z.string().max(1000).optional(),
|
|
categoryId: z.string().min(1),
|
|
priority: z.enum(['none', 'low', 'medium', 'high', 'critical']),
|
|
dueDate: z.date().nullable().optional(),
|
|
dueTime: z.string().optional(),
|
|
subtasks: z.array(z.object({ title: z.string() })).optional(),
|
|
});
|
|
```
|
|
|
|
### Field Components
|
|
Each field is a controlled component using `Controller`:
|
|
- `CategorySelector` - Horizontal chip selection
|
|
- `TaskNameInput` - Text input with validation
|
|
- `SubtasksSection` - FieldArray for dynamic subtasks
|
|
- `DateTimePicker` - Native date/time pickers
|
|
- `PrioritySelector` - Segmented control
|
|
- `DescriptionInput` - Multiline text area
|
|
|
|
## Animations
|
|
|
|
### Reanimated 3 Patterns
|
|
|
|
1. **FAB Rotation** (`FloatingActionButton.tsx`)
|
|
```typescript
|
|
const rotateAnim = useSharedValue(0);
|
|
rotateAnim.value = (rotateAnim.value + 0.25) % 1;
|
|
```
|
|
|
|
2. **Category Selection** (`CategoryFilter.tsx`)
|
|
```typescript
|
|
const scaleAnim = useSharedValue(1);
|
|
scaleAnim.value = withTiming(selected ? 1.05 : 1);
|
|
```
|
|
|
|
3. **Checkbox Toggle** (`TaskItem.tsx`)
|
|
```typescript
|
|
const checkedAnim = useSharedValue(0);
|
|
checkedAnim.value = withTiming(task.completed ? 1 : 0);
|
|
```
|
|
|
|
## Platform-Specific Considerations
|
|
|
|
### iOS
|
|
- DateTimePicker: `display="spinner"`
|
|
- Keyboard: `behavior="padding"`
|
|
- Safe areas: Automatic via `react-native-safe-area-context`
|
|
|
|
### Android
|
|
- DateTimePicker: `display="default"`
|
|
- Keyboard: `behavior="height"`
|
|
- Elevation shadows for Material Design
|
|
|
|
### Web
|
|
- React Native Web for DOM rendering
|
|
- DateTimePicker falls back to HTML inputs
|
|
- No native SQLite (uses IndexedDB via expo-sqlite web polyfill)
|
|
|
|
## Performance Optimizations
|
|
|
|
1. **Lazy Loading**: Components loaded on demand
|
|
2. **Memoization**: `React.memo` for list items
|
|
3. **Virtualized Lists**: FlatList with `windowSize`
|
|
4. **Query Optimization**: Indexed columns for common queries
|
|
5. **Batch Writes**: Database transactions for multiple operations
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
- Hooks: `useTasks`, `useCategories`
|
|
- Validators: Zod schemas
|
|
- Utilities: Date formatting, priority sorting
|
|
|
|
### Integration Tests
|
|
- Database CRUD operations
|
|
- Sync adapter with mock server
|
|
- Form submission flow
|
|
|
|
### E2E Tests
|
|
- Critical user flows (add task, complete, filter)
|
|
- Offline/online transitions
|
|
- Cross-platform consistency
|
|
|
|
## Deployment Checklist
|
|
|
|
- [ ] Configure `app.json` with production values
|
|
- [ ] Set `EXPO_PUBLIC_API_URL` environment variable
|
|
- [ ] Enable EAS Build for iOS/Android
|
|
- [ ] Configure code signing certificates
|
|
- [ ] Set up CI/CD pipeline
|
|
- [ ] Test offline sync scenarios
|
|
- [ ] Verify push notifications (if enabled)
|
|
- [ ] Performance profiling on device
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
**Database not initializing**
|
|
- Check `expo-sqlite` installation
|
|
- Verify `DatabaseProvider` wraps app root
|
|
- Check Metro bundler cache: `npx expo start -c`
|
|
|
|
**Sync failing**
|
|
- Verify API endpoint accessibility
|
|
- Check CORS headers on server
|
|
- Inspect network requests in React Native Debugger
|
|
|
|
**TypeScript errors with WatermelonDB**
|
|
- Ensure `experimentalDecorators: true` in tsconfig
|
|
- Check `@nozbe/watermelondb` version compatibility
|
|
- Run `npx expo-doctor` for dependency issues
|
|
|
|
**Animation not working**
|
|
- Verify `react-native-reanimated` babel plugin
|
|
- Check `useNativeDriver: true` for supported properties
|
|
- Ensure worklet functions are properly extracted |