359 lines
11 KiB
Markdown
359 lines
11 KiB
Markdown
# Carry Your Live - Mobile To-Do & Calendar App
|
|
|
|
A minimalist, offline-first task management app built with Expo, React Native, and WatermelonDB. Features three primary screens: To-Do List, Add New Task, and Calendar with bottom navigation.
|
|
|
|
## Features
|
|
|
|
### Core Functionality
|
|
- **Offline-First Architecture**: Full CRUD operations work offline with WatermelonDB + SQLite
|
|
- **Auto-Sync**: Background synchronization with custom Postgres API when online
|
|
- **Cross-Platform**: iOS, Android, and Web from single codebase
|
|
|
|
### Screens
|
|
|
|
#### 1. To-Do List Screen
|
|
- **Header**: App logo + "TODO" title with rounded corners
|
|
- **Category Filter**: Horizontal scrollable chips (Work, Personal, School, Shopping, Health, Finance, All)
|
|
- Single selection with visual feedback (thicker border, shadow, scale animation)
|
|
- Instant task filtering
|
|
- **Task List**: Vertical scrolling with:
|
|
- Checkbox (empty/checked states with animation)
|
|
- Task title (truncated with ellipsis)
|
|
- Three-dot overflow menu (Edit, Delete, Duplicate, Mark Complete, Change Category, Change Priority)
|
|
- Priority badges with colors
|
|
- Due date/time with overdue/due-today highlighting
|
|
- Completed section (collapsible)
|
|
- **Floating Action Button**: Bottom-right, opens Add Task screen with press animation
|
|
|
|
#### 2. Add New Task Screen
|
|
- **Category Selector**: Horizontal row of colored circles (required)
|
|
- **Task Name**: Single-line input, 100 char max (required)
|
|
- **Subtasks**: Unlimited, add/remove individually
|
|
- **Date & Time**: Date picker, time picker, calendar button
|
|
- **Priority**: Dropdown (None, Low, Medium, High, Critical) with color indicators
|
|
- **Description**: Multi-line text area, 1000 char max
|
|
- **Bottom Actions**: Submit (primary) / Cancel (secondary) fixed buttons
|
|
|
|
#### 3. Calendar Screen
|
|
- **Date Strip**: Horizontal scrollable days of current month
|
|
- **Month Label**: Centered (e.g., "JAN 2026")
|
|
- **Task List**: Tasks for selected day with category color, title, time, completion status
|
|
- **Empty State**: "No tasks scheduled."
|
|
|
|
#### 4. Settings Screen
|
|
- Dark Mode toggle
|
|
- Push Notifications toggle
|
|
- Reminder Preferences
|
|
- Default Category selector
|
|
- Sort Tasks By (Date, Priority, Alphabetically, Creation Date)
|
|
- Backup & Sync
|
|
- Version, Privacy Policy, Terms of Service
|
|
|
|
### Task States
|
|
- **Pending**: Empty checkbox, normal text
|
|
- **Completed**: Checked checkbox, strikethrough, reduced opacity
|
|
- **Overdue**: Red due date, warning indicator
|
|
- **Due Today**: Blue highlight
|
|
- **High Priority**: Priority badge
|
|
|
|
### Interactions
|
|
- Tap task → Edit screen
|
|
- Long press → Selection mode (multi-delete, multi-complete, change category/priority)
|
|
- Swipe left → Delete
|
|
- Swipe right → Complete
|
|
- Pull down → Refresh
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Technology |
|
|
|-------|------------|
|
|
| Framework | Expo (React Native) |
|
|
| Language | TypeScript |
|
|
| Navigation | Expo Router (file-based) |
|
|
| Database | WatermelonDB + SQLite |
|
|
| State | WatermelonDB (reactive queries, no Redux needed) |
|
|
| Forms | React Hook Form + Zod validation |
|
|
| UI Components | React Native Paper + Custom SVG icons |
|
|
| Animations | React Native Reanimated |
|
|
| Date/Time | @react-native-community/datetimepicker + date-fns |
|
|
| Build | EAS Build / Gradle (local APK) |
|
|
| CI/CD | Gitea Actions (`.gitea/workflows/build-apk.yml`) |
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
carry-your-live/
|
|
├── app/
|
|
│ ├── _layout.tsx # Root layout + DatabaseProvider
|
|
│ ├── (tabs)/
|
|
│ │ ├── _layout.tsx # Bottom tab navigator
|
|
│ │ ├── index.tsx # Tasks screen
|
|
│ │ ├── calendar.tsx # Calendar screen
|
|
│ │ └── settings.tsx # Settings screen
|
|
│ └── add-task.tsx # Add Task screen
|
|
├── src/
|
|
│ ├── components/ # Reusable UI components
|
|
│ │ ├── Header.tsx
|
|
│ │ ├── CategoryFilter.tsx
|
|
│ │ ├── TaskList.tsx
|
|
│ │ ├── TaskItem.tsx
|
|
│ │ ├── FloatingActionButton.tsx
|
|
│ │ ├── TabBarIcon.tsx
|
|
│ │ ├── CategorySelector.tsx
|
|
│ │ ├── TaskNameInput.tsx
|
|
│ │ ├── SubtasksSection.tsx
|
|
│ │ ├── DateTimePicker.tsx
|
|
│ │ ├── PrioritySelector.tsx
|
|
│ │ ├── DescriptionInput.tsx
|
|
│ │ ├── FormButtons.tsx
|
|
│ │ └── ListItem.tsx
|
|
│ ├── database/
|
|
│ │ ├── schema.ts # WatermelonDB schema
|
|
│ │ ├── index.ts # Database instance + collections
|
|
│ │ └── sync.ts # Custom API sync adapter
|
|
│ ├── models/
|
|
│ │ ├── Task.ts
|
|
│ │ ├── Category.ts
|
|
│ │ └── Subtask.ts
|
|
│ ├── hooks/
|
|
│ │ ├── useDatabase.tsx # Database context & providers
|
|
│ │ └── useTasks.tsx # Task queries with reactive state
|
|
│ ├── constants/
|
|
│ │ └── index.ts # Categories, priorities, colors
|
|
│ └── types/
|
|
│ └── index.ts # TypeScript interfaces
|
|
```
|
|
|
|
## Data Models
|
|
|
|
### Task
|
|
```typescript
|
|
{
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
categoryId: string;
|
|
priority: 'none' | 'low' | 'medium' | 'high' | 'critical';
|
|
completed: boolean;
|
|
dueDate: number; // timestamp
|
|
dueTime: string; // "HH:MM"
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
subtasks: Subtask[];
|
|
}
|
|
```
|
|
|
|
### Category
|
|
```typescript
|
|
{
|
|
id: string;
|
|
name: string;
|
|
color: '#HEX';
|
|
order: number;
|
|
}
|
|
```
|
|
|
|
### Subtask
|
|
```typescript
|
|
{
|
|
id: string;
|
|
taskId: string;
|
|
title: string;
|
|
completed: boolean;
|
|
order: number;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
```
|
|
|
|
## Default Categories
|
|
| Name | Color | Order |
|
|
|------|-------|-------|
|
|
| Work | #E53935 (Red) | 0 |
|
|
| Personal | #43A047 (Green) | 1 |
|
|
| School | #1E88E5 (Blue) | 2 |
|
|
| Shopping | #FB8C00 (Orange) | 3 |
|
|
| Health | #8E24AA (Purple) | 4 |
|
|
| Finance | #FDD835 (Yellow) | 5 |
|
|
|
|
## Priority Colors
|
|
| Priority | Color |
|
|
|----------|-------|
|
|
| None | #9E9E9E |
|
|
| Low | #43A047 |
|
|
| Medium | #FB8C00 |
|
|
| High | #E53935 |
|
|
| Critical | #B71C1C |
|
|
|
|
## Getting Started
|
|
|
|
### Prerequisites
|
|
- Node.js 20+
|
|
- npm or yarn
|
|
- Expo CLI (`npm install -g expo-cli`)
|
|
- iOS Simulator (Mac) / Android Studio for mobile testing
|
|
|
|
### Installation
|
|
```bash
|
|
cd carry-your-live
|
|
npm install
|
|
```
|
|
|
|
### Development
|
|
```bash
|
|
# Start Expo dev server
|
|
npx expo start
|
|
|
|
# Run on specific platform
|
|
npx expo start --ios
|
|
npx expo start --android
|
|
npx expo start --web
|
|
```
|
|
|
|
### Building
|
|
|
|
#### EAS Build (cloud)
|
|
```bash
|
|
# Install EAS CLI
|
|
npm install -g eas-cli
|
|
|
|
# Configure project
|
|
eas build:configure
|
|
|
|
# Build for platforms
|
|
eas build --platform ios
|
|
eas build --platform android
|
|
eas build --platform web
|
|
```
|
|
Profiles are defined in `eas.json` (`development`, `preview`, `production`; production auto-increments version code).
|
|
|
|
#### Gradle (local Android APK)
|
|
The native Android project lives in `carry-your-live/android/`. Requires JDK 17+ and the Android SDK (platform 35, build-tools 35.0.0, NDK 27.1).
|
|
|
|
```bash
|
|
cd carry-your-live/android
|
|
|
|
# Debug APK (unsigned)
|
|
./gradlew assembleDebug
|
|
|
|
# Release APK (currently signed with the debug keystore)
|
|
./gradlew assembleRelease
|
|
```
|
|
Output:
|
|
```
|
|
android/app/build/outputs/apk/debug/app-debug.apk
|
|
android/app/build/outputs/apk/release/app-release.apk
|
|
```
|
|
Key notes:
|
|
- The debug keystore (`android/app/debug.keystore`) is generated by the CI pipeline and must exist for release builds — create it locally with the same command used in the pipeline (see below) if it's missing.
|
|
- Release builds use `signingConfig signingConfigs.debug` until a production keystore is configured.
|
|
- Versions are set in `android/app/build.gradle` (`versionCode`, `versionName`).
|
|
|
|
## CI/CD Pipeline (Gitea Actions)
|
|
|
|
`.gitea/workflows/build-apk.yml` builds a release APK on every push to `main` (also manually triggerable via workflow_dispatch):
|
|
|
|
1. **Checkout** and setup Node 22, JDK 17 (Temurin)
|
|
2. **Android SDK**: installs cmdline-tools, licenses, platform-tools, `platforms;android-35`, `build-tools;35.0.0`, `ndk;27.1.12297006`
|
|
3. **JS deps**: `npm ci` in `carry-your-live/`
|
|
4. **Debug keystore**: generates `carry-your-live/android/app/debug.keystore` with `keytool` (alias `androiddebugkey`, passwords `android`)
|
|
5. **Build**: `./gradlew assembleRelease` in `carry-your-live/android/`
|
|
6. **Upload**: the APK is saved as the `carry-your-live-release` artifact (downloadable from the run's artifacts page)
|
|
|
|
To run the build locally exactly as CI does:
|
|
|
|
```bash
|
|
keytool -genkeypair -v \
|
|
-keystore carry-your-live/android/app/debug.keystore \
|
|
-alias androiddebugkey -storepass android -keypass android \
|
|
-keyalg RSA -keysize 2048 -validity 10000 \
|
|
-dname "CN=Android Debug,O=Android,C=US"
|
|
cd carry-your-live/android && ./gradlew assembleRelease
|
|
```
|
|
|
|
## Sync API Specification
|
|
|
|
The app expects a custom API at `http://localhost:3000/api` (configurable in `src/database/sync.ts`):
|
|
|
|
### Pull Changes
|
|
```
|
|
GET /sync?since={timestamp}
|
|
```
|
|
Response:
|
|
```json
|
|
{
|
|
"categories": [{ "id", "name", "color", "order", "createdAt", "updatedAt" }],
|
|
"tasks": [{ "id", "title", "description", "categoryId", "priority", "completed", "dueDate", "dueTime", "createdAt", "updatedAt" }],
|
|
"subtasks": [{ "id", "taskId", "title", "completed", "order", "createdAt", "updatedAt" }],
|
|
"timestamp": 1234567890
|
|
}
|
|
```
|
|
|
|
### Push Changes
|
|
```
|
|
POST /sync/push
|
|
```
|
|
Body:
|
|
```json
|
|
{
|
|
"changes": {
|
|
"categories": [...],
|
|
"tasks": [...],
|
|
"subtasks": [...]
|
|
},
|
|
"lastPulledAt": 1234567890
|
|
}
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
Create `.env` file:
|
|
```
|
|
EXPO_PUBLIC_API_URL=http://your-api-url/api
|
|
```
|
|
|
|
### App Config
|
|
Modify `app.json` for:
|
|
- App name, slug, version
|
|
- Icon, splash screen
|
|
- Permissions (notifications, etc.)
|
|
- Orientation, theme
|
|
|
|
## Visual Style Guide
|
|
|
|
- **Background**: White (#FFFFFF)
|
|
- **Primary Accent**: #E53935 (Pink/Red)
|
|
- **Typography**: Black (#212121) primary, #757575 secondary, #9E9E9E tertiary
|
|
- **Borders**: Thin (#E0E0E0), selected #E53935
|
|
- **Radius**: 16-20px for cards/buttons/inputs
|
|
- **Spacing**: 8px grid system
|
|
- **Shadows**: Minimal (elevation 1-3)
|
|
- **Animations**: 150-250ms, cubic easing
|
|
|
|
## Offline-First Architecture
|
|
|
|
```
|
|
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
|
│ Expo App │────▶│ WatermelonDB │────▶│ Your API │
|
|
│ (iOS/Android│ │ (SQLite) │ │ (Postgres) │
|
|
│ Web) │◀───▶│ + Sync │◀───▶│ │
|
|
└─────────────┘ └──────────────┘ └─────────────┘
|
|
```
|
|
|
|
1. All writes go to local SQLite first
|
|
2. Reactive queries update UI instantly
|
|
3. Background sync pushes/pulls changes
|
|
4. Conflict resolution: last-write-wins (customizable)
|
|
|
|
## Contributing
|
|
|
|
1. Follow existing code conventions
|
|
2. Use TypeScript strict mode
|
|
3. Write components with React Hook Form + Zod
|
|
4. Use WatermelonDB decorators for models
|
|
5. Test offline scenarios
|
|
|
|
## License
|
|
|
|
MIT |