# 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