diff --git a/.github/workflows/ci-pr.yaml b/.github/workflows/ci-pr.yaml new file mode 100644 index 0000000..70e9e7e --- /dev/null +++ b/.github/workflows/ci-pr.yaml @@ -0,0 +1,42 @@ +name: Pull Request Simple Sanity Check +on: + pull_request: + branches: [develop, main] +jobs: + build-test-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build (debug) + run: ./gradlew assembleDebug --stacktrace + + - name: Unit Tests + run: ./gradlew tests + + - name: Android Lint + run: ./gradlew lint + + - name: Kotlin style (ktlint) + static analysis (detekt) + run: ./gradlew ktlintCheck detekt + + - name: Dependency updates (report only) + run: ./gradlew dependencyUpdates + + - name: Upload reports + uses: actions/upload-artifact@v4 + with: + name: ci-reports + path: | + **/build/reports/** + **/build/outputs/apk/debug/*.apk \ No newline at end of file diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 0000000..d8b91d4 --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,34 @@ +name: CodeQL +on: + pull_request: + branches: [develop, main] + schedule: + - cron: '0 0 * * 0' # Weekly on Sundays at midnight +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: [ 'kotlin' ] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore index ce9982b..783ffbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,18 @@ -*.db + __pycache__/ *.log -.env \ No newline at end of file +.env + +# IntelliJ / Android Studio +.idea/ +!.idea/codeStyles +!.idea/inspectionProfiles + +# Gradle +.gradle/ +build/ +**/build/ + +# Kotlin/Android +local.properties +captures/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7bc07ec --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Environment-dependent path to Maven home directory +/mavenHomeManager.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/Find-My-Ride.iml b/.idea/Find-My-Ride.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/Find-My-Ride.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/IntelliLang.xml b/.idea/IntelliLang.xml new file mode 100644 index 0000000..8cfd409 --- /dev/null +++ b/.idea/IntelliLang.xml @@ -0,0 +1,286 @@ + + + + + Apache HttpClient 4 HTTP Header (org.apache.http) + + + + + + Apache HttpClient 5 HTTP Header (org.apache.hc.core5) + + + + + + + + + AsyncQueryRunner (org.apache.commons.dbutils) + + + + + + + + + + + + + + + + + + Jodd (jodd.db) + + + + + + + + MockServer Header (org.mockserver) + + + + + + + MyBatis @Select/@Delete/@Insert/@Update + + + + + + + + QueryRunner (org.apache.commons.dbutils) + + + + + + + + + + + + + + + + + + R2DBC (io.r2dbc) + + + + + + Reactiverse Postgres Client (io.reactiverse) + + + + + + + + + + + + + RestAssured HTTP Header (io.restassured) + + + + + + + + SmallRye Axle SqlClient (io.vertx.axle.sqlclient) + + + + + + SmallRye Mutiny SqlClient (io.vertx.mutiny.sqlclient) + + + + + + SmallRye Mutiny SqlConnection (io.vertx.mutiny.sqlclient) + + + + + + + + Spring @Cacheable and @CacheEvict + + + + + + + + + + + + Spring HttpHeaders (org.springframework.http) + + + + + + + Spring Integration/Messaging + + + + + + + Spring JDBC (org.springframework.jdbc.core.JdbcOperations) + + + + + + + + + + + + + + + + Spring JDBC (org.springframework.jdbc.core.PreparedStatementCreatorFactory) + + + + + + + Spring JDBC (org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator) + + + + + + + + Spring Security @PostAuthorize/@PostFilter/@PreAuthorize/@PreFilter/@AuthenticationPrincipal + + + + + + + + + + Spring State Machine + + + + + + + + Vert.x SQL Extensions (io.vertx.ext.sql) + + + + + + + Vert.x SQL Reactive Extensions (io.vertx.reactivex.ext.sql) + + + + + + + + + + Vert.x SqlClient (io.vertx.sqlclient) + + + + + + + + + + + Vert.x SqlClient RxJava2 (io.vertx.reactivex.sqlclient) + + + + + + + + + + + + WireMock (com.github.tomakehurst.wiremock.client) + + + + + + + + WireMock (com.github.tomakehurst.wiremock.client) + + + + + + + WireMock (com.github.tomakehurst.wiremock.client) + + + + + + + + jOOQ (org.jooq.DSLContext) + + + + + + + + rxjava2-jdbc (org.davidmoten.rx.jdbc) + + + + + + + SpEL for Spring Cache + + + + + + + \ No newline at end of file diff --git a/.idea/artifacts/composeApp_js.xml b/.idea/artifacts/composeApp_js.xml new file mode 100644 index 0000000..a2c8ee1 --- /dev/null +++ b/.idea/artifacts/composeApp_js.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/app/composeApp/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/composeApp_jvm.xml b/.idea/artifacts/composeApp_jvm.xml new file mode 100644 index 0000000..c09eb24 --- /dev/null +++ b/.idea/artifacts/composeApp_jvm.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/app/composeApp/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/composeApp_wasm_js.xml b/.idea/artifacts/composeApp_wasm_js.xml new file mode 100644 index 0000000..21b6074 --- /dev/null +++ b/.idea/artifacts/composeApp_wasm_js.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/app/composeApp/build/libs + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..6e52cd1 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..87b2916 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..5aae76b --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..483affb --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 7ad2e73..fedd083 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,487 @@ -# Find-My-Ride +# Find My Ride – Kotlin Multiplatform Ride-Sharing App -## Database Setup +### Drexel University – CS 461 Final Project + +### Built by: +- Mustafa Bookwala +- Samii Shabuse +- Kennan Lu + +--- + +## Table of Contents +- [Overview](#overview) +- [Tech Stack](#tech-stack) +- [Project Structure](#project-structure) +- [Prerequisites & Setup](#prerequisites--setup) +- [How To Run the Project](#how-to-run-the-project) +- [Database Setup](#database-setup) +- [CRUD Implementation Details](#crud-implementation-details) +- [Key Implementation Notes](#key-implementation-notes) +- [Documentation & Demo](#documentation--demo) +- [Future Improvements](#future-improvements) + +--- + +## Overview + +**Find My Ride** is a full-stack **Kotlin Multiplatform (KMP)** mobile application that simulates a Drexel-focused ride-sharing service. The app allows students to: + +- Create an account & log in +- View and edit their profile +- Browse available ride offers +- Publish a ride offer as a driver +- Send and receive messages from other users +- Store all data in a pre-loaded SQLite database -1. Install SQLite if you don’t have it (`sqlite3 --version` to check). - - Using Version: 3.50.4 2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3 (64-bit) -2. In the root project folder, run: +The purpose of the project is to demonstrate **end-to-end database integration**, clean architecture, multiplatform UI, and **CRUD (Create, Read, Update, Delete) operations** learned in CS461. + +--- + +## Tech Stack + +### **Frontend / App UI** +- **Kotlin Multiplatform (KMP)** – Write once, run on multiple platforms +- **JetBrains Compose Multiplatform** – Modern declarative UI framework +- **Material3 Components** – Google's latest Material Design system +- **Navigation** – Custom implementation using sealed classes for type-safe routing + +### **Database Layer** +- **SQLite** – Lightweight, embedded relational database +- **Pre-loaded Database** – `findmyride.db` stored in `assets/` folder +- **Custom Repository Pattern** – Clean separation of concerns for each database table +- **Android SQLiteOpenHelper** – Native Android database management +- **Raw SQL Queries** – Direct SQL for maximum control and learning (no ORM framework) + +### **Platforms Supported** +- **Android** (Primary target – fully functional) +- **Compose Desktop** (Compilation supported, database layer not implemented) +- **iOS/JS** – KMP structure prepared, but database implementation is Android-only for this assignment + +--- + +## Project Structure +``` +FindMyRide/ +├── composeApp/ +│ ├── src/ +│ │ ├── commonMain/ # Shared code across platforms +│ │ │ ├── kotlin/ +│ │ │ │ ├── feature/ +│ │ │ │ │ ├── auth/ # Login & Registration screens +│ │ │ │ │ ├── profile/ # User profile screen & logic +│ │ │ │ │ ├── rides/ # Find Ride & Offer Ride features +│ │ │ │ │ ├── messages/ # Messaging system UI & logic +│ │ │ │ │ └── db/ # Repository interfaces & data models +│ │ │ │ ├── App.kt # Root navigation (Login vs Main) +│ │ │ │ └── MainRoute.kt # In-app navigation hub +│ │ │ └── resources/ # Shared resources +│ │ │ +│ │ ├── androidMain/ # Android-specific implementations +│ │ │ ├── kotlin/ +│ │ │ │ ├── db/ +│ │ │ │ │ ├── FindMyRideDbProvider.kt # Database initialization +│ │ │ │ │ ├── AndroidAuthRepository.kt # USER table CRUD +│ │ │ │ │ ├── AndroidProfileRepository.kt # USER + VEHICLE CRUD +│ │ │ │ │ ├── AndroidMessagesRepository.kt # MESSAGE table CRUD +│ │ │ │ │ └── AndroidRideRepository.kt # RIDE_OFFER + RIDE_REQUEST CRUD +│ │ │ │ └── MainActivity.kt # Android app entry point +│ │ │ └── assets/ +│ │ │ └── findmyride.db # Pre-populated SQLite database +│ │ │ +│ │ └── iosMain/ # iOS implementations (not used) +│ │ +│ └── build.gradle.kts # App-level build configuration +│ +├── database/ +│ └── schema.sql # SQL schema for database creation +│ +├── docs/ +│ └── Deliverable_4/ # **Contains live demo & CRUD documentation** +│ +├── gradle/ # Gradle wrapper files +├── build.gradle.kts # Project-level build configuration +├── settings.gradle.kts # Project settings +└── README.md # This file +``` + +--- + +## Prerequisites & Setup + +### **System Requirements** + +1. **Operating System**: Windows 10/11, macOS 10.15+, or Linux +2. **RAM**: Minimum 8GB (16GB recommended for smooth emulator performance) +3. **Disk Space**: At least 10GB free space + +### **Required Software** + +#### 1. **Install Java Development Kit (JDK)** +- **Required Version**: JDK 17 or higher +- **Download**: [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) or [OpenJDK](https://adoptium.net/) +- **Verify Installation**: ```bash -sqlite3 findmyride.db < database/schema.sql + java -version + # Should show version 17+ ``` -3. You can view the tables with: + +#### 2. **Install Android Studio** +- **Required Version**: Android Studio Ladybug (2024.2.1) or newer +- **Download**: [Android Studio Official Site](https://developer.android.com/studio) +- **Important**: During installation, make sure to install: + - Android SDK + - Android SDK Platform + - Android Virtual Device (AVD) + +#### 3. **Install Kotlin Multiplatform Plugin** +After installing Android Studio: +1. Open Android Studio +2. Go to **File → Settings** (or **Android Studio → Preferences** on macOS) +3. Navigate to **Plugins** +4. Search for "**Kotlin Multiplatform**" +5. Click **Install** and restart Android Studio + +#### 4. **Configure Android SDK** +1. In Android Studio, go to **File → Settings → Appearance & Behavior → System Settings → Android SDK** +2. Ensure these are installed: + - **Android SDK Platform 34** (or higher) + - **Android SDK Build-Tools 34.0.0** (or higher) + - **Android Emulator** + - **Android SDK Platform-Tools** + +#### 5. **Set Up Android Emulator** +1. In Android Studio, go to **Tools → Device Manager** +2. Click **Create Device** +3. Select a device definition (e.g., **Pixel 5**) +4. Download and select a system image: + - Recommended: **API Level 34** (Android 14.0) + - Select **x86_64** or **ARM64** based on your system +5. Click **Finish** to create the AVD + +#### 6. **Install SQLite (Optional - for database development)** +- **Windows**: Download from [SQLite Download Page](https://www.sqlite.org/download.html) +- **macOS**: Pre-installed, or install via Homebrew: ```bash -sqlite3 findmyride.db -sqlite> .tables -``` \ No newline at end of file + brew install sqlite3 +``` +- **Linux**: +```bash + sudo apt-get install sqlite3 # Ubuntu/Debian + sudo yum install sqlite # Fedora/RHEL +``` +- **Verify Installation**: +```bash + sqlite3 --version + # Should show version 3.x.x +``` + +--- + +## How To Run the Project + +### **Step 1: Clone the Repository** +```bash +git clone https://github.com/bookwalamustafa/Find-My-Ride.git +cd FindMyRide +``` + +### **Step 2: Open Project in Android Studio** +1. Launch **Android Studio** +2. Select **File → Open** +3. Navigate to the `FindMyRide` folder and click **OK** +4. Wait for **Gradle sync** to complete (this may take 3-5 minutes on first run) + - Watch the bottom status bar for "Gradle Build" progress + - If prompted, accept any SDK licenses + +### **Step 3: Verify Kotlin Multiplatform Setup** +1. Check that no errors appear in the **Build** output window +2. Verify the project structure shows `commonMain`, `androidMain`, and `iosMain` folders +3. If you see any errors related to KMP, ensure the Kotlin Multiplatform plugin is installed + +### **Step 4: Select Run Configuration** +1. In the top toolbar, find the **Run Configuration** dropdown +2. Select **composeApp:androidApp** (or just **composeApp**) +3. If you don't see this option, try **File → Sync Project with Gradle Files** + +### **Step 5: Launch the App** +**Option A: Using Android Emulator (Recommended)** +1. In the device dropdown (next to Run Configuration), select your created AVD +2. Click the **Run** button (green play icon) or press **Shift + F10** +3. Wait for the emulator to boot (30-60 seconds on first launch) +4. The app will automatically install and launch + +**Option B: Using Physical Android Device** +1. Enable **Developer Options** on your Android device: + - Go to **Settings → About Phone** + - Tap **Build Number** 7 times +2. Enable **USB Debugging**: + - Go to **Settings → Developer Options** + - Enable **USB Debugging** +3. Connect your device via USB +4. Select your device from the device dropdown +5. Click **Run** + +### **Step 6: First Launch** +- On first launch, the app will automatically copy the SQLite database from `assets/` to the device storage +- No additional configuration is needed +- The database comes pre-populated with sample data for testing + +### **Troubleshooting Common Issues** + +| Issue | Solution | +|-------|----------| +| "SDK not found" error | Install Android SDK Platform 34 via SDK Manager | +| Gradle sync fails | Check internet connection, clear Gradle cache (**File → Invalidate Caches**) | +| Emulator won't start | Ensure hardware acceleration (Intel HAXM/AMD WHPX) is enabled in BIOS | +| "Module not specified" error | Select **composeApp** run configuration | +| Database not loading | Check that `findmyride.db` exists in `composeApp/src/androidMain/assets/` | + +--- + +## Database Setup + +### **Using the Pre-loaded Database** +The project comes with a ready-to-use SQLite database (`findmyride.db`) that includes: +- Sample user accounts +- Pre-populated ride offers +- Example messages +- Vehicle information + +**No manual database setup is required for normal use.** + +### **Creating a Fresh Database (Advanced)** + +If you want to create a new database from scratch: + +#### **Step 1: Navigate to Project Root** +```bash +cd /path/to/FindMyRide +``` + +#### **Step 2: Generate Database from Schema** +Run the command ```python database/generate_database.py``` + +And the entire database should be populated with mock data. +--- + +## CRUD Implementation Details + +Our application implements comprehensive **CRUD (Create, Read, Update, Delete)** operations across all major database tables. Below is a detailed breakdown of how each operation is implemented. + +### **Architecture Overview** +``` +UI Layer (Compose) + ↓ +Repository Interface (commonMain) + ↓ +Repository Implementation (androidMain) + ↓ +SQLite Database (findmyride.db) +``` + +### **1. USER Table CRUD** (`AndroidAuthRepository.kt`) + +#### **Create (Registration)** +[FILL IN: Explain how user registration works] +- Example: "When a user registers, the `registerUser()` function..." +- SQL query used +- Password hashing approach +- Validation checks + +#### **Read (Login & Profile Loading)** +[FILL IN: Explain how user authentication and profile retrieval works] +- Example: "Login validates credentials using..." +- How user sessions are managed +- Profile data retrieval process + +#### **Update (Profile Editing)** +[FILL IN: Explain how profile updates work] +- Example: "Users can update their profile information through..." +- Fields that can be updated +- Validation logic + +#### **Delete (Account Deletion)** +[FILL IN: Explain account deletion] +- Example: "Account deletion is handled by..." +- Cascade behavior with related data +- Soft delete vs hard delete approach + +--- + +### **2. VEHICLE Table CRUD** (`AndroidProfileRepository.kt`) + +#### **Create (Add Vehicle)** +[FILL IN: Vehicle creation details] + +#### **Read (View Vehicles)** +[FILL IN: How vehicles are displayed] + +#### **Update (Edit Vehicle)** +[FILL IN: Vehicle editing process] + +#### **Delete (Remove Vehicle)** +[FILL IN: Vehicle removal logic] + +--- + +### **3. RIDE_OFFER Table CRUD** (`AndroidRideRepository.kt`) + +#### **Create (Publish Ride)** +[FILL IN: How drivers create ride offers] + +#### **Read (Browse Rides)** +[FILL IN: How rides are listed and filtered] + +#### **Update (Modify Ride Details)** +[FILL IN: Editing existing ride offers] + +#### **Delete (Cancel Ride)** +[FILL IN: Ride cancellation process] + +--- + +### **4. RIDE_REQUEST Table CRUD** (`AndroidRideRepository.kt`) + +#### **Create (Request a Ride)** +[FILL IN: How passengers request rides] + +#### **Read (View Requests)** +[FILL IN: Displaying ride requests] + +#### **Update (Update Request Status)** +[FILL IN: Accepting/rejecting requests] + +#### **Delete (Cancel Request)** +[FILL IN: Request cancellation] + +--- + +### **5. MESSAGE Table CRUD** (`AndroidMessagesRepository.kt`) + +#### **Create (Send Message)** +[FILL IN: Message sending implementation] + +#### **Read (View Messages)** +[FILL IN: Message retrieval and display] + +#### **Update (Edit Message)** +[FILL IN: If supported, how messages can be edited] + +#### **Delete (Delete Message)** +[FILL IN: Message deletion process] + +--- + +### **Database Transaction Handling** + +[FILL IN: Explain how you handle database transactions] +- Example: "All CRUD operations use database transactions to ensure..." +- Error handling approach +- Rollback strategy for failed operations + +### **Data Validation** + +[FILL IN: Describe validation layers] +- Example: "Input validation occurs at three levels..." +- UI-level validation +- Repository-level validation +- Database constraints + +--- + +## Key Implementation Notes + +### **State Management** +- **UI State**: Uses Jetpack Compose's `remember { mutableStateOf() }` for reactive UI updates +- **State Hoisting**: State is lifted to appropriate composable parents for sharing +- **Side Effects**: `LaunchedEffect` and `DisposableEffect` for lifecycle-aware operations + +### **Navigation System** +- **Sealed Classes**: Type-safe navigation using `sealed class RootScreen` and `sealed class HomePage` +- **Manual Implementation**: No NavHost – custom navigation logic for learning purposes +- **State Preservation**: Navigation state maintained across configuration changes + +### **Repository Pattern** +- **Interface Definition**: All repositories defined as interfaces in `commonMain` +- **Platform Implementation**: Concrete implementations in `androidMain` using SQLite +- **Dependency Injection**: Repositories passed down through composition +- **Single Source of Truth**: Database is the authoritative data source + +### **Database Management** +- **Database Provider**: `FindMyRideDbProvider` handles initialization and copying from assets +- **Connection Pooling**: Single database instance reused across app lifecycle +- **Raw SQL Queries**: Direct SQL for transparency and learning (no ORM like Room or SQLDelight) +- **Query Optimization**: Indexed columns for frequently queried fields + +### **Code Organization** +- **Feature-Based Structure**: Each feature (auth, profile, rides, messages) is self-contained +- **Separation of Concerns**: UI, business logic, and data access are clearly separated +- **Shared Code**: Maximum code sharing through `commonMain` for cross-platform readiness + +### **Android-Specific Considerations** +- **SQLiteOpenHelper**: Native Android database management for direct control +- **Context Handling**: Database requires Android `Context` for file operations +- **Assets Access**: Database file copied from `assets/` to internal storage on first run + +--- + +## Documentation & Demo + +### **Live Demo & CRUD Operations** + +For a comprehensive demonstration of the application and detailed CRUD operation walkthroughs, please refer to: + + **`docs/Deliverable_4/`** + +This folder contains: +- **Live Demo Video**: Complete walkthrough of all application features +- **CRUD Operation Examples**: Step-by-step demonstration of Create, Read, Update, and Delete operations for each database table +- **Database Schema Documentation**: Detailed table structures and relationships +- **Testing Scenarios**: Various use cases and edge cases tested during development + +**Recommended Viewing Order:** +1. Start with the live demo video for an overview +2. Review CRUD operation examples for technical details +3. Reference database schema for understanding data relationships + +--- + +## Future Improvements + +### **Feature Enhancements** +- **Advanced Filtering**: Filter rides by time, price, available seats, and route +- **Vehicle Picker**: Allow drivers to select from their registered vehicles when offering rides +- **Enhanced Messaging**: Full chat UI with real-time updates per ride +- **Notifications**: Push notifications for new ride offers and messages +- **Matching Algorithm**: Intelligent ride request matching based on routes and preferences +- **Rating System**: Allow users to rate drivers and passengers +- **Payment Integration**: Add payment processing for ride fares + +### **Technical Improvements** +- **iOS Support**: Implement database layer using SQLDelight or KMP-SQLite for iOS compatibility +- **Backend Server**: Move to a centralized server with REST API for real-time data sync +- **Enhanced Security**: Implement JWT authentication and encrypted data storage +- **Testing**: Add unit tests, integration tests, and UI tests +- **Theming**: Dark mode support and customizable themes +- **Maps Integration**: Visual route display using Google Maps or Mapbox +- **Analytics**: Track app usage and user behavior for insights + +### **Platform Expansion** +- **iOS Native App**: Full iOS implementation with native database layer +- **Web Version**: Browser-based version using Kotlin/JS +- **Desktop Support**: Complete desktop app with database support + +--- + +## Contact & Support + +For questions, issues, or contributions: + +- **Mustafa Bookwala**: [mmb479@drexel.edu/https://github.com/bookwalamustafa] +- **Samii Shabuse**: [sus24@drexel.edu/https://github.com/sshabuse] +- **Kennan Lu**: [kjl354@drexel.edu/kennanLu] + +**Course**: CS 461 - Database Systems +**Institution**: Drexel University +**Term**: Fall 2025 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..adfa9bf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,19 @@ +*.iml +.kotlin +.gradle +**/build/ +xcuserdata +!src/**/build/ +local.properties +.idea +.DS_Store +captures +.externalNativeBuild +.cxx +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings +node_modules/ diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..b405920 --- /dev/null +++ b/app/README.md @@ -0,0 +1,80 @@ +This is a Kotlin Multiplatform project targeting Android, iOS, Web, Desktop (JVM). + +* [/composeApp](composeApp/src) is for code that will be shared across your Compose Multiplatform applications. + It contains several subfolders: + - [commonMain](composeApp/src/commonMain/kotlin) is for code that’s common for all targets. + - Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. + For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, + the [iosMain](composeApp/src/iosMain/kotlin) folder would be the right place for such calls. + Similarly, if you want to edit the Desktop (JVM) specific part, the [jvmMain](composeApp/src/jvmMain/kotlin) + folder is the appropriate location. + +* [/iosApp](iosApp/iosApp) contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, + you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project. + +### Build and Run Android Application + +To build and run the development version of the Android app, use the run configuration from the run widget +in your IDE’s toolbar or build it directly from the terminal: + +- on macOS/Linux + ```shell + ./gradlew :composeApp:assembleDebug + ``` +- on Windows + ```shell + .\gradlew.bat :composeApp:assembleDebug + ``` + +### Build and Run Desktop (JVM) Application + +To build and run the development version of the desktop app, use the run configuration from the run widget +in your IDE’s toolbar or run it directly from the terminal: + +- on macOS/Linux + ```shell + ./gradlew :composeApp:run + ``` +- on Windows + ```shell + .\gradlew.bat :composeApp:run + ``` + +### Build and Run Web Application + +To build and run the development version of the web app, use the run configuration from the run widget +in your IDE's toolbar or run it directly from the terminal: + +- for the Wasm target (faster, modern browsers): + - on macOS/Linux + ```shell + ./gradlew :composeApp:wasmJsBrowserDevelopmentRun + ``` + - on Windows + ```shell + .\gradlew.bat :composeApp:wasmJsBrowserDevelopmentRun + ``` +- for the JS target (slower, supports older browsers): + - on macOS/Linux + ```shell + ./gradlew :composeApp:jsBrowserDevelopmentRun + ``` + - on Windows + ```shell + .\gradlew.bat :composeApp:jsBrowserDevelopmentRun + ``` + +### Build and Run iOS Application + +To build and run the development version of the iOS app, use the run configuration from the run widget +in your IDE’s toolbar or open the [/iosApp](iosApp) directory in Xcode and run it from there. + +--- + +Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html), +[Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/#compose-multiplatform), +[Kotlin/Wasm](https://kotl.in/wasm/)… + +We would appreciate your feedback on Compose/Web and Kotlin/Wasm in the public Slack +channel [#compose-web](https://slack-chats.kotlinlang.org/c/compose-web). +If you face any issues, please report them on [YouTrack](https://youtrack.jetbrains.com/newIssue?project=CMP). \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..98ddb8c --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + // this is necessary to avoid the plugins to be loaded multiple times + // in each subproject's classloader + alias(libs.plugins.androidApplication) apply false + alias(libs.plugins.androidLibrary) apply false + alias(libs.plugins.composeHotReload) apply false + alias(libs.plugins.composeMultiplatform) apply false + alias(libs.plugins.composeCompiler) apply false + alias(libs.plugins.kotlinMultiplatform) apply false +} \ No newline at end of file diff --git a/app/composeApp/build.gradle.kts b/app/composeApp/build.gradle.kts new file mode 100644 index 0000000..d290519 --- /dev/null +++ b/app/composeApp/build.gradle.kts @@ -0,0 +1,110 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidApplication) + alias(libs.plugins.composeMultiplatform) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.composeHotReload) +} + +kotlin { + androidTarget { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } + } + + listOf( + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + jvm() + + js { + browser() + binaries.executable() + } + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + binaries.executable() + } + + sourceSets { + androidMain.dependencies { + implementation(compose.preview) + implementation(libs.androidx.activity.compose) + } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.androidx.lifecycle.viewmodelCompose) + implementation(libs.androidx.lifecycle.runtimeCompose) + implementation(compose.materialIconsExtended) + } + commonTest.dependencies { + implementation(libs.kotlin.test) + } + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + implementation(libs.kotlinx.coroutinesSwing) + } + } +} + +android { + namespace = "com.example.app" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + applicationId = "com.example.app" + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } +} + +dependencies { + debugImplementation(compose.uiTooling) +} + +compose.desktop { + application { + mainClass = "com.example.app.MainKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "com.example.app" + packageVersion = "1.0.0" + } + } +} diff --git a/app/composeApp/src/androidMain/AndroidManifest.xml b/app/composeApp/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000..d973028 --- /dev/null +++ b/app/composeApp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/composeApp/src/androidMain/assets/findmyride.db b/app/composeApp/src/androidMain/assets/findmyride.db new file mode 100644 index 0000000..afaffe5 Binary files /dev/null and b/app/composeApp/src/androidMain/assets/findmyride.db differ diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidAuthRepository.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidAuthRepository.kt new file mode 100644 index 0000000..8e74451 --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidAuthRepository.kt @@ -0,0 +1,116 @@ +package com.example.demo + +import app.composeapp.generated.resources.Res +import com.example.demo.feature.auth.data.AuthRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Android implementation of AuthRepository that talks to the findmyride.db SQLite database. + */ +class AndroidAuthRepository( + private val dbProvider: FindMyRideDbProvider +) : AuthRepository { + + + override suspend fun login(email: String, password: String): Result = + withContext(Dispatchers.IO) { + val db = dbProvider.getReadableDatabase() + + val cursor = db.rawQuery( + """ + SELECT user_id, email, username, role + FROM "USER" + WHERE email = ? AND password_hash = ? + """.trimIndent(), + arrayOf(email, password) + ) + + cursor.use { + return@withContext if (it.moveToFirst()) { + val idxId = it.getColumnIndexOrThrow("user_id") + val idxEmail = it.getColumnIndexOrThrow("email") + val idxUsername = it.getColumnIndexOrThrow("username") + val idxRole = it.getColumnIndexOrThrow("role") + + // Remember who is logged in + CurrentUserStore.userId = it.getLong(idxId) + CurrentUserStore.email = it.getString(idxEmail) + CurrentUserStore.username = it.getString(idxUsername) + CurrentUserStore.role = it.getString(idxRole) + + Result.success(Unit) + } else { + // Clear any previous user + CurrentUserStore.userId = null + CurrentUserStore.email = null + CurrentUserStore.username = null + CurrentUserStore.role = null + + Result.failure(IllegalArgumentException("Invalid email or password")) + } + } + } + + // Basic implementation so Sign Up / Forgot Password don't crash, + // we can improve these later. + override suspend fun signUp( + fullName: String, + email: String, + password: String + ): Result = + withContext(Dispatchers.IO) { + val db = dbProvider.getWritableDatabase() + + // Check if email already exists + db.rawQuery( + """SELECT 1 FROM "USER" WHERE email = ? LIMIT 1;""", + arrayOf(email) + ).use { c -> + if (c.moveToFirst()) { + return@withContext Result.failure( + IllegalArgumentException("Email already registered") + ) + } + } + + val username = if (fullName.isNotBlank()) { + fullName.trim().lowercase().replace(" ", "_") + } else { + email.substringBefore('@') + } + + val stmt = db.compileStatement( + """ + INSERT INTO "USER"(email, username, password_hash, role) + VALUES(?, ?, ?, 'rider'); + """.trimIndent() + ) + stmt.bindString(1, email) + stmt.bindString(2, username) + stmt.bindString(3, password) + + val rowId = stmt.executeInsert() + if (rowId == -1L) { + Result.failure(IllegalStateException("Failed to create account")) + } else { + Result.success(Unit) + } + } + + override suspend fun sendPasswordReset(email: String): Result = + withContext(Dispatchers.IO) { + val db = dbProvider.getReadableDatabase() + db.rawQuery( + """SELECT 1 FROM "USER" WHERE email = ? LIMIT 1;""", + arrayOf(email) + ).use { c -> + return@withContext if (c.moveToFirst()) { + // Pretend an email was sent + Result.success(Unit) + } else { + Result.failure(IllegalArgumentException("No account found for this email")) + } + } + } +} diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidMessagesRepository.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidMessagesRepository.kt new file mode 100644 index 0000000..600f517 --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidMessagesRepository.kt @@ -0,0 +1,239 @@ +package com.example.demo.feature.messages.data + +import com.example.demo.CurrentUserStore +import com.example.demo.FindMyRideDbProvider +import com.example.demo.feature.messages.chat.ChatMessageUi +import com.example.demo.feature.messages.list.MessageThreadUi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class AndroidMessagesRepository( + private val dbProvider: FindMyRideDbProvider +) : MessagesRepository { + + /** + * Use logged-in user if available, otherwise fall back to parameter. + */ + private fun resolveCurrentUserId(paramUserId: Int): Int { +// val stored = CurrentUserStore.userId +// return stored?.toInt() ?: paramUserId + return 1; + } + +// override suspend fun getThreadsForUser(userId: Int): List = +// withContext(Dispatchers.IO) { +// listOf( +// MessageThreadUi( +// id = 1, +// senderName = "Debug User", +// initials = "DU", +// lastMessage = "If you see this, repo wiring works!", +// timeAgo = "now", +// unreadCount = 0 +// ) +// ) +// } + + private fun seedFakeMessagesIfEmpty(db: android.database.sqlite.SQLiteDatabase) { + // Check if we already seeded + db.rawQuery("SELECT COUNT(*) FROM MESSAGE_THREAD;", null).use { c -> + if (c.moveToFirst()) { + val count = c.getInt(0) + if (count > 0) return // already has data, no need to seed + } + } + + // ---- THREADS ---- + // All involving user_id = 1 so they'll show when you log in as that user + db.execSQL(""" + INSERT INTO MESSAGE_THREAD (thread_id, user1_id, user2_id) + VALUES + (1, 1, 2), + (2, 1, 3), + (3, 1, 4); + """.trimIndent()) + + // ---- MESSAGES ---- + // Thread 1: Abdul <-> Quincy + db.execSQL(""" + INSERT INTO MESSAGE (thread_id, sender_id, body) VALUES + (1, 1, 'Hey Quincy, are we still on for 5:30 PM?'), + (1, 2, 'Yes! I''ll be there in 10 minutes.'), + (1, 1, 'Perfect, see you soon.'); + """.trimIndent()) + + // Thread 2: Abdul <-> Ame + db.execSQL(""" + INSERT INTO MESSAGE (thread_id, sender_id, body) VALUES + (2, 3, 'Hey Abdul, do you still need a ride tomorrow?'), + (2, 1, 'Yeah! Morning around 9 would be amazing.'), + (2, 3, 'Got you, I''ll swing by then.'); + """.trimIndent()) + + // Thread 3: Abdul <-> Kennan + db.execSQL(""" + INSERT INTO MESSAGE (thread_id, sender_id, body) VALUES + (3, 1, 'Thanks again for the last ride!'), + (3, 4, 'No problem, happy to help.'), + (3, 1, 'I left you a 5-star rating too :)'); + """.trimIndent()) + } + + + override suspend fun getThreadsForUser(userId: Int): List = + withContext(Dispatchers.IO) { + val db = dbProvider.getWritableDatabase() + val currentUserId = 1 // you’re logging in as user_id = 1 + + seedFakeMessagesIfEmpty(db) + + val sql = """ + SELECT + t.thread_id, + CASE + WHEN t.user1_id = ? THEN u2.username + ELSE u1.username + END AS contact_name, + COALESCE(last_msg.body, 'No messages yet') AS last_message, + last_msg.sent_at AS last_sent_at + FROM MESSAGE_THREAD t + JOIN "USER" u1 ON t.user1_id = u1.user_id + JOIN "USER" u2 ON t.user2_id = u2.user_id + LEFT JOIN MESSAGE last_msg ON last_msg.message_id = ( + SELECT m.message_id + FROM MESSAGE m + WHERE m.thread_id = t.thread_id + ORDER BY m.sent_at DESC, m.message_id DESC + LIMIT 1 + ) + WHERE t.user1_id = ? OR t.user2_id = ? + ORDER BY + (last_sent_at IS NULL), + last_sent_at DESC, + t.thread_id DESC; + """.trimIndent() + + val args = arrayOf( + currentUserId.toString(), + currentUserId.toString(), + currentUserId.toString() + ) + + val cursor = db.rawQuery(sql, args) + val result = mutableListOf() + + cursor.use { c -> + val idxThreadId = c.getColumnIndexOrThrow("thread_id") + val idxName = c.getColumnIndexOrThrow("contact_name") + val idxLastMsg = c.getColumnIndexOrThrow("last_message") + val idxLastSentAt = c.getColumnIndexOrThrow("last_sent_at") + + while (c.moveToNext()) { + val name = c.getString(idxName) + val lastMsg = c.getString(idxLastMsg) + val sentAt = c.getString(idxLastSentAt) ?: "" + + result += MessageThreadUi( + id = c.getInt(idxThreadId), + senderName = name, + initials = initialsFromName(name), + lastMessage = lastMsg, + timeAgo = sentAt, + unreadCount = 0 + ) + } + } + + result + } + + override suspend fun getMessagesForThread(threadId: Int): List = + withContext(Dispatchers.IO) { + val db = dbProvider.getReadableDatabase() + val currentUserId = resolveCurrentUserId(1) + + val cursor = db.rawQuery( + """ + SELECT message_id, sender_id, body, sent_at + FROM MESSAGE + WHERE thread_id = ? + ORDER BY sent_at ASC, message_id ASC; + """.trimIndent(), + arrayOf(threadId.toString()) + ) + + val result = mutableListOf() + cursor.use { c -> + val idxId = c.getColumnIndexOrThrow("message_id") + val idxSender= c.getColumnIndexOrThrow("sender_id") + val idxBody = c.getColumnIndexOrThrow("body") + val idxTime = c.getColumnIndexOrThrow("sent_at") + + while (c.moveToNext()) { + val senderId = c.getInt(idxSender) + result += ChatMessageUi( + id = c.getInt(idxId), + isMe = (senderId == currentUserId), + text = c.getString(idxBody), + time = c.getString(idxTime) + ) + } + } + result + } + + override suspend fun sendMessage(threadId: Int, text: String): ChatMessageUi = + withContext(Dispatchers.IO) { + val db = dbProvider.getWritableDatabase() + val currentUserId = resolveCurrentUserId(1) + + // Insert message + val insertStmt = db.compileStatement( + """ + INSERT INTO MESSAGE(thread_id, sender_id, body) + VALUES (?, ?, ?); + """.trimIndent() + ) + insertStmt.bindLong(1, threadId.toLong()) + insertStmt.bindLong(2, currentUserId.toLong()) + insertStmt.bindString(3, text) + val newIdLong = insertStmt.executeInsert() + + // Get timestamp (sent_at) + val cursor = db.rawQuery( + """ + SELECT sent_at + FROM MESSAGE + WHERE message_id = ?; + """.trimIndent(), + arrayOf(newIdLong.toString()) + ) + + var sentAt = "Now" + cursor.use { c -> + if (c.moveToFirst()) { + sentAt = c.getString(c.getColumnIndexOrThrow("sent_at")) + } + } + + ChatMessageUi( + id = newIdLong.toInt(), + isMe = true, + text = text, + time = sentAt + ) + } + + private fun initialsFromName(name: String): String { + val parts = name.trim().split(" ") + .filter { it.isNotBlank() } + + val chars = when { + parts.isEmpty() -> listOf('U') + parts.size == 1 -> listOf(parts[0].first()) + else -> listOf(parts[0].first(), parts[1].first()) + } + + return chars.joinToString("").uppercase() + } +} diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidProfileRepository.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidProfileRepository.kt new file mode 100644 index 0000000..1c9ba60 --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidProfileRepository.kt @@ -0,0 +1,197 @@ +package com.example.demo.feature.profile.data + +import com.example.demo.CurrentUserStore +import com.example.demo.FindMyRideDbProvider +import com.example.demo.feature.profile.ProfileUiState +import com.example.demo.feature.profile.VehicleUi + +/** + * Android implementation of ProfileRepository that reads/writes + * the USER and VEHICLE tables from findmyride.db + */ +class AndroidProfileRepository( + private val dbProvider: FindMyRideDbProvider +) : ProfileRepository { + + private fun requireCurrentUserId(): Long { + return CurrentUserStore.userId + ?: error("No logged-in user. Make sure login ran successfully before opening Profile.") + } + + override fun loadInitialProfile(): ProfileUiState { + val currentUserId = requireCurrentUserId() + val db = dbProvider.getReadableDatabase() + + // ----- USER ----- + var name = "Unknown" + var email = "" + var phone = "" + var rating = 0.0 + + db.rawQuery( + """ + SELECT username, email, phone_number, rating_avg + FROM "USER" + WHERE user_id = ? + LIMIT 1; + """.trimIndent(), + arrayOf(currentUserId.toString()) + ).use { c -> + if (c.moveToFirst()) { + name = c.getString(c.getColumnIndexOrThrow("username")) + email = c.getString(c.getColumnIndexOrThrow("email")) + phone = c.getString(c.getColumnIndexOrThrow("phone_number")) ?: "" + rating = c.getDouble(c.getColumnIndexOrThrow("rating_avg")) + } + } + + // ----- VEHICLES ----- + val vehicles = mutableListOf() + db.rawQuery( + """ + SELECT vehicle_id, make, model, color, plate, seats_total, year, fun_fact + FROM "VEHICLE" + WHERE owner_user_id = ? + ORDER BY vehicle_id; + """.trimIndent(), + arrayOf(currentUserId.toString()) + ).use { c -> + val idxId = c.getColumnIndexOrThrow("vehicle_id") + val idxMake = c.getColumnIndexOrThrow("make") + val idxModel = c.getColumnIndexOrThrow("model") + val idxColor = c.getColumnIndexOrThrow("color") + val idxPlate = c.getColumnIndexOrThrow("plate") + val idxSeats = c.getColumnIndexOrThrow("seats_total") + val idxYear = c.getColumnIndexOrThrow("year") + val idxFunFact = c.getColumnIndexOrThrow("fun_fact") + + while (c.moveToNext()) { + vehicles += VehicleUi( + id = c.getInt(idxId), + ownerUserId = currentUserId.toInt(), + make = c.getString(idxMake), + model = c.getString(idxModel), + color = c.getString(idxColor) ?: "", + plate = c.getString(idxPlate), + seatsTotal = c.getInt(idxSeats), + year = c.getInt(idxYear), + funFact = c.getString(idxFunFact) ?: "" + ) + } + } + + // Build ProfileUiState using DB values + return ProfileUiState( + name = name, + email = email, + rating = rating, + vehicles = vehicles, + account = ProfileUiState().account.copy( + fullName = name, + email = email, + phone = phone, + // we don't show real password here + password = "*******" + ) + ) + } + + override fun saveProfile(state: ProfileUiState) { + val currentUserId = requireCurrentUserId() + val db = dbProvider.getWritableDatabase() + db.beginTransaction() + try { + // ----- UPDATE USER ----- + db.compileStatement( + """ + UPDATE "USER" + SET username = ?, email = ?, phone_number = ? + WHERE user_id = ?; + """.trimIndent() + ).apply { + bindString(1, state.account.fullName) + bindString(2, state.account.email) + bindString(3, state.account.phone) + bindLong(4, currentUserId) + executeUpdateDelete() + } + + // ----- SYNC VEHICLES ----- + // Get existing vehicle ids in DB + val existingIds = mutableSetOf() + db.rawQuery( + """ + SELECT vehicle_id + FROM "VEHICLE" + WHERE owner_user_id = ?; + """.trimIndent(), + arrayOf(currentUserId.toString()) + ).use { c -> + val idx = c.getColumnIndexOrThrow("vehicle_id") + while (c.moveToNext()) existingIds += c.getInt(idx) + } + + val desiredIds = state.vehicles.map { it.id }.toSet() + + // Upsert each vehicle from UI state + state.vehicles.forEach { v -> + if (existingIds.contains(v.id)) { + // UPDATE + db.compileStatement( + """ + UPDATE "VEHICLE" + SET make = ?, model = ?, color = ?, plate = ?, + seats_total = ?, year = ?, fun_fact = ? + WHERE vehicle_id = ?; + """.trimIndent() + ).apply { + bindString(1, v.make) + bindString(2, v.model) + bindString(3, v.color) + bindString(4, v.plate) + bindLong(5, v.seatsTotal.toLong()) + bindLong(6, v.year.toLong()) + bindString(7, v.funFact) + bindLong(8, v.id.toLong()) + executeUpdateDelete() + } + } else { + // INSERT with explicit vehicle_id (matches the id created in ViewModel) + db.compileStatement( + """ + INSERT INTO "VEHICLE"( + vehicle_id, owner_user_id, make, model, color, + plate, seats_total, year, fun_fact + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + """.trimIndent() + ).apply { + bindLong(1, v.id.toLong()) + bindLong(2, currentUserId) + bindString(3, v.make) + bindString(4, v.model) + bindString(5, v.color) + bindString(6, v.plate) + bindLong(7, v.seatsTotal.toLong()) + bindLong(8, v.year.toLong()) + bindString(9, v.funFact) + executeInsert() + } + } + } + + // Delete vehicles that were removed in the UI + (existingIds - desiredIds).forEach { idToDelete -> + db.compileStatement( + """DELETE FROM "VEHICLE" WHERE vehicle_id = ?;""".trimIndent() + ).apply { + bindLong(1, idToDelete.toLong()) + executeUpdateDelete() + } + } + + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } + } +} diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidRideRepository.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidRideRepository.kt new file mode 100644 index 0000000..335ba12 --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidRideRepository.kt @@ -0,0 +1,117 @@ +package com.example.demo + +import android.content.ContentValues +import com.example.demo.feature.db.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class AndroidRideRepository( + private val dbProvider: FindMyRideDbProvider +) : RideRepository { + + override suspend fun getOpenRideOffers(): List = + withContext(Dispatchers.IO) { + val db = dbProvider.getReadableDatabase() + + // Example: join RIDE_OFFER with LOCATION to get names + val sql = """ + SELECT + o.offer_id, + o.driver_id, + o.vehicle_id, + lo_from.name AS from_name, + lo_to.name AS to_name, + o.depart_at, + o.seats_available, + o.price_base + FROM RIDE_OFFER o + JOIN LOCATION lo_from ON o.original_location_id = lo_from.location_id + JOIN LOCATION lo_to ON o.dest_location_id = lo_to.location_id + WHERE o.status = 'open' + ORDER BY o.depart_at ASC; + """.trimIndent() + + val cursor = db.rawQuery(sql, null) + + val list = mutableListOf() + cursor.use { + val idxOfferId = it.getColumnIndexOrThrow("offer_id") + val idxDriverId = it.getColumnIndexOrThrow("driver_id") + val idxVehicleId = it.getColumnIndexOrThrow("vehicle_id") + val idxFromName = it.getColumnIndexOrThrow("from_name") + val idxToName = it.getColumnIndexOrThrow("to_name") + val idxDepartAt = it.getColumnIndexOrThrow("depart_at") + val idxSeatsAvail = it.getColumnIndexOrThrow("seats_available") + val idxPriceBase = it.getColumnIndexOrThrow("price_base") + + while (it.moveToNext()) { + list.add( + RideOffer( + offerId = it.getLong(idxOfferId), + driverId = it.getLong(idxDriverId), + vehicleId = it.getLong(idxVehicleId), + fromName = it.getString(idxFromName), + toName = it.getString(idxToName), + departAt = it.getString(idxDepartAt), + seatsAvailable = it.getInt(idxSeatsAvail), + priceBase = it.getDouble(idxPriceBase) + ) + ) + } + } + list + } + + override suspend fun createRideRequest( + riderId: Long, + pickupLocationId: Long, + dropoffLocationId: Long, + earliestPickup: String, + latestPickup: String?, + seatsNeeded: Int + ) { + withContext(Dispatchers.IO) { + val db = dbProvider.getWritableDatabase() + val values = ContentValues().apply { + put("rider_id", riderId) + put("pickup_location_id", pickupLocationId) + put("dropoff_location_id", dropoffLocationId) + put("earliest_pickup", earliestPickup) + put("latest_pickup", latestPickup) + put("seats_needed", seatsNeeded) + put("status", "open") + } + db.insert("RIDE_REQUEST", null, values) + } + } + + override suspend fun createRideOffer( + driverId: Long, + vehicleId: Long, + originalLocationId: Long, + destLocationId: Long, + departAt: String, + seatsAvailable: Int, + priceBase: Double, + pricePerMile: Double + ) { + withContext(Dispatchers.IO) { + val db = dbProvider.getWritableDatabase() + + val values = ContentValues().apply { + put("driver_id", driverId) + put("vehicle_id", vehicleId) + put("original_location_id", originalLocationId) + put("dest_location_id", destLocationId) + put("depart_at", departAt) + put("seats_available", seatsAvailable) + put("price_base", priceBase) + put("price_per_mile", pricePerMile) + put("status", "open") + } + + db.insert("RIDE_OFFER", null, values) + } + } + +} diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/CurrentUserStore.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/CurrentUserStore.kt new file mode 100644 index 0000000..cf175de --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/CurrentUserStore.kt @@ -0,0 +1,14 @@ +package com.example.demo + +/** + * Simple in-memory store for the currently logged-in user. + * Android only; commonMain doesn't see this directly. + */ +object CurrentUserStore { + var userId: Long? = null + var email: String? = null + var username: String? = null + var role: String? = null +} + +// This is just a global holder on the Android side. \ No newline at end of file diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/FindMyRideDbProvider.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/FindMyRideDbProvider.kt new file mode 100644 index 0000000..6adb08a --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/FindMyRideDbProvider.kt @@ -0,0 +1,48 @@ +package com.example.demo + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import java.io.FileOutputStream + +class FindMyRideDbProvider(private val context: Context) { + private val dbName = "findmyride.db" + + private fun ensureDbCopied() { + val dbFile = context.getDatabasePath(dbName) + + // Always overwrite old database to ensure latest version is used + dbFile.parentFile?.mkdirs() + + // Delete any existing DB + if (dbFile.exists()) { + dbFile.delete() + } + + // Copy fresh DB from assets + context.assets.open(dbName).use { input -> + dbFile.outputStream().use { output -> + input.copyTo(output) + } + } + } + + fun getReadableDatabase(): SQLiteDatabase { + ensureDbCopied() + val dbFile = context.getDatabasePath(dbName) + return SQLiteDatabase.openDatabase( + dbFile.path, + null, + SQLiteDatabase.OPEN_READONLY + ) + } + + fun getWritableDatabase(): SQLiteDatabase { + ensureDbCopied() + val dbFile = context.getDatabasePath(dbName) + return SQLiteDatabase.openDatabase( + dbFile.path, + null, + SQLiteDatabase.OPEN_READWRITE + ) + } +} \ No newline at end of file diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/MainActivity.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/MainActivity.kt new file mode 100644 index 0000000..9ff986d --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/MainActivity.kt @@ -0,0 +1,43 @@ +package com.example.rideshare + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.example.demo.App +import com.example.demo.AndroidRideRepository +import com.example.demo.AndroidAuthRepository +import com.example.demo.FindMyRideDbProvider +import com.example.demo.CurrentUserStore +import com.example.demo.feature.profile.data.AndroidProfileRepository +import com.example.demo.feature.messages.data.AndroidMessagesRepository + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MaterialTheme { + Surface { + val context = LocalContext.current + val dbProvider = remember { FindMyRideDbProvider(context) } + + val rideRepo = remember { AndroidRideRepository(dbProvider) } + val profileRepo = remember { AndroidProfileRepository(dbProvider) } + val authRepo = remember { AndroidAuthRepository(dbProvider) } + val messagesRepo = remember { AndroidMessagesRepository(dbProvider) } + + App( + rideRepository = rideRepo, + profileRepository = profileRepo, + authRepository = authRepo, + messagesRepository = messagesRepo + ) + } + } + } + } +} diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/Platform.android.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/Platform.android.kt new file mode 100644 index 0000000..ba841d1 --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/Platform.android.kt @@ -0,0 +1,9 @@ +package com.example.app + +import android.os.Build + +class AndroidPlatform : Platform { + override val name: String = "Android ${Build.VERSION.SDK_INT}" +} + +actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/app/composeApp/src/androidMain/kotlin/com/example/demo/RideShareDbHelper.kt b/app/composeApp/src/androidMain/kotlin/com/example/demo/RideShareDbHelper.kt new file mode 100644 index 0000000..777c33d --- /dev/null +++ b/app/composeApp/src/androidMain/kotlin/com/example/demo/RideShareDbHelper.kt @@ -0,0 +1,29 @@ +package com.example.demo + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper + +class RideShareDbHelper(context: Context) : + SQLiteOpenHelper(context, "rideshare.db", null, 1) { + + override fun onCreate(db: SQLiteDatabase) { + // Create a super simple table for now + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS rides( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pickup TEXT NOT NULL, + dropoff TEXT NOT NULL, + ride_time TEXT NOT NULL + ); + """.trimIndent() + ) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + // Easiest upgrade strategy for school project + db.execSQL("DROP TABLE IF EXISTS rides;") + onCreate(db) + } +} diff --git a/app/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/app/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..bda706c --- /dev/null +++ b/app/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/app/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..3c40f44 --- /dev/null +++ b/app/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/app/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/app/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/app/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/app/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/app/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/app/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/app/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/app/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/app/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/app/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/app/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/composeApp/src/androidMain/res/values/strings.xml b/app/composeApp/src/androidMain/res/values/strings.xml new file mode 100644 index 0000000..61e5c6a --- /dev/null +++ b/app/composeApp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + app + \ No newline at end of file diff --git a/app/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/app/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml new file mode 100644 index 0000000..eba956a --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/composeApp/src/commonMain/composeResources/drawable/ic_back_arrow.xml b/app/composeApp/src/commonMain/composeResources/drawable/ic_back_arrow.xml new file mode 100644 index 0000000..125cf9c --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/ic_back_arrow.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/composeApp/src/commonMain/composeResources/drawable/ic_calendar.xml b/app/composeApp/src/commonMain/composeResources/drawable/ic_calendar.xml new file mode 100644 index 0000000..0cd1ed0 --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/ic_calendar.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/composeApp/src/commonMain/composeResources/drawable/ic_clock.xml b/app/composeApp/src/commonMain/composeResources/drawable/ic_clock.xml new file mode 100644 index 0000000..df68293 --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/ic_clock.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/composeApp/src/commonMain/composeResources/drawable/ic_dollar_sign.xml b/app/composeApp/src/commonMain/composeResources/drawable/ic_dollar_sign.xml new file mode 100644 index 0000000..21e87fc --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/ic_dollar_sign.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/composeApp/src/commonMain/composeResources/drawable/ic_location_ping.xml b/app/composeApp/src/commonMain/composeResources/drawable/ic_location_ping.xml new file mode 100644 index 0000000..fae4e07 --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/ic_location_ping.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/composeApp/src/commonMain/composeResources/drawable/ic_two_people.xml b/app/composeApp/src/commonMain/composeResources/drawable/ic_two_people.xml new file mode 100644 index 0000000..05f423f --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/ic_two_people.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/composeApp/src/commonMain/composeResources/drawable/ic_vehicle.xml b/app/composeApp/src/commonMain/composeResources/drawable/ic_vehicle.xml new file mode 100644 index 0000000..b50b2b1 --- /dev/null +++ b/app/composeApp/src/commonMain/composeResources/drawable/ic_vehicle.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/App.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/App.kt new file mode 100644 index 0000000..129cb15 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/App.kt @@ -0,0 +1,57 @@ +package com.example.demo + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import com.example.demo.feature.auth.data.AuthRepository +import com.example.demo.feature.auth.login.LoginRoute +import com.example.demo.feature.auth.signup.SignUpRoute +import com.example.demo.feature.auth.forgot.ForgotPasswordRoute +import com.example.demo.feature.db.RideRepository +import com.example.demo.feature.main.MainRoute +import com.example.demo.feature.messages.data.MessagesRepository +import com.example.demo.feature.profile.data.ProfileRepository + +// Top-level screens in your app +enum class RootScreen { + Login, + SignUp, + ForgotPassword, + Main +} + +@Composable +fun App( + rideRepository: RideRepository, + profileRepository: ProfileRepository, + authRepository: AuthRepository, + messagesRepository: MessagesRepository +) { + var currentScreen by remember { mutableStateOf(RootScreen.Login) } + + MaterialTheme { + when (currentScreen) { + + RootScreen.Login -> LoginRoute( + onNavigateToSignUp = { currentScreen = RootScreen.SignUp }, + onNavigateToForgotPassword = { currentScreen = RootScreen.ForgotPassword }, + onLoginSuccess = { currentScreen = RootScreen.Main }, + authRepository = authRepository, + ) + + RootScreen.SignUp -> SignUpRoute( + onNavigateToLogin = { currentScreen = RootScreen.Login } + ) + + RootScreen.ForgotPassword -> ForgotPasswordRoute( + onNavigateBack = { currentScreen = RootScreen.Login } + ) + + RootScreen.Main -> MainRoute( + rideRepository = rideRepository, + profileRepository = profileRepository, + messagesRepository = messagesRepository, + + ) + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/Greeting.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/Greeting.kt new file mode 100644 index 0000000..035f767 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/Greeting.kt @@ -0,0 +1,9 @@ +package com.example.app + +class Greeting { + private val platform = getPlatform() + + fun greet(): String { + return "Hello, ${platform.name}!" + } +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/Platform.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/Platform.kt new file mode 100644 index 0000000..6b7133c --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/Platform.kt @@ -0,0 +1,7 @@ +package com.example.app + +interface Platform { + val name: String +} + +expect fun getPlatform(): Platform \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/AuthComponents.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/AuthComponents.kt new file mode 100644 index 0000000..d222478 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/AuthComponents.kt @@ -0,0 +1,73 @@ +package com.example.demo.feature.auth + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import com.example.demo.ui.theme.DrexelGold +import com.example.demo.ui.theme.FieldBackground +import com.example.demo.ui.theme.HintGrey + +@Composable +fun AppLogo() { + Box( + modifier = Modifier + .size(80.dp) + .background(DrexelGold, CircleShape), + contentAlignment = Alignment.Center + ) { + Text("🚗", fontSize = MaterialTheme.typography.headlineMedium.fontSize) + } +} + +@Composable +fun AuthTextField( + label: String, + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + isPassword: Boolean +) { + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = Color.White + ) + Spacer(Modifier.height(4.dp)) + + OutlinedTextField( + value = value, + onValueChange = onValueChange, + placeholder = { Text(placeholder, color = HintGrey) }, + singleLine = true, + visualTransformation = if (isPassword) { + PasswordVisualTransformation() + } else { + VisualTransformation.None + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + disabledBorderColor = Color.Transparent, + errorBorderColor = Color.Transparent, + focusedContainerColor = FieldBackground, + unfocusedContainerColor = FieldBackground, + cursorColor = DrexelGold, + focusedTextColor = Color.White, + unfocusedTextColor = Color.White + ) + ) +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AppLogo.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AppLogo.kt new file mode 100644 index 0000000..9a469c2 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AppLogo.kt @@ -0,0 +1,48 @@ +package com.example.demo.feature.auth.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@Composable +fun AppLogo() { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(DrexelGold), + contentAlignment = Alignment.Center + ) { + Text("🚗", fontSize = MaterialTheme.typography.headlineMedium.fontSize) + } + + Spacer(Modifier.height(16.dp)) + + Text( + text = "Find My Ride", + style = MaterialTheme.typography.titleMedium, + color = Color.White + ) + + Spacer(Modifier.height(4.dp)) + + Text( + text = "Share the journey, save the planet", + style = MaterialTheme.typography.bodyMedium, + color = Color.White.copy(alpha = 0.8f) + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AuthScaffold.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AuthScaffold.kt new file mode 100644 index 0000000..ebdbc48 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AuthScaffold.kt @@ -0,0 +1,29 @@ +package com.example.demo.feature.auth.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.demo.ui.theme.DrexelBlue + +@Composable +fun AuthScaffold( + content: @Composable ColumnScope.() -> Unit +) { + Box( + modifier = Modifier + .fillMaxSize() + .background(DrexelBlue), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + content = content + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AuthTextField.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AuthTextField.kt new file mode 100644 index 0000000..71a811e --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/components/AuthTextField.kt @@ -0,0 +1,53 @@ +package com.example.demo.feature.auth.components + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import com.example.demo.ui.theme.DrexelGold +import com.example.demo.ui.theme.FieldBackground +import com.example.demo.ui.theme.HintGrey + +@Composable +fun AuthTextField( + label: String, + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + isPassword: Boolean, + modifier: Modifier = Modifier +) { + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = Color.White + ) + + OutlinedTextField( + value = value, + onValueChange = onValueChange, + placeholder = { Text(placeholder, color = HintGrey) }, + singleLine = true, + visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None , + modifier = modifier, + shape = RoundedCornerShape(8.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + disabledBorderColor = Color.Transparent, + errorBorderColor = Color.Transparent, + focusedContainerColor = FieldBackground, + unfocusedContainerColor = FieldBackground, + cursorColor = DrexelGold, + focusedTextColor = Color.White, + unfocusedTextColor = Color.White + ) + ) +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/data/AuthRepository.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/data/AuthRepository.kt new file mode 100644 index 0000000..66b6688 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/data/AuthRepository.kt @@ -0,0 +1,28 @@ +package com.example.demo.feature.auth.data + +import kotlinx.coroutines.delay + +// Later we will change this to connect to our database +interface AuthRepository { + + suspend fun login(email: String, password: String): Result + + suspend fun signUp( + fullName: String, + email: String, + password: String + ): Result + + suspend fun sendPasswordReset(email: String): Result +} + + + + + + + + + + + diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/data/FakeAuthRepository.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/data/FakeAuthRepository.kt new file mode 100644 index 0000000..2235753 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/data/FakeAuthRepository.kt @@ -0,0 +1,46 @@ +import com.example.demo.feature.auth.data.AuthRepository +import kotlinx.coroutines.delay + +/** + * In-memory / fake implementation for now + * Pretend everything works, with generic validation + */ +class FakeAuthRepository : AuthRepository { + + private val existingUsers = mutableSetOf() + + override suspend fun login(email: String, password: String): Result { + delay(1000) + if (email.isBlank() || password.isBlank()) { + return Result.failure(IllegalArgumentException("Email and password required")) + } + + // Auto-sign-in new users + existingUsers.add(email) + + return Result.success(Unit) + } + + + override suspend fun signUp( + fullName: String, + email: String, + password: String + ): Result { + delay(1000) + if (email.isBlank() || password.length < 6) { + return Result.failure(IllegalArgumentException("Invalid sign up data")) + } + existingUsers.add(email) + return Result.success(Unit) + } + + override suspend fun sendPasswordReset(email: String): Result { + delay(1000) + return if (existingUsers.contains(email)) { + Result.success(Unit) + } else { + Result.failure(IllegalArgumentException("No account found for this email")) + } + } +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordEvent.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordEvent.kt new file mode 100644 index 0000000..509629f --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordEvent.kt @@ -0,0 +1,6 @@ +package com.example.demo.feature.auth.forgot + +sealed interface ForgotPasswordEvent { + data class EmailChanged(val value: String) : ForgotPasswordEvent + data object Submit : ForgotPasswordEvent +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordRoute.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordRoute.kt new file mode 100644 index 0000000..239d87d --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordRoute.kt @@ -0,0 +1,20 @@ +package com.example.demo.feature.auth.forgot + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember + +@Composable +fun ForgotPasswordRoute( + onNavigateBack: () -> Unit, + viewModel: ForgotPasswordViewModel = remember { ForgotPasswordViewModel() } +) { + val state by viewModel.uiState.collectAsState() + + ForgotPasswordScreen( + state = state, + onEvent = viewModel::onEvent, + onNavigateBack = onNavigateBack + ) +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordScreen.kt new file mode 100644 index 0000000..853cf6d --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordScreen.kt @@ -0,0 +1,88 @@ +package com.example.demo.feature.auth.forgot + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.feature.auth.components.AppLogo +import com.example.demo.feature.auth.components.AuthScaffold +import com.example.demo.feature.auth.components.AuthTextField +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@Composable +fun ForgotPasswordScreen( + state: ForgotPasswordUiState, + onEvent: (ForgotPasswordEvent) -> Unit, + onNavigateBack: () -> Unit +) { + AuthScaffold { + + AppLogo() + + Spacer(Modifier.height(16.dp)) + + Text( + text = "Reset Password", + style = MaterialTheme.typography.titleMedium, + color = Color.White + ) + + Spacer(Modifier.height(4.dp)) + + Text( + text = "Enter your email to receive a reset link.", + style = MaterialTheme.typography.bodyMedium, + color = Color.White.copy(alpha = 0.8f) + ) + + Spacer(Modifier.height(32.dp)) + + AuthTextField( + label = "Email", + value = state.email, + onValueChange = { onEvent(ForgotPasswordEvent.EmailChanged(it)) }, + placeholder = "your@email.com", + isPassword = false, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(16.dp)) + + Button( + onClick = { onEvent(ForgotPasswordEvent.Submit) }, + modifier = Modifier + .fillMaxWidth() + .height(52.dp), + enabled = !state.isLoading, + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ) + ) { + if (state.isLoading) { + Text("Sending...") + } else { + Text("Send Reset Link") + } + } + + state.errorMessage?.let { + Spacer(Modifier.height(8.dp)) + Text(it, color = Color.Red) + } + + state.successMessage?.let { + Spacer(Modifier.height(8.dp)) + Text(it, color = Color.Green) + } + + Spacer(Modifier.height(24.dp)) + + TextButton(onClick = onNavigateBack) { + Text("Back to Login", color = DrexelGold) + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordUiState.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordUiState.kt new file mode 100644 index 0000000..7d52ce8 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordUiState.kt @@ -0,0 +1,8 @@ +package com.example.demo.feature.auth.forgot + +data class ForgotPasswordUiState( + val email: String = "", + val isLoading: Boolean = false, + val successMessage: String? = null, + val errorMessage: String? = null +) diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordViewModel.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordViewModel.kt new file mode 100644 index 0000000..13c4a3f --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/forgot/ForgotPasswordViewModel.kt @@ -0,0 +1,94 @@ +package com.example.demo.feature.auth.forgot + +import FakeAuthRepository +import com.example.demo.feature.auth.data.AuthRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class ForgotPasswordViewModel( + private val repository: AuthRepository = FakeAuthRepository() +) { + + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + private val _uiState = MutableStateFlow(ForgotPasswordUiState()) + val uiState: StateFlow = _uiState + + fun onEvent(event: ForgotPasswordEvent) { + when (event) { + is ForgotPasswordEvent.EmailChanged -> + _uiState.value = _uiState.value.copy( + email = event.value, + errorMessage = null, + successMessage = null + ) + + ForgotPasswordEvent.Submit -> submit() + } + } + + private fun submit() { + val current = _uiState.value + val email = current.email.trim() + + // 1) Empty check + if (email.isBlank()) { + _uiState.value = current.copy( + errorMessage = "Please enter your email.", + successMessage = null + ) + return + } + + // 2) Very simple email validation (KMP-friendly) + if (!isValidEmail(email)) { + _uiState.value = current.copy( + errorMessage = "Please enter a valid email address.", + successMessage = null + ) + return + } + + // 3) Set loading state + _uiState.value = current.copy( + isLoading = true, + errorMessage = null, + successMessage = null + ) + + // 4) Call repository + scope.launch { + val result = repository.sendPasswordReset(email) + + _uiState.value = if (result.isSuccess) { + _uiState.value.copy( + isLoading = false, + successMessage = "If an account exists for $email, a reset link has been sent.", + errorMessage = null + ) + } else { + _uiState.value.copy( + isLoading = false, + errorMessage = result.exceptionOrNull()?.message + ?: "Could not send reset link. Please try again.", + successMessage = null + ) + } + } + } + + // Simple, KMP-safe email validator (no Android APIs) + private fun isValidEmail(email: String): Boolean { + val atIndex = email.indexOf('@') + if (atIndex <= 0 || atIndex == email.lastIndex) return false + + val dotIndex = email.lastIndexOf('.') + if (dotIndex <= atIndex + 1 || dotIndex == email.lastIndex) return false + + return true + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginEvent.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginEvent.kt new file mode 100644 index 0000000..0db7b04 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginEvent.kt @@ -0,0 +1,10 @@ +package com.example.demo.feature.auth.login + + +// Contract of what the user can do on the screen. +// navigation to signup/forgot stay in app / higher level not events +sealed interface LoginEvent { + data class EmailChanged(val value: String) : LoginEvent + data class PasswordChanged(val value: String) : LoginEvent + data object Submit : LoginEvent +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginRoute.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginRoute.kt new file mode 100644 index 0000000..a75e553 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginRoute.kt @@ -0,0 +1,33 @@ +package com.example.demo.feature.auth.login + +import FakeAuthRepository +import androidx.compose.runtime.* +import androidx.compose.runtime.collectAsState +import com.example.demo.feature.auth.data.AuthRepository + +@Composable +fun LoginRoute( + onNavigateToSignUp: () -> Unit, + onNavigateToForgotPassword: () -> Unit, + onLoginSuccess: () -> Unit, + authRepository: AuthRepository +) { + val viewModel = remember(authRepository) { + LoginViewModel(authRepository) + } + val state by viewModel.uiState.collectAsState() + + // When login succeeds, trigger navigation once + LaunchedEffect(state.loginSuccess) { + if (state.loginSuccess) { + onLoginSuccess() + } + } + + LoginScreen( + state = state, + onEvent = viewModel::onEvent, + onNavigateToSignUp = onNavigateToSignUp, + onNavigateToForgotPassword = onNavigateToForgotPassword + ) +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginScreen.kt new file mode 100644 index 0000000..8a5adb2 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginScreen.kt @@ -0,0 +1,106 @@ +package com.example.demo.feature.auth.login + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.feature.auth.components.AppLogo +import com.example.demo.feature.auth.components.AuthScaffold +import com.example.demo.feature.auth.components.AuthTextField +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@Composable +fun LoginScreen( + state: LoginUiState, + onEvent: (LoginEvent) -> Unit, + onNavigateToSignUp: () -> Unit, + onNavigateToForgotPassword: () -> Unit +) { + AuthScaffold { + AppLogo() + + Spacer(Modifier.height(32.dp)) + + AuthTextField( + label = "Email", + value = state.email, + onValueChange = { onEvent(LoginEvent.EmailChanged(it)) }, + placeholder = "your@email.com", + isPassword = false, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(12.dp)) + + AuthTextField( + label = "Password", + value = state.password, + onValueChange = { onEvent(LoginEvent.PasswordChanged(it)) }, + placeholder = "••••••••", + isPassword = true, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = onNavigateToForgotPassword) { + Text( + text = "Forgot password?", + style = MaterialTheme.typography.labelMedium, + color = DrexelGold + ) + } + } + + Spacer(Modifier.height(16.dp)) + + Button( + onClick = { onEvent(LoginEvent.Submit) }, + enabled = !state.isLoading, + modifier = Modifier + .fillMaxWidth() + .height(52.dp), + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ) + ) { + Text(if (state.isLoading) "Loading..." else "Log In") + } + + state.errorMessage?.let { error -> + Spacer(Modifier.height(8.dp)) + Text( + text = error, + color = Color.Red, + style = MaterialTheme.typography.bodySmall + ) + } + + Spacer(Modifier.height(24.dp)) + + Text( + text = "Don't have an account?", + style = MaterialTheme.typography.bodyMedium, + color = Color.White + ) + + Spacer(Modifier.height(4.dp)) + + TextButton(onClick = onNavigateToSignUp) { + Text( + text = "Sign up now", + style = MaterialTheme.typography.bodyMedium, + color = DrexelGold + ) + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginUiState.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginUiState.kt new file mode 100644 index 0000000..6b8ee52 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginUiState.kt @@ -0,0 +1,9 @@ +package com.example.demo.feature.auth.login + +data class LoginUiState( + val email: String = "", + val password: String = "", + val isLoading: Boolean = false, + val errorMessage: String? = null, + val loginSuccess: Boolean = false, +) \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginViewModel.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginViewModel.kt new file mode 100644 index 0000000..7ec7094 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/login/LoginViewModel.kt @@ -0,0 +1,93 @@ +package com.example.demo.feature.auth.login + +import com.example.demo.feature.auth.data.AuthRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class LoginViewModel( + private val repository: AuthRepository +) { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + private val _uiState = MutableStateFlow(LoginUiState()) + val uiState: StateFlow = _uiState + + fun onEvent(event: LoginEvent) { + when (event) { + is LoginEvent.EmailChanged -> { + _uiState.value = _uiState.value.copy( + email = event.value, + errorMessage = null, + loginSuccess = false + ) + } + + is LoginEvent.PasswordChanged -> { + _uiState.value = _uiState.value.copy( + password = event.value, + errorMessage = null, + loginSuccess = false + ) + } + + LoginEvent.Submit -> submit() + } + } + + private fun submit() { + val state = _uiState.value + + if (!isValidEmail(state.email)) { + setError("Please enter a valid email") + return + } + if (state.password.isBlank()) { + setError("Password is required") + return + } + + // start loading + _uiState.value = state.copy( + isLoading = true, + errorMessage = null, + loginSuccess = false + ) + + scope.launch { + val result = repository.login(state.email.trim(), state.password) + + _uiState.value = if (result.isSuccess) { + _uiState.value.copy( + isLoading = false, + loginSuccess = true, + errorMessage = null + ) + } else { + _uiState.value.copy( + isLoading = false, + loginSuccess = false, + errorMessage = result.exceptionOrNull()?.message ?: "Login failed" + ) + } + } + } + + private fun setError(message: String) { + _uiState.value = _uiState.value.copy( + errorMessage = message, + isLoading = false, + loginSuccess = false + ) + } + + // Same simple cross-platform email validation you used before + private fun isValidEmail(email: String): Boolean { + val at = email.indexOf('@') + val dot = email.lastIndexOf('.') + return at > 0 && dot > at + 1 && dot < email.length - 1 + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpEvent.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpEvent.kt new file mode 100644 index 0000000..bf61c34 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpEvent.kt @@ -0,0 +1,9 @@ +package com.example.demo.feature.auth.signup + +sealed interface SignUpEvent{ + data class NameChanged(val value: String) : SignUpEvent + data class EmailChanged(val value: String) : SignUpEvent + data class PasswordChanged(val value: String) : SignUpEvent + data class ConfirmPasswordChanged(val value: String) : SignUpEvent + data object Submit : SignUpEvent +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpRoute.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpRoute.kt new file mode 100644 index 0000000..edcf3d2 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpRoute.kt @@ -0,0 +1,20 @@ +package com.example.demo.feature.auth.signup + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember + +@Composable +fun SignUpRoute( + onNavigateToLogin: () -> Unit, + viewModel: SignUpViewModel = remember { SignUpViewModel() } +) { + val state by viewModel.uiState.collectAsState() + + SignUpScreen( + state = state, + onEvent = viewModel::onEvent, + onNavigateToLogin = onNavigateToLogin + ) +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpScreen.kt new file mode 100644 index 0000000..16dc345 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpScreen.kt @@ -0,0 +1,129 @@ +package com.example.demo.feature.auth.signup + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.feature.auth.components.AppLogo +import com.example.demo.feature.auth.components.AuthScaffold +import com.example.demo.feature.auth.components.AuthTextField +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@Composable +fun SignUpScreen( + state: SignUpUiState, + onEvent: (SignUpEvent) -> Unit, + onNavigateToLogin: () -> Unit +) { + AuthScaffold { + AppLogo() + + Spacer(Modifier.height(16.dp)) + + Text( + text = "Create Account", + style = MaterialTheme.typography.titleMedium, + color = Color.White + ) + + Spacer(Modifier.height(4.dp)) + + Text( + text = "Join the ride-sharing community", + style = MaterialTheme.typography.bodyMedium, + color = Color.White.copy(alpha = 0.8f) + ) + + Spacer(Modifier.height(32.dp)) + + AuthTextField( + label = "Name", + value = state.fullName, + onValueChange = { onEvent(SignUpEvent.NameChanged(it)) }, + placeholder = "Your name", + isPassword = false, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(12.dp)) + + AuthTextField( + label = "Email", + value = state.email, + onValueChange = { onEvent(SignUpEvent.EmailChanged(it)) }, + placeholder = "your@email.com", + isPassword = false, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(12.dp)) + + AuthTextField( + label = "Password", + value = state.password, + onValueChange = { onEvent(SignUpEvent.PasswordChanged(it)) }, + placeholder = "••••••••", + isPassword = true, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(12.dp)) + + AuthTextField( + label = "Confirm Password", + value = state.confirmPassword, + onValueChange = { onEvent(SignUpEvent.ConfirmPasswordChanged(it)) }, + placeholder = "••••••••", + isPassword = true, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(16.dp)) + + Button( + onClick = { onEvent(SignUpEvent.Submit) }, + enabled = !state.isLoading, + modifier = Modifier + .fillMaxWidth() + .height(52.dp), + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ) + ) { + Text(if (state.isLoading) "Signing up..." else "Sign Up") + } + + state.errorMessage?.let { error -> + Spacer(Modifier.height(8.dp)) + Text( + text = error, + color = Color.Red, + style = MaterialTheme.typography.bodySmall + ) + } + + Spacer(Modifier.height(24.dp)) + + Text( + text = "Already have an account?", + style = MaterialTheme.typography.bodyMedium, + color = Color.White + ) + + Spacer(Modifier.height(4.dp)) + + TextButton(onClick = onNavigateToLogin) { + Text( + text = "Log in", + style = MaterialTheme.typography.bodyMedium, + color = DrexelGold + ) + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpUiState.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpUiState.kt new file mode 100644 index 0000000..7035fda --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpUiState.kt @@ -0,0 +1,11 @@ +package com.example.demo.feature.auth.signup + +data class SignUpUiState( + val fullName: String = "", + val email: String = "", + val password: String = "", + val confirmPassword: String = "", + val isLoading: Boolean = false, + val errorMessage: String? = null, + val success: Boolean = false +) \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpViewModel.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpViewModel.kt new file mode 100644 index 0000000..6ec694c --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/auth/signup/SignUpViewModel.kt @@ -0,0 +1,106 @@ +package com.example.demo.feature.auth.signup + +import FakeAuthRepository +import com.example.demo.feature.auth.data.AuthRepository +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class SignUpViewModel( + private val repository: AuthRepository = FakeAuthRepository() +) { + + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + private val _uiState = MutableStateFlow(SignUpUiState()) + val uiState: StateFlow = _uiState + + fun onEvent(event: SignUpEvent) { + when (event) { + is SignUpEvent.NameChanged -> + _uiState.value = _uiState.value.copy(fullName = event.value, errorMessage = null) + + is SignUpEvent.EmailChanged -> + _uiState.value = _uiState.value.copy(email = event.value, errorMessage = null) + + is SignUpEvent.PasswordChanged -> + _uiState.value = _uiState.value.copy(password = event.value, errorMessage = null) + + is SignUpEvent.ConfirmPasswordChanged -> + _uiState.value = _uiState.value.copy(confirmPassword = event.value, errorMessage = null) + + SignUpEvent.Submit -> submit() + } + } + + private fun submit() { + val state = _uiState.value + val name = state.fullName.trim() + val email = state.email.trim() + val pass = state.password + val confirm = state.confirmPassword + + // 1) Require all fields + if (name.isBlank() || email.isBlank() || pass.isBlank() || confirm.isBlank()) { + setError("Please fill in all fields.") + return + } + + // 2) Email validation + if (!isValidEmail(email)) { + setError("Please enter a valid email.") + return + } + + // 3) Password match + if (pass != confirm) { + setError("Passwords do not match.") + return + } + + // 4) Password strength + if (pass.length < 6) { + setError("Password must be at least 6 characters.") + return + } + + // Clear old errors + show loading + _uiState.value = state.copy( + isLoading = true, + errorMessage = null + ) + + // 5) Call repository + scope.launch { + val result = repository.signUp(name, email, pass) + + _uiState.value = if (result.isSuccess) { + _uiState.value.copy( + isLoading = false, + errorMessage = null, + success = true // you can use this to auto-navigate later + ) + } else { + _uiState.value.copy( + isLoading = false, + errorMessage = result.exceptionOrNull()?.message + ?: "Sign up failed. Try again." + ) + } + } + } + + private fun setError(msg: String) { + _uiState.value = _uiState.value.copy( + errorMessage = msg, + isLoading = false + ) + } + + // Simple cross-platform email validation + private fun isValidEmail(email: String): Boolean { + val at = email.indexOf('@') + val dot = email.lastIndexOf('.') + return at > 0 && dot > at + 1 && dot < email.length - 1 + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/db/RideRepository.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/db/RideRepository.kt new file mode 100644 index 0000000..d61f199 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/db/RideRepository.kt @@ -0,0 +1,35 @@ +package com.example.demo.feature.db + +data class RideOffer( + val offerId: Long, + val driverId: Long, + val vehicleId: Long, + val fromName: String, + val toName: String, + val departAt: String, + val seatsAvailable: Int, + val priceBase: Double +) + +interface RideRepository { + suspend fun getOpenRideOffers(): List + suspend fun createRideRequest( + riderId: Long, + pickupLocationId: Long, + dropoffLocationId: Long, + earliestPickup: String, + latestPickup: String?, + seatsNeeded: Int + ) + + suspend fun createRideOffer( + driverId: Long, + vehicleId: Long, + originalLocationId: Long, + destLocationId: Long, + departAt: String, + seatsAvailable: Int, + priceBase: Double, + pricePerMile: Double + ) +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/HomeScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/HomeScreen.kt new file mode 100644 index 0000000..d7b996d --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/HomeScreen.kt @@ -0,0 +1,238 @@ +package com.example.demo.feature.main + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DirectionsCar +import androidx.compose.material.icons.filled.People +import androidx.compose.material.icons.filled.Settings +import androidx.compose.ui.graphics.vector.ImageVector +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold +import com.example.demo.ui.theme.FieldBackground +import com.example.demo.ui.theme.HintGrey + + +@Composable +fun HomeScreen( + modifier: Modifier = Modifier, + onFindRideClick: () -> Unit = {}, + onOfferRideClick: () -> Unit = {} +) { + Column( + modifier = modifier + .fillMaxSize() + .background(FieldBackground) + ) { + // ---------- HEADER ---------- + Box( + modifier = Modifier + .fillMaxWidth() + .background(DrexelBlue) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 24.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + text = "Welcome back!", + style = MaterialTheme.typography.headlineSmall, + color = Color.White, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Where would you like to go?", + style = MaterialTheme.typography.bodyMedium, + color = Color.White.copy(alpha = 0.8f) + ) + } + + IconButton(onClick = { /* settings later */ }) { + Icon( + imageVector = Icons.Filled.Settings, + contentDescription = "Settings", + tint = Color.White + ) + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // ---------- MAIN CARDS ---------- + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .weight(1f, fill = true), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Find a Ride card + HomeActionCard( + title = "Find a Ride", + subtitle = "Search for available rides to your destination", + iconBgColor = Color(0xFFE3F2FD), + iconTint = DrexelBlue, + icon = Icons.Filled.People, + buttonText = "Get started →", + onClick = onFindRideClick + ) + + // Offer a Ride card + HomeActionCard( + title = "Offer a Ride", + subtitle = "Share your trip and earn money", + iconBgColor = Color(0xFFFFF4CC), + iconTint = DrexelGold, + icon = Icons.Filled.DirectionsCar, + buttonText = "Get started →", + onClick = onOfferRideClick + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // Stats row + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + StatCard( + label = "Rides", + value = "12", + modifier = Modifier.weight(1f) + ) + StatCard( + label = "Rating", + value = "4.8★", + modifier = Modifier.weight(1f) + ) + StatCard( + label = "Saved", + value = "$142", + modifier = Modifier.weight(1f) + ) + } + } + } +} + +@Composable +private fun HomeActionCard( + title: String, + subtitle: String, + iconBgColor: Color, + iconTint: Color, + icon: ImageVector, + buttonText: String, + onClick: () -> Unit +) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(24.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + ) { + Column( + modifier = Modifier + .padding(horizontal = 20.dp, vertical = 16.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + color = DrexelBlue + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = subtitle, + style = MaterialTheme.typography.bodyMedium, + color = HintGrey + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(44.dp) + .clip(CircleShape) + .background(iconBgColor), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = iconTint + ) + } + + TextButton(onClick = onClick) { + Text( + text = buttonText, + color = DrexelBlue, + fontWeight = FontWeight.SemiBold + ) + } + } + } + } +} + +@Composable +private fun StatCard( + label: String, + value: String, + modifier: Modifier = Modifier, +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(18.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column( + modifier = Modifier + .padding(vertical = 12.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = value, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = DrexelBlue + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = label, + style = MaterialTheme.typography.bodySmall, + color = HintGrey + ) + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/MainRoute.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/MainRoute.kt new file mode 100644 index 0000000..1b85323 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/MainRoute.kt @@ -0,0 +1,145 @@ +package com.example.demo.feature.main + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.feature.profile.ProfileRoute +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import com.example.demo.feature.db.RideRepository +import com.example.demo.feature.messages.MessagesRoute +import com.example.demo.feature.messages.data.MessagesRepository +import com.example.demo.feature.profile.data.ProfileRepository +import com.example.demo.feature.rides.AvailableOfferScreen +import com.example.demo.feature.rides.AvailableRidesScreen +import com.example.demo.feature.rides.MyRidesScreen +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +enum class MainTab { Home, Rides, Messages, Profile } +private enum class HomePage { Dashboard, AvailableRides, OfferRide } + +@Composable +fun MainRoute( + rideRepository: RideRepository, + profileRepository: ProfileRepository, + messagesRepository: MessagesRepository, +) { + var currentTab by remember { mutableStateOf(MainTab.Home) } + var homePage by remember { mutableStateOf(HomePage.Dashboard) } + + Scaffold( + bottomBar = { + MainBottomNav( + currentTab = currentTab, + onTabSelected = { tab -> + currentTab = tab + if (tab == MainTab.Home) { + homePage = HomePage.Dashboard // reset when returning to Home tab + } + } + ) + }, + containerColor = Color(0xFFF5F5F7) + ) { padding -> + when (currentTab) { + MainTab.Home -> { + when (homePage) { + HomePage.Dashboard -> { + HomeScreen( + modifier = Modifier.padding(padding), + onFindRideClick = { homePage = HomePage.AvailableRides }, + onOfferRideClick = { homePage = HomePage.OfferRide }, + ) + } + HomePage.AvailableRides -> { + AvailableRidesScreen( + modifier = Modifier.padding(padding), + onBack = { homePage = HomePage.Dashboard }, + rideRepository = rideRepository + ) + } + HomePage.OfferRide -> { + AvailableOfferScreen( + modifier = Modifier.padding(padding), + onBack = { homePage = HomePage.Dashboard }, + onPublish = { + homePage = HomePage.Dashboard + } + ) + } + } + } + MainTab.Rides -> { + MyRidesScreen( + modifier = Modifier.padding(padding) + ) + } + MainTab.Messages -> { + MessagesRoute( + modifier = Modifier.padding(padding), + repository = messagesRepository, + ) + } + MainTab.Profile -> { + ProfileRoute( + modifier = Modifier.padding(padding), + repository = profileRepository + ) + } + } + } +} + + +@Composable +private fun MainBottomNav( + currentTab: MainTab, + onTabSelected: (MainTab) -> Unit +) { + NavigationBar( + containerColor = Color.White, + tonalElevation = 4.dp + ) { + val itemColors = NavigationBarItemDefaults.colors( + selectedIconColor = DrexelBlue, + selectedTextColor = DrexelBlue, + unselectedIconColor = Color.Gray, + unselectedTextColor = Color.Gray, + indicatorColor = DrexelGold.copy(alpha = 0.18f) // subtle gold pill behind selected + ) + + NavigationBarItem( + selected = currentTab == MainTab.Home, + onClick = { onTabSelected(MainTab.Home) }, + icon = { Icon(Icons.Default.Home, contentDescription = "Home") }, + label = { Text("Home") }, + colors = itemColors + ) + NavigationBarItem( + selected = currentTab == MainTab.Rides, + onClick = { onTabSelected(MainTab.Rides) }, + icon = { Icon(Icons.Default.CalendarToday, contentDescription = "My Rides") }, + label = { Text("My Rides") }, + colors = itemColors + ) + NavigationBarItem( + selected = currentTab == MainTab.Messages, + onClick = { onTabSelected(MainTab.Messages) }, + icon = { Icon(Icons.Default.ChatBubbleOutline, contentDescription = "Messages") }, + label = { Text("Messages") }, + colors = itemColors + ) + NavigationBarItem( + selected = currentTab == MainTab.Profile, + onClick = { onTabSelected(MainTab.Profile) }, + icon = { Icon(Icons.Default.Person, contentDescription = "Profile") }, + label = { Text("Profile") }, + colors = itemColors + ) + } +} + diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/MyRideScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/MyRideScreen.kt new file mode 100644 index 0000000..8e7733f --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/main/MyRideScreen.kt @@ -0,0 +1,352 @@ +package com.example.demo.feature.rides + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CalendarToday +import androidx.compose.material.icons.filled.LocationOn +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.FieldBackground +import com.example.demo.ui.theme.HintGrey + +private enum class MyRidesTab { Upcoming, Completed } + +data class RideHistoryItem( + val id: Int, + val status: String, + val role: String, + val driverName: String, + val pickup: String, + val dropoff: String, + val date: String, + val time: String, + val price: String +) + +@Composable +fun MyRidesScreen( + modifier: Modifier = Modifier +) { + // fake data for now + val upcomingRides = listOf( + RideHistoryItem( + id = 1, + status = "Confirmed", + role = "Passenger", + driverName = "Abdul B.", + pickup = "30th Street Station", + dropoff = "Cira Green", + date = "Nov 11, 2025", + time = "5:30 PM", + price = "$11.71" + ) + ) + + val completedRides = listOf( + RideHistoryItem( + id = 2, + status = "Passenger", // only pill we show + role = "", // leave empty so no second pill + driverName = "Sarah M.", + pickup = "University Crossings", + dropoff = "Downtown Philadelphia", + date = "Nov 5, 2025", + time = "8:00 AM", + price = "$15.5" + ), + RideHistoryItem( + id = 3, + status = "Driver", + role = "", + driverName = "You", // or whoever + pickup = "West Philadelphia", + dropoff = "King of Prussia", + date = "Nov 1, 2025", + time = "6:30 PM", + price = "$28" + ), + RideHistoryItem( + id = 4, + status = "Passenger", + role = "", + driverName = "Michael T.", + pickup = "Temple University", + dropoff = "Chestnut Hill", + date = "Oct 28, 2025", + time = "3:15 PM", + price = "$12.75" + ) + ) + + var currentTab by remember { mutableStateOf(MyRidesTab.Upcoming) } + + Column( + modifier = modifier + .fillMaxSize() + .background(FieldBackground) + ) { + // ------- HEADER -------- + Box( + modifier = Modifier + .fillMaxWidth() + .background(DrexelBlue) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 24.dp) + ) { + Text( + text = "My Rides", + style = MaterialTheme.typography.headlineSmall, + color = Color.White, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Your ride history & upcoming trips", + style = MaterialTheme.typography.bodyMedium, + color = Color.White.copy(alpha = 0.8f) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // segmented control + Surface( + shape = RoundedCornerShape(24.dp), + color = Color.White, + tonalElevation = 4.dp, + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + ) { + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically + ) { + SegmentedTab( + text = "Upcoming", + selected = currentTab == MyRidesTab.Upcoming, + modifier = Modifier.weight(1f) + ) { currentTab = MyRidesTab.Upcoming } + + SegmentedTab( + text = "Completed", + selected = currentTab == MyRidesTab.Completed, + modifier = Modifier.weight(1f) + ) { currentTab = MyRidesTab.Completed } + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // ------- LIST CONTENT ------- + val ridesToShow = + if (currentTab == MyRidesTab.Upcoming) upcomingRides else completedRides + + LazyColumn( + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(ridesToShow, key = { it.id }) { ride -> + RideHistoryCard(ride) + } + + if (ridesToShow.isEmpty()) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "No rides in this section yet.", + color = HintGrey + ) + } + } + } + } + } +} + +@Composable +private fun SegmentedTab( + text: String, + selected: Boolean, + modifier: Modifier = Modifier, + onClick: () -> Unit +) { + val bg = if (selected) Color.White else Color.Transparent + val textColor = if (selected) DrexelBlue else HintGrey + + Box( + modifier = modifier + .fillMaxHeight() + ) { + TextButton( + onClick = onClick, + modifier = Modifier + .fillMaxSize(), + contentPadding = PaddingValues(0.dp) + ) { + Surface( + shape = RoundedCornerShape(24.dp), + color = if (selected) Color(0xFFEFF3FF) else Color.Transparent + ) { + Box( + modifier = Modifier + .padding(horizontal = 12.dp, vertical = 6.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = text, + fontSize = 14.sp, + fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal, + color = textColor + ) + } + } + } + } +} + +@Composable +private fun RideHistoryCard(ride: RideHistoryItem) { + Card( + shape = RoundedCornerShape(24.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier + .padding(horizontal = 20.dp, vertical = 16.dp) + ) { + // status row + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + // always show the first pill + PillChip( + text = ride.status, + bg = Color(0xFFE3F8EA), + textColor = Color(0xFF2C7A43) + ) + + // only show second pill when role is non-blank + if (ride.role.isNotBlank()) { + PillChip( + text = ride.role, + bg = Color(0xFFEAF0FF), + textColor = DrexelBlue + ) + } + } + Text( + text = ride.price, + fontWeight = FontWeight.Bold, + color = DrexelBlue + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Driver: ${ride.driverName}", + style = MaterialTheme.typography.bodyMedium, + color = HintGrey + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // locations + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Filled.LocationOn, + contentDescription = null, + tint = HintGrey, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = ride.pickup, + style = MaterialTheme.typography.bodyMedium, + color = HintGrey + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Filled.LocationOn, + contentDescription = null, + tint = HintGrey, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = ride.dropoff, + style = MaterialTheme.typography.bodyMedium, + color = HintGrey + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + // date + time + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Filled.CalendarToday, + contentDescription = null, + tint = HintGrey, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "${ride.date} ${ride.time}", + style = MaterialTheme.typography.bodyMedium, + color = HintGrey + ) + } + } + } +} + +@Composable +private fun PillChip( + text: String, + bg: Color, + textColor: Color +) { + Surface( + color = bg, + shape = RoundedCornerShape(50) + ) { + Text( + text = text, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + color = textColor, + modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp) + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/MessagesRoute.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/MessagesRoute.kt new file mode 100644 index 0000000..3d1d1e4 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/MessagesRoute.kt @@ -0,0 +1,48 @@ +package com.example.demo.feature.messages + +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import com.example.demo.feature.messages.chat.ChatDetailRoute +import com.example.demo.feature.messages.data.MessagesRepository +import com.example.demo.feature.messages.list.MessageThreadUi +import com.example.demo.feature.messages.list.MessagesListRoute + +private enum class MessagesPage { List, Conversation } + +@Composable +fun MessagesRoute( + modifier: Modifier = Modifier, + repository: MessagesRepository +) { + var currentPage by remember { mutableStateOf(MessagesPage.List) } + var activeThread by remember { mutableStateOf(null) } + + when (currentPage) { + MessagesPage.List -> { + MessagesListRoute( + modifier = modifier, + repository = repository, + onOpenConversation = { thread -> + activeThread = thread + currentPage = MessagesPage.Conversation + } + ) + } + MessagesPage.Conversation -> { + val thread = activeThread + if (thread == null) { + currentPage = MessagesPage.List + } else { + ChatDetailRoute( + threadId = thread.id, + contactName = thread.senderName, + initials = thread.initials, + repository = repository, + onBack = { currentPage = MessagesPage.List }, + modifier = modifier + ) + } + } + } +} + diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailEvent.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailEvent.kt new file mode 100644 index 0000000..9f8fd69 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailEvent.kt @@ -0,0 +1,6 @@ +package com.example.demo.feature.messages.chat + +sealed interface ChatDetailEvent { + data class MessageTextChanged(val value: String) : ChatDetailEvent + data object SendClicked : ChatDetailEvent +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailRoute.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailRoute.kt new file mode 100644 index 0000000..aa6ed9f --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailRoute.kt @@ -0,0 +1,27 @@ +package com.example.demo.feature.messages.chat + +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import com.example.demo.feature.messages.data.MessagesRepository + +@Composable +fun ChatDetailRoute( + threadId: Int, + contactName: String, + initials: String, + repository: MessagesRepository, + onBack: () -> Unit, + modifier: Modifier = Modifier +) { + val viewModel = remember(threadId, repository) { + ChatDetailViewModel(threadId, contactName, initials, repository) + } + val state by viewModel.uiState.collectAsState() + + ChatDetailScreen( + state = state, + onEvent = viewModel::onEvent, + onBack = onBack, + modifier = modifier + ) +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailScreen.kt new file mode 100644 index 0000000..bf8f9e8 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailScreen.kt @@ -0,0 +1,168 @@ +package com.example.demo.feature.messages.chat + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.Send +import androidx.compose.material.icons.filled.Send +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChatDetailScreen( + state: ChatDetailUiState, + onEvent: (ChatDetailEvent) -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = { + Row(verticalAlignment = Alignment.CenterVertically) { + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(DrexelBlue), + contentAlignment = Alignment.Center + ) { + Text( + text = state.initials, + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + } + Spacer(Modifier.width(8.dp)) + Text(state.contactName) + } + }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = DrexelBlue, + titleContentColor = Color.White, + navigationIconContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .background(Color(0xFFF5F5F7)) + ) { + // Messages + LazyColumn( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + items(state.messages) { msg -> + ChatBubble(message = msg) + Spacer(Modifier.height(6.dp)) + } + } + + // Input bar + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.White) + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + value = state.newMessageText, + onValueChange = { onEvent(ChatDetailEvent.MessageTextChanged(it)) }, + modifier = Modifier.weight(1f), + placeholder = { Text("Type a message...") }, + maxLines = 3, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = DrexelBlue, + unfocusedBorderColor = Color(0xFFE5E5EA), + cursorColor = DrexelBlue + ), + shape = RoundedCornerShape(20.dp) + ) + + Spacer(Modifier.width(8.dp)) + + IconButton( + onClick = { onEvent(ChatDetailEvent.SendClicked) } + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.Send, + contentDescription = "Send", + tint = DrexelGold + ) + } + } + } + } +} + +@Composable +private fun ChatBubble(message: ChatMessageUi) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = if (message.isMe) Arrangement.End else Arrangement.Start + ) { + Column( + horizontalAlignment = if (message.isMe) Alignment.End else Alignment.Start + ) { + Box( + modifier = Modifier + .widthIn(max = 260.dp) + .clip( + RoundedCornerShape( + topStart = 18.dp, + topEnd = 18.dp, + bottomStart = if (message.isMe) 18.dp else 4.dp, + bottomEnd = if (message.isMe) 4.dp else 18.dp + ) + ) + .background(if (message.isMe) DrexelBlue else Color.White) + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + Text( + text = message.text, + color = if (message.isMe) Color.White else Color(0xFF1C1C1E), + fontSize = 14.sp + ) + } + Spacer(Modifier.height(2.dp)) + Text( + text = message.time, + fontSize = 11.sp, + color = Color(0xFF8E8E93) + ) + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailUiState.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailUiState.kt new file mode 100644 index 0000000..e4d4673 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailUiState.kt @@ -0,0 +1,17 @@ +package com.example.demo.feature.messages.chat + +data class ChatMessageUi( + val id: Int, + val isMe: Boolean, + val text: String, + val time: String +) + +data class ChatDetailUiState( + val contactName: String = "", + val initials: String = "", + val isLoading: Boolean = false, + val messages: List = emptyList(), + val newMessageText: String = "", + val errorMessage: String? = null +) diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailViewModel.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailViewModel.kt new file mode 100644 index 0000000..86eebad --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/chat/ChatDetailViewModel.kt @@ -0,0 +1,87 @@ +package com.example.demo.feature.messages.chat + +import com.example.demo.feature.messages.data.MessagesRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class ChatDetailViewModel( + private val threadId: Int, + contactName: String, + initials: String, + private val repository: MessagesRepository +) { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + private val _uiState = MutableStateFlow( + ChatDetailUiState( + contactName = contactName, + initials = initials, + isLoading = true + ) + ) + val uiState: StateFlow = _uiState + + init { + loadMessages() + } + + private fun loadMessages() { + scope.launch { + try { + val msgs = repository.getMessagesForThread(threadId) + _uiState.update { + it.copy( + isLoading = false, + messages = msgs, + errorMessage = null + ) + } + } catch (e: Exception) { + _uiState.update { + it.copy( + isLoading = false, + errorMessage = e.message ?: "Failed to load messages" + ) + } + } + } + } + + fun onEvent(event: ChatDetailEvent) { + when (event) { + is ChatDetailEvent.MessageTextChanged -> + _uiState.update { it.copy(newMessageText = event.value) } + + ChatDetailEvent.SendClicked -> sendMessage() + } + } + + private fun sendMessage() { + val text = _uiState.value.newMessageText.trim() + if (text.isBlank()) return + + scope.launch { + try { + val msg = repository.sendMessage(threadId, text) + _uiState.update { + it.copy( + messages = it.messages + msg, + newMessageText = "", + errorMessage = null + ) + } + } catch (e: Exception) { + _uiState.update { + it.copy( + errorMessage = e.message ?: "Failed to send message" + ) + } + } + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/data/FakeMessageRepository.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/data/FakeMessageRepository.kt new file mode 100644 index 0000000..61f89c1 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/data/FakeMessageRepository.kt @@ -0,0 +1,81 @@ +package com.example.demo.feature.messages.data + +import com.example.demo.feature.messages.chat.ChatMessageUi +import com.example.demo.feature.messages.list.MessageThreadUi +import kotlinx.coroutines.delay + +class FakeMessagesRepository : MessagesRepository { + + // pretend current user is id=1 + private val fakeThreads = mutableListOf( + MessageThreadUi( + id = 1, + senderName = "Alex Johnson", + initials = "AJ", + lastMessage = "Are we still on for 5:30 PM?", + timeAgo = "2m ago", + unreadCount = 1 + ), + MessageThreadUi( + id = 2, + senderName = "Campus Carpool", + initials = "CC", + lastMessage = "Thanks again for organizing the rides!", + timeAgo = "3h ago", + unreadCount = 0 + ), + MessageThreadUi( + id = 3, + senderName = "Taylor Smith", + initials = "TS", + lastMessage = "I’ll grab the front seat next time 😄", + timeAgo = "Yesterday", + unreadCount = 0 + ) + ) + + // in-memory messages per thread + private val fakeMessages = mutableMapOf( + 1 to mutableListOf( + ChatMessageUi(1, isMe = false, "Hey, thanks for the ride!", "3h ago"), + ChatMessageUi(2, isMe = true, "Of course! Happy to help.", "3h ago"), + ChatMessageUi(3, isMe = false, "Are we still on for 5:30 PM?", "2m ago"), + ), + 2 to mutableListOf( + ChatMessageUi(4, isMe = false, "Carpool this Friday?", "1d ago"), + ChatMessageUi(5, isMe = true, "Yes, let’s do it!", "1d ago"), + ) + ) + + override suspend fun getThreadsForUser(userId: Int): List { + delay(200) // fake network/db delay + return fakeThreads + } + + override suspend fun getMessagesForThread(threadId: Int): List { + delay(150) + return fakeMessages[threadId]?.toList() ?: emptyList() + } + + override suspend fun sendMessage(threadId: Int, text: String): ChatMessageUi { + delay(100) + val list = fakeMessages.getOrPut(threadId) { mutableListOf() } + val newId = (fakeMessages.values.flatten().maxOfOrNull { it.id } ?: 0) + 1 + val msg = ChatMessageUi( + id = newId, + isMe = true, + text = text, + time = "Now" + ) + list.add(msg) + + // update last message in thread + val idx = fakeThreads.indexOfFirst { it.id == threadId } + if (idx >= 0) { + val thread = fakeThreads[idx] + fakeThreads[idx] = thread.copy(lastMessage = text, timeAgo = "Now") + } + + return msg + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/data/MessagesRepository.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/data/MessagesRepository.kt new file mode 100644 index 0000000..2b2eecf --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/data/MessagesRepository.kt @@ -0,0 +1,13 @@ +package com.example.demo.feature.messages.data + +import com.example.demo.feature.messages.chat.ChatMessageUi +import com.example.demo.feature.messages.list.MessageThreadUi + +interface MessagesRepository { + + suspend fun getThreadsForUser(userId: Int): List + + suspend fun getMessagesForThread(threadId: Int): List + + suspend fun sendMessage(threadId: Int, text: String): ChatMessageUi +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesEvent.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesEvent.kt new file mode 100644 index 0000000..97293fc --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesEvent.kt @@ -0,0 +1,5 @@ +package com.example.demo.feature.messages.list + +sealed interface MessagesEvent { + data class SearchQueryChanged(val value: String) : MessagesEvent +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesListRoute.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesListRoute.kt new file mode 100644 index 0000000..c446f70 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesListRoute.kt @@ -0,0 +1,30 @@ +package com.example.demo.feature.messages.list + +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import com.example.demo.feature.messages.data.MessagesRepository + +@Composable +fun MessagesListRoute( + modifier: Modifier = Modifier, + onOpenConversation: (MessageThreadUi) -> Unit, + repository: MessagesRepository +) { + // For now, we use userId = 1; AndroidMessagesRepository internally + // will override this with CurrentUserStore.userId when available. + val viewModel = remember(repository) { + MessagesViewModel( + repository = repository, + userId = 1 + ) + } + + val state by viewModel.uiState.collectAsState() + + MessagesScreen( + state = state, + onEvent = viewModel::onEvent, + onOpenConversation = onOpenConversation, + modifier = modifier + ) +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesScreen.kt new file mode 100644 index 0000000..69f3c9d --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesScreen.kt @@ -0,0 +1,173 @@ +package com.example.demo.feature.messages.list + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MessagesScreen( + state: MessagesUiState, + onEvent: (MessagesEvent) -> Unit, + onOpenConversation: (MessageThreadUi) -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = { Text("Messages") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = DrexelBlue, + titleContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .background(Color(0xFFF5F5F7)) + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + // Search bar + OutlinedTextField( + value = state.searchQuery, + onValueChange = { onEvent(MessagesEvent.SearchQueryChanged(it)) }, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text("Search messages") }, + leadingIcon = { + Icon(Icons.Default.Search, contentDescription = null) + }, + singleLine = true, + shape = RoundedCornerShape(20.dp) + ) + + Spacer(Modifier.height(12.dp)) + + if (state.isLoading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } else { + val filtered = state.threads.filter { + val q = state.searchQuery.trim().lowercase() + q.isEmpty() || + it.senderName.lowercase().contains(q) || + it.lastMessage.lowercase().contains(q) + } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(filtered) { thread -> + MessageThreadRow(thread, onClick = { onOpenConversation(thread) }) + } + } + } + } + } +} + +@Composable +private fun MessageThreadRow( + thread: MessageThreadUi, + onClick: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) + ) { + Row( + modifier = Modifier.padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(DrexelGold), + contentAlignment = Alignment.Center + ) { + Text( + text = thread.initials, + color = Color.Blue, + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + } + + Spacer(Modifier.width(12.dp)) + + Column(modifier = Modifier.weight(1f)) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = thread.senderName, + fontWeight = FontWeight.SemiBold, + fontSize = 15.sp + ) + Spacer(Modifier.width(8.dp)) + Text( + text = thread.timeAgo, + fontSize = 12.sp, + color = Color.Gray + ) + } + + Spacer(Modifier.height(4.dp)) + + Text( + text = thread.lastMessage, + fontSize = 13.sp, + color = Color(0xFF555555), + maxLines = 1 + ) + } + + if (thread.unreadCount > 0) { + Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(Color(0xFFFF3B30)), + contentAlignment = Alignment.Center + ) { + Text( + text = thread.unreadCount.toString(), + color = Color.White, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + } + } + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesUiState.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesUiState.kt new file mode 100644 index 0000000..1b3682e --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesUiState.kt @@ -0,0 +1,17 @@ +package com.example.demo.feature.messages.list + +data class MessageThreadUi( + val id: Int, + val senderName: String, + val initials: String, + val lastMessage: String, + val timeAgo: String, + val unreadCount: Int = 0 +) + +data class MessagesUiState( + val isLoading: Boolean = false, + val searchQuery: String = "", + val threads: List = emptyList(), + val errorMessage: String? = null +) diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesViewModel.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesViewModel.kt new file mode 100644 index 0000000..b1bed78 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/messages/list/MessagesViewModel.kt @@ -0,0 +1,48 @@ +package com.example.demo.feature.messages.list + +import com.example.demo.feature.messages.data.MessagesRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class MessagesViewModel( + private val repository: MessagesRepository, + private val userId: Int +) { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + private val _uiState = MutableStateFlow(MessagesUiState()) + val uiState: StateFlow = _uiState + + init { + loadThreads() + } + + private fun loadThreads() { + scope.launch { + try { + val threads = repository.getThreadsForUser(userId) + _uiState.value = _uiState.value.copy( + isLoading = false, + threads = threads, + errorMessage = null + ) + } catch (e: Exception) { + _uiState.value = _uiState.value.copy( + isLoading = false, + errorMessage = e.message ?: "Failed to load messages" + ) + } + } + } + + fun onEvent(event: MessagesEvent) { + when (event) { + is MessagesEvent.SearchQueryChanged -> + _uiState.value = _uiState.value.copy(searchQuery = event.value) + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileEvent.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileEvent.kt new file mode 100644 index 0000000..8c22af0 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileEvent.kt @@ -0,0 +1,56 @@ +package com.example.demo.feature.profile + +enum class SettingsOption { + AccountSetting, + Preferences, + PrivacySetting, +} +sealed interface ProfileEvent { + + // Vehicles + data class VehicleClicked(val vehicleId: Int) : ProfileEvent + data object VehicleDialogDismissed : ProfileEvent + data class VehicleEditMakeChanged(val value: String) : ProfileEvent + data class VehicleEditModelChanged(val value: String) : ProfileEvent + data class VehicleEditColorChanged(val value: String) : ProfileEvent + data class VehicleEditPlateChanged(val value: String) : ProfileEvent + data class VehicleEditSeatsChanged(val value: String) : ProfileEvent + data class VehicleEditYearChanged(val value: String) : ProfileEvent + data class VehicleEditFunFactChanged(val value: String) : ProfileEvent + data object AddVehicleClicked : ProfileEvent + data object DeleteVehicleClicked : ProfileEvent + data object SaveVehicleChanges : ProfileEvent + + + data class PreferencesChanged( + val notificationsEnabled: Boolean, + val emailUpdatesEnabled: Boolean, + val darkModeEnabled: Boolean, + ) : ProfileEvent + + data object SavePreferences : ProfileEvent + + // Settings + data class SettingsClicked(val option: SettingsOption) : ProfileEvent + data object SettingsDialogDismissed : ProfileEvent + + data class AccountSettingsChanged( + val fullName: String? = null, + val email: String? = null, + val phone: String? = null, + val password: String? = null, + ) : ProfileEvent + + data object SaveAccountSettings : ProfileEvent + data object DeleteAccount : ProfileEvent + + data class PrivacySafetyChanged( + val showProfilePublicly: Boolean, + val allowMessagesFromNonContacts: Boolean, + val shareTripHistoryWithFriends: Boolean, + val twoFactorEnabled: Boolean + ) : ProfileEvent + + data object SavePrivacySafety : ProfileEvent + +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileScreen.kt new file mode 100644 index 0000000..cbc6fc5 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileScreen.kt @@ -0,0 +1,324 @@ +package com.example.demo.feature.profile + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.demo.feature.profile.components.SettingsCard +import com.example.demo.feature.profile.components.VehicleEditDialog +import com.example.demo.feature.profile.components.VehiclesCard +import com.example.demo.feature.profile.data.InMemoryProfileRepository +import com.example.demo.feature.profile.data.ProfileRepository +import com.example.demo.feature.profile.pages.AccountSettingsScreen +import com.example.demo.feature.profile.pages.PreferencesScreen +import com.example.demo.feature.profile.pages.PrivacySafetyScreen +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +enum class ProfilePage { + Overview, + AccountSettings, + Preferences, + PrivacySafety +} + +@Composable +fun ProfileRoute( + modifier: Modifier = Modifier, + repository: ProfileRepository +) { + val viewModel = remember(repository) { ProfileViewModel(repository) } + var currentPage by remember { mutableStateOf(ProfilePage.Overview) } + val state by viewModel.uiState.collectAsState() + + when (currentPage) { + ProfilePage.Overview -> ProfileScreen( + state = state, + onEvent = viewModel::onEvent, + onAccountSettingsClick = { currentPage = ProfilePage.AccountSettings }, + onPreferencesClick = { currentPage = ProfilePage.Preferences }, + onPrivacySafetyClick = { currentPage = ProfilePage.PrivacySafety }, + modifier = modifier + ) + + ProfilePage.AccountSettings -> AccountSettingsScreen( + state = state.account, + onChange = { updated -> + viewModel.onEvent( + ProfileEvent.AccountSettingsChanged( + fullName = updated.fullName, + email = updated.email, + phone = updated.phone, + password = updated.password + ) + ) + }, + onSave = { viewModel.onEvent(ProfileEvent.SaveAccountSettings) }, + onDelete = { viewModel.onEvent(ProfileEvent.DeleteAccount) }, + onBack = { currentPage = ProfilePage.Overview } + ) + + ProfilePage.Preferences -> PreferencesScreen( + prefs = state.preferences, + onChange = { newPrefs -> + viewModel.onEvent( + ProfileEvent.PreferencesChanged( + notificationsEnabled = newPrefs.notificationsEnabled, + emailUpdatesEnabled = newPrefs.emailUpdatesEnabled, + darkModeEnabled = newPrefs.darkModeEnabled + ) + ) + }, + onSave = { viewModel.onEvent(ProfileEvent.SavePreferences) }, + onBack = { currentPage = ProfilePage.Overview }, + modifier = modifier + ) + + ProfilePage.PrivacySafety -> PrivacySafetyScreen( + state = state.privacy, + onChange = { updated -> + viewModel.onEvent( + ProfileEvent.PrivacySafetyChanged( + showProfilePublicly = updated.showProfilePublicly, + allowMessagesFromNonContacts = updated.allowMessagesFromNonContacts, + shareTripHistoryWithFriends = updated.shareTripHistoryWithFriends, + twoFactorEnabled = updated.twoFactorEnabled + ) + ) + }, + onSave = { viewModel.onEvent(ProfileEvent.SavePrivacySafety) }, + onBack = { currentPage = ProfilePage.Overview }, + modifier = modifier + ) + } +} + + +@Composable +fun ProfileScreen( + state: ProfileUiState, + onEvent: (ProfileEvent) -> Unit, + onAccountSettingsClick: () -> Unit, + onPreferencesClick: () -> Unit, + onPrivacySafetyClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier.fillMaxSize() + ) { + ProfileHeader(state) + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + StatsCard(state) + VerifiedCard() + VehiclesCard(state, onEvent) + SettingsCard( + onAccountSettingsClick = onAccountSettingsClick, + onPreferencesClick = onPreferencesClick, + onPrivacySafetyClick = onPrivacySafetyClick + ) + } + } + + if (state.isVehicleDialogOpen) { + VehicleEditDialog(state, onEvent) + } +} + + +@Composable +private fun ProfileHeader(state: ProfileUiState) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(DrexelBlue) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 24.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + // Avatar circle with initials + Box( + modifier = Modifier + .size(64.dp) + .clip(CircleShape) + .background(DrexelGold), + contentAlignment = Alignment.Center + ) { + Text( + text = state.name.split(" ") + .take(2) + .joinToString("") { it.first().uppercase() }, + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = DrexelBlue + ) + } + + Spacer(Modifier.width(16.dp)) + + Column { + Text( + text = state.name, + style = MaterialTheme.typography.titleMedium, + color = Color.White + ) + Text( + text = state.email, + style = MaterialTheme.typography.bodyMedium, + color = Color.White.copy(alpha = 0.9f) + ) + + Spacer(Modifier.height(4.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Default.Star, + contentDescription = null, + tint = DrexelGold, + modifier = Modifier.size(16.dp) + ) + Text( + text = "${state.rating}", + style = MaterialTheme.typography.bodyMedium, + color = Color.White, + modifier = Modifier.padding(start = 4.dp, end = 8.dp) + ) + Text( + text = "${state.totalRides} rides", + style = MaterialTheme.typography.bodySmall, + color = Color.White.copy(alpha = 0.9f) + ) + } + } + } + } + } +} + +@Composable +private fun StatsCard(state: ProfileUiState) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + StatItem( + title = "\$${state.savedAmount}", + subtitle = "Saved" + ) + StatItem( + title = "${state.passengerRides}", + subtitle = "As Passenger" + ) + StatItem( + title = "${state.driverRides}", + subtitle = "As Driver" + ) + } + } +} + +@Composable +private fun StatItem(title: String, subtitle: String) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Spacer(Modifier.height(4.dp)) + Text( + text = subtitle, + style = MaterialTheme.typography.bodySmall, + color = Color.Gray + ) + } +} + +@Composable +private fun VerifiedCard() { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Verified, + contentDescription = null, + tint = Color(0xFF42B883), + modifier = Modifier.size(24.dp) + ) + Spacer(Modifier.width(12.dp)) + Column { + Text("Verified Account", fontWeight = FontWeight.SemiBold) + Text( + "ID and phone verified", + style = MaterialTheme.typography.bodySmall, + color = Color.Gray + ) + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileUiState.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileUiState.kt new file mode 100644 index 0000000..756ae4a --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileUiState.kt @@ -0,0 +1,81 @@ +package com.example.demo.feature.profile + +data class VehicleUi( + val id: Int, + val ownerUserId: Int, + val make: String, + val model: String, + val color: String, + val plate: String, + val seatsTotal: Int, + val year: Int, + val funFact: String +) + +// Used only while editing in the dialog +data class VehicleEditState( + val id: Int? = null, + val ownerUserId: Int? = null, + val make: String = "", + val model: String = "", + val color: String = "", + val plate: String = "", + val seatsTotal: String = "", // keep as String for text field + val year: String = "", + val funFact: String = "" +) + +data class ProfileUiState( + val name: String = "John Doe", + val email: String = "john.doe@email.com", + val rating: Double = 4.8, + val totalRides: Int = 12, + val savedAmount: Int = 142, + val passengerRides: Int = 8, + val driverRides: Int = 4, + val isVerified: Boolean = true, + val vehicles: List = listOf( + VehicleUi( + id = 1, + ownerUserId = 42, // placeholder + make = "Tesla", + model = "Model Y", + color = "Blue", + plate = "ABC-1234", + seatsTotal = 5, + year = 2022, + funFact = "First EV on campus" + ) + ), + val isSettingsDialogOpen: Boolean = false, + val selectedSettingsOption: SettingsOption? = null, + + // dialog / editing state + val isVehicleDialogOpen: Boolean = false, + val vehicleEdit: VehicleEditState = VehicleEditState(), + val preferences: PreferencesState = PreferencesState(), + val account: AccountSettingsState = AccountSettingsState(), + val privacy: PrivacySafetyState = PrivacySafetyState(), +) + + +data class PreferencesState( + val notificationsEnabled: Boolean = true, + val emailUpdatesEnabled: Boolean = false, + val darkModeEnabled: Boolean = false, +) + +data class AccountSettingsState( + val fullName: String = "John Doe", + val email: String = "john.doe@email.com", + val phone: String = "0123456789", + val password: String = "*******", +) + +data class PrivacySafetyState( + val showProfilePublicly: Boolean = true, + val allowMessagesFromNonContacts: Boolean = true, + val shareTripHistoryWithFriends: Boolean = false, + val twoFactorEnabled: Boolean = false +) + diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileViewModel.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileViewModel.kt new file mode 100644 index 0000000..6d063f1 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/ProfileViewModel.kt @@ -0,0 +1,259 @@ +package com.example.demo.feature.profile + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +import com.example.demo.feature.profile.data.InMemoryProfileRepository +import com.example.demo.feature.profile.data.ProfileRepository + +class ProfileViewModel( + private val repository: ProfileRepository +) { + + private val _uiState = MutableStateFlow(repository.loadInitialProfile()) + val uiState: StateFlow = _uiState + + fun onEvent(event: ProfileEvent) { + when (event) { + + is ProfileEvent.VehicleClicked -> { + val vehicle = _uiState.value.vehicles.firstOrNull { it.id == event.vehicleId } + ?: return + + _uiState.value = _uiState.value.copy( + isVehicleDialogOpen = true, + vehicleEdit = VehicleEditState( + id = vehicle.id, + ownerUserId = vehicle.ownerUserId, + make = vehicle.make, + model = vehicle.model, + color = vehicle.color, + plate = vehicle.plate, + seatsTotal = vehicle.seatsTotal.toString(), + year = vehicle.year.toString(), + funFact = vehicle.funFact + ) + ) + } + + ProfileEvent.VehicleDialogDismissed -> { + _uiState.value = _uiState.value.copy( + isVehicleDialogOpen = false, + vehicleEdit = VehicleEditState() + ) + } + + is ProfileEvent.VehicleEditMakeChanged -> + updateEdit { it.copy(make = event.value) } + + is ProfileEvent.VehicleEditModelChanged -> + updateEdit { it.copy(model = event.value) } + + is ProfileEvent.VehicleEditColorChanged -> + updateEdit { it.copy(color = event.value) } + + is ProfileEvent.VehicleEditPlateChanged -> + updateEdit { it.copy(plate = event.value) } + + is ProfileEvent.VehicleEditSeatsChanged -> + updateEdit { it.copy(seatsTotal = event.value) } + + is ProfileEvent.VehicleEditYearChanged -> + updateEdit { it.copy(year = event.value) } + + is ProfileEvent.VehicleEditFunFactChanged -> + updateEdit { it.copy(funFact = event.value) } + + ProfileEvent.SaveVehicleChanges -> { + saveVehicle() + persist() // save to repo + } + + is ProfileEvent.AddVehicleClicked -> { + _uiState.value = _uiState.value.copy( + isVehicleDialogOpen = true, + vehicleEdit = VehicleEditState( + id = null, + ownerUserId = 1, // TODO: replace with real user id later + make = "", + model = "", + color = "", + plate = "", + seatsTotal = "", + year = "", + funFact = "", + ) + ) + } + + is ProfileEvent.DeleteVehicleClicked -> { + val edit = _uiState.value.vehicleEdit + val id = edit.id ?: return + + _uiState.value = _uiState.value.copy( + vehicles = _uiState.value.vehicles.filterNot { it.id == id }, + isVehicleDialogOpen = false, + vehicleEdit = VehicleEditState() + ) + persist() // save to repo + } + + is ProfileEvent.SettingsClicked -> { + _uiState.value = _uiState.value.copy( + isSettingsDialogOpen = true, + selectedSettingsOption = event.option + ) + } + + ProfileEvent.SettingsDialogDismissed -> { + _uiState.value = _uiState.value.copy( + isSettingsDialogOpen = false, + selectedSettingsOption = null + ) + } + + // ---------- Preferences ---------- + + is ProfileEvent.PreferencesChanged -> { + _uiState.value = _uiState.value.copy( + preferences = _uiState.value.preferences.copy( + notificationsEnabled = event.notificationsEnabled, + emailUpdatesEnabled = event.emailUpdatesEnabled, + darkModeEnabled = event.darkModeEnabled + ) + ) + } + + ProfileEvent.SavePreferences -> { + println("Preferences saved: ${_uiState.value.preferences}") + persist() // save to repo + } + + // ---------- Account Settings ---------- + + is ProfileEvent.AccountSettingsChanged -> { + val current = _uiState.value.account + _uiState.value = _uiState.value.copy( + account = current.copy( + fullName = event.fullName ?: current.fullName, + email = event.email ?: current.email, + phone = event.phone ?: current.phone, + password = event.password ?: current.password + ) + ) + } + + ProfileEvent.SaveAccountSettings -> { + println("ACCOUNT UPDATED: ${_uiState.value.account}") + persist() // save to repo + } + + ProfileEvent.DeleteAccount -> { + println("ACCOUNT DELETED (TODO: real delete + logout)") + // You might later clear profile / navigate away etc. + persist() // still log/save current state + } + + // ---------- Privacy & Safety ---------- + + is ProfileEvent.PrivacySafetyChanged -> { + _uiState.value = _uiState.value.copy( + privacy = _uiState.value.privacy.copy( + showProfilePublicly = event.showProfilePublicly, + allowMessagesFromNonContacts = event.allowMessagesFromNonContacts, + shareTripHistoryWithFriends = event.shareTripHistoryWithFriends, + twoFactorEnabled = event.twoFactorEnabled + ) + ) + } + + ProfileEvent.SavePrivacySafety -> { + println("Privacy & Safety saved: ${_uiState.value.privacy}") + persist() // save to repo + } + } + } + + private fun updateEdit(transform: (VehicleEditState) -> VehicleEditState) { + _uiState.value = _uiState.value.copy( + vehicleEdit = transform(_uiState.value.vehicleEdit) + ) + } + + fun saveVehicle() { + val state = _uiState.value + val edit = state.vehicleEdit + + // --- Enforce DB constraints safely --- + + // Seats: parse, clamp 1..8, default to 4 if blank/bad + val seats = edit.seatsTotal + .toIntOrNull() + ?.coerceIn(1, 8) + ?: 4 + + // Year: parse, clamp 1900..2100, default to 2024 if blank/bad + val safeYear = edit.year + .toIntOrNull() + ?.coerceIn(1900, 2100) + ?: 2024 + + // Required strings: make sure they are not blank + val safeMake = edit.make.ifBlank { "Unknown" } + val safeModel = edit.model.ifBlank { "Car" } + val safeColor = edit.color.ifBlank { "Unknown" } + val basePlate = edit.plate.ifBlank { "TEMP" } + + val updatedVehicles = if (edit.id == null) { + // New vehicle → CREATE + // Use negative IDs so we never collide with existing positive DB IDs + val currentMinId = state.vehicles.minOfOrNull { it.id } ?: 0 + val newId = if (currentMinId > 0) -1 else currentMinId - 1 + + // Make sure plate is unique-ish if user left it blank + val safePlate = if (edit.plate.isBlank()) { + "$basePlate-$newId" // <-- use whatever base string you defined above + } else { + edit.plate + } + + state.vehicles + VehicleUi( + id = newId, + ownerUserId = edit.ownerUserId ?: 1, // current user id placeholder + make = safeMake, + model = safeModel, + color = safeColor, + plate = safePlate, + seatsTotal = seats, + year = safeYear, + funFact = edit.funFact + ) + } else { + // Existing vehicle → UPDATE + state.vehicles.map { v -> + if (v.id == edit.id) { + v.copy( + make = safeMake, + model = safeModel, + color = safeColor, + plate = if (edit.plate.isBlank()) v.plate else edit.plate, + seatsTotal = seats, + year = safeYear, + funFact = edit.funFact + ) + } else v + } + } + + _uiState.value = state.copy( + vehicles = updatedVehicles, + isVehicleDialogOpen = false, + vehicleEdit = VehicleEditState() + ) + } + + // Single place to write to our "fake DB" + private fun persist() { + repository.saveProfile(_uiState.value) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/SettingsCard.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/SettingsCard.kt new file mode 100644 index 0000000..de56c08 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/SettingsCard.kt @@ -0,0 +1,87 @@ +package com.example.demo.feature.profile.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Shield +import androidx.compose.material.icons.filled.Tune +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.graphics.vector.ImageVector + +@Composable +fun SettingsCard( + onAccountSettingsClick: () -> Unit, + onPreferencesClick: () -> Unit, + onPrivacySafetyClick: () -> Unit, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Settings", fontWeight = FontWeight.SemiBold) + Spacer(Modifier.height(12.dp)) + + SettingsRow( + icon = Icons.Default.Settings, + label = "Account Settings", + onClick = onAccountSettingsClick + ) + SettingsRow( + icon = Icons.Default.Tune, + label = "Preferences", + onClick = onPreferencesClick + ) + SettingsRow( + icon = Icons.Default.Shield, + label = "Privacy & Safety", + onClick = onPrivacySafetyClick + ) + } + } +} + +@Composable +private fun SettingsRow( + icon: ImageVector, + label: String, + onClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .clickable { onClick() }, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = Color.Gray, + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.width(12.dp)) + Text(label, modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium) + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = Color.Gray + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/VehicleEditDialog.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/VehicleEditDialog.kt new file mode 100644 index 0000000..6894239 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/VehicleEditDialog.kt @@ -0,0 +1,156 @@ +package com.example.demo.feature.profile.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.example.demo.feature.profile.ProfileEvent +import com.example.demo.feature.profile.ProfileUiState +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@Composable +fun VehicleEditDialog( + state: ProfileUiState, + onEvent: (ProfileEvent) -> Unit +) { + val edit = state.vehicleEdit + val isEditing = edit.id != null + + Dialog(onDismissRequest = { onEvent(ProfileEvent.VehicleDialogDismissed) }) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .clip(RoundedCornerShape(20.dp)) + .background(Color.White) + ) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + + // Header + Box( + modifier = Modifier + .fillMaxWidth() + .background(DrexelBlue) + .padding(16.dp), + contentAlignment = Alignment.CenterStart + ) { + Text( + text = if (isEditing) "Edit Vehicle" else "Add Vehicle", + color = Color.White, + style = MaterialTheme.typography.titleMedium + ) + } + + // Form fields + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + OutlinedTextField( + value = edit.make, + onValueChange = { onEvent(ProfileEvent.VehicleEditMakeChanged(it)) }, + label = { Text("Make") }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = edit.model, + onValueChange = { onEvent(ProfileEvent.VehicleEditModelChanged(it)) }, + label = { Text("Model") }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = edit.color, + onValueChange = { onEvent(ProfileEvent.VehicleEditColorChanged(it)) }, + label = { Text("Color") }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = edit.plate, + onValueChange = { onEvent(ProfileEvent.VehicleEditPlateChanged(it)) }, + label = { Text("Plate") }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = edit.seatsTotal, + onValueChange = { onEvent(ProfileEvent.VehicleEditSeatsChanged(it)) }, + label = { Text("Seats (total)") }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = edit.year, + onValueChange = { onEvent(ProfileEvent.VehicleEditYearChanged(it)) }, + label = { Text("Year") }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = edit.funFact, + onValueChange = { onEvent(ProfileEvent.VehicleEditFunFactChanged(it)) }, + label = { Text("Fun fact about this car") }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) + } + + // Buttons (Delete + Cancel + Save) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(15.dp), + verticalAlignment = Alignment.CenterVertically + ) { + + // Only show Delete when editing an existing car + if (isEditing) { + OutlinedButton( + onClick = { onEvent(ProfileEvent.DeleteVehicleClicked) }, + border = BorderStroke(1.dp, Color.Red), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = Color.Red + ) + ) { + Text("Delete") + } + } + + Spacer(Modifier.weight(1f)) + + TextButton( + onClick = { onEvent(ProfileEvent.VehicleDialogDismissed) } + ) { + Text("Cancel", color = DrexelBlue) + } + + Spacer(Modifier.width(8.dp)) + + Button( + onClick = { onEvent(ProfileEvent.SaveVehicleChanges) }, + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ), + shape = RoundedCornerShape(10.dp) + ) { + Text("Save") + } + } + } + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/VehiclesCard.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/VehiclesCard.kt new file mode 100644 index 0000000..b093849 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/components/VehiclesCard.kt @@ -0,0 +1,105 @@ +package com.example.demo.feature.profile.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.DirectionsCar +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.example.demo.feature.profile.ProfileEvent +import com.example.demo.feature.profile.ProfileUiState +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@Composable +fun VehiclesCard( + state: ProfileUiState, + onEvent: (ProfileEvent) -> Unit +) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("My Vehicles", fontWeight = FontWeight.SemiBold) + + TextButton( + onClick = { onEvent(ProfileEvent.AddVehicleClicked) }, + contentPadding = PaddingValues(0.dp) + ) { + Text( + text = "+ Add", + color = DrexelGold, + style = MaterialTheme.typography.bodyMedium + ) + } + } + + Spacer(Modifier.height(12.dp)) + + state.vehicles.forEach { vehicle -> + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clickable { + onEvent(ProfileEvent.VehicleClicked(vehicle.id)) + }, + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFFF3F6FF)), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.DirectionsCar, + contentDescription = null, + tint = DrexelBlue, + modifier = Modifier.size(24.dp) + ) + Spacer(Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + "${vehicle.make} ${vehicle.model}", + fontWeight = FontWeight.SemiBold + ) + Text( + "${vehicle.color} · ${vehicle.plate}", + style = MaterialTheme.typography.bodySmall, + color = Color.Gray + ) + Text( + "${vehicle.seatsTotal} seats · ${vehicle.year}", + style = MaterialTheme.typography.bodySmall, + color = Color.Gray + ) + } + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = Color.Gray + ) + } + } + } + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/data/ProfileRepository.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/data/ProfileRepository.kt new file mode 100644 index 0000000..d9e160b --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/data/ProfileRepository.kt @@ -0,0 +1,29 @@ +package com.example.demo.feature.profile.data + +import com.example.demo.feature.profile.ProfileUiState + +/** + * This is our "data source" API. + * Later you can have a real DB implementation that also implements this. + */ +interface ProfileRepository { + fun loadInitialProfile(): ProfileUiState + fun saveProfile(state: ProfileUiState) +} + +/** + * Simple in-memory implementation + * Acts like a fake database for now. + */ +class InMemoryProfileRepository : ProfileRepository { + + // This is our "stored" profile (fake DB row) + private var current: ProfileUiState = ProfileUiState() + + override fun loadInitialProfile(): ProfileUiState = current + + override fun saveProfile(state: ProfileUiState) { + current = state + println(" [InMemoryProfileRepository] ProfileRepository.loadInitialProfile() current: $current") + } +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/AccountSettingsScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/AccountSettingsScreen.kt new file mode 100644 index 0000000..8824116 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/AccountSettingsScreen.kt @@ -0,0 +1,130 @@ +package com.example.demo.feature.profile.pages + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.feature.profile.AccountSettingsState +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccountSettingsScreen( + state: AccountSettingsState, + onChange: (AccountSettingsState) -> Unit, + onSave: () -> Unit, + onDelete: () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + topBar = { + TopAppBar( + title = { Text("Account Settings") }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = DrexelBlue, + titleContentColor = Color.White, + navigationIconContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = modifier + .padding(padding) + .fillMaxSize() + .background(Color(0xFFF5F5F7)) + .verticalScroll(rememberScrollState()) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + + val textFieldColors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = DrexelBlue, + unfocusedBorderColor = Color.LightGray, + focusedLabelColor = DrexelBlue, + cursorColor = DrexelBlue + ) + + Text("Profile", style = MaterialTheme.typography.titleMedium, color = DrexelBlue) + + OutlinedTextField( + value = state.fullName, + onValueChange = { onChange(state.copy(fullName = it)) }, + label = { Text("Full Name") }, + modifier = Modifier.fillMaxWidth(), + colors = textFieldColors + ) + + OutlinedTextField( + value = state.email, + onValueChange = { onChange(state.copy(email = it)) }, + label = { Text("Email") }, + modifier = Modifier.fillMaxWidth(), + colors = textFieldColors + ) + + OutlinedTextField( + value = state.phone, + onValueChange = { onChange(state.copy(phone = it)) }, + label = { Text("Phone") }, + modifier = Modifier.fillMaxWidth(), + colors = textFieldColors + ) + + Spacer(Modifier.height(8.dp)) + + Text("Security", style = MaterialTheme.typography.titleMedium, color = DrexelBlue) + + OutlinedTextField( + value = state.password, + onValueChange = { onChange(state.copy(password = it)) }, + label = { Text("Password") }, + modifier = Modifier.fillMaxWidth(), + colors = textFieldColors + ) + + Spacer(Modifier.height(16.dp)) + + Button( + onClick = onSave, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ) + ) { Text("Update Account") } + + Spacer(Modifier.height(24.dp)) + + Text("Danger Zone", style = MaterialTheme.typography.titleMedium, color = Color.Red) + + OutlinedButton( + onClick = onDelete, + modifier = Modifier.fillMaxWidth(), + border = BorderStroke(1.dp, Color.Red), + colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.Red) + ) { + Text("Delete Account") + } + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/PreferencesScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/PreferencesScreen.kt new file mode 100644 index 0000000..58392fe --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/PreferencesScreen.kt @@ -0,0 +1,152 @@ +package com.example.demo.feature.profile.pages + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.feature.profile.PreferencesState +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PreferencesScreen( + prefs: PreferencesState, + onChange: (PreferencesState) -> Unit, + onSave: () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + topBar = { + TopAppBar( + title = { Text("Preferences") }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = DrexelBlue, + titleContentColor = Color.White, + navigationIconContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = modifier + .padding(padding) + .fillMaxSize() + .background(Color(0xFFF5F5F7)) + .verticalScroll(rememberScrollState()) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + + Text( + "Notifications", + style = MaterialTheme.typography.titleMedium, + color = DrexelBlue + ) + + PreferenceSwitchRow( + title = "Push notifications", + subtitle = "Get alerts about upcoming rides and messages", + checked = prefs.notificationsEnabled, + onCheckedChange = { + onChange( + prefs.copy(notificationsEnabled = it) + ) + } + ) + + PreferenceSwitchRow( + title = "Email updates", + subtitle = "Receive trip summaries and announcements", + checked = prefs.emailUpdatesEnabled, + onCheckedChange = { + onChange( + prefs.copy(emailUpdatesEnabled = it) + ) + } + ) + + Spacer(Modifier.height(16.dp)) + + Text( + "Appearance", + style = MaterialTheme.typography.titleMedium, + color = DrexelBlue + ) + + PreferenceSwitchRow( + title = "Dark mode", + subtitle = "Use a darker color scheme at night", + checked = prefs.darkModeEnabled, + onCheckedChange = { + onChange( + prefs.copy(darkModeEnabled = it) + ) + } + ) + + Spacer(Modifier.height(24.dp)) + + Button( + onClick = onSave, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ) + ) { + Text("Save Preferences") + } + } + } +} + +@Composable +fun PreferenceSwitchRow( + title: String, + subtitle: String, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + val switchColors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = DrexelBlue, + uncheckedThumbColor = Color.White, + uncheckedTrackColor = Color.LightGray, + checkedBorderColor = DrexelBlue, + uncheckedBorderColor = Color.LightGray + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f)) { + Text(title, style = MaterialTheme.typography.bodyLarge) + Text(subtitle, style = MaterialTheme.typography.bodySmall, color = Color.Gray) + } + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + colors = switchColors + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/PrivacySafetyScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/PrivacySafetyScreen.kt new file mode 100644 index 0000000..0ac5ff2 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/profile/pages/PrivacySafetyScreen.kt @@ -0,0 +1,162 @@ +package com.example.demo.feature.profile.pages + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.demo.feature.profile.PrivacySafetyState +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PrivacySafetyScreen( + state: PrivacySafetyState, + onChange: (PrivacySafetyState) -> Unit, + onSave: () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + topBar = { + TopAppBar( + title = { Text("Privacy & Safety") }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = DrexelBlue, + titleContentColor = Color.White, + navigationIconContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = modifier + .padding(padding) + .fillMaxSize() + .background(Color(0xFFF5F5F7)) + .verticalScroll(rememberScrollState()) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + + Text( + "Profile Visibility", + style = MaterialTheme.typography.titleMedium, + color = DrexelBlue + ) + + PrivacySwitchRow( + title = "Show profile publicly", + subtitle = "Allow other students to view your basic profile", + checked = state.showProfilePublicly, + onCheckedChange = { + onChange(state.copy(showProfilePublicly = it)) + } + ) + + PrivacySwitchRow( + title = "Allow messages from non-contacts", + subtitle = "Let people who haven't ridden with you send requests", + checked = state.allowMessagesFromNonContacts, + onCheckedChange = { + onChange(state.copy(allowMessagesFromNonContacts = it)) + } + ) + + Spacer(Modifier.height(12.dp)) + + Text( + "Sharing", + style = MaterialTheme.typography.titleMedium, + color = DrexelBlue + ) + + PrivacySwitchRow( + title = "Share trip history with friends", + subtitle = "Friends can see your past rides and eco impact", + checked = state.shareTripHistoryWithFriends, + onCheckedChange = { + onChange(state.copy(shareTripHistoryWithFriends = it)) + } + ) + + Spacer(Modifier.height(12.dp)) + + Text( + "Security", + style = MaterialTheme.typography.titleMedium, + color = DrexelBlue + ) + + PrivacySwitchRow( + title = "Two-factor authentication", + subtitle = "Add an extra step when logging into your account", + checked = state.twoFactorEnabled, + onCheckedChange = { + onChange(state.copy(twoFactorEnabled = it)) + } + ) + + Spacer(Modifier.height(24.dp)) + + Button( + onClick = onSave, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ) + ) { + Text("Save Privacy Settings") + } + } + } +} + +@Composable +private fun PrivacySwitchRow( + title: String, + subtitle: String, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + val switchColors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = DrexelBlue, + uncheckedThumbColor = Color.White, + uncheckedTrackColor = Color.LightGray, + checkedBorderColor = DrexelBlue, + uncheckedBorderColor = Color.LightGray + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f)) { + Text(title, style = MaterialTheme.typography.bodyLarge) + Text(subtitle, style = MaterialTheme.typography.bodySmall, color = Color.Gray) + } + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + colors = switchColors + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/.gitkeep b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/AvailableOffersScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/AvailableOffersScreen.kt new file mode 100644 index 0000000..58d8143 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/AvailableOffersScreen.kt @@ -0,0 +1,352 @@ +package com.example.demo.feature.rides + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold +import com.example.demo.ui.theme.FieldBackground +import com.example.demo.ui.theme.HintGrey + +@Composable +fun AvailableOfferScreen( + modifier: Modifier = Modifier, + onBack: () -> Unit = {}, + onPublish: () -> Unit = {} +) { + var origin by remember { mutableStateOf("University Crossings") } + var destination by remember { mutableStateOf("Korman Center") } + var departure by remember { mutableStateOf("") } + + var selectedVehicle by remember { mutableStateOf("Tesla Model Y (Blue)") } + val vehicleOptions = listOf( + "Tesla Model Y (Blue)", + "Honda Civic (Black)", + "Toyota Camry (Silver)" + ) + + var selectedSeats by remember { mutableStateOf("2 Seats") } + val seatOptions = listOf("1 Seat", "2 Seats", "3 Seats", "4 Seats") + + var basePrice by remember { mutableStateOf("5.00") } + var perMilePrice by remember { mutableStateOf("0.50") } + + var scrollState = rememberScrollState() + + Column( + modifier = modifier + .fillMaxSize() + .background(FieldBackground) + .verticalScroll(scrollState) + ) { + // ---------- HEADER ---------- + Box( + modifier = Modifier + .fillMaxWidth() + .background(DrexelBlue) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 24.dp) + ) { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "Back", + tint = Color.White + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Offer a Ride", + style = MaterialTheme.typography.headlineSmall, + color = Color.White, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = "Share your journey details", + style = MaterialTheme.typography.bodyMedium, + color = Color.White.copy(alpha = 0.8f) + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // ---------- MAIN CARD ---------- + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(24.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Origin + LabeledIconField( + label = "Origin Location", + value = origin, + onValueChange = { origin = it }, + icon = Icons.Default.Place, + placeholder = "University Crossings" + ) + + // Destination + LabeledIconField( + label = "Destination", + value = destination, + onValueChange = { destination = it }, + icon = Icons.Default.Place, + placeholder = "Korman Center" + ) + + // Departure Time + LabeledIconField( + label = "Departure Time", + value = departure, + onValueChange = { departure = it }, + icon = Icons.Default.AccessTime, + placeholder = "mm/dd/yyyy --:-- --" + ) + + // Vehicle dropdown + LabeledDropdownField( + label = "Choose Vehicle", + value = selectedVehicle, + onValueChange = { selectedVehicle = it }, + options = vehicleOptions, + leadingIcon = Icons.Default.DirectionsCar + ) + + // Seats dropdown + LabeledDropdownField( + label = "Available Seats", + value = selectedSeats, + onValueChange = { selectedSeats = it }, + options = seatOptions, + leadingIcon = Icons.Default.People + ) + + // Prices row + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.fillMaxWidth() + ) { + LabeledPriceField( + label = "Base Price", + value = basePrice, + onValueChange = { basePrice = it }, + modifier = Modifier.weight(1f) + ) + + LabeledPriceField( + label = "Per Mile", + value = perMilePrice, + onValueChange = { perMilePrice = it }, + modifier = Modifier.weight(1f) + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Publish button + Button( + onClick = onPublish, + modifier = Modifier + .fillMaxWidth() + .height(52.dp), + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = "Publish Offer", + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +/* ---------- Reusable components used on the screen ---------- */ + +@Composable +private fun FieldLabel(text: String) { + Text( + text = text, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = DrexelBlue, + modifier = Modifier.padding(bottom = 4.dp) + ) +} + +@Composable +private fun LabeledIconField( + label: String, + value: String, + onValueChange: (String) -> Unit, + icon: androidx.compose.ui.graphics.vector.ImageVector, + placeholder: String +) { + Column { + FieldLabel(label) + + OutlinedTextField( + value = value, + onValueChange = onValueChange, + modifier = Modifier.fillMaxWidth(), + leadingIcon = { + Icon( + imageVector = icon, + contentDescription = null, + tint = HintGrey + ) + }, + placeholder = { Text(text = placeholder, color = HintGrey) }, + shape = RoundedCornerShape(10.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = Color(0xFFF5F6F8), + unfocusedContainerColor = Color(0xFFF5F6F8), + disabledContainerColor = Color(0xFFF5F6F8), + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + focusedTextColor = DrexelBlue, + unfocusedTextColor = DrexelBlue + ), + singleLine = true + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun LabeledDropdownField( + label: String, + value: String, + onValueChange: (String) -> Unit, + options: List, + leadingIcon: androidx.compose.ui.graphics.vector.ImageVector +) { + var expanded by remember { mutableStateOf(false) } + + Column { + FieldLabel(label) + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded } + ) { + OutlinedTextField( + value = value, + onValueChange = {}, + readOnly = true, + modifier = Modifier + .menuAnchor() + .fillMaxWidth(), + leadingIcon = { + Icon( + imageVector = leadingIcon, + contentDescription = null, + tint = HintGrey + ) + }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) + }, + shape = RoundedCornerShape(10.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = Color(0xFFF5F6F8), + unfocusedContainerColor = Color(0xFFF5F6F8), + disabledContainerColor = Color(0xFFF5F6F8), + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + focusedTextColor = DrexelBlue, + unfocusedTextColor = DrexelBlue + ), + singleLine = true + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + options.forEach { option -> + DropdownMenuItem( + text = { Text(option) }, + onClick = { + onValueChange(option) + expanded = false + } + ) + } + } + } + } +} + +@Composable +private fun LabeledPriceField( + label: String, + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier = modifier) { + FieldLabel(label) + + OutlinedTextField( + value = value, + onValueChange = onValueChange, + modifier = Modifier.fillMaxWidth(), + leadingIcon = { + Text( + text = "$", + color = HintGrey, + fontWeight = FontWeight.SemiBold + ) + }, + shape = RoundedCornerShape(10.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = Color(0xFFF5F6F8), + unfocusedContainerColor = Color(0xFFF5F6F8), + disabledContainerColor = Color(0xFFF5F6F8), + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + focusedTextColor = DrexelBlue, + unfocusedTextColor = DrexelBlue + ), + singleLine = true + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/AvailableRidesScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/AvailableRidesScreen.kt new file mode 100644 index 0000000..b43a64c --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/AvailableRidesScreen.kt @@ -0,0 +1,224 @@ +package com.example.demo.feature.rides + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.DrexelGold +import com.example.demo.ui.theme.HintGrey +import com.example.demo.ui.theme.FieldBackground +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.Icon + +import androidx.compose.runtime.* +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import kotlinx.coroutines.launch +import com.example.demo.feature.db.RideOffer +import com.example.demo.feature.db.RideRepository + +@Composable +fun AvailableRidesScreen( + modifier: Modifier = Modifier, + onBack: () -> Unit = {}, + rideRepository: RideRepository +) { + val coroutineScope = rememberCoroutineScope() + + var isLoading by remember { mutableStateOf(true) } + var errorMessage by remember { mutableStateOf(null) } + var rideOffers by remember { mutableStateOf>(emptyList()) } + + // load from DB on first composition + LaunchedEffect(Unit) { + try { + isLoading = true + errorMessage = null + rideOffers = rideRepository.getOpenRideOffers() + } catch (e: Exception) { + errorMessage = e.message ?: "Failed to load rides" + } finally { + isLoading = false + } + } + + // simple “best / other” split (first 3 vs rest) + val bestMatches: List = + if (rideOffers.size > 3) rideOffers.take(3) else rideOffers + val otherMatches: List = + if (rideOffers.size > 3) rideOffers.drop(3) else emptyList() + + Column( + modifier = modifier + .fillMaxSize() + .background(FieldBackground) + ) { + // your existing header / back button code stays the same + + if (isLoading) { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = DrexelBlue) + } + return@Column + } + + if (errorMessage != null) { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = errorMessage!!, + color = Color.Red + ) + } + return@Column + } + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + if (bestMatches.isNotEmpty()) { + item { + SectionBadge( + text = "Best Matches", + color = DrexelGold, + textColor = DrexelBlue + ) + Spacer(modifier = Modifier.height(8.dp)) + } + + items(bestMatches) { offer -> + RideCard(offer = offer) + } + } + + if (otherMatches.isNotEmpty()) { + item { + Spacer(modifier = Modifier.height(16.dp)) + SectionBadge( + text = "Other Matches", + color = Color.White, + textColor = HintGrey, + isBordered = true + ) + Spacer(modifier = Modifier.height(8.dp)) + } + + items(otherMatches) { offer -> + RideCard(offer = offer) + } + } + } + } +} + + + +@Composable +fun RideCard(offer: RideOffer) { + Card( + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + // You don't currently join driver name / rating / car model in RideOffer, + // so we’ll show what we *do* have and use placeholders where needed. + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column { + Text( + text = "Driver #${offer.driverId}", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = DrexelBlue + ) + Text( + text = "Vehicle #${offer.vehicleId}", + style = MaterialTheme.typography.bodySmall, + color = HintGrey + ) + } + + Column(horizontalAlignment = Alignment.End) { + Text( + text = "$${"%.2f".format(offer.priceBase)}", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = DrexelBlue + ) + Text( + text = "${offer.seatsAvailable} seats", + style = MaterialTheme.typography.bodySmall, + color = HintGrey + ) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = "${offer.fromName} → ${offer.toName}", + style = MaterialTheme.typography.bodyMedium, + color = DrexelBlue + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = offer.departAt, + style = MaterialTheme.typography.bodyMedium, + color = DrexelBlue + ) + } + } +} + +@Composable +fun SectionBadge( + text: String, + color: Color, + textColor: Color, + isBordered: Boolean = false +) { + Surface( + color = color, + shape = RoundedCornerShape(50), + border = if (isBordered) null else null, + modifier = Modifier.wrapContentSize() + ) { + Text( + text = text, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold, + color = textColor + ) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/FieldInputs.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/FieldInputs.kt new file mode 100644 index 0000000..baf2f79 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/FieldInputs.kt @@ -0,0 +1,80 @@ +package com.example.demo.feature.rides + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.example.demo.ui.theme.DrexelBlue +import com.example.demo.ui.theme.HintGrey + +@Composable +fun RideInput( + label: String, + icon: ImageVector, + placeholder: String, + modifier: Modifier = Modifier +) { + var textState by remember { mutableStateOf("") } + val fieldColor = Color(0xFFF3F4F6) + + Column(modifier = modifier) { + Text( + text = label, + style = MaterialTheme.typography.titleMedium, + color = DrexelBlue, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(bottom = 4.dp) + ) + + OutlinedTextField( + value = textState, + onValueChange = { textState = it }, + modifier = Modifier.fillMaxWidth(), + + placeholder = { + Text( + text = placeholder, + color = HintGrey + ) + }, + + leadingIcon = { + Icon( + imageVector = icon, + contentDescription = null, + tint = HintGrey, + modifier = Modifier.size(28.dp) + ) + }, + + shape = RoundedCornerShape(8.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = fieldColor, + unfocusedContainerColor = fieldColor, + disabledContainerColor = fieldColor, + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + focusedTextColor = DrexelBlue, + unfocusedTextColor = DrexelBlue + ), + singleLine = true, + ) + } +} \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/FindRideScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/FindRideScreen.kt new file mode 100644 index 0000000..83313d1 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/FindRideScreen.kt @@ -0,0 +1,144 @@ +package com.example.demo.ui.theme + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.demo.feature.rides.RideInput +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.Icon + +@Composable +fun FindRideScreen() { + val bgColor = Color(0xFFF3F4F6) + val scrollState = rememberScrollState() + + Box( + modifier = Modifier + .fillMaxSize() + .background(bgColor) + ) { + // 1. Header Section + Column( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .background(DrexelBlue) + .padding(24.dp) + ) { + IconButton(onClick = { /* TODO: handle back nav */ }) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Find a Ride", + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Enter your trip details", + style = MaterialTheme.typography.titleLarge, + color = HintGrey + ) + } + + // 2. The Floating Card + Card( + modifier = Modifier + .fillMaxWidth() + .padding(top = 175.dp, start = 16.dp, end = 16.dp, bottom = 16.dp) + .verticalScroll(scrollState), // Make form scrollable on small screens + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Locations + RideInput( + label = "Pickup Location", + icon = Icons.Filled.LocationOn, + placeholder = "30th Street Station" + ) + RideInput( + label = "Drop-off Location", + icon = Icons.Filled.LocationOn, + placeholder = "Cira Green" + ) + + // Date & Time + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + RideInput( + label = "Date", + icon = Icons.Filled.CalendarMonth, + placeholder = "mm/dd/yyyy", + modifier = Modifier.weight(1f) + ) + RideInput( + label = "Time", + icon = Icons.Filled.AccessTime, + placeholder = "--:-- --", + modifier = Modifier.weight(1f) + ) + } + + // Seats & Price + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + RideInput( + label = "Seats Needed", + icon = Icons.Filled.Group, + placeholder = "2", + modifier = Modifier.weight(1f) + ) + RideInput( + label = "Max Price", + icon = Icons.Filled.AttachMoney, + placeholder = "20", + modifier = Modifier.weight(1f) + ) + } + + Spacer(modifier = Modifier.height(4.dp)) + + // Search Button + Button( + onClick = { /* TODO: trigger search */ }, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = "Search for Rides", + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) + } + } + } + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/OfferRideScreen.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/OfferRideScreen.kt new file mode 100644 index 0000000..815342f --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/OfferRideScreen.kt @@ -0,0 +1,147 @@ +package com.example.demo.ui.theme + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.demo.feature.rides.RideInput + +@Composable +fun OfferRideScreen( + modifier: Modifier = Modifier, + onBack: () -> Unit = {}, + onPublish: () -> Unit = {} +) { + val bgColor = Color(0xFFF3F4F6) + + Column( + modifier = modifier + .fillMaxSize() + .background(bgColor) + ) { + // ---------- HEADER (smaller) ---------- + Column( + modifier = Modifier + .fillMaxWidth() + .background(DrexelBlue) + .padding(horizontal = 20.dp, vertical = 16.dp) + ) { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White + ) + } + + Text( + text = "Offer a Ride", + style = MaterialTheme.typography.titleMedium, // smaller + color = Color.White + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = "Share your journey details", + style = MaterialTheme.typography.bodySmall, + color = Color.White.copy(alpha = 0.8f) + ) + } + + Spacer(modifier = Modifier.height(12.dp)) + + // ---------- CARD ---------- + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) // tighter spacing + ) { + // Origin & Destination + RideInput( + label = "Origin", + icon = Icons.Filled.LocationOn, + placeholder = "University Crossings" + ) + RideInput( + label = "Destination", + icon = Icons.Filled.LocationOn, + placeholder = "Korman Center" + ) + + // Time + Seats (2 per row to save height) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RideInput( + label = "Time", + icon = Icons.Filled.AccessTime, + placeholder = "mm/dd hh:mm", + modifier = Modifier.weight(1f) + ) + RideInput( + label = "Seats", + icon = Icons.Filled.Group, + placeholder = "2", + modifier = Modifier.weight(1f) + ) + } + + // Vehicle + Price (2 per row) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RideInput( + label = "Vehicle", + icon = Icons.Filled.DirectionsCar, + placeholder = "Model Y", + modifier = Modifier.weight(1f) + ) + RideInput( + label = "Price", + icon = Icons.Filled.AttachMoney, + placeholder = "5.00", + modifier = Modifier.weight(1f) + ) + } + + Spacer(modifier = Modifier.height(4.dp)) + + // ---------- BUTTON (always visible) ---------- + Button( + onClick = onPublish, + modifier = Modifier + .fillMaxWidth() + .height(44.dp), // a bit shorter + colors = ButtonDefaults.buttonColors( + containerColor = DrexelGold, + contentColor = DrexelBlue + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = "Publish Offer", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp // smaller + ) + } + } + } + + // tiny spacer so it doesn’t touch bottom nav + Spacer(modifier = Modifier.height(8.dp)) + } +} diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/RideOption.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/RideOption.kt new file mode 100644 index 0000000..f50636d --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/feature/rides/RideOption.kt @@ -0,0 +1,14 @@ +package com.example.demo.feature.rides + +// Simple data class to hold ride info +data class RideOption( + val driverName: String, + val rating: Double, + val price: Double, + val seats: Int, + val carModel: String, + val pickup: String, + val dropoff: String, + val time: String, + val isBestMatch: Boolean = false +) \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/ui/theme/Color.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/ui/theme/Color.kt new file mode 100644 index 0000000..80727f1 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/ui/theme/Color.kt @@ -0,0 +1,9 @@ +package com.example.demo.ui.theme + +import androidx.compose.ui.graphics.Color + +// Drexel University Colors +val DrexelBlue = Color(0xFF07294D) // #07294D +val DrexelGold = Color(0xFFFFC600) // #FFC600 +val FieldBackground = Color(0xFF073B70) +val HintGrey = Color(0xFF9AA4B2) \ No newline at end of file diff --git a/app/composeApp/src/commonMain/kotlin/com/example/demo/ui/theme/Theme.kt b/app/composeApp/src/commonMain/kotlin/com/example/demo/ui/theme/Theme.kt new file mode 100644 index 0000000..dca8c74 --- /dev/null +++ b/app/composeApp/src/commonMain/kotlin/com/example/demo/ui/theme/Theme.kt @@ -0,0 +1,23 @@ +package com.example.demo.ui.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +@Composable +fun FindMyRideTheme(content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = lightColorScheme(), + typography = MaterialTheme.typography, + content = content, + ) +} + +@Composable +fun lightColorScheme() = androidx.compose.material3.lightColorScheme( + primary = DrexelGold, + onPrimary = DrexelBlue, + background = DrexelBlue, + onBackground = Color.White, +) \ No newline at end of file diff --git a/app/composeApp/src/commonTest/kotlin/com/example/demo/ComposeAppCommonTest.kt b/app/composeApp/src/commonTest/kotlin/com/example/demo/ComposeAppCommonTest.kt new file mode 100644 index 0000000..6cd3a0a --- /dev/null +++ b/app/composeApp/src/commonTest/kotlin/com/example/demo/ComposeAppCommonTest.kt @@ -0,0 +1,12 @@ +package com.example.app + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ComposeAppCommonTest { + + @Test + fun example() { + assertEquals(3, 1 + 2) + } +} \ No newline at end of file diff --git a/app/composeApp/src/iosMain/kotlin/com/example/demo/MainViewController.kt b/app/composeApp/src/iosMain/kotlin/com/example/demo/MainViewController.kt new file mode 100644 index 0000000..e65b194 --- /dev/null +++ b/app/composeApp/src/iosMain/kotlin/com/example/demo/MainViewController.kt @@ -0,0 +1,5 @@ +package com.example.app + +import androidx.compose.ui.window.ComposeUIViewController + +fun MainViewController() = ComposeUIViewController { App() } \ No newline at end of file diff --git a/app/composeApp/src/iosMain/kotlin/com/example/demo/Platform.ios.kt b/app/composeApp/src/iosMain/kotlin/com/example/demo/Platform.ios.kt new file mode 100644 index 0000000..ab94116 --- /dev/null +++ b/app/composeApp/src/iosMain/kotlin/com/example/demo/Platform.ios.kt @@ -0,0 +1,9 @@ +package com.example.app + +import platform.UIKit.UIDevice + +class IOSPlatform : Platform { + override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion +} + +actual fun getPlatform(): Platform = IOSPlatform() \ No newline at end of file diff --git a/app/composeApp/src/jsMain/kotlin/com/example/demo/Platform.js.kt b/app/composeApp/src/jsMain/kotlin/com/example/demo/Platform.js.kt new file mode 100644 index 0000000..72a25f9 --- /dev/null +++ b/app/composeApp/src/jsMain/kotlin/com/example/demo/Platform.js.kt @@ -0,0 +1,7 @@ +package com.example.app + +class JsPlatform : Platform { + override val name: String = "Web with Kotlin/JS" +} + +actual fun getPlatform(): Platform = JsPlatform() \ No newline at end of file diff --git a/app/composeApp/src/jvmMain/kotlin/com/example/demo/Platform.jvm.kt b/app/composeApp/src/jvmMain/kotlin/com/example/demo/Platform.jvm.kt new file mode 100644 index 0000000..52ce616 --- /dev/null +++ b/app/composeApp/src/jvmMain/kotlin/com/example/demo/Platform.jvm.kt @@ -0,0 +1,7 @@ +package com.example.app + +class JVMPlatform : Platform { + override val name: String = "Java ${System.getProperty("java.version")}" +} + +actual fun getPlatform(): Platform = JVMPlatform() \ No newline at end of file diff --git a/app/composeApp/src/jvmMain/kotlin/com/example/demo/main.kt b/app/composeApp/src/jvmMain/kotlin/com/example/demo/main.kt new file mode 100644 index 0000000..1c17f1f --- /dev/null +++ b/app/composeApp/src/jvmMain/kotlin/com/example/demo/main.kt @@ -0,0 +1,13 @@ +package com.example.app + +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "app", + ) { + App() + } +} \ No newline at end of file diff --git a/app/composeApp/src/wasmJsMain/kotlin/com/example/demo/Platform.wasmJs.kt b/app/composeApp/src/wasmJsMain/kotlin/com/example/demo/Platform.wasmJs.kt new file mode 100644 index 0000000..e95802e --- /dev/null +++ b/app/composeApp/src/wasmJsMain/kotlin/com/example/demo/Platform.wasmJs.kt @@ -0,0 +1,7 @@ +package com.example.app + +class WasmPlatform : Platform { + override val name: String = "Web with Kotlin/Wasm" +} + +actual fun getPlatform(): Platform = WasmPlatform() \ No newline at end of file diff --git a/app/composeApp/src/webMain/kotlin/com/example/demo/main.kt b/app/composeApp/src/webMain/kotlin/com/example/demo/main.kt new file mode 100644 index 0000000..756528d --- /dev/null +++ b/app/composeApp/src/webMain/kotlin/com/example/demo/main.kt @@ -0,0 +1,11 @@ +package com.example.app + +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.ComposeViewport + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + ComposeViewport { + App() + } +} \ No newline at end of file diff --git a/app/composeApp/src/webMain/resources/index.html b/app/composeApp/src/webMain/resources/index.html new file mode 100644 index 0000000..1b4370f --- /dev/null +++ b/app/composeApp/src/webMain/resources/index.html @@ -0,0 +1,12 @@ + + + + + + app + + + + + + \ No newline at end of file diff --git a/app/composeApp/src/webMain/resources/styles.css b/app/composeApp/src/webMain/resources/styles.css new file mode 100644 index 0000000..0549b10 --- /dev/null +++ b/app/composeApp/src/webMain/resources/styles.css @@ -0,0 +1,7 @@ +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; +} \ No newline at end of file diff --git a/app/composeApp/webpack.config.d/watch.js b/app/composeApp/webpack.config.d/watch.js new file mode 100644 index 0000000..04713d2 --- /dev/null +++ b/app/composeApp/webpack.config.d/watch.js @@ -0,0 +1,21 @@ +/* + * Temporary workaround for [KT-80582](https://youtrack.jetbrains.com/issue/KT-80582) + * + * This file should be safe to be removed once the ticket is closed and the project is updated to Kotlin version which solves that issue. + */ +config.watchOptions = config.watchOptions || { + ignored: ["**/*.kt", "**/node_modules"] +} + +if (config.devServer) { + config.devServer.static = config.devServer.static.map(file => { + if (typeof file === "string") { + return { + directory: file, + watch: false, + } + } else { + return file + } + }) +} \ No newline at end of file diff --git a/app/gradle.properties b/app/gradle.properties new file mode 100644 index 0000000..ff91b57 --- /dev/null +++ b/app/gradle.properties @@ -0,0 +1,12 @@ +#Kotlin +kotlin.code.style=official +kotlin.daemon.jvmargs=-Xmx3072M +#Gradle +org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 +org.gradle.configuration-cache=true +org.gradle.caching=true +#Android +android.nonTransitiveRClass=true +android.useAndroidX=true +# Force Gradle to use JDK 21 (not JBR or a JRE) +org.gradle.java.home=C\:\\Program Files\\Java\\jdk-21 diff --git a/app/gradle/libs.versions.toml b/app/gradle/libs.versions.toml new file mode 100644 index 0000000..cac3f9e --- /dev/null +++ b/app/gradle/libs.versions.toml @@ -0,0 +1,37 @@ +[versions] +agp = "8.11.2" +android-compileSdk = "36" +android-minSdk = "24" +android-targetSdk = "36" +androidx-activity = "1.11.0" +androidx-appcompat = "1.7.1" +androidx-core = "1.17.0" +androidx-espresso = "3.7.0" +androidx-lifecycle = "2.9.5" +androidx-testExt = "1.3.0" +composeHotReload = "1.0.0-rc02" +composeMultiplatform = "1.9.1" +junit = "4.13.2" +kotlin = "2.2.20" +kotlinx-coroutines = "1.10.2" + +[libraries] +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +junit = { module = "junit:junit", version.ref = "junit" } +androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" } +androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } + +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } +androidLibrary = { id = "com.android.library", version.ref = "agp" } +composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" } +composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } +composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file diff --git a/app/gradle/wrapper/gradle-wrapper.jar b/app/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/app/gradle/wrapper/gradle-wrapper.properties b/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d4081da --- /dev/null +++ b/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/app/gradlew b/app/gradlew new file mode 100644 index 0000000..23d15a9 --- /dev/null +++ b/app/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/app/gradlew.bat b/app/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/app/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/app/iosApp/Configuration/Config.xcconfig b/app/iosApp/Configuration/Config.xcconfig new file mode 100644 index 0000000..8fdfea9 --- /dev/null +++ b/app/iosApp/Configuration/Config.xcconfig @@ -0,0 +1,7 @@ +TEAM_ID= + +PRODUCT_NAME=app +PRODUCT_BUNDLE_IDENTIFIER=com.example.app.app$(TEAM_ID) + +CURRENT_PROJECT_VERSION=1 +MARKETING_VERSION=1.0 \ No newline at end of file diff --git a/app/iosApp/iosApp.xcodeproj/project.pbxproj b/app/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..465beec --- /dev/null +++ b/app/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,373 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + 7A380F3429DBE0DB29C95EE4 /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 1092A42DE9B4AD61F5B2C54C /* Exceptions for "iosApp" folder in "iosApp" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = CD1B80002B634D87B36F80DB /* iosApp */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 10A6EC1D812604177B0009FB /* iosApp */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 1092A42DE9B4AD61F5B2C54C /* Exceptions for "iosApp" folder in "iosApp" target */, + ); + path = iosApp; + sourceTree = ""; + }; + DB82D36C9EA4FA1259DEEAF7 /* Configuration */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Configuration; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + D165D7170AC0D4B9D475265B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4AC475FF37A34EEADD7B2792 = { + isa = PBXGroup; + children = ( + DB82D36C9EA4FA1259DEEAF7 /* Configuration */, + 10A6EC1D812604177B0009FB /* iosApp */, + D4720B5C452919CC0AD4747C /* Products */, + ); + sourceTree = ""; + }; + D4720B5C452919CC0AD4747C /* Products */ = { + isa = PBXGroup; + children = ( + 7A380F3429DBE0DB29C95EE4 /* app.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CD1B80002B634D87B36F80DB /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5AAE3A3C287BA96EBB9E8785 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + 6B8B1E6D425DF20B442A1F17 /* Compile Kotlin Framework */, + 0A6ABA9517D1D1FEC2A70CFC /* Sources */, + D165D7170AC0D4B9D475265B /* Frameworks */, + 50197EB1FF0366B234823AC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 10A6EC1D812604177B0009FB /* iosApp */, + ); + name = iosApp; + packageProductDependencies = ( + ); + productName = iosApp; + productReference = 7A380F3429DBE0DB29C95EE4 /* app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F586D3F54435FE2812B6E7DD /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1620; + TargetAttributes = { + CD1B80002B634D87B36F80DB = { + CreatedOnToolsVersion = 16.2; + }; + }; + }; + buildConfigurationList = F05A1F281F222D43AFA07DFB /* Build configuration list for PBXProject "iosApp" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4AC475FF37A34EEADD7B2792; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = D4720B5C452919CC0AD4747C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CD1B80002B634D87B36F80DB /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 50197EB1FF0366B234823AC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6B8B1E6D425DF20B442A1F17 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0A6ABA9517D1D1FEC2A70CFC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + B107BF0BF662703690766292 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = DB82D36C9EA4FA1259DEEAF7 /* Configuration */; + baseConfigurationReferenceRelativePath = Config.xcconfig; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 406B74BC13444CE80EBB1DA4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = DB82D36C9EA4FA1259DEEAF7 /* Configuration */; + baseConfigurationReferenceRelativePath = Config.xcconfig; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + F3177536EF5792537521DA90 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 20CC850455FA1663AF73B642 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F05A1F281F222D43AFA07DFB /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B107BF0BF662703690766292 /* Debug */, + 406B74BC13444CE80EBB1DA4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5AAE3A3C287BA96EBB9E8785 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F3177536EF5792537521DA90 /* Debug */, + 20CC850455FA1663AF73B642 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F586D3F54435FE2812B6E7DD /* Project object */; +} \ No newline at end of file diff --git a/app/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/app/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/app/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/app/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/app/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..0afb3cf --- /dev/null +++ b/app/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors": [ + { + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..fbe7ce1 --- /dev/null +++ b/app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images": [ + { + "filename": "app-icon-1024.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "tinted" + } + ], + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 0000000..53fc536 Binary files /dev/null and b/app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png differ diff --git a/app/iosApp/iosApp/Assets.xcassets/Contents.json b/app/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000..74d6a72 --- /dev/null +++ b/app/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/app/iosApp/iosApp/ContentView.swift b/app/iosApp/iosApp/ContentView.swift new file mode 100644 index 0000000..c765ff2 --- /dev/null +++ b/app/iosApp/iosApp/ContentView.swift @@ -0,0 +1,21 @@ +import UIKit +import SwiftUI +import ComposeApp + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea() + } +} + + + diff --git a/app/iosApp/iosApp/Info.plist b/app/iosApp/iosApp/Info.plist new file mode 100644 index 0000000..11845e1 --- /dev/null +++ b/app/iosApp/iosApp/Info.plist @@ -0,0 +1,8 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/app/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/app/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..74d6a72 --- /dev/null +++ b/app/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/app/iosApp/iosApp/iOSApp.swift b/app/iosApp/iosApp/iOSApp.swift new file mode 100644 index 0000000..d83dca6 --- /dev/null +++ b/app/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} \ No newline at end of file diff --git a/app/kotlin-js-store/wasm/yarn.lock b/app/kotlin-js-store/wasm/yarn.lock new file mode 100644 index 0000000..5f4567d --- /dev/null +++ b/app/kotlin-js-store/wasm/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== diff --git a/app/settings.gradle.kts b/app/settings.gradle.kts new file mode 100644 index 0000000..132559c --- /dev/null +++ b/app/settings.gradle.kts @@ -0,0 +1,36 @@ +rootProject.name = "app" +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +pluginManagement { + repositories { + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositories { + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } + mavenCentral() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +include(":composeApp") +project(":composeApp").projectDir = file("composeApp") diff --git a/database/export_all.sql b/database/export_all.sql index f994ec2..ca4b49e 100644 --- a/database/export_all.sql +++ b/database/export_all.sql @@ -22,4 +22,10 @@ SELECT * FROM "RIDE_MATCH" ORDER BY match_id; .output exports/RATING.csv SELECT * FROM "RATING" ORDER BY rating_id; -.output stdout +.output exports/MESSAGE_THREAD.csv +SELECT * FROM "MESSAGE_THREAD" ORDER BY thread_id; + +.output exports/MESSAGE.csv +SELECT * FROM "MESSAGE" ORDER BY message_id; + +.output stdout \ No newline at end of file diff --git a/database/findmyride.db b/database/findmyride.db new file mode 100644 index 0000000..afaffe5 Binary files /dev/null and b/database/findmyride.db differ diff --git a/database/generate_database.py b/database/generate_database.py new file mode 100644 index 0000000..8424809 --- /dev/null +++ b/database/generate_database.py @@ -0,0 +1,542 @@ +#!/usr/bin/env python3 +""" +generate_database.py +1. Generates database/schema.sql and database/populate.sql. +2. Creates 'findmyride.db' from scratch in the database folder. +3. Executes the schema and population scripts against the database. +4. Copies the finished database to ./app/composeApp/src/androidMain/assets/findmyride.db so that it can be used by the application +""" + +import os +import random +import datetime +import sqlite3 +import shutil +from pathlib import Path + +random.seed(42) + +# --- CONFIGURATION --- + +# SQL Output Paths +OUT_DIR = Path("database") +OUT_DIR.mkdir(parents=True, exist_ok=True) +SCHEMA_PATH = OUT_DIR / "schema.sql" +POPULATE_PATH = OUT_DIR / "populate.sql" + +# DB Path +DB_FILENAME = OUT_DIR / "findmyride.db" +CURRENT_DB_PATH = Path(DB_FILENAME) + +# Target Asset Path +ASSET_DIR = Path("./app/composeApp/src/androidMain/assets") +ASSET_DB_PATH = ASSET_DIR / "findmyride.db" + +# --- SCHEMA DEFINITION --- +SCHEMA_SQL = """ +PRAGMA foreign_keys = ON; + +CREATE TABLE "USER" ( + user_id INTEGER PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + username TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + phone_number TEXT, + role TEXT NOT NULL CHECK (role IN ('rider','driver','both')), + rating_avg REAL DEFAULT 2.5 CHECK (rating_avg BETWEEN 0 AND 5), + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE "VEHICLE" ( + vehicle_id INTEGER PRIMARY KEY, + owner_user_id INTEGER NOT NULL, + make TEXT NOT NULL, + model TEXT NOT NULL, + color TEXT, + plate TEXT NOT NULL UNIQUE, + seats_total INTEGER NOT NULL CHECK (seats_total BETWEEN 1 AND 8), + year INTEGER CHECK (year BETWEEN 1900 AND 2100), + fun_fact TEXT, + FOREIGN KEY (owner_user_id) REFERENCES "USER"(user_id) +); + +CREATE TABLE "LOCATION" ( + location_id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + address TEXT NOT NULL +); + +CREATE TABLE "RIDE_OFFER" ( + offer_id INTEGER PRIMARY KEY, + driver_id INTEGER NOT NULL, + vehicle_id INTEGER NOT NULL, + original_location_id INTEGER NOT NULL, + dest_location_id INTEGER NOT NULL, + depart_at TEXT NOT NULL, + seats_available INTEGER NOT NULL CHECK (seats_available >= 0), + price_base NUMERIC(10,2) NOT NULL CHECK (price_base >= 0), + price_per_mile NUMERIC(10,2) NOT NULL CHECK (price_per_mile >= 0), + status TEXT NOT NULL CHECK (status IN ('open','closed','cancelled','full')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (driver_id) REFERENCES "USER"(user_id), + FOREIGN KEY (vehicle_id) REFERENCES "VEHICLE"(vehicle_id), + FOREIGN KEY (original_location_id) REFERENCES "LOCATION"(location_id), + FOREIGN KEY (dest_location_id) REFERENCES "LOCATION"(location_id) +); + +CREATE TABLE "RIDE_REQUEST" ( + request_id INTEGER PRIMARY KEY, + rider_id INTEGER NOT NULL, + pickup_location_id INTEGER NOT NULL, + dropoff_location_id INTEGER NOT NULL, + earliest_pickup TEXT NOT NULL, + latest_pickup TEXT, + seats_needed INTEGER NOT NULL CHECK (seats_needed >= 1), + status TEXT NOT NULL CHECK (status IN ('open','matched','cancelled','expired')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (rider_id) REFERENCES "USER"(user_id), + FOREIGN KEY (pickup_location_id) REFERENCES "LOCATION"(location_id), + FOREIGN KEY (dropoff_location_id) REFERENCES "LOCATION"(location_id), + CHECK (latest_pickup IS NULL OR latest_pickup >= earliest_pickup) +); + +CREATE TABLE "RIDE_MATCH" ( + match_id INTEGER PRIMARY KEY, + request_id INTEGER NOT NULL, + offer_id INTEGER NOT NULL, + seats_booked INTEGER NOT NULL CHECK (seats_booked >= 1), + price_total NUMERIC(10,2) NOT NULL CHECK (price_total >= 0), + state TEXT NOT NULL CHECK (state IN ('pending','confirmed','completed','cancelled','no_show')), + matched_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (request_id) REFERENCES "RIDE_REQUEST"(request_id), + FOREIGN KEY (offer_id) REFERENCES "RIDE_OFFER"(offer_id), + UNIQUE (request_id, offer_id) +); + +CREATE TABLE "RATING" ( + rating_id INTEGER PRIMARY KEY, + match_id INTEGER NOT NULL, + from_user_id INTEGER NOT NULL, + to_user_id INTEGER NOT NULL, + stars INTEGER NOT NULL CHECK (stars BETWEEN 1 AND 5), + comment TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (match_id) REFERENCES "RIDE_MATCH"(match_id), + FOREIGN KEY (from_user_id) REFERENCES "USER"(user_id), + FOREIGN KEY (to_user_id) REFERENCES "USER"(user_id), + CHECK (from_user_id <> to_user_id), + UNIQUE (match_id, from_user_id, to_user_id) +); + +CREATE TABLE "MESSAGE_THREAD" ( + thread_id INTEGER PRIMARY KEY, + user1_id INTEGER NOT NULL, + user2_id INTEGER NOT NULL, + ride_match_id INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (user1_id) REFERENCES "USER"(user_id), + FOREIGN KEY (user2_id) REFERENCES "USER"(user_id), + FOREIGN KEY (ride_match_id) REFERENCES "RIDE_MATCH"(match_id), + CHECK (user1_id <> user2_id) +); + +CREATE TABLE "MESSAGE" ( + message_id INTEGER PRIMARY KEY, + thread_id INTEGER NOT NULL, + sender_id INTEGER NOT NULL, + body TEXT NOT NULL, + sent_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (thread_id) REFERENCES "MESSAGE_THREAD"(thread_id), + FOREIGN KEY (sender_id) REFERENCES "USER"(user_id) +); +""" + +# --- 1. HARDCODED DATA GENERATION --- + +# Hardcoded Preexisting Users +users_existing = [ + (1, "abdul_bookwala", "abdul.bookwala1@example.edu", "8569861234", "driver", 4.72, "2025-10-02T00:00:00"), + (2, "quincy_lu", "quincy.lu2@example.edu", "8569862334", "driver", 4.67, "2025-10-09T00:00:00"), + (3, "ame_shabuse", "ame.shabuse3@example.edu", "8569861334", "rider", 4.55, "2025-10-02T00:00:00"), + (4, "ame_lu", "ame.lu4@example.edu", "8569831234", "both", 4.54, "2025-10-10T00:00:00"), + (5, "kennan_shajid", "kennan.shajid5@example.edu", "8569861234", "both", 4.70, "2025-10-01T00:00:00"), + (1001, "driver.drexel", "driver.drexel@drexel.edu", "8560001001", "driver", 4.80, "2025-09-01 09:05:00"), + (1002, "rider.drexel", "rider.drexel@drexel.edu", "8560001002", "rider", 3.50, "2025-09-02 10:00:00"), + (1003, "both.guy", "both.guy@drexel.edu", "8560001003", "both", 4.20, "2025-09-03 11:00:00"), + (1004, "chill.rider", "chill.rider@drexel.edu", "8560001004", "rider", 5.00, "2025-09-04 12:00:00"), + (1005, "late.canceller", "late.canceller@drexel.edu", "8560001005", "rider", 1.90, "2025-09-05 13:00:00"), +] + +# Hardcoded Preexisting Vehicles +vehicles_existing = [ + (1,2,"Tesla","Model Y","Blue","NJ-7909",7,2025,"n/a"), + (2,5,"Tesla","Model 3","Gray","NJ-6737",5,2024,"n/a"), + (3,6,"Toyota","Camry","Silver","NJ-8767",4,2015,"n/a"), + (4,8,"Chevrolet","Malibu","Red","NJ-8301",5,2021,"n/a"), + (5,9,"Toyota","Camry","Blue","NJ-6823",4,2024,"n/a"), + (6,14,"Honda","Civic","White","NJ-7519",5,2022,"n/a"), + (100, 1, "Tesla", "Model 3", "Midnight Silver", "PA-ABDUL3", 5, 2024, "Always has lo-fi playing."), + (101, 1, "Subaru", "Outback", "Forest Green", "PA-ABDULSUB", 5, 2023, "Perfect for Costco and grocery runs."), + (102, 1001, "Honda", "Civic", "Blue", "PA-DRV222", 4, 2018, "Has a Drexel dragon sticker on the back."), + (103, 1004, "Toyota", "RAV4", "White", "PA-RAV404", 5, 2022, "Trunk is full of CS textbooks."), +] + +# Hardcoded Preexisting Locations +locations_existing = [ + (1,"Drexel Main Building","3141 Chestnut St"), + (2,"Korman Center","3220-26 Woodland Walk"), + (3,"University Crossings","3175 JFK Blvd"), + (4,"30th Street Station","2955 Market St"), + (5,"Queen Lane Campus","2900 Queen Ln"), + (6,"Vidas Athletic Complex","43rd & Powelton"), + (7,"Cira Green","129 S 30th St"), + (8,"Wawa 34th Market","3400 Market St"), + (9, "PISB - Papadakis Integrated Sciences Building", "3245 Chestnut St"), + (10, "Drexel Recreation Center", "3301 Market St"), + (11, "Liberty Place", "1625 Chestnut St"), +] + +# Hardcoded Preexisting Message Threads +threads_existing = [(1, 1, 2, None, "2025-11-30 19:25:00")] + +# Hardcoded Preexisting Messages +messages_existing = [ + (1, 1, 2, "Hey, are we still on for 5:30 PM?", "2025-11-30 03:03:42"), + (2, 1, 1, "Yes, I will be there in 10 minutes.", "2025-11-30 03:03:42"), + (3, 1, 2, "Hey Quincy, are we still on for 5:30 PM?", "2025-11-30 19:29:48"), + (4, 1, 1, "Yes absolutely! Leaving now.", "2025-11-30 19:29:48"), + (5, 1, 2, "Perfect, see you soon!", "2025-11-30 19:29:48"), + (6, 1, 1, "Hey Abdul, do you still need a ride tomorrow?", "2025-11-30 19:29:51"), + (7, 1, 2, "Yes please! Around 9 AM if possible.", "2025-11-30 19:29:51"), + (8, 1, 1, "I ll be there.", "2025-11-30 19:29:51"), + (9, 1, 2, "Got you!", "2025-11-30 19:29:51"), + (10, 1, 1, "Thanks for the ride earlier!", "2025-11-30 19:29:55"), + (11, 1, 2, "Anytime man!", "2025-11-30 19:29:55"), + (12, 1, 1, "Left you a 5-star rating too :)", "2025-11-30 19:29:55"), + (13, 1, 2, "Hey bro, heading out soon?", "2025-11-30 19:32:38"), + (14, 1, 1, "Yeah give me 5 minutes.", "2025-11-30 19:32:38"), + (15, 1, 2, "Bet, take your time.", "2025-11-30 19:32:38"), + (16, 1, 1, "Traffic is crazy today.", "2025-11-30 19:32:38"), + (17, 1, 2, "Fr bro, Drexel roads are wild.", "2025-11-30 19:32:38"), + (18, 1, 1, "Want to grab food after?", "2025-11-30 19:32:38"), + (19, 1, 2, "m down for halal cart.", "2025-11-30 19:32:38"), + (20, 1, 1, "Say less.", "2025-11-30 19:32:38"), + (21, 1, 2, "Almost downstairs.", "2025-11-30 19:32:38"), + (22, 1, 1, "I see you.", "2025-11-30 19:32:38"), + (23, 1, 2, "Don t forget the aux today.", "2025-11-30 19:32:38"), + (24, 1, 1, "Haha alright.", "2025-11-30 19:32:38"), + (25, 1, 2, "Let s go.", "2025-11-30 19:32:38"), + (26, 1, 1, "On my way now.", "2025-11-30 19:32:38"), + (27, 1, 2, "You driving today?", "2025-11-30 19:32:38"), + (28, 1, 1, "Yeah I got it.", "2025-11-30 19:32:38"), + (29, 1, 2, "Fire.", "2025-11-30 19:32:38"), + (30, 1, 1, "Good morning! Still need a ride?", "2025-11-30 19:32:38"), + (31, 1, 2, "Yes please! Appreciate you.", "2025-11-30 19:32:38"), + (32, 1, 1, "Leaving in 10.", "2025-11-30 19:32:38"), + (33, 1, 2, "ll meet you outside.", "2025-11-30 19:32:38"), + (34, 1, 1, "Perfect.", "2025-11-30 19:32:38"), + (35, 1, 2, "Did you do the homework for CS 281?", "2025-11-30 19:32:38"), + (36, 1, 1, "Barely, that class is wild.", "2025-11-30 19:32:38"), + (37, 1, 2, "Facts bro.", "2025-11-30 19:32:38"), + (38, 1, 1, "ll quiz you in the car lol.", "2025-11-30 19:32:38"), + (39, 1, 2, "Bet.", "2025-11-30 19:32:38"), + (40, 1, 1, "m by the curb.", "2025-11-30 19:32:38"), + (41, 1, 2, "Coming now.", "2025-11-30 19:32:38"), + (42, 1, 1, "You want coffee?", "2025-11-30 19:32:38"), + (43, 1, 2, "Nah I m good, thanks.", "2025-11-30 19:32:38"), + (44, 1, 1, "Alright see you.", "2025-11-30 19:32:38"), + (45, 1, 2, "Almost there.", "2025-11-30 19:32:38"), + (46, 1, 1, "Thanks again for covering me last ride bro.", "2025-11-30 19:32:38"), + (47, 1, 2, "No problem at all.", "2025-11-30 19:32:38"), + (48, 1, 1, "You good for later today?", "2025-11-30 19:32:38"), + (49, 1, 2, "Yep, same time as usual.", "2025-11-30 19:32:38"), + (50, 1, 1, "Bet.", "2025-11-30 19:32:38"), + (51, 1, 2, "You wanna stop at Wawa after?", "2025-11-30 19:32:38"), + (52, 1, 1, "Always bro.", "2025-11-30 19:32:38"), + (53, 1, 2, "Haha say less.", "2025-11-30 19:32:38"), + (54, 1, 1, "You see the game last night?", "2025-11-30 19:32:38"), + (55, 1, 2, "Bro Embiid is insane.", "2025-11-30 19:32:38"), + (56, 1, 1, "MVP season.", "2025-11-30 19:32:38"), + (57, 1, 2, "100%.", "2025-11-30 19:32:38"), + (58, 1, 1, "Heading out now.", "2025-11-30 19:32:38"), + (59, 1, 2, "m already outside.", "2025-11-30 19:32:38"), + (60, 1, 1, "Coming.", "2025-11-30 19:32:38"), +] + +# Hardcoded Preexisting Ride Offers +ride_offers_existing = [ + (1,5,3,3,8,"2025-10-16T01:45:00",2,4.17,0.72,"closed","2025-10-15T01:45:00"), + (2,1,6,6,10,"2025-10-15T18:45:00",1,5.31,0.86,"closed","2025-10-14T18:45:00"), + (3,12,1,2,3,"2025-10-14T13:00:00",2,4.63,1.13,"open","2025-10-13T13:00:00"), + (4,6,2,3,8,"2025-10-14T17:45:00",3,8.52,0.9,"closed","2025-10-13T17:45:00"), +] + +# Hardcoded Preexisting Ride Requests +ride_requests_existing = [ + (1,11,7,10,"2025-10-14T19:05:00","2025-10-14T19:45:00",1,"cancelled","2025-10-13T19:15:00"), + (2,10,6,8,"2025-10-14T13:15:00","2025-10-14T13:25:00",1,"open","2025-10-13T13:15:00"), + (3,7,1,9,"2025-10-14T07:20:00","2025-10-14T07:40:00",1,"matched","2025-10-13T07:30:00"), + (4,13,6,7,"2025-10-13T23:50:00","2025-10-14T00:30:00",2,"matched","2025-10-13T00:00:00"), + (5,10,7,2,"2025-10-15T21:45:00","2025-10-15T21:55:00",1,"open","2025-10-14T21:45:00"), + (6,11,3,2,"2025-10-15T03:00:00","2025-10-15T03:10:00",1,"cancelled","2025-10-14T03:00:00"), +] + +# Hardcoded Preexisting Ride Matches +matches_existing = [ + (1,3,3,1,11.71,"completed","2025-10-13T13:30:00"), +] + +# Hardcoded Preexisting Ratings +ratings_existing = [ + (1,1,2,12,5,"Friendly","2025-10-13T15:30:00"), +] + +# --- RANDOM MOCK DATA GENERATION LOGIC --- +EXTRA = 100 +start_user_id = 6 +start_location_id = 12 +start_offer_id = max(o[0] for o in ride_offers_existing) + 1 +start_request_id = max(r[0] for r in ride_requests_existing) + 1 +start_match_id = max(m[0] for m in matches_existing) + 1 +start_rating_id = max(r[0] for r in ratings_existing) + 1 + +first_names = ["Alex","Jordan","Taylor","Morgan","Cameron","Riley","Casey","Jamie","Avery","Sam","Sydney","Charlie","Drew","Logan","Peyton","Harper","Blake","Quinn","Rowan","Elliot","Maria","Jamal","Priya","Diego","Lina","Marcus"] +last_names = ["Smith","Johnson","Williams","Brown","Jones","Miller","Davis","Garcia","Rodriguez","Wilson","Martinez","Anderson","Taylor","Thomas","Hernandez","Moore","Martin","Jackson","Thompson","White"] +car_makes_models = [("Toyota","Camry"),("Honda","Civic"),("Tesla","Model 3"),("Tesla","Model Y"),("Chevrolet","Malibu"),("Hyundai","Elantra"),("Ford","Focus"),("Nissan","Altima")] +colors = ["Blue","Gray","Silver","Red","Black","White","Green","Gold","Maroon"] +roles = ["rider","driver","both"] + +def rand_datetime_oct13_to_nov30(): + base = datetime.datetime(2025,10,13) + delta = datetime.timedelta(days=random.randint(0,48), hours=random.randint(0,23), minutes=random.randint(0,59)) + return (base + delta).isoformat(timespec="seconds") + +# Users +users_generated = [] +for i in range(EXTRA): + uid = start_user_id + i + fn = random.choice(first_names) + ln = random.choice(last_names) + username = f"{fn.lower()}.{ln.lower()}{uid}" + email = f"{fn.lower()}.{ln.lower()}{uid}@drexel.edu" + phone = f"215{random.randint(2000000,9999999)}" + role = random.choices(roles, weights=[0.45,0.40,0.15], k=1)[0] + rating_avg = round(max(2.5, min(5.0, random.gauss(4.4,0.4))),2) + created_at = (datetime.datetime(2025,10,1) + datetime.timedelta(days=random.randint(0,60))).isoformat(timespec="seconds") + users_generated.append((uid, username, email, phone, role, rating_avg, created_at)) +users_all = users_existing + users_generated + +# Locations +locations_generated = [] +for i in range(EXTRA): + lid = start_location_id + i + name = f"Philly Point {lid}" + address = f"{random.randint(100,3999)} Market St" + locations_generated.append((lid, name, address)) +locations_all = locations_existing + locations_generated + +# Vehicles +driver_candidates = [u for u in users_all if u[4] in ("driver","both")] +if len(driver_candidates) < 40: + for j in range(30): + idx = j % len(users_generated) + u = users_generated[idx] + users_generated[idx] = (u[0], u[1], u[2], u[3], "driver", u[5], u[6]) + users_all = users_existing + users_generated + driver_candidates = [u for u in users_all if u[4] in ("driver","both")] + +used_vids = set(v[0] for v in vehicles_existing) +vehicles_generated = [] +vid_counter = 7 +generated_count = 0 +while generated_count < EXTRA: + if vid_counter not in used_vids: + owner = random.choice(driver_candidates)[0] + make, model = random.choice(car_makes_models) + color = random.choice(colors) + plate = f"NJ-{1000 + vid_counter:04d}" + seats = random.choice([4,4,5,5,6]) + year = random.randint(2010,2025) + fun_fact = random.choice(["n/a","student vehicle","rideshare-ready","garage kept"]) + vehicles_generated.append((vid_counter, owner, make, model, color, plate, seats, year, fun_fact)) + generated_count += 1 + vid_counter += 1 +vehicles_all = vehicles_existing + vehicles_generated + +# Offers +offers_generated = [] +for i in range(EXTRA): + oid = start_offer_id + i + driver = random.choice(driver_candidates)[0] + owned = [v for v in vehicles_all if v[1] == driver] + vehicle_id = random.choice(owned)[0] if owned else random.choice(vehicles_all)[0] + origin = random.choice(locations_all)[0] + dest = random.choice(locations_all)[0] + if origin == dest: + dest = (dest % (start_location_id + EXTRA - 1)) + 1 + depart_at = rand_datetime_oct13_to_nov30() + seats_available = random.choice([1,1,2,2,3,4]) + price_base = round(random.uniform(3.0,9.0),2) + price_per_mile = round(random.uniform(0.5,1.6),2) + status = random.choices(["open","closed"], weights=[0.6,0.4])[0] + created_at = (datetime.datetime.fromisoformat(depart_at) - datetime.timedelta(days=random.randint(0,7))).isoformat(timespec="seconds") + offers_generated.append((oid, driver, vehicle_id, origin, dest, depart_at, seats_available, price_base, price_per_mile, status, created_at)) +offers_all = ride_offers_existing + offers_generated + +# Requests +riders = [u for u in users_all if u[4] in ("rider","both")] +requests_generated = [] +for i in range(EXTRA): + rid = start_request_id + i + rider = random.choice(riders)[0] + pickup = random.choice(locations_all)[0] + dropoff = random.choice(locations_all)[0] + if pickup == dropoff: + dropoff = (dropoff % (start_location_id + EXTRA - 1)) + 1 + earliest_dt = datetime.datetime(2025,10,13) + datetime.timedelta(days=random.randint(0,48), hours=random.randint(6,20), minutes=random.randint(0,59)) + latest_dt = earliest_dt + datetime.timedelta(minutes=random.randint(10,60)) + seats_needed = random.choice([1,1,2]) + status = random.choices(["open","matched","cancelled"], weights=[0.55,0.35,0.10])[0] + created_at = (earliest_dt - datetime.timedelta(days=random.randint(0,5))).isoformat(timespec="seconds") + requests_generated.append((rid, rider, pickup, dropoff, earliest_dt.isoformat(timespec="seconds"), latest_dt.isoformat(timespec="seconds"), seats_needed, status, created_at)) +requests_all = ride_requests_existing + requests_generated + +# Matches +matches_generated = [] +match_id = start_match_id +for req in requests_generated: + if random.random() < 0.6: + candidates = [o for o in offers_all if o[6] >= req[6]] + if not candidates: + continue + offer = random.choice(candidates) + seats_booked = min(offer[6], req[6]) + miles = random.uniform(2.0,12.0) + price_total = round(offer[7] + offer[8]*miles, 2) if isinstance(offer[7], float) else round(offer[7] + offer[8]*miles,2) + state = random.choices(["confirmed","completed","cancelled","no_show"], weights=[0.4,0.35,0.15,0.10])[0] + matched_at = (datetime.datetime.fromisoformat(req[4]) - datetime.timedelta(hours=random.randint(0,4))).isoformat(timespec="seconds") + matches_generated.append((match_id, req[0], offer[0], seats_booked, price_total, state, matched_at)) + match_id += 1 +matches_all = matches_existing + matches_generated + +# Ratings +ratings_generated = [] +rating_id = start_rating_id +for m in matches_generated: + if m[5] == "completed" and random.random() < 0.85: + req = next((r for r in requests_all if r[0] == m[1]), None) + offer = next((o for o in offers_all if o[0] == m[2]), None) + if not req or not offer: + continue + from_user = req[1] + to_user = offer[1] + if from_user == to_user: + continue + stars = random.choices([5,4,3,2,1], weights=[0.6,0.25,0.08,0.04,0.03])[0] + comment = random.choice(["Great ride","Friendly driver","On time","Would ride again","Car was clean","Driver was late","Helpful with bags"]) + created_at = (datetime.datetime.fromisoformat(m[6]) + datetime.timedelta(hours=random.randint(1,48))).isoformat(timespec="seconds") + ratings_generated.append((rating_id, m[0], from_user, to_user, stars, comment, created_at)) + rating_id += 1 + +cleaned_ratings = [] +seen_pairs = set() +for r in ratings_generated: + rid, match_id, from_user, to_user, stars, comment, created_at = r + if from_user == to_user: + continue + pair_key = (from_user, to_user) + if pair_key in seen_pairs: + continue + seen_pairs.add(pair_key) + cleaned_ratings.append(r) +ratings_generated = cleaned_ratings +ratings_all = ratings_existing + ratings_generated + + +# --- 2. WRITING FILES --- +def sql_quote(val): + if val is None: + return "NULL" + if isinstance(val, str): + return "'" + val.replace("'", "''") + "'" + return str(val) + +# Write schema.sql +with open(SCHEMA_PATH, 'w', encoding='utf-8') as f: + f.write(SCHEMA_SQL) +print(f"Created schema file: {SCHEMA_PATH}") + +# Write populate.sql +with open(POPULATE_PATH, 'w', encoding='utf-8') as f: + f.write("-- USERS\n") + for u in users_all: + f.write("INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s);\n" % ( + sql_quote(u[0]), sql_quote(u[1]), sql_quote(u[2]), "'x'", sql_quote(u[3]), sql_quote(u[4]), sql_quote(u[5]), sql_quote(u[6]) + )) + f.write("\n-- VEHICLE\n") + for v in vehicles_all: + f.write("INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s);\n" % tuple(sql_quote(x) for x in v)) + f.write("\n-- LOCATION\n") + for loc in locations_all: + f.write("INSERT INTO LOCATION (location_id,name,address) VALUES (%s,%s,%s);\n" % (sql_quote(loc[0]), sql_quote(loc[1]), sql_quote(loc[2]))) + f.write("\n-- RIDE_OFFER\n") + for o in offers_all: + f.write("INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);\n" % ( + sql_quote(o[0]), sql_quote(o[1]), sql_quote(o[2]), sql_quote(o[3]), sql_quote(o[4]), sql_quote(o[5]), sql_quote(o[6]), sql_quote(o[7]), sql_quote(o[8]), sql_quote(o[9]), sql_quote(o[10]) + )) + f.write("\n-- RIDE_REQUEST\n") + for r in requests_all: + f.write("INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s);\n" % ( + sql_quote(r[0]), sql_quote(r[1]), sql_quote(r[2]), sql_quote(r[3]), sql_quote(r[4]), sql_quote(r[5]), sql_quote(r[6]), sql_quote(r[7]), sql_quote(r[8]) + )) + f.write("\n-- RIDE_MATCH\n") + for m in matches_all: + f.write("INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (%s,%s,%s,%s,%s,%s,%s);\n" % ( + sql_quote(m[0]), sql_quote(m[1]), sql_quote(m[2]), sql_quote(m[3]), sql_quote(m[4]), sql_quote(m[5]), sql_quote(m[6]) + )) + f.write("\n-- RATING\n") + for rt in ratings_all: + f.write("INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s);\n" % ( + sql_quote(rt[0]), sql_quote(rt[1]), sql_quote(rt[2]), sql_quote(rt[3]), sql_quote(rt[4]), sql_quote(rt[5]), sql_quote(rt[6]) + )) + f.write("\n-- MESSAGE_THREAD\n") + for th in threads_existing: + f.write("INSERT INTO MESSAGE_THREAD (thread_id,user1_id,user2_id,ride_match_id,created_at) VALUES (%s,%s,%s,%s,%s);\n" % ( + sql_quote(th[0]), sql_quote(th[1]), sql_quote(th[2]), sql_quote(th[3]), sql_quote(th[4]) + )) + f.write("\n-- MESSAGE\n") + for msg in messages_existing: + f.write("INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (%s,%s,%s,%s,%s);\n" % ( + sql_quote(msg[0]), sql_quote(msg[1]), sql_quote(msg[2]), sql_quote(msg[3]), sql_quote(msg[4]) + )) +print(f"Created data file: {POPULATE_PATH}") + + +# --- 3. BUILD DATABASE --- +if os.path.exists(CURRENT_DB_PATH): + os.remove(CURRENT_DB_PATH) + print(f"Removed existing {CURRENT_DB_PATH} to start fresh.") + +print(f"Creating new {CURRENT_DB_PATH}...") +conn = sqlite3.connect(CURRENT_DB_PATH) + +# Read and execute schema.sql against the database +with open(SCHEMA_PATH, 'r', encoding='utf-8') as f: + schema_script = f.read() + conn.executescript(schema_script) + print("Executed schema.sql successfully.") + +# Read and execute populate.sql against the database +with open(POPULATE_PATH, 'r', encoding='utf-8') as f: + populate_script = f.read() + conn.executescript(populate_script) + print("Executed populate.sql successfully.") + +conn.commit() +conn.close() +print("Done! Database initialized and populated with mock data.") + +# --- 4. COPY TO ASSETS --- +print(f"Copying database to {ASSET_DB_PATH}...") +shutil.copy2(CURRENT_DB_PATH, ASSET_DB_PATH) +print("Database copied successfully.") \ No newline at end of file diff --git a/database/populate.sql b/database/populate.sql index 8e1efa7..4139c13 100644 --- a/database/populate.sql +++ b/database/populate.sql @@ -1,109 +1,114 @@ -- USERS -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (1,'abdul.bookwala1@example.edu','abdul_bookwala','x','8569861234','driver',4.72,'2025-10-02T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (2,'quincy.lu2@example.edu','quincy_lu','x','8569862334','driver',4.67,'2025-10-09T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (3,'ame.shabuse3@example.edu','ame_shabuse','x','8569861334','rider',4.55,'2025-10-02T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (4,'ame.lu4@example.edu','ame_lu','x','8569831234','both',4.54,'2025-10-10T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (5,'kennan.shajid5@example.edu','kennan_shajid','x','8569861234','both',4.7,'2025-10-01T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (6,'maria.brown6@drexel.edu','maria.brown6','x','2152209805','driver',4.41,'2025-10-07T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (7,'jamal.jackson7@drexel.edu','jamal.jackson7','x','2152729295','driver',4.62,'2025-10-03T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (8,'alex.williams8@drexel.edu','alex.williams8','x','2153834068','rider',3.99,'2025-11-15T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (9,'maria.jackson9@drexel.edu','maria.jackson9','x','2155519187','rider',4.09,'2025-11-07T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (10,'avery.smith10@drexel.edu','avery.smith10','x','2158365337','driver',4.28,'2025-10-10T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (11,'casey.martinez11@drexel.edu','casey.martinez11','x','2152857401','rider',4.05,'2025-10-07T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (12,'charlie.anderson12@drexel.edu','charlie.anderson12','x','2157064421','rider',4.83,'2025-10-08T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (13,'drew.williams13@drexel.edu','drew.williams13','x','2156630852','rider',4.52,'2025-11-10T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (14,'elliot.anderson14@drexel.edu','elliot.anderson14','x','2156843180','rider',4.93,'2025-11-19T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (15,'sam.williams15@drexel.edu','sam.williams15','x','2159174925','rider',4.65,'2025-10-07T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (16,'drew.rodriguez16@drexel.edu','drew.rodriguez16','x','2155803481','driver',4.15,'2025-10-14T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (17,'jamal.rodriguez17@drexel.edu','jamal.rodriguez17','x','2157887295','both',4.69,'2025-11-11T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (18,'taylor.white18@drexel.edu','taylor.white18','x','2157326583','rider',4.37,'2025-10-25T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (19,'avery.jackson19@drexel.edu','avery.jackson19','x','2153842265','driver',4.16,'2025-11-23T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (20,'lina.johnson20@drexel.edu','lina.johnson20','x','2153921394','driver',4.54,'2025-10-05T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (21,'casey.thompson21@drexel.edu','casey.thompson21','x','2159351504','driver',4.02,'2025-10-14T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (22,'maria.moore22@drexel.edu','maria.moore22','x','2155318800','both',4.26,'2025-10-09T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (23,'jamie.jackson23@drexel.edu','jamie.jackson23','x','2156521269','rider',4.23,'2025-11-07T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (24,'logan.thompson24@drexel.edu','logan.thompson24','x','2155350414','rider',4.62,'2025-11-01T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (25,'taylor.johnson25@drexel.edu','taylor.johnson25','x','2159223454','rider',4.4,'2025-11-10T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (26,'riley.thomas26@drexel.edu','riley.thomas26','x','2157003041','rider',3.42,'2025-11-03T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (27,'avery.jackson27@drexel.edu','avery.jackson27','x','2159220743','both',5.0,'2025-11-13T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (28,'diego.brown28@drexel.edu','diego.brown28','x','2157718601','both',4.4,'2025-10-22T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (29,'morgan.wilson29@drexel.edu','morgan.wilson29','x','2155647075','rider',3.72,'2025-10-01T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (30,'diego.rodriguez30@drexel.edu','diego.rodriguez30','x','2156199220','driver',4.21,'2025-11-10T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (31,'sam.martin31@drexel.edu','sam.martin31','x','2157108412','rider',4.39,'2025-10-24T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (32,'lina.miller32@drexel.edu','lina.miller32','x','2156524639','both',5.0,'2025-11-08T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (33,'sydney.moore33@drexel.edu','sydney.moore33','x','2152163382','rider',3.98,'2025-10-24T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (34,'marcus.wilson34@drexel.edu','marcus.wilson34','x','2154008671','rider',5.0,'2025-10-06T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (35,'diego.moore35@drexel.edu','diego.moore35','x','2158845299','rider',3.73,'2025-11-18T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (36,'quinn.jones36@drexel.edu','quinn.jones36','x','2153077025','driver',4.63,'2025-11-03T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (37,'elliot.thomas37@drexel.edu','elliot.thomas37','x','2153776692','both',4.32,'2025-11-18T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (38,'diego.davis38@drexel.edu','diego.davis38','x','2157980649','rider',4.98,'2025-10-29T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (39,'blake.hernandez39@drexel.edu','blake.hernandez39','x','2153015056','rider',4.38,'2025-10-05T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (40,'sydney.smith40@drexel.edu','sydney.smith40','x','2156935091','driver',4.36,'2025-11-15T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (41,'maria.johnson41@drexel.edu','maria.johnson41','x','2153920502','rider',4.37,'2025-10-03T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (42,'sydney.williams42@drexel.edu','sydney.williams42','x','2156313054','rider',4.26,'2025-10-09T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (43,'diego.thompson43@drexel.edu','diego.thompson43','x','2156833613','driver',4.16,'2025-11-20T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (44,'harper.thomas44@drexel.edu','harper.thomas44','x','2153597274','rider',4.2,'2025-10-27T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (45,'peyton.johnson45@drexel.edu','peyton.johnson45','x','2157648591','driver',4.09,'2025-11-11T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (46,'morgan.johnson46@drexel.edu','morgan.johnson46','x','2155377432','driver',4.46,'2025-10-13T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (47,'casey.jackson47@drexel.edu','casey.jackson47','x','2155763243','rider',4.22,'2025-10-12T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (48,'avery.hernandez48@drexel.edu','avery.hernandez48','x','2154095528','both',5.0,'2025-11-24T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (49,'quinn.brown49@drexel.edu','quinn.brown49','x','2152424365','driver',4.73,'2025-11-04T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (50,'alex.williams50@drexel.edu','alex.williams50','x','2159770929','driver',4.43,'2025-10-31T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (51,'casey.taylor51@drexel.edu','casey.taylor51','x','2159570239','rider',4.81,'2025-10-25T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (52,'alex.taylor52@drexel.edu','alex.taylor52','x','2154224684','both',4.47,'2025-11-14T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (53,'diego.jackson53@drexel.edu','diego.jackson53','x','2157552178','driver',4.08,'2025-10-10T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (54,'casey.wilson54@drexel.edu','casey.wilson54','x','2153826207','both',3.96,'2025-11-17T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (55,'sydney.johnson55@drexel.edu','sydney.johnson55','x','2152420624','driver',4.16,'2025-11-02T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (56,'blake.miller56@drexel.edu','blake.miller56','x','2152477140','both',4.62,'2025-11-08T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (57,'taylor.garcia57@drexel.edu','taylor.garcia57','x','2155387114','rider',4.52,'2025-11-26T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (58,'rowan.garcia58@drexel.edu','rowan.garcia58','x','2156856325','driver',4.09,'2025-11-07T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (59,'rowan.martin59@drexel.edu','rowan.martin59','x','2154653902','both',4.12,'2025-10-14T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (60,'jamal.martinez60@drexel.edu','jamal.martinez60','x','2154002242','rider',4.79,'2025-10-30T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (61,'sydney.williams61@drexel.edu','sydney.williams61','x','2152078143','driver',4.82,'2025-11-06T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (62,'morgan.williams62@drexel.edu','morgan.williams62','x','2156510004','rider',4.31,'2025-11-26T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (63,'taylor.garcia63@drexel.edu','taylor.garcia63','x','2155099817','rider',5.0,'2025-10-29T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (64,'quinn.wilson64@drexel.edu','quinn.wilson64','x','2157131124','both',4.57,'2025-11-12T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (65,'quinn.wilson65@drexel.edu','quinn.wilson65','x','2159816136','driver',3.94,'2025-11-30T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (66,'cameron.rodriguez66@drexel.edu','cameron.rodriguez66','x','2152968285','both',4.39,'2025-10-19T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (67,'elliot.davis67@drexel.edu','elliot.davis67','x','2158019767','rider',4.17,'2025-11-13T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (68,'maria.rodriguez68@drexel.edu','maria.rodriguez68','x','2156239678','driver',5.0,'2025-10-06T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (69,'maria.thomas69@drexel.edu','maria.thomas69','x','2158957076','rider',3.97,'2025-10-01T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (70,'sydney.jones70@drexel.edu','sydney.jones70','x','2157344494','both',4.63,'2025-11-15T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (71,'logan.jackson71@drexel.edu','logan.jackson71','x','2152081115','rider',4.77,'2025-11-30T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (72,'priya.jones72@drexel.edu','priya.jones72','x','2156576743','rider',4.05,'2025-10-28T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (73,'cameron.johnson73@drexel.edu','cameron.johnson73','x','2154585858','rider',4.77,'2025-11-29T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (74,'marcus.johnson74@drexel.edu','marcus.johnson74','x','2159540726','rider',4.15,'2025-10-23T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (75,'lina.jackson75@drexel.edu','lina.jackson75','x','2159416659','both',3.86,'2025-11-09T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (76,'diego.jones76@drexel.edu','diego.jones76','x','2159765869','both',5.0,'2025-11-21T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (77,'riley.thomas77@drexel.edu','riley.thomas77','x','2152207923','rider',3.58,'2025-11-29T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (78,'sydney.thomas78@drexel.edu','sydney.thomas78','x','2158729636','driver',4.37,'2025-10-11T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (79,'marcus.brown79@drexel.edu','marcus.brown79','x','2155208992','both',4.1,'2025-11-24T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (80,'harper.garcia80@drexel.edu','harper.garcia80','x','2153674138','driver',4.07,'2025-11-20T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (81,'jamie.garcia81@drexel.edu','jamie.garcia81','x','2152198511','driver',4.48,'2025-10-26T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (82,'sydney.rodriguez82@drexel.edu','sydney.rodriguez82','x','2159250718','rider',4.45,'2025-11-02T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (83,'drew.jackson83@drexel.edu','drew.jackson83','x','2154777886','both',4.03,'2025-10-08T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (84,'avery.miller84@drexel.edu','avery.miller84','x','2156870339','both',4.38,'2025-10-28T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (85,'charlie.martinez85@drexel.edu','charlie.martinez85','x','2155660737','driver',4.59,'2025-11-02T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (86,'morgan.taylor86@drexel.edu','morgan.taylor86','x','2159545592','driver',4.38,'2025-10-01T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (87,'blake.jackson87@drexel.edu','blake.jackson87','x','2157761775','driver',5.0,'2025-11-17T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (88,'diego.davis88@drexel.edu','diego.davis88','x','2155055311','rider',5.0,'2025-11-09T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (89,'sydney.brown89@drexel.edu','sydney.brown89','x','2158037736','both',4.12,'2025-11-02T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (90,'sam.thomas90@drexel.edu','sam.thomas90','x','2154736220','rider',4.34,'2025-10-27T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (91,'jamal.taylor91@drexel.edu','jamal.taylor91','x','2157681754','driver',4.6,'2025-10-12T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (92,'elliot.thompson92@drexel.edu','elliot.thompson92','x','2154524550','rider',4.57,'2025-10-14T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (93,'logan.thompson93@drexel.edu','logan.thompson93','x','2157089046','driver',4.11,'2025-10-30T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (94,'peyton.hernandez94@drexel.edu','peyton.hernandez94','x','2157667572','rider',3.55,'2025-11-20T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (95,'diego.miller95@drexel.edu','diego.miller95','x','2157526840','rider',4.54,'2025-11-02T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (96,'jamal.white96@drexel.edu','jamal.white96','x','2154811762','rider',4.68,'2025-10-20T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (97,'jamie.davis97@drexel.edu','jamie.davis97','x','2153236094','rider',4.31,'2025-10-16T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (98,'harper.white98@drexel.edu','harper.white98','x','2159128116','driver',3.6,'2025-11-06T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (99,'casey.taylor99@drexel.edu','casey.taylor99','x','2156147136','rider',4.63,'2025-10-10T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (100,'maria.smith100@drexel.edu','maria.smith100','x','2159488173','driver',4.42,'2025-10-28T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (101,'jamie.miller101@drexel.edu','jamie.miller101','x','2158745362','both',4.21,'2025-11-03T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (102,'peyton.johnson102@drexel.edu','peyton.johnson102','x','2156675858','rider',4.66,'2025-11-21T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (103,'peyton.martin103@drexel.edu','peyton.martin103','x','2156688605','driver',4.04,'2025-11-30T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (104,'lina.hernandez104@drexel.edu','lina.hernandez104','x','2157139300','driver',4.73,'2025-11-28T00:00:00'); -INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (105,'quinn.hernandez105@drexel.edu','quinn.hernandez105','x','2159526327','rider',4.14,'2025-11-25T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (1,'abdul_bookwala','abdul.bookwala1@example.edu','x','8569861234','driver',4.72,'2025-10-02T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (2,'quincy_lu','quincy.lu2@example.edu','x','8569862334','driver',4.67,'2025-10-09T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (3,'ame_shabuse','ame.shabuse3@example.edu','x','8569861334','rider',4.55,'2025-10-02T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (4,'ame_lu','ame.lu4@example.edu','x','8569831234','both',4.54,'2025-10-10T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (5,'kennan_shajid','kennan.shajid5@example.edu','x','8569861234','both',4.7,'2025-10-01T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (1001,'driver.drexel','driver.drexel@drexel.edu','x','8560001001','driver',4.8,'2025-09-01 09:05:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (1002,'rider.drexel','rider.drexel@drexel.edu','x','8560001002','rider',3.5,'2025-09-02 10:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (1003,'both.guy','both.guy@drexel.edu','x','8560001003','both',4.2,'2025-09-03 11:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (1004,'chill.rider','chill.rider@drexel.edu','x','8560001004','rider',5.0,'2025-09-04 12:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (1005,'late.canceller','late.canceller@drexel.edu','x','8560001005','rider',1.9,'2025-09-05 13:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (6,'maria.brown6','maria.brown6@drexel.edu','x','2152209805','driver',4.41,'2025-10-07T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (7,'jamal.jackson7','jamal.jackson7@drexel.edu','x','2152729295','driver',4.62,'2025-10-03T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (8,'alex.williams8','alex.williams8@drexel.edu','x','2153834068','rider',3.99,'2025-11-15T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (9,'maria.jackson9','maria.jackson9@drexel.edu','x','2155519187','rider',4.09,'2025-11-07T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (10,'avery.smith10','avery.smith10@drexel.edu','x','2158365337','driver',4.28,'2025-10-10T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (11,'casey.martinez11','casey.martinez11@drexel.edu','x','2152857401','rider',4.05,'2025-10-07T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (12,'charlie.anderson12','charlie.anderson12@drexel.edu','x','2157064421','rider',4.83,'2025-10-08T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (13,'drew.williams13','drew.williams13@drexel.edu','x','2156630852','rider',4.52,'2025-11-10T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (14,'elliot.anderson14','elliot.anderson14@drexel.edu','x','2156843180','rider',4.93,'2025-11-19T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (15,'sam.williams15','sam.williams15@drexel.edu','x','2159174925','rider',4.65,'2025-10-07T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (16,'drew.rodriguez16','drew.rodriguez16@drexel.edu','x','2155803481','driver',4.15,'2025-10-14T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (17,'jamal.rodriguez17','jamal.rodriguez17@drexel.edu','x','2157887295','both',4.69,'2025-11-11T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (18,'taylor.white18','taylor.white18@drexel.edu','x','2157326583','rider',4.37,'2025-10-25T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (19,'avery.jackson19','avery.jackson19@drexel.edu','x','2153842265','driver',4.16,'2025-11-23T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (20,'lina.johnson20','lina.johnson20@drexel.edu','x','2153921394','driver',4.54,'2025-10-05T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (21,'casey.thompson21','casey.thompson21@drexel.edu','x','2159351504','driver',4.02,'2025-10-14T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (22,'maria.moore22','maria.moore22@drexel.edu','x','2155318800','both',4.26,'2025-10-09T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (23,'jamie.jackson23','jamie.jackson23@drexel.edu','x','2156521269','rider',4.23,'2025-11-07T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (24,'logan.thompson24','logan.thompson24@drexel.edu','x','2155350414','rider',4.62,'2025-11-01T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (25,'taylor.johnson25','taylor.johnson25@drexel.edu','x','2159223454','rider',4.4,'2025-11-10T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (26,'riley.thomas26','riley.thomas26@drexel.edu','x','2157003041','rider',3.42,'2025-11-03T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (27,'avery.jackson27','avery.jackson27@drexel.edu','x','2159220743','both',5.0,'2025-11-13T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (28,'diego.brown28','diego.brown28@drexel.edu','x','2157718601','both',4.4,'2025-10-22T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (29,'morgan.wilson29','morgan.wilson29@drexel.edu','x','2155647075','rider',3.72,'2025-10-01T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (30,'diego.rodriguez30','diego.rodriguez30@drexel.edu','x','2156199220','driver',4.21,'2025-11-10T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (31,'sam.martin31','sam.martin31@drexel.edu','x','2157108412','rider',4.39,'2025-10-24T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (32,'lina.miller32','lina.miller32@drexel.edu','x','2156524639','both',5.0,'2025-11-08T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (33,'sydney.moore33','sydney.moore33@drexel.edu','x','2152163382','rider',3.98,'2025-10-24T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (34,'marcus.wilson34','marcus.wilson34@drexel.edu','x','2154008671','rider',5.0,'2025-10-06T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (35,'diego.moore35','diego.moore35@drexel.edu','x','2158845299','rider',3.73,'2025-11-18T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (36,'quinn.jones36','quinn.jones36@drexel.edu','x','2153077025','driver',4.63,'2025-11-03T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (37,'elliot.thomas37','elliot.thomas37@drexel.edu','x','2153776692','both',4.32,'2025-11-18T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (38,'diego.davis38','diego.davis38@drexel.edu','x','2157980649','rider',4.98,'2025-10-29T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (39,'blake.hernandez39','blake.hernandez39@drexel.edu','x','2153015056','rider',4.38,'2025-10-05T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (40,'sydney.smith40','sydney.smith40@drexel.edu','x','2156935091','driver',4.36,'2025-11-15T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (41,'maria.johnson41','maria.johnson41@drexel.edu','x','2153920502','rider',4.37,'2025-10-03T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (42,'sydney.williams42','sydney.williams42@drexel.edu','x','2156313054','rider',4.26,'2025-10-09T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (43,'diego.thompson43','diego.thompson43@drexel.edu','x','2156833613','driver',4.16,'2025-11-20T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (44,'harper.thomas44','harper.thomas44@drexel.edu','x','2153597274','rider',4.2,'2025-10-27T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (45,'peyton.johnson45','peyton.johnson45@drexel.edu','x','2157648591','driver',4.09,'2025-11-11T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (46,'morgan.johnson46','morgan.johnson46@drexel.edu','x','2155377432','driver',4.46,'2025-10-13T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (47,'casey.jackson47','casey.jackson47@drexel.edu','x','2155763243','rider',4.22,'2025-10-12T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (48,'avery.hernandez48','avery.hernandez48@drexel.edu','x','2154095528','both',5.0,'2025-11-24T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (49,'quinn.brown49','quinn.brown49@drexel.edu','x','2152424365','driver',4.73,'2025-11-04T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (50,'alex.williams50','alex.williams50@drexel.edu','x','2159770929','driver',4.43,'2025-10-31T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (51,'casey.taylor51','casey.taylor51@drexel.edu','x','2159570239','rider',4.81,'2025-10-25T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (52,'alex.taylor52','alex.taylor52@drexel.edu','x','2154224684','both',4.47,'2025-11-14T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (53,'diego.jackson53','diego.jackson53@drexel.edu','x','2157552178','driver',4.08,'2025-10-10T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (54,'casey.wilson54','casey.wilson54@drexel.edu','x','2153826207','both',3.96,'2025-11-17T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (55,'sydney.johnson55','sydney.johnson55@drexel.edu','x','2152420624','driver',4.16,'2025-11-02T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (56,'blake.miller56','blake.miller56@drexel.edu','x','2152477140','both',4.62,'2025-11-08T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (57,'taylor.garcia57','taylor.garcia57@drexel.edu','x','2155387114','rider',4.52,'2025-11-26T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (58,'rowan.garcia58','rowan.garcia58@drexel.edu','x','2156856325','driver',4.09,'2025-11-07T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (59,'rowan.martin59','rowan.martin59@drexel.edu','x','2154653902','both',4.12,'2025-10-14T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (60,'jamal.martinez60','jamal.martinez60@drexel.edu','x','2154002242','rider',4.79,'2025-10-30T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (61,'sydney.williams61','sydney.williams61@drexel.edu','x','2152078143','driver',4.82,'2025-11-06T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (62,'morgan.williams62','morgan.williams62@drexel.edu','x','2156510004','rider',4.31,'2025-11-26T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (63,'taylor.garcia63','taylor.garcia63@drexel.edu','x','2155099817','rider',5.0,'2025-10-29T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (64,'quinn.wilson64','quinn.wilson64@drexel.edu','x','2157131124','both',4.57,'2025-11-12T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (65,'quinn.wilson65','quinn.wilson65@drexel.edu','x','2159816136','driver',3.94,'2025-11-30T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (66,'cameron.rodriguez66','cameron.rodriguez66@drexel.edu','x','2152968285','both',4.39,'2025-10-19T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (67,'elliot.davis67','elliot.davis67@drexel.edu','x','2158019767','rider',4.17,'2025-11-13T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (68,'maria.rodriguez68','maria.rodriguez68@drexel.edu','x','2156239678','driver',5.0,'2025-10-06T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (69,'maria.thomas69','maria.thomas69@drexel.edu','x','2158957076','rider',3.97,'2025-10-01T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (70,'sydney.jones70','sydney.jones70@drexel.edu','x','2157344494','both',4.63,'2025-11-15T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (71,'logan.jackson71','logan.jackson71@drexel.edu','x','2152081115','rider',4.77,'2025-11-30T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (72,'priya.jones72','priya.jones72@drexel.edu','x','2156576743','rider',4.05,'2025-10-28T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (73,'cameron.johnson73','cameron.johnson73@drexel.edu','x','2154585858','rider',4.77,'2025-11-29T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (74,'marcus.johnson74','marcus.johnson74@drexel.edu','x','2159540726','rider',4.15,'2025-10-23T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (75,'lina.jackson75','lina.jackson75@drexel.edu','x','2159416659','both',3.86,'2025-11-09T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (76,'diego.jones76','diego.jones76@drexel.edu','x','2159765869','both',5.0,'2025-11-21T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (77,'riley.thomas77','riley.thomas77@drexel.edu','x','2152207923','rider',3.58,'2025-11-29T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (78,'sydney.thomas78','sydney.thomas78@drexel.edu','x','2158729636','driver',4.37,'2025-10-11T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (79,'marcus.brown79','marcus.brown79@drexel.edu','x','2155208992','both',4.1,'2025-11-24T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (80,'harper.garcia80','harper.garcia80@drexel.edu','x','2153674138','driver',4.07,'2025-11-20T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (81,'jamie.garcia81','jamie.garcia81@drexel.edu','x','2152198511','driver',4.48,'2025-10-26T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (82,'sydney.rodriguez82','sydney.rodriguez82@drexel.edu','x','2159250718','rider',4.45,'2025-11-02T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (83,'drew.jackson83','drew.jackson83@drexel.edu','x','2154777886','both',4.03,'2025-10-08T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (84,'avery.miller84','avery.miller84@drexel.edu','x','2156870339','both',4.38,'2025-10-28T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (85,'charlie.martinez85','charlie.martinez85@drexel.edu','x','2155660737','driver',4.59,'2025-11-02T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (86,'morgan.taylor86','morgan.taylor86@drexel.edu','x','2159545592','driver',4.38,'2025-10-01T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (87,'blake.jackson87','blake.jackson87@drexel.edu','x','2157761775','driver',5.0,'2025-11-17T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (88,'diego.davis88','diego.davis88@drexel.edu','x','2155055311','rider',5.0,'2025-11-09T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (89,'sydney.brown89','sydney.brown89@drexel.edu','x','2158037736','both',4.12,'2025-11-02T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (90,'sam.thomas90','sam.thomas90@drexel.edu','x','2154736220','rider',4.34,'2025-10-27T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (91,'jamal.taylor91','jamal.taylor91@drexel.edu','x','2157681754','driver',4.6,'2025-10-12T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (92,'elliot.thompson92','elliot.thompson92@drexel.edu','x','2154524550','rider',4.57,'2025-10-14T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (93,'logan.thompson93','logan.thompson93@drexel.edu','x','2157089046','driver',4.11,'2025-10-30T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (94,'peyton.hernandez94','peyton.hernandez94@drexel.edu','x','2157667572','rider',3.55,'2025-11-20T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (95,'diego.miller95','diego.miller95@drexel.edu','x','2157526840','rider',4.54,'2025-11-02T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (96,'jamal.white96','jamal.white96@drexel.edu','x','2154811762','rider',4.68,'2025-10-20T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (97,'jamie.davis97','jamie.davis97@drexel.edu','x','2153236094','rider',4.31,'2025-10-16T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (98,'harper.white98','harper.white98@drexel.edu','x','2159128116','driver',3.6,'2025-11-06T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (99,'casey.taylor99','casey.taylor99@drexel.edu','x','2156147136','rider',4.63,'2025-10-10T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (100,'maria.smith100','maria.smith100@drexel.edu','x','2159488173','driver',4.42,'2025-10-28T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (101,'jamie.miller101','jamie.miller101@drexel.edu','x','2158745362','both',4.21,'2025-11-03T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (102,'peyton.johnson102','peyton.johnson102@drexel.edu','x','2156675858','rider',4.66,'2025-11-21T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (103,'peyton.martin103','peyton.martin103@drexel.edu','x','2156688605','driver',4.04,'2025-11-30T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (104,'lina.hernandez104','lina.hernandez104@drexel.edu','x','2157139300','driver',4.73,'2025-11-28T00:00:00'); +INSERT INTO USER (user_id,username,email,password_hash,phone_number,role,rating_avg,created_at) VALUES (105,'quinn.hernandez105','quinn.hernandez105@drexel.edu','x','2159526327','rider',4.14,'2025-11-25T00:00:00'); -- VEHICLE INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (1,2,'Tesla','Model Y','Blue','NJ-7909',7,2025,'n/a'); @@ -112,106 +117,110 @@ INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (4,8,'Chevrolet','Malibu','Red','NJ-8301',5,2021,'n/a'); INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (5,9,'Toyota','Camry','Blue','NJ-6823',4,2024,'n/a'); INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (6,14,'Honda','Civic','White','NJ-7519',5,2022,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (7,76,'Toyota','Camry','Gray','NJ-1007',5,2014,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (8,30,'Toyota','Camry','Black','NJ-1008',5,2020,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (9,61,'Hyundai','Elantra','White','NJ-1009',5,2018,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (10,43,'Honda','Civic','Gold','NJ-1010',4,2011,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (11,37,'Honda','Civic','Blue','NJ-1011',4,2017,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (12,104,'Toyota','Camry','Silver','NJ-1012',4,2014,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (13,84,'Honda','Civic','Red','NJ-1013',5,2018,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (14,28,'Honda','Civic','Silver','NJ-1014',5,2013,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (15,48,'Ford','Focus','Green','NJ-1015',4,2012,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (16,20,'Chevrolet','Malibu','Gray','NJ-1016',6,2011,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (17,70,'Ford','Focus','White','NJ-1017',4,2020,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (18,56,'Nissan','Altima','Gray','NJ-1018',5,2021,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (19,87,'Tesla','Model 3','Green','NJ-1019',4,2018,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (20,61,'Ford','Focus','Black','NJ-1020',5,2017,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (21,45,'Nissan','Altima','Red','NJ-1021',5,2022,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (22,7,'Nissan','Altima','White','NJ-1022',4,2025,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (23,52,'Chevrolet','Malibu','White','NJ-1023',5,2018,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (24,68,'Tesla','Model Y','Gray','NJ-1024',4,2023,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (25,75,'Tesla','Model Y','Gold','NJ-1025',5,2024,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (26,19,'Chevrolet','Malibu','Red','NJ-1026',5,2017,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (27,84,'Hyundai','Elantra','Gold','NJ-1027',6,2021,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (28,91,'Hyundai','Elantra','White','NJ-1028',5,2018,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (29,43,'Tesla','Model Y','Gray','NJ-1029',4,2020,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (30,91,'Tesla','Model 3','Red','NJ-1030',4,2025,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (31,89,'Chevrolet','Malibu','Gray','NJ-1031',4,2019,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (32,53,'Tesla','Model 3','Black','NJ-1032',4,2014,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (33,10,'Toyota','Camry','Maroon','NJ-1033',5,2014,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (34,20,'Toyota','Camry','Black','NJ-1034',5,2025,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (35,50,'Tesla','Model 3','Blue','NJ-1035',5,2025,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (36,103,'Honda','Civic','Green','NJ-1036',5,2012,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (37,27,'Tesla','Model 3','Black','NJ-1037',4,2017,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (38,75,'Ford','Focus','Red','NJ-1038',6,2022,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (39,59,'Chevrolet','Malibu','Green','NJ-1039',5,2011,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (40,93,'Tesla','Model Y','Red','NJ-1040',5,2012,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (41,40,'Tesla','Model 3','Maroon','NJ-1041',4,2015,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (42,56,'Nissan','Altima','Gold','NJ-1042',5,2011,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (43,46,'Chevrolet','Malibu','Gold','NJ-1043',4,2017,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (44,100,'Tesla','Model Y','Green','NJ-1044',4,2017,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (45,45,'Tesla','Model 3','Gray','NJ-1045',4,2015,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (46,79,'Chevrolet','Malibu','Gold','NJ-1046',4,2024,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (47,86,'Ford','Focus','Black','NJ-1047',6,2025,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (48,19,'Toyota','Camry','Green','NJ-1048',5,2018,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (49,19,'Tesla','Model Y','Blue','NJ-1049',5,2011,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (50,64,'Nissan','Altima','Black','NJ-1050',4,2023,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (51,19,'Nissan','Altima','White','NJ-1051',5,2020,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (52,84,'Honda','Civic','Silver','NJ-1052',5,2023,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (53,46,'Ford','Focus','Maroon','NJ-1053',4,2024,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (54,49,'Chevrolet','Malibu','White','NJ-1054',4,2022,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (55,84,'Nissan','Altima','Green','NJ-1055',4,2016,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (56,80,'Nissan','Altima','Gold','NJ-1056',4,2016,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (57,75,'Tesla','Model 3','Black','NJ-1057',5,2025,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (58,7,'Tesla','Model Y','Silver','NJ-1058',5,2010,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (59,19,'Tesla','Model Y','Gray','NJ-1059',5,2013,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (60,65,'Chevrolet','Malibu','Maroon','NJ-1060',5,2023,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (61,64,'Tesla','Model Y','Gold','NJ-1061',6,2014,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (62,32,'Tesla','Model 3','Gray','NJ-1062',5,2023,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (63,100,'Chevrolet','Malibu','Blue','NJ-1063',5,2019,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (64,27,'Nissan','Altima','Maroon','NJ-1064',5,2021,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (65,75,'Ford','Focus','Gold','NJ-1065',5,2016,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (66,76,'Ford','Focus','Red','NJ-1066',5,2011,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (67,91,'Nissan','Altima','Green','NJ-1067',5,2014,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (68,10,'Tesla','Model 3','Maroon','NJ-1068',6,2020,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (69,59,'Honda','Civic','Maroon','NJ-1069',5,2010,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (70,56,'Tesla','Model 3','Gray','NJ-1070',5,2018,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (71,80,'Ford','Focus','Gray','NJ-1071',5,2022,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (72,81,'Nissan','Altima','Maroon','NJ-1072',4,2012,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (73,81,'Chevrolet','Malibu','Red','NJ-1073',4,2023,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (74,93,'Honda','Civic','Gold','NJ-1074',4,2019,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (75,10,'Hyundai','Elantra','Blue','NJ-1075',5,2021,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (76,58,'Tesla','Model 3','Red','NJ-1076',6,2023,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (77,28,'Tesla','Model 3','Gray','NJ-1077',6,2022,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (78,65,'Tesla','Model 3','Red','NJ-1078',5,2018,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (79,43,'Toyota','Camry','Gold','NJ-1079',5,2015,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (80,59,'Hyundai','Elantra','Black','NJ-1080',5,2018,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (81,48,'Tesla','Model Y','Green','NJ-1081',5,2013,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (82,54,'Hyundai','Elantra','Black','NJ-1082',5,2010,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (83,45,'Toyota','Camry','Blue','NJ-1083',6,2025,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (84,98,'Tesla','Model Y','White','NJ-1084',4,2016,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (85,85,'Tesla','Model 3','Gray','NJ-1085',4,2019,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (86,10,'Hyundai','Elantra','Silver','NJ-1086',4,2019,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (87,91,'Ford','Focus','Silver','NJ-1087',4,2014,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (88,68,'Chevrolet','Malibu','Silver','NJ-1088',5,2025,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (89,91,'Hyundai','Elantra','Gray','NJ-1089',5,2012,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (90,93,'Tesla','Model Y','Green','NJ-1090',6,2021,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (91,100,'Ford','Focus','Blue','NJ-1091',5,2013,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (92,53,'Chevrolet','Malibu','Green','NJ-1092',5,2013,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (93,64,'Toyota','Camry','Maroon','NJ-1093',5,2017,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (94,81,'Nissan','Altima','Black','NJ-1094',5,2013,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (95,10,'Toyota','Camry','Black','NJ-1095',5,2013,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (96,40,'Tesla','Model 3','Green','NJ-1096',5,2021,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (97,78,'Tesla','Model 3','Green','NJ-1097',4,2025,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (98,45,'Toyota','Camry','White','NJ-1098',4,2024,'garage kept'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (99,40,'Hyundai','Elantra','Gray','NJ-1099',5,2021,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (100,55,'Chevrolet','Malibu','Red','NJ-1100',4,2024,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (101,84,'Tesla','Model Y','Blue','NJ-1101',4,2020,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (102,22,'Tesla','Model Y','Gray','NJ-1102',6,2016,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (103,103,'Tesla','Model Y','White','NJ-1103',4,2010,'rideshare-ready'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (104,27,'Tesla','Model 3','Maroon','NJ-1104',5,2015,'n/a'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (105,84,'Toyota','Camry','Silver','NJ-1105',4,2021,'student vehicle'); -INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (106,78,'Hyundai','Elantra','Blue','NJ-1106',4,2018,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (100,1,'Tesla','Model 3','Midnight Silver','PA-ABDUL3',5,2024,'Always has lo-fi playing.'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (101,1,'Subaru','Outback','Forest Green','PA-ABDULSUB',5,2023,'Perfect for Costco and grocery runs.'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (102,1001,'Honda','Civic','Blue','PA-DRV222',4,2018,'Has a Drexel dragon sticker on the back.'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (103,1004,'Toyota','RAV4','White','PA-RAV404',5,2022,'Trunk is full of CS textbooks.'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (7,64,'Toyota','Camry','Gray','NJ-1007',5,2014,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (8,19,'Toyota','Camry','Black','NJ-1008',5,2020,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (9,53,'Hyundai','Elantra','White','NJ-1009',5,2018,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (10,28,'Honda','Civic','Gold','NJ-1010',4,2011,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (11,22,'Honda','Civic','Blue','NJ-1011',4,2017,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (12,91,'Toyota','Camry','Silver','NJ-1012',4,2014,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (13,76,'Honda','Civic','Red','NJ-1013',5,2018,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (14,17,'Honda','Civic','Silver','NJ-1014',5,2013,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (15,104,'Chevrolet','Malibu','Green','NJ-1015',5,2016,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (16,65,'Tesla','Model Y','Gray','NJ-1016',5,2013,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (17,43,'Ford','Focus','White','NJ-1017',4,2020,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (18,93,'Ford','Focus','Gold','NJ-1018',4,2023,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (19,70,'Nissan','Altima','Silver','NJ-1019',5,2015,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (20,68,'Nissan','Altima','Gold','NJ-1020',5,2018,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (21,93,'Tesla','Model Y','Gray','NJ-1021',5,2024,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (22,84,'Nissan','Altima','Green','NJ-1022',5,2010,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (23,93,'Hyundai','Elantra','Silver','NJ-1023',5,2016,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (24,87,'Chevrolet','Malibu','White','NJ-1024',5,2018,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (25,58,'Tesla','Model Y','Gray','NJ-1025',4,2023,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (26,61,'Tesla','Model Y','Gold','NJ-1026',5,2024,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (27,1003,'Chevrolet','Malibu','Red','NJ-1027',5,2017,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (28,76,'Hyundai','Elantra','Gold','NJ-1028',6,2021,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (29,83,'Hyundai','Elantra','White','NJ-1029',5,2018,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (30,28,'Tesla','Model Y','Gray','NJ-1030',4,2020,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (31,83,'Tesla','Model 3','Red','NJ-1031',4,2025,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (32,81,'Chevrolet','Malibu','Gray','NJ-1032',4,2019,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (33,45,'Tesla','Model 3','Black','NJ-1033',4,2014,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (34,4,'Toyota','Camry','Maroon','NJ-1034',5,2014,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (35,6,'Toyota','Camry','Black','NJ-1035',5,2025,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (36,40,'Tesla','Model 3','Blue','NJ-1036',5,2025,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (37,89,'Honda','Civic','Green','NJ-1037',5,2012,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (38,16,'Tesla','Model 3','Black','NJ-1038',4,2017,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (39,61,'Ford','Focus','Red','NJ-1039',6,2022,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (40,103,'Nissan','Altima','Black','NJ-1040',6,2023,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (41,64,'Toyota','Camry','Gray','NJ-1041',4,2016,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (42,76,'Honda','Civic','Silver','NJ-1042',4,2015,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (43,17,'Toyota','Camry','Green','NJ-1043',5,2025,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (44,4,'Tesla','Model Y','Black','NJ-1044',5,2024,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (45,78,'Tesla','Model Y','Black','NJ-1045',6,2016,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (46,7,'Tesla','Model Y','Silver','NJ-1046',5,2014,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (47,5,'Tesla','Model 3','Black','NJ-1047',6,2019,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (48,7,'Nissan','Altima','Black','NJ-1048',5,2018,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (49,52,'Honda','Civic','Blue','NJ-1049',5,2020,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (50,2,'Honda','Civic','Red','NJ-1050',6,2010,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (51,64,'Toyota','Camry','Silver','NJ-1051',5,2024,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (52,19,'Ford','Focus','Gold','NJ-1052',4,2025,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (53,49,'Hyundai','Elantra','White','NJ-1053',4,2015,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (54,49,'Nissan','Altima','Black','NJ-1054',5,2011,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (55,1003,'Hyundai','Elantra','Black','NJ-1055',5,2013,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (56,98,'Toyota','Camry','Maroon','NJ-1056',5,2023,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (57,20,'Hyundai','Elantra','Gold','NJ-1057',5,2011,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (58,30,'Tesla','Model 3','Black','NJ-1058',5,2025,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (59,2,'Tesla','Model Y','Silver','NJ-1059',5,2010,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (60,1003,'Tesla','Model Y','Gray','NJ-1060',5,2013,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (61,55,'Chevrolet','Malibu','Maroon','NJ-1061',5,2023,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (62,54,'Tesla','Model Y','Gold','NJ-1062',6,2014,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (63,20,'Tesla','Model 3','Gray','NJ-1063',5,2023,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (64,104,'Chevrolet','Malibu','Blue','NJ-1064',5,2019,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (65,98,'Tesla','Model 3','Gold','NJ-1065',6,2025,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (66,40,'Ford','Focus','Gold','NJ-1066',5,2016,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (67,64,'Ford','Focus','Red','NJ-1067',5,2011,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (68,83,'Nissan','Altima','Green','NJ-1068',5,2014,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (69,4,'Tesla','Model 3','Maroon','NJ-1069',6,2020,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (70,98,'Nissan','Altima','Gray','NJ-1070',6,2024,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (71,81,'Tesla','Model 3','Green','NJ-1071',4,2012,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (72,86,'Chevrolet','Malibu','White','NJ-1072',6,2022,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (73,93,'Hyundai','Elantra','Maroon','NJ-1073',5,2020,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (74,98,'Toyota','Camry','Gray','NJ-1074',4,2019,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (75,83,'Honda','Civic','Green','NJ-1075',4,2013,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (76,17,'Chevrolet','Malibu','Blue','NJ-1076',4,2020,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (77,32,'Hyundai','Elantra','White','NJ-1077',5,2014,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (78,58,'Ford','Focus','Silver','NJ-1078',4,2015,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (79,68,'Ford','Focus','Red','NJ-1079',5,2014,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (80,53,'Chevrolet','Malibu','Gold','NJ-1080',5,2010,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (81,101,'Chevrolet','Malibu','Maroon','NJ-1081',4,2012,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (82,43,'Chevrolet','Malibu','Green','NJ-1082',5,2024,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (83,20,'Ford','Focus','Gold','NJ-1083',4,2017,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (84,64,'Hyundai','Elantra','Black','NJ-1084',5,2010,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (85,30,'Toyota','Camry','Blue','NJ-1085',6,2025,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (86,85,'Tesla','Model Y','White','NJ-1086',4,2016,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (87,78,'Tesla','Model 3','Gray','NJ-1087',4,2019,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (88,4,'Hyundai','Elantra','Silver','NJ-1088',4,2019,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (89,83,'Ford','Focus','Silver','NJ-1089',4,2014,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (90,58,'Chevrolet','Malibu','Silver','NJ-1090',5,2025,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (91,83,'Hyundai','Elantra','Gray','NJ-1091',5,2012,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (92,84,'Tesla','Model Y','Green','NJ-1092',6,2021,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (93,86,'Ford','Focus','Blue','NJ-1093',5,2013,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (94,45,'Chevrolet','Malibu','Green','NJ-1094',5,2013,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (95,54,'Toyota','Camry','Maroon','NJ-1095',5,2017,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (96,70,'Nissan','Altima','Black','NJ-1096',5,2013,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (97,4,'Toyota','Camry','Black','NJ-1097',5,2013,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (98,27,'Tesla','Model 3','Green','NJ-1098',5,2021,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (99,65,'Tesla','Model 3','Green','NJ-1099',4,2025,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (104,30,'Toyota','Camry','White','NJ-1104',4,2024,'garage kept'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (105,27,'Hyundai','Elantra','Gray','NJ-1105',5,2021,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (106,48,'Chevrolet','Malibu','Red','NJ-1106',4,2024,'n/a'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (107,76,'Tesla','Model Y','Blue','NJ-1107',4,2020,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (108,10,'Tesla','Model Y','Gray','NJ-1108',6,2016,'student vehicle'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (109,89,'Tesla','Model Y','White','NJ-1109',4,2010,'rideshare-ready'); +INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (110,93,'Tesla','Model 3','Silver','NJ-1110',6,2018,'student vehicle'); -- LOCATION INSERT INTO LOCATION (location_id,name,address) VALUES (1,'Drexel Main Building','3141 Chestnut St'); @@ -222,212 +231,215 @@ INSERT INTO LOCATION (location_id,name,address) VALUES (5,'Queen Lane Campus','2 INSERT INTO LOCATION (location_id,name,address) VALUES (6,'Vidas Athletic Complex','43rd & Powelton'); INSERT INTO LOCATION (location_id,name,address) VALUES (7,'Cira Green','129 S 30th St'); INSERT INTO LOCATION (location_id,name,address) VALUES (8,'Wawa 34th Market','3400 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (9,'Philly Point 9','2044 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (10,'Philly Point 10','1943 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (11,'Philly Point 11','1161 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (12,'Philly Point 12','3179 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (13,'Philly Point 13','1112 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (14,'Philly Point 14','3540 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (15,'Philly Point 15','2711 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (16,'Philly Point 16','1235 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (17,'Philly Point 17','3236 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (18,'Philly Point 18','3285 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (19,'Philly Point 19','2235 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (20,'Philly Point 20','2084 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (21,'Philly Point 21','2667 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (22,'Philly Point 22','1079 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (23,'Philly Point 23','1224 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (24,'Philly Point 24','1901 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (25,'Philly Point 25','417 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (26,'Philly Point 26','3022 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (27,'Philly Point 27','1270 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (28,'Philly Point 28','1060 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (29,'Philly Point 29','1212 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (30,'Philly Point 30','1475 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (31,'Philly Point 31','1409 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (32,'Philly Point 32','3757 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (33,'Philly Point 33','2312 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (34,'Philly Point 34','430 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (35,'Philly Point 35','666 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (36,'Philly Point 36','717 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (37,'Philly Point 37','1047 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (38,'Philly Point 38','1668 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (39,'Philly Point 39','2942 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (40,'Philly Point 40','725 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (41,'Philly Point 41','2993 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (42,'Philly Point 42','976 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (43,'Philly Point 43','363 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (44,'Philly Point 44','1799 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (45,'Philly Point 45','1769 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (46,'Philly Point 46','1455 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (47,'Philly Point 47','2322 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (48,'Philly Point 48','2008 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (49,'Philly Point 49','1803 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (50,'Philly Point 50','355 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (51,'Philly Point 51','947 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (52,'Philly Point 52','3511 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (53,'Philly Point 53','1820 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (54,'Philly Point 54','1695 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (55,'Philly Point 55','3806 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (56,'Philly Point 56','3253 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (57,'Philly Point 57','2492 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (58,'Philly Point 58','3975 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (59,'Philly Point 59','2948 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (60,'Philly Point 60','180 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (61,'Philly Point 61','3609 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (62,'Philly Point 62','3707 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (63,'Philly Point 63','3235 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (64,'Philly Point 64','2458 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (65,'Philly Point 65','1658 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (66,'Philly Point 66','2053 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (67,'Philly Point 67','124 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (68,'Philly Point 68','3961 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (69,'Philly Point 69','1540 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (70,'Philly Point 70','1323 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (71,'Philly Point 71','3186 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (72,'Philly Point 72','1697 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (73,'Philly Point 73','3595 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (74,'Philly Point 74','3752 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (75,'Philly Point 75','3523 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (76,'Philly Point 76','1816 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (77,'Philly Point 77','2304 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (78,'Philly Point 78','3161 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (79,'Philly Point 79','3109 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (80,'Philly Point 80','2336 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (81,'Philly Point 81','3376 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (82,'Philly Point 82','2570 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (83,'Philly Point 83','3777 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (84,'Philly Point 84','1003 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (85,'Philly Point 85','2099 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (86,'Philly Point 86','998 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (87,'Philly Point 87','1217 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (88,'Philly Point 88','1885 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (89,'Philly Point 89','2089 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (90,'Philly Point 90','218 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (91,'Philly Point 91','1692 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (92,'Philly Point 92','1476 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (93,'Philly Point 93','2839 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (94,'Philly Point 94','2881 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (95,'Philly Point 95','3369 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (96,'Philly Point 96','1756 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (97,'Philly Point 97','3066 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (98,'Philly Point 98','776 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (99,'Philly Point 99','3542 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (100,'Philly Point 100','2014 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (101,'Philly Point 101','3866 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (102,'Philly Point 102','622 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (103,'Philly Point 103','2648 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (104,'Philly Point 104','2287 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (105,'Philly Point 105','210 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (106,'Philly Point 106','3814 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (107,'Philly Point 107','1713 Market St'); -INSERT INTO LOCATION (location_id,name,address) VALUES (108,'Philly Point 108','2524 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (9,'PISB - Papadakis Integrated Sciences Building','3245 Chestnut St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (10,'Drexel Recreation Center','3301 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (11,'Liberty Place','1625 Chestnut St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (12,'Philly Point 12','2044 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (13,'Philly Point 13','1943 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (14,'Philly Point 14','1161 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (15,'Philly Point 15','3179 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (16,'Philly Point 16','1112 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (17,'Philly Point 17','3540 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (18,'Philly Point 18','2711 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (19,'Philly Point 19','1235 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (20,'Philly Point 20','3236 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (21,'Philly Point 21','3285 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (22,'Philly Point 22','2235 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (23,'Philly Point 23','2084 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (24,'Philly Point 24','2667 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (25,'Philly Point 25','1079 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (26,'Philly Point 26','1224 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (27,'Philly Point 27','1901 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (28,'Philly Point 28','417 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (29,'Philly Point 29','3022 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (30,'Philly Point 30','1270 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (31,'Philly Point 31','1060 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (32,'Philly Point 32','1212 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (33,'Philly Point 33','1475 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (34,'Philly Point 34','1409 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (35,'Philly Point 35','3757 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (36,'Philly Point 36','2312 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (37,'Philly Point 37','430 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (38,'Philly Point 38','666 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (39,'Philly Point 39','717 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (40,'Philly Point 40','1047 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (41,'Philly Point 41','1668 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (42,'Philly Point 42','2942 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (43,'Philly Point 43','725 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (44,'Philly Point 44','2993 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (45,'Philly Point 45','976 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (46,'Philly Point 46','363 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (47,'Philly Point 47','1799 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (48,'Philly Point 48','1769 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (49,'Philly Point 49','1455 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (50,'Philly Point 50','2322 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (51,'Philly Point 51','2008 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (52,'Philly Point 52','1803 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (53,'Philly Point 53','355 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (54,'Philly Point 54','947 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (55,'Philly Point 55','3511 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (56,'Philly Point 56','1820 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (57,'Philly Point 57','1695 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (58,'Philly Point 58','3806 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (59,'Philly Point 59','3253 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (60,'Philly Point 60','2492 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (61,'Philly Point 61','3975 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (62,'Philly Point 62','2948 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (63,'Philly Point 63','180 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (64,'Philly Point 64','3609 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (65,'Philly Point 65','3707 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (66,'Philly Point 66','3235 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (67,'Philly Point 67','2458 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (68,'Philly Point 68','1658 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (69,'Philly Point 69','2053 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (70,'Philly Point 70','124 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (71,'Philly Point 71','3961 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (72,'Philly Point 72','1540 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (73,'Philly Point 73','1323 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (74,'Philly Point 74','3186 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (75,'Philly Point 75','1697 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (76,'Philly Point 76','3595 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (77,'Philly Point 77','3752 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (78,'Philly Point 78','3523 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (79,'Philly Point 79','1816 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (80,'Philly Point 80','2304 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (81,'Philly Point 81','3161 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (82,'Philly Point 82','3109 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (83,'Philly Point 83','2336 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (84,'Philly Point 84','3376 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (85,'Philly Point 85','2570 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (86,'Philly Point 86','3777 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (87,'Philly Point 87','1003 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (88,'Philly Point 88','2099 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (89,'Philly Point 89','998 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (90,'Philly Point 90','1217 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (91,'Philly Point 91','1885 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (92,'Philly Point 92','2089 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (93,'Philly Point 93','218 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (94,'Philly Point 94','1692 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (95,'Philly Point 95','1476 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (96,'Philly Point 96','2839 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (97,'Philly Point 97','2881 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (98,'Philly Point 98','3369 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (99,'Philly Point 99','1756 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (100,'Philly Point 100','3066 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (101,'Philly Point 101','776 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (102,'Philly Point 102','3542 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (103,'Philly Point 103','2014 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (104,'Philly Point 104','3866 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (105,'Philly Point 105','622 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (106,'Philly Point 106','2648 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (107,'Philly Point 107','2287 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (108,'Philly Point 108','210 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (109,'Philly Point 109','3814 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (110,'Philly Point 110','1713 Market St'); +INSERT INTO LOCATION (location_id,name,address) VALUES (111,'Philly Point 111','2524 Market St'); -- RIDE_OFFER INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (1,5,3,3,8,'2025-10-16T01:45:00',2,4.17,0.72,'closed','2025-10-15T01:45:00'); INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (2,1,6,6,10,'2025-10-15T18:45:00',1,5.31,0.86,'closed','2025-10-14T18:45:00'); INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (3,12,1,2,3,'2025-10-14T13:00:00',2,4.63,1.13,'open','2025-10-13T13:00:00'); INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (4,6,2,3,8,'2025-10-14T17:45:00',3,8.52,0.9,'closed','2025-10-13T17:45:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (5,22,102,68,15,'2025-11-29T02:30:00',2,7.67,1.06,'open','2025-11-26T02:30:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (6,80,56,94,101,'2025-11-24T16:19:00',2,6.86,0.53,'closed','2025-11-18T16:19:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (7,58,76,63,92,'2025-11-10T02:57:00',1,4.93,0.66,'open','2025-11-05T02:57:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (8,54,82,59,65,'2025-11-20T13:06:00',4,3.69,1.22,'closed','2025-11-17T13:06:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (9,58,76,30,53,'2025-11-03T14:25:00',2,7.38,0.84,'open','2025-10-30T14:25:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (10,53,32,88,61,'2025-10-17T02:53:00',1,3.56,0.61,'closed','2025-10-15T02:53:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (11,75,25,76,72,'2025-11-17T10:42:00',1,5.46,1.46,'closed','2025-11-11T10:42:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (12,89,31,37,77,'2025-11-01T11:06:00',3,6.04,0.67,'open','2025-10-31T11:06:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (13,52,23,15,98,'2025-10-30T18:14:00',2,8.07,1.57,'closed','2025-10-30T18:14:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (14,79,46,4,24,'2025-10-30T22:48:00',2,8.53,0.89,'open','2025-10-28T22:48:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (15,76,66,9,19,'2025-11-29T20:01:00',1,7.48,0.74,'open','2025-11-24T20:01:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (16,28,77,40,93,'2025-11-02T18:38:00',1,8.3,0.67,'closed','2025-11-02T18:38:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (17,85,85,35,57,'2025-11-24T13:31:00',3,5.65,0.8,'closed','2025-11-23T13:31:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (18,52,23,15,37,'2025-11-25T21:37:00',2,6.16,0.84,'open','2025-11-25T21:37:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (19,6,3,39,28,'2025-10-21T08:18:00',2,3.72,1.05,'open','2025-10-19T08:18:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (20,54,82,65,72,'2025-11-24T11:04:00',2,8.17,0.55,'open','2025-11-23T11:04:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (21,49,54,74,52,'2025-11-27T20:26:00',2,3.69,0.52,'open','2025-11-20T20:26:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (22,104,12,12,56,'2025-10-19T07:27:00',3,5.4,0.59,'closed','2025-10-14T07:27:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (23,37,11,100,22,'2025-10-17T16:40:00',1,6.18,0.71,'closed','2025-10-12T16:40:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (24,89,31,31,14,'2025-10-22T08:12:00',1,6.61,1.34,'closed','2025-10-20T08:12:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (25,98,84,60,97,'2025-11-18T18:28:00',4,8.54,1.12,'closed','2025-11-13T18:28:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (26,81,73,20,57,'2025-10-17T15:28:00',4,4.82,0.8,'open','2025-10-16T15:28:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (27,48,81,58,5,'2025-10-16T11:53:00',2,3.46,1.45,'closed','2025-10-10T11:53:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (28,61,9,58,104,'2025-11-18T20:12:00',2,6.63,1.05,'closed','2025-11-11T20:12:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (29,20,34,92,11,'2025-11-14T20:11:00',1,4.49,0.98,'open','2025-11-12T20:11:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (30,53,92,37,50,'2025-11-08T10:43:00',3,3.31,1.19,'open','2025-11-03T10:43:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (31,20,34,37,33,'2025-11-28T21:58:00',3,8.24,0.87,'open','2025-11-26T21:58:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (32,52,23,84,90,'2025-11-24T12:08:00',3,7.25,0.59,'open','2025-11-19T12:08:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (33,103,36,86,90,'2025-11-29T21:58:00',3,3.56,1.24,'open','2025-11-29T21:58:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (34,53,92,24,28,'2025-11-03T15:12:00',1,9.0,0.67,'open','2025-11-02T15:12:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (35,66,99,70,107,'2025-11-29T16:02:00',4,5.02,1.34,'open','2025-11-23T16:02:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (36,27,37,24,107,'2025-11-26T19:51:00',1,7.33,0.55,'open','2025-11-23T19:51:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (37,59,80,37,97,'2025-11-29T14:14:00',3,4.44,1.56,'closed','2025-11-26T14:14:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (38,53,92,60,99,'2025-10-31T12:32:00',3,5.51,0.68,'open','2025-10-29T12:32:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (39,43,10,83,62,'2025-11-05T17:59:00',1,7.27,1.07,'open','2025-11-04T17:59:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (40,93,40,35,58,'2025-11-14T04:53:00',2,3.55,1.5,'closed','2025-11-09T04:53:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (41,7,58,7,51,'2025-11-14T11:15:00',2,8.97,0.91,'open','2025-11-13T11:15:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (42,104,12,102,19,'2025-10-21T01:18:00',2,7.18,0.65,'closed','2025-10-14T01:18:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (43,80,56,11,3,'2025-10-29T06:53:00',1,6.29,1.3,'open','2025-10-28T06:53:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (44,98,84,31,39,'2025-10-20T01:15:00',2,6.83,1.19,'open','2025-10-13T01:15:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (45,79,46,81,66,'2025-11-18T07:45:00',1,4.75,0.5,'open','2025-11-12T07:45:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (46,30,8,68,47,'2025-10-17T16:34:00',3,7.72,1.06,'open','2025-10-10T16:34:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (47,10,86,48,33,'2025-11-29T00:22:00',1,5.07,1.31,'closed','2025-11-24T00:22:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (48,22,102,46,70,'2025-11-03T20:11:00',4,5.79,1.26,'closed','2025-11-01T20:11:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (49,17,92,100,59,'2025-10-15T09:12:00',1,7.75,1.47,'open','2025-10-11T09:12:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (50,66,51,105,70,'2025-11-12T08:02:00',4,4.15,0.89,'closed','2025-11-07T08:02:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (51,45,21,103,48,'2025-11-09T12:47:00',2,8.37,0.93,'closed','2025-11-02T12:47:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (52,86,47,48,103,'2025-11-15T08:51:00',1,7.36,0.59,'closed','2025-11-13T08:51:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (53,70,17,42,14,'2025-10-18T10:42:00',2,4.84,1.16,'open','2025-10-11T10:42:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (54,52,23,6,94,'2025-11-04T19:27:00',2,6.84,1.37,'open','2025-10-29T19:27:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (55,53,32,4,19,'2025-11-20T21:50:00',2,3.21,0.57,'closed','2025-11-15T21:50:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (56,53,92,73,5,'2025-11-20T04:43:00',2,8.69,0.91,'closed','2025-11-18T04:43:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (57,68,88,51,41,'2025-11-23T08:15:00',1,3.16,0.7,'open','2025-11-22T08:15:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (58,43,29,91,58,'2025-10-26T19:18:00',4,8.5,1.04,'open','2025-10-25T19:18:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (59,59,39,92,57,'2025-10-18T21:54:00',2,7.01,1.28,'open','2025-10-14T21:54:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (60,48,15,92,91,'2025-11-26T20:11:00',2,6.05,0.63,'open','2025-11-24T20:11:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (61,40,96,4,47,'2025-11-17T18:23:00',2,7.82,1.11,'closed','2025-11-16T18:23:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (62,17,40,51,92,'2025-11-28T15:33:00',2,7.61,1.41,'open','2025-11-23T15:33:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (63,83,10,58,60,'2025-11-25T16:22:00',1,8.27,1.36,'closed','2025-11-23T16:22:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (64,98,84,56,65,'2025-10-16T03:33:00',1,4.82,0.68,'closed','2025-10-13T03:33:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (65,52,23,11,33,'2025-10-25T20:35:00',2,3.75,0.83,'open','2025-10-23T20:35:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (66,78,97,22,85,'2025-11-21T23:57:00',3,5.03,1.52,'open','2025-11-21T23:57:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (67,19,26,83,99,'2025-11-18T08:41:00',1,7.6,0.96,'closed','2025-11-11T08:41:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (68,81,94,38,83,'2025-11-01T15:15:00',4,5.44,1.0,'closed','2025-10-30T15:15:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (69,59,69,62,60,'2025-10-26T10:38:00',1,4.88,1.29,'closed','2025-10-21T10:38:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (70,55,100,98,48,'2025-11-14T17:06:00',2,4.45,0.63,'open','2025-11-12T17:06:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (71,20,16,38,50,'2025-11-21T13:15:00',1,7.88,1.53,'closed','2025-11-18T13:15:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (72,93,40,64,66,'2025-11-11T15:56:00',2,5.98,0.6,'open','2025-11-04T15:56:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (73,40,41,75,46,'2025-10-16T01:18:00',2,6.59,1.43,'closed','2025-10-12T01:18:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (74,70,17,14,56,'2025-10-21T08:46:00',2,7.58,0.9,'open','2025-10-18T08:46:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (75,53,92,10,50,'2025-11-14T14:48:00',3,4.68,1.59,'closed','2025-11-13T14:48:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (76,22,102,51,48,'2025-11-03T17:23:00',1,4.19,1.06,'open','2025-11-03T17:23:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (77,10,68,92,43,'2025-11-12T16:29:00',1,6.64,1.07,'open','2025-11-07T16:29:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (78,28,77,79,95,'2025-11-01T18:21:00',3,7.97,1.09,'closed','2025-10-28T18:21:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (79,64,50,48,43,'2025-11-25T03:26:00',3,4.85,1.49,'closed','2025-11-25T03:26:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (80,79,46,34,84,'2025-11-19T18:14:00',4,3.31,1.03,'open','2025-11-13T18:14:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (81,27,104,32,5,'2025-11-18T22:07:00',1,3.11,0.85,'open','2025-11-15T22:07:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (82,56,70,100,79,'2025-11-12T23:46:00',1,7.23,1.07,'open','2025-11-05T23:46:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (83,68,88,41,23,'2025-11-11T17:21:00',3,5.13,1.35,'closed','2025-11-07T17:21:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (84,80,71,25,32,'2025-10-30T17:19:00',1,8.65,1.58,'closed','2025-10-27T17:19:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (85,86,47,41,62,'2025-11-04T17:59:00',4,4.64,0.63,'closed','2025-10-29T17:59:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (86,55,100,99,103,'2025-10-22T09:02:00',2,8.73,0.59,'closed','2025-10-18T09:02:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (87,91,87,28,26,'2025-11-16T08:59:00',3,7.18,0.65,'closed','2025-11-13T08:59:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (88,40,41,86,68,'2025-10-27T20:14:00',1,3.6,0.86,'open','2025-10-25T20:14:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (89,6,3,53,84,'2025-11-12T15:41:00',1,7.54,0.82,'open','2025-11-12T15:41:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (90,98,84,84,74,'2025-10-27T17:47:00',4,8.1,0.54,'closed','2025-10-21T17:47:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (91,104,12,5,108,'2025-11-07T15:11:00',4,8.77,1.52,'closed','2025-11-07T15:11:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (92,48,15,43,37,'2025-11-11T20:34:00',3,5.96,1.53,'closed','2025-11-04T20:34:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (93,45,45,104,15,'2025-11-03T05:46:00',2,6.89,1.29,'open','2025-10-29T05:46:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (94,100,63,73,87,'2025-11-30T06:11:00',3,8.14,1.49,'closed','2025-11-25T06:11:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (95,19,51,86,13,'2025-10-24T04:30:00',2,8.61,0.51,'open','2025-10-17T04:30:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (96,93,74,43,39,'2025-11-19T23:36:00',1,4.57,0.9,'open','2025-11-18T23:36:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (97,61,20,21,52,'2025-11-25T16:59:00',4,7.62,1.26,'closed','2025-11-21T16:59:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (98,53,32,29,18,'2025-11-12T04:29:00',4,8.93,0.91,'closed','2025-11-05T04:29:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (99,93,90,103,86,'2025-10-26T07:43:00',3,8.24,0.59,'open','2025-10-21T07:43:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (100,17,73,15,8,'2025-11-17T16:12:00',3,6.22,0.68,'closed','2025-11-10T16:12:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (101,21,88,27,92,'2025-11-19T15:05:00',3,5.67,0.56,'open','2025-11-13T15:05:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (102,61,9,72,60,'2025-11-25T09:46:00',1,5.38,1.4,'closed','2025-11-24T09:46:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (103,10,86,45,90,'2025-10-17T17:03:00',1,8.92,1.02,'open','2025-10-15T17:03:00'); -INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (104,98,84,99,83,'2025-11-28T20:26:00',2,8.96,0.92,'closed','2025-11-22T20:26:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (5,7,46,17,2,'2025-11-04T07:37:00',2,3.09,0.79,'open','2025-10-29T07:37:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (6,58,25,96,9,'2025-11-12T14:49:00',2,6.08,0.62,'open','2025-11-12T14:49:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (7,81,71,59,83,'2025-10-14T01:30:00',2,5.56,0.62,'closed','2025-10-07T01:30:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (8,1001,102,42,78,'2025-10-22T02:08:00',2,6.75,1.14,'closed','2025-10-16T02:08:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (9,66,64,38,59,'2025-11-14T19:27:00',1,7.76,0.63,'closed','2025-11-11T19:27:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (10,50,54,30,53,'2025-11-03T14:25:00',2,7.38,0.84,'open','2025-10-30T14:25:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (11,45,33,88,61,'2025-10-17T02:53:00',1,3.56,0.61,'closed','2025-10-15T02:53:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (12,61,26,76,72,'2025-11-17T10:42:00',1,5.46,1.46,'closed','2025-11-11T10:42:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (13,98,56,37,77,'2025-11-01T11:06:00',3,6.04,0.67,'open','2025-10-31T11:06:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (14,43,82,15,98,'2025-10-30T18:14:00',2,8.07,1.57,'closed','2025-10-30T18:14:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (15,66,81,107,89,'2025-10-30T00:11:00',2,7.22,0.84,'open','2025-10-30T00:11:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (16,19,8,73,85,'2025-11-07T02:09:00',4,6.8,0.53,'closed','2025-11-04T02:09:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (17,46,50,59,44,'2025-10-23T11:19:00',4,4.95,1.54,'open','2025-10-23T11:19:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (18,16,38,97,80,'2025-10-16T21:05:00',2,5.66,0.97,'closed','2025-10-10T21:05:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (19,30,58,97,66,'2025-10-20T11:27:00',1,4.7,1.25,'open','2025-10-16T11:27:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (20,4,44,51,77,'2025-10-16T00:13:00',2,8.67,1.34,'closed','2025-10-12T00:13:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (21,37,12,1,64,'2025-11-29T13:11:00',1,5.28,1.27,'open','2025-11-24T13:11:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (22,1001,102,111,95,'2025-10-15T13:01:00',2,8.53,1.45,'open','2025-10-09T13:01:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (23,80,78,54,38,'2025-10-20T12:01:00',2,4.03,1.54,'open','2025-10-15T12:01:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (24,1003,55,109,14,'2025-10-28T13:37:00',2,6.14,0.94,'open','2025-10-23T13:37:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (25,22,11,100,22,'2025-10-17T16:40:00',1,6.18,0.71,'closed','2025-10-12T16:40:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (26,81,32,31,14,'2025-10-22T08:12:00',1,6.61,1.34,'closed','2025-10-20T08:12:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (27,85,86,60,97,'2025-11-18T18:28:00',4,8.54,1.12,'closed','2025-11-13T18:28:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (28,98,70,20,57,'2025-10-17T15:28:00',4,4.82,0.8,'open','2025-10-16T15:28:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (29,36,56,58,5,'2025-10-16T11:53:00',2,3.46,1.45,'closed','2025-10-10T11:53:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (30,53,9,58,104,'2025-11-18T20:12:00',2,6.63,1.05,'closed','2025-11-11T20:12:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (31,6,35,92,11,'2025-11-14T20:11:00',1,4.49,0.98,'open','2025-11-12T20:11:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (32,45,94,37,50,'2025-11-08T10:43:00',3,3.31,1.19,'open','2025-11-03T10:43:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (33,6,35,37,33,'2025-11-28T21:58:00',3,8.24,0.87,'open','2025-11-26T21:58:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (34,103,40,40,84,'2025-11-26T21:25:00',1,6.57,1.53,'open','2025-11-20T21:25:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (35,75,98,43,105,'2025-10-21T21:44:00',4,8.98,1.5,'open','2025-10-15T21:44:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (36,56,43,3,47,'2025-11-01T05:13:00',2,8.69,1.03,'open','2025-10-30T05:13:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (37,16,38,38,109,'2025-10-19T16:49:00',3,8.01,1.47,'open','2025-10-14T16:49:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (38,100,95,80,17,'2025-11-20T12:09:00',1,4.09,1.26,'closed','2025-11-18T12:09:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (39,81,71,6,53,'2025-11-05T21:46:00',1,8.82,1.17,'closed','2025-10-29T21:46:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (40,22,11,40,104,'2025-11-12T06:23:00',4,8.68,1.58,'open','2025-11-08T06:23:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (41,85,86,65,68,'2025-11-08T05:52:00',1,7.81,0.65,'open','2025-11-01T05:52:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (42,98,70,71,14,'2025-11-27T16:54:00',1,4.71,1.34,'open','2025-11-25T16:54:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (43,91,12,12,29,'2025-11-10T11:59:00',1,5.49,0.94,'open','2025-11-04T11:59:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (44,1003,55,29,4,'2025-11-02T03:53:00',4,6.9,1.37,'open','2025-10-29T03:53:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (45,103,40,90,107,'2025-10-21T22:30:00',2,6.69,1.5,'open','2025-10-18T22:30:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (46,91,12,71,94,'2025-11-20T16:27:00',1,7.66,0.76,'open','2025-11-17T16:27:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (47,49,54,9,15,'2025-11-13T19:34:00',1,6.79,1.13,'closed','2025-11-09T19:34:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (48,50,1,79,46,'2025-10-28T18:26:00',1,6.99,0.59,'closed','2025-10-27T18:26:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (49,58,90,65,101,'2025-11-14T17:01:00',2,8.24,0.55,'open','2025-11-09T17:01:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (50,28,10,46,101,'2025-10-17T11:15:00',4,6.94,0.61,'open','2025-10-12T11:15:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (51,10,108,46,70,'2025-11-03T20:11:00',4,5.79,1.26,'closed','2025-11-01T20:11:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (52,1001,102,5,38,'2025-10-25T01:50:00',1,8.31,0.85,'open','2025-10-19T01:50:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (53,89,109,33,5,'2025-11-30T20:12:00',2,5.14,1.36,'closed','2025-11-25T20:12:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (54,30,58,103,48,'2025-11-09T12:47:00',2,8.37,0.93,'closed','2025-11-02T12:47:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (55,79,60,48,103,'2025-11-15T08:51:00',1,7.36,0.59,'closed','2025-11-13T08:51:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (56,59,34,42,14,'2025-10-18T10:42:00',2,4.84,1.16,'open','2025-10-11T10:42:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (57,43,82,6,94,'2025-11-04T19:27:00',2,6.84,1.37,'open','2025-10-29T19:27:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (58,45,33,4,19,'2025-11-20T21:50:00',2,3.21,0.57,'closed','2025-11-15T21:50:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (59,45,94,73,5,'2025-11-20T04:43:00',2,8.69,0.91,'closed','2025-11-18T04:43:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (60,58,78,51,41,'2025-11-23T08:15:00',1,3.16,0.7,'open','2025-11-22T08:15:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (61,28,30,91,58,'2025-10-26T19:18:00',4,8.5,1.04,'open','2025-10-25T19:18:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (62,52,49,92,57,'2025-10-18T21:54:00',2,7.01,1.28,'open','2025-10-14T21:54:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (63,101,81,109,21,'2025-11-27T22:59:00',4,6.83,1.37,'open','2025-11-26T22:59:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (64,20,57,31,102,'2025-11-13T00:23:00',3,6.43,1.01,'closed','2025-11-11T00:23:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (65,68,20,9,40,'2025-11-07T22:46:00',2,6.15,1.35,'closed','2025-11-06T22:46:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (66,10,108,83,10,'2025-11-10T14:43:00',3,5.07,1.47,'closed','2025-11-08T14:43:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (67,85,86,56,65,'2025-10-16T03:33:00',1,4.82,0.68,'closed','2025-10-13T03:33:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (68,43,82,109,11,'2025-10-29T06:40:00',3,4.65,1.19,'closed','2025-10-28T06:40:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (69,56,79,22,76,'2025-11-19T04:10:00',4,6.75,1.49,'open','2025-11-19T04:10:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (70,89,37,11,6,'2025-11-23T18:16:00',4,4.26,1.13,'closed','2025-11-23T18:16:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (71,55,61,83,39,'2025-11-12T07:51:00',4,5.44,1.0,'closed','2025-11-10T07:51:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (72,52,49,62,60,'2025-10-26T10:38:00',1,4.88,1.29,'closed','2025-10-21T10:38:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (73,48,106,98,48,'2025-11-14T17:06:00',2,4.45,0.63,'open','2025-11-12T17:06:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (74,6,3,38,50,'2025-11-21T13:15:00',1,7.88,1.53,'closed','2025-11-18T13:15:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (75,84,22,64,66,'2025-11-11T15:56:00',2,5.98,0.6,'open','2025-11-04T15:56:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (76,27,98,75,46,'2025-10-16T01:18:00',2,6.59,1.43,'closed','2025-10-12T01:18:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (77,59,2,109,14,'2025-11-09T04:56:00',2,7.37,1.34,'open','2025-11-03T04:56:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (78,5,2,47,71,'2025-10-31T02:24:00',3,5.7,1.1,'closed','2025-10-30T02:24:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (79,10,108,51,48,'2025-11-03T17:23:00',1,4.19,1.06,'open','2025-11-03T17:23:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (80,4,44,92,43,'2025-11-12T16:29:00',1,6.64,1.07,'open','2025-11-07T16:29:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (81,17,43,79,95,'2025-11-01T18:21:00',3,7.97,1.09,'closed','2025-10-28T18:21:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (82,54,62,48,43,'2025-11-25T03:26:00',3,4.85,1.49,'closed','2025-11-25T03:26:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (83,66,57,34,84,'2025-11-19T18:14:00',4,3.31,1.03,'open','2025-11-13T18:14:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (84,16,38,5,74,'2025-11-26T03:12:00',1,5.65,0.96,'open','2025-11-23T03:12:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (85,49,54,109,95,'2025-11-28T01:45:00',1,6.11,1.12,'closed','2025-11-21T01:45:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (86,58,78,41,23,'2025-11-11T17:21:00',3,5.13,1.35,'closed','2025-11-07T17:21:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (87,68,79,25,32,'2025-10-30T17:19:00',1,8.65,1.58,'closed','2025-10-27T17:19:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (88,79,87,63,41,'2025-11-12T11:35:00',4,4.64,0.63,'closed','2025-11-06T11:35:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (89,101,81,105,45,'2025-10-22T09:02:00',2,8.73,0.59,'closed','2025-10-18T09:02:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (90,83,75,28,26,'2025-11-16T08:59:00',3,7.18,0.65,'closed','2025-11-13T08:59:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (91,27,98,86,68,'2025-10-27T20:14:00',1,3.6,0.86,'open','2025-10-25T20:14:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (92,1,100,53,84,'2025-11-12T15:41:00',1,7.54,0.82,'open','2025-11-12T15:41:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (93,103,40,84,74,'2025-10-27T17:47:00',4,8.1,0.54,'closed','2025-10-21T17:47:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (94,100,108,23,5,'2025-11-07T15:11:00',4,8.77,1.52,'closed','2025-11-07T15:11:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (95,36,69,78,14,'2025-11-03T09:29:00',4,6.26,1.04,'closed','2025-10-27T09:29:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (96,30,58,104,15,'2025-11-03T05:46:00',2,6.89,1.29,'open','2025-10-29T05:46:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (97,86,93,73,87,'2025-11-30T06:11:00',3,8.14,1.49,'closed','2025-11-25T06:11:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (98,1003,55,86,13,'2025-10-24T04:30:00',2,8.61,0.51,'open','2025-10-17T04:30:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (99,84,92,43,39,'2025-11-19T23:36:00',1,4.57,0.9,'open','2025-11-18T23:36:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (100,53,80,21,52,'2025-11-25T16:59:00',4,7.62,1.26,'closed','2025-11-21T16:59:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (101,45,33,29,18,'2025-11-12T04:29:00',4,8.93,0.91,'closed','2025-11-05T04:29:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (102,84,22,98,32,'2025-11-25T19:55:00',1,6.15,1.08,'open','2025-11-24T19:55:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (103,5,2,74,69,'2025-10-22T05:20:00',3,5.65,1.25,'closed','2025-10-15T05:20:00'); +INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (104,1003,60,58,104,'2025-10-16T14:08:00',3,5.49,1.12,'open','2025-10-12T14:08:00'); -- RIDE_REQUEST INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (1,11,7,10,'2025-10-14T19:05:00','2025-10-14T19:45:00',1,'cancelled','2025-10-13T19:15:00'); @@ -436,196 +448,253 @@ INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_locatio INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (4,13,6,7,'2025-10-13T23:50:00','2025-10-14T00:30:00',2,'matched','2025-10-13T00:00:00'); INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (5,10,7,2,'2025-10-15T21:45:00','2025-10-15T21:55:00',1,'open','2025-10-14T21:45:00'); INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (6,11,3,2,'2025-10-15T03:00:00','2025-10-15T03:10:00',1,'cancelled','2025-10-14T03:00:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (7,75,11,88,'2025-11-24T19:34:00','2025-11-24T19:52:00',2,'matched','2025-11-24T19:34:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (8,37,69,51,'2025-11-15T08:46:00','2025-11-15T09:10:00',1,'matched','2025-11-13T08:46:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (9,94,87,93,'2025-11-16T12:34:00','2025-11-16T13:08:00',1,'open','2025-11-14T12:34:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (10,33,36,25,'2025-11-28T18:07:00','2025-11-28T18:19:00',2,'open','2025-11-28T18:07:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (11,51,27,9,'2025-10-19T15:02:00','2025-10-19T15:40:00',2,'matched','2025-10-19T15:02:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (12,52,95,6,'2025-11-07T13:14:00','2025-11-07T13:58:00',1,'matched','2025-11-07T13:14:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (13,31,65,38,'2025-10-27T19:58:00','2025-10-27T20:54:00',2,'open','2025-10-23T19:58:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (14,67,31,39,'2025-10-22T16:33:00','2025-10-22T16:57:00',1,'open','2025-10-22T16:33:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (15,37,81,87,'2025-11-09T14:31:00','2025-11-09T14:44:00',1,'cancelled','2025-11-04T14:31:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (16,75,101,68,'2025-11-02T17:26:00','2025-11-02T18:02:00',1,'open','2025-11-01T17:26:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (17,95,31,29,'2025-11-01T19:45:00','2025-11-01T20:04:00',1,'cancelled','2025-10-28T19:45:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (18,82,54,72,'2025-11-15T08:24:00','2025-11-15T08:49:00',1,'open','2025-11-10T08:24:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (19,23,58,48,'2025-10-18T14:46:00','2025-10-18T15:08:00',1,'open','2025-10-13T14:46:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (20,14,10,25,'2025-11-30T15:46:00','2025-11-30T16:38:00',2,'open','2025-11-29T15:46:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (21,69,39,2,'2025-10-26T20:12:00','2025-10-26T21:09:00',1,'matched','2025-10-23T20:12:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (22,52,90,78,'2025-11-27T09:25:00','2025-11-27T09:50:00',2,'open','2025-11-25T09:25:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (23,75,60,69,'2025-11-23T11:19:00','2025-11-23T11:45:00',1,'open','2025-11-20T11:19:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (24,94,13,103,'2025-11-28T13:48:00','2025-11-28T14:18:00',1,'open','2025-11-25T13:48:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (25,14,73,29,'2025-11-29T08:01:00','2025-11-29T08:27:00',2,'cancelled','2025-11-25T08:01:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (26,83,38,20,'2025-10-25T11:14:00','2025-10-25T11:48:00',2,'matched','2025-10-22T11:14:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (27,70,33,98,'2025-11-13T17:41:00','2025-11-13T18:38:00',1,'open','2025-11-08T17:41:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (28,72,22,18,'2025-11-28T14:31:00','2025-11-28T14:52:00',2,'cancelled','2025-11-28T14:31:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (29,13,108,10,'2025-11-24T06:48:00','2025-11-24T06:58:00',1,'open','2025-11-19T06:48:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (30,48,9,91,'2025-10-22T06:13:00','2025-10-22T06:55:00',1,'open','2025-10-18T06:13:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (31,96,85,63,'2025-10-14T06:34:00','2025-10-14T07:19:00',1,'open','2025-10-10T06:34:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (32,59,69,37,'2025-10-14T14:52:00','2025-10-14T15:46:00',2,'open','2025-10-13T14:52:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (33,26,13,68,'2025-10-22T09:12:00','2025-10-22T10:01:00',2,'open','2025-10-20T09:12:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (34,57,102,51,'2025-10-18T11:25:00','2025-10-18T12:04:00',2,'open','2025-10-13T11:25:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (35,47,39,88,'2025-10-18T16:54:00','2025-10-18T17:45:00',1,'open','2025-10-15T16:54:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (36,75,71,61,'2025-10-16T16:00:00','2025-10-16T16:54:00',1,'open','2025-10-13T16:00:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (37,69,73,13,'2025-11-15T20:02:00','2025-11-15T20:26:00',1,'cancelled','2025-11-10T20:02:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (38,95,35,6,'2025-10-17T16:59:00','2025-10-17T17:26:00',2,'matched','2025-10-17T16:59:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (39,37,41,3,'2025-10-26T15:09:00','2025-10-26T16:07:00',2,'matched','2025-10-26T15:09:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (40,63,21,73,'2025-10-28T15:53:00','2025-10-28T16:27:00',2,'matched','2025-10-26T15:53:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (41,76,97,95,'2025-10-21T18:44:00','2025-10-21T19:40:00',1,'open','2025-10-19T18:44:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (42,15,13,56,'2025-10-27T19:04:00','2025-10-27T19:35:00',2,'matched','2025-10-23T19:04:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (43,77,100,42,'2025-10-14T16:17:00','2025-10-14T17:17:00',1,'open','2025-10-12T16:17:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (44,75,56,24,'2025-11-25T15:42:00','2025-11-25T16:16:00',1,'matched','2025-11-23T15:42:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (45,52,92,10,'2025-10-18T10:09:00','2025-10-18T10:43:00',2,'matched','2025-10-17T10:09:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (46,76,41,47,'2025-10-19T07:00:00','2025-10-19T07:29:00',1,'open','2025-10-17T07:00:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (47,26,17,12,'2025-10-24T12:28:00','2025-10-24T13:13:00',2,'open','2025-10-24T12:28:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (48,12,12,46,'2025-11-17T07:38:00','2025-11-17T08:26:00',1,'matched','2025-11-17T07:38:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (49,62,53,50,'2025-10-18T17:57:00','2025-10-18T18:42:00',1,'matched','2025-10-17T17:57:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (50,75,22,18,'2025-10-30T10:17:00','2025-10-30T10:58:00',1,'open','2025-10-27T10:17:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (51,59,54,39,'2025-11-12T18:04:00','2025-11-12T18:37:00',1,'cancelled','2025-11-07T18:04:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (52,99,77,79,'2025-10-25T13:06:00','2025-10-25T13:24:00',1,'open','2025-10-22T13:06:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (53,69,108,80,'2025-11-06T18:55:00','2025-11-06T19:26:00',1,'open','2025-11-01T18:55:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (54,31,39,42,'2025-11-20T17:12:00','2025-11-20T17:52:00',1,'open','2025-11-17T17:12:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (55,66,38,95,'2025-11-26T16:31:00','2025-11-26T17:17:00',1,'open','2025-11-24T16:31:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (56,77,47,15,'2025-11-18T09:37:00','2025-11-18T10:21:00',1,'matched','2025-11-14T09:37:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (57,12,94,60,'2025-11-27T09:28:00','2025-11-27T09:56:00',2,'open','2025-11-24T09:28:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (58,99,18,82,'2025-11-01T09:16:00','2025-11-01T10:08:00',1,'matched','2025-10-29T09:16:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (59,22,58,77,'2025-11-12T15:25:00','2025-11-12T16:09:00',2,'cancelled','2025-11-07T15:25:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (60,83,70,5,'2025-11-05T17:57:00','2025-11-05T18:41:00',2,'matched','2025-11-05T17:57:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (61,26,99,32,'2025-11-24T16:22:00','2025-11-24T16:42:00',2,'matched','2025-11-20T16:22:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (62,79,97,43,'2025-11-09T07:00:00','2025-11-09T07:16:00',1,'open','2025-11-04T07:00:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (63,105,72,75,'2025-11-26T15:14:00','2025-11-26T15:52:00',1,'open','2025-11-21T15:14:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (64,101,20,45,'2025-10-14T13:06:00','2025-10-14T13:34:00',1,'cancelled','2025-10-14T13:06:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (65,27,107,94,'2025-10-22T11:19:00','2025-10-22T11:50:00',1,'matched','2025-10-18T11:19:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (66,96,45,61,'2025-10-19T13:46:00','2025-10-19T14:40:00',1,'open','2025-10-17T13:46:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (67,14,104,91,'2025-10-20T06:54:00','2025-10-20T07:25:00',2,'open','2025-10-19T06:54:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (68,52,67,23,'2025-11-17T08:21:00','2025-11-17T09:06:00',1,'cancelled','2025-11-16T08:21:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (69,79,81,24,'2025-10-24T16:42:00','2025-10-24T17:19:00',1,'open','2025-10-20T16:42:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (70,41,58,76,'2025-11-09T12:00:00','2025-11-09T12:55:00',1,'open','2025-11-04T12:00:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (71,18,74,14,'2025-11-16T08:23:00','2025-11-16T08:53:00',1,'open','2025-11-14T08:23:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (72,97,68,82,'2025-11-02T15:24:00','2025-11-02T16:13:00',1,'matched','2025-10-31T15:24:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (73,72,59,80,'2025-10-24T19:43:00','2025-10-24T20:38:00',1,'cancelled','2025-10-20T19:43:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (74,23,87,18,'2025-11-02T07:15:00','2025-11-02T07:44:00',1,'open','2025-10-28T07:15:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (75,32,66,50,'2025-11-08T15:08:00','2025-11-08T15:54:00',1,'open','2025-11-05T15:08:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (76,37,72,22,'2025-11-13T10:08:00','2025-11-13T10:29:00',1,'matched','2025-11-09T10:08:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (77,15,46,2,'2025-11-13T08:12:00','2025-11-13T08:46:00',2,'open','2025-11-10T08:12:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (78,82,88,63,'2025-11-08T17:53:00','2025-11-08T18:31:00',1,'open','2025-11-04T17:53:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (79,12,102,98,'2025-10-27T10:02:00','2025-10-27T10:29:00',1,'open','2025-10-26T10:02:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (80,92,73,96,'2025-11-13T14:32:00','2025-11-13T14:49:00',2,'open','2025-11-09T14:32:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (81,73,70,106,'2025-11-30T06:48:00','2025-11-30T07:44:00',1,'open','2025-11-27T06:48:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (82,26,95,107,'2025-11-23T18:15:00','2025-11-23T18:44:00',1,'open','2025-11-21T18:15:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (83,24,57,16,'2025-10-28T09:51:00','2025-10-28T10:48:00',2,'matched','2025-10-23T09:51:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (84,84,22,79,'2025-10-21T18:13:00','2025-10-21T18:36:00',1,'matched','2025-10-17T18:13:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (85,59,77,69,'2025-10-23T11:45:00','2025-10-23T12:13:00',1,'matched','2025-10-19T11:45:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (86,25,18,97,'2025-11-08T20:03:00','2025-11-08T20:30:00',2,'open','2025-11-07T20:03:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (87,52,19,92,'2025-11-02T19:15:00','2025-11-02T20:13:00',2,'open','2025-11-01T19:15:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (88,57,81,54,'2025-11-06T13:04:00','2025-11-06T13:54:00',1,'open','2025-11-01T13:04:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (89,59,89,48,'2025-11-11T13:20:00','2025-11-11T14:07:00',1,'matched','2025-11-06T13:20:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (90,24,94,59,'2025-11-22T16:44:00','2025-11-22T17:16:00',1,'matched','2025-11-19T16:44:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (91,44,55,106,'2025-10-26T13:17:00','2025-10-26T13:47:00',1,'open','2025-10-22T13:17:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (92,29,73,63,'2025-11-03T20:43:00','2025-11-03T21:41:00',1,'open','2025-10-29T20:43:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (93,92,3,16,'2025-10-23T13:29:00','2025-10-23T13:39:00',1,'open','2025-10-18T13:29:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (94,29,84,39,'2025-10-23T19:59:00','2025-10-23T20:26:00',1,'matched','2025-10-21T19:59:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (95,23,48,86,'2025-11-23T08:03:00','2025-11-23T08:38:00',2,'open','2025-11-18T08:03:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (96,48,55,84,'2025-10-18T17:06:00','2025-10-18T17:16:00',1,'open','2025-10-17T17:06:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (97,47,67,88,'2025-11-10T06:00:00','2025-11-10T06:54:00',1,'matched','2025-11-07T06:00:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (98,29,62,10,'2025-10-27T12:05:00','2025-10-27T13:01:00',1,'open','2025-10-25T12:05:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (99,63,18,49,'2025-11-30T19:08:00','2025-11-30T19:59:00',2,'open','2025-11-26T19:08:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (100,9,79,84,'2025-10-23T13:22:00','2025-10-23T14:18:00',1,'matched','2025-10-22T13:22:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (101,82,79,88,'2025-11-10T19:13:00','2025-11-10T19:28:00',1,'open','2025-11-10T19:13:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (102,76,45,55,'2025-11-02T18:08:00','2025-11-02T18:33:00',1,'matched','2025-11-01T18:08:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (103,60,100,89,'2025-10-14T19:54:00','2025-10-14T20:46:00',1,'open','2025-10-10T19:54:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (104,102,25,96,'2025-11-07T10:41:00','2025-11-07T10:54:00',1,'open','2025-11-07T10:41:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (105,51,64,82,'2025-11-20T07:33:00','2025-11-20T07:43:00',1,'cancelled','2025-11-19T07:33:00'); -INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (106,76,108,73,'2025-11-08T11:34:00','2025-11-08T12:27:00',1,'matched','2025-11-08T11:34:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (7,5,51,33,'2025-10-13T17:13:00','2025-10-13T18:00:00',1,'open','2025-10-11T17:13:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (8,9,70,8,'2025-10-17T20:30:00','2025-10-17T20:42:00',1,'open','2025-10-16T20:30:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (9,73,48,49,'2025-11-10T19:58:00','2025-11-10T20:32:00',1,'open','2025-11-05T19:58:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (10,97,18,84,'2025-11-04T07:11:00','2025-11-04T07:55:00',1,'open','2025-10-30T07:11:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (11,35,107,1,'2025-11-30T06:19:00','2025-11-30T06:58:00',2,'matched','2025-11-27T06:19:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (12,96,49,106,'2025-10-27T09:29:00','2025-10-27T10:01:00',1,'open','2025-10-22T09:29:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (13,17,5,104,'2025-11-24T12:39:00','2025-11-24T13:38:00',1,'open','2025-11-24T12:39:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (14,14,77,5,'2025-11-10T15:43:00','2025-11-10T16:38:00',1,'open','2025-11-10T15:43:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (15,71,57,30,'2025-11-16T09:48:00','2025-11-16T10:47:00',1,'open','2025-11-14T09:48:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (16,37,105,94,'2025-11-18T11:36:00','2025-11-18T12:24:00',2,'matched','2025-11-17T11:36:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (17,52,19,85,'2025-11-15T09:26:00','2025-11-15T09:55:00',1,'open','2025-11-11T09:26:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (18,28,81,87,'2025-11-09T14:31:00','2025-11-09T14:44:00',1,'cancelled','2025-11-04T14:31:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (19,67,101,68,'2025-11-02T17:26:00','2025-11-02T18:02:00',1,'open','2025-11-01T17:26:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (20,96,61,31,'2025-10-27T10:54:00','2025-10-27T11:49:00',1,'matched','2025-10-27T10:54:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (21,101,53,54,'2025-11-17T14:08:00','2025-11-17T14:42:00',1,'open','2025-11-15T14:08:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (22,12,58,109,'2025-11-05T07:34:00','2025-11-05T08:30:00',1,'open','2025-11-02T07:34:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (23,1004,10,25,'2025-11-30T15:46:00','2025-11-30T16:38:00',2,'open','2025-11-29T15:46:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (24,59,39,2,'2025-10-26T20:12:00','2025-10-26T21:09:00',1,'matched','2025-10-23T20:12:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (25,39,90,78,'2025-11-27T09:25:00','2025-11-27T09:50:00',2,'open','2025-11-25T09:25:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (26,67,60,69,'2025-11-23T11:19:00','2025-11-23T11:45:00',1,'open','2025-11-20T11:19:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (27,82,13,103,'2025-11-28T13:48:00','2025-11-28T14:18:00',1,'open','2025-11-25T13:48:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (28,1004,73,111,'2025-10-27T17:09:00','2025-10-27T17:20:00',1,'matched','2025-10-23T17:09:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (29,73,38,20,'2025-10-25T11:14:00','2025-10-25T11:48:00',2,'matched','2025-10-22T11:14:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (30,99,84,88,'2025-11-03T10:48:00','2025-11-03T11:29:00',2,'matched','2025-10-31T10:48:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (31,79,22,94,'2025-11-04T08:08:00','2025-11-04T09:04:00',2,'open','2025-10-31T08:08:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (32,8,68,5,'2025-10-17T19:42:00','2025-10-17T19:55:00',1,'open','2025-10-12T19:42:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (33,37,9,91,'2025-10-22T06:13:00','2025-10-22T06:55:00',1,'open','2025-10-18T06:13:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (34,84,85,63,'2025-10-14T06:34:00','2025-10-14T07:19:00',1,'open','2025-10-10T06:34:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (35,47,69,37,'2025-10-14T14:52:00','2025-10-14T15:46:00',2,'open','2025-10-13T14:52:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (36,15,13,68,'2025-10-22T09:12:00','2025-10-22T10:01:00',2,'open','2025-10-20T09:12:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (37,44,102,51,'2025-10-18T11:25:00','2025-10-18T12:04:00',2,'open','2025-10-13T11:25:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (38,35,39,88,'2025-10-18T16:54:00','2025-10-18T17:45:00',1,'open','2025-10-15T16:54:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (39,67,71,61,'2025-10-16T16:00:00','2025-10-16T16:54:00',1,'open','2025-10-13T16:00:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (40,59,73,110,'2025-10-19T20:33:00','2025-10-19T20:45:00',1,'open','2025-10-14T20:33:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (41,102,61,35,'2025-10-15T20:04:00','2025-10-15T20:57:00',1,'matched','2025-10-11T20:04:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (42,1003,23,109,'2025-11-02T06:13:00','2025-11-02T07:00:00',1,'matched','2025-10-28T06:13:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (43,70,10,39,'2025-10-23T15:15:00','2025-10-23T16:01:00',1,'matched','2025-10-19T15:15:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (44,59,50,97,'2025-11-29T08:50:00','2025-11-29T09:44:00',2,'open','2025-11-24T08:50:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (45,62,7,13,'2025-11-09T09:53:00','2025-11-09T10:07:00',1,'matched','2025-11-05T09:53:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (46,70,100,42,'2025-10-14T16:17:00','2025-10-14T17:17:00',1,'open','2025-10-12T16:17:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (47,99,49,56,'2025-10-24T16:37:00','2025-10-24T17:29:00',1,'open','2025-10-20T16:37:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (48,51,103,32,'2025-11-27T07:05:00','2025-11-27T07:32:00',1,'open','2025-11-22T07:05:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (49,25,95,50,'2025-11-02T11:06:00','2025-11-02T11:21:00',1,'cancelled','2025-10-30T11:06:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (50,64,98,35,'2025-10-19T08:05:00','2025-10-19T08:26:00',1,'open','2025-10-15T08:05:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (51,92,53,14,'2025-10-14T07:22:00','2025-10-14T08:07:00',1,'matched','2025-10-12T07:22:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (52,69,2,38,'2025-11-08T12:49:00','2025-11-08T13:04:00',2,'cancelled','2025-11-07T12:49:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (53,105,67,22,'2025-11-25T12:10:00','2025-11-25T12:28:00',1,'open','2025-11-22T12:10:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (54,24,9,22,'2025-11-09T10:26:00','2025-11-09T10:55:00',1,'matched','2025-11-07T10:26:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (55,41,32,93,'2025-11-22T13:38:00','2025-11-22T14:27:00',1,'open','2025-11-21T13:38:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (56,52,1,51,'2025-11-03T19:39:00','2025-11-03T20:13:00',1,'open','2025-10-31T19:39:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (57,23,39,42,'2025-11-20T17:12:00','2025-11-20T17:52:00',1,'open','2025-11-17T17:12:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (58,56,38,95,'2025-11-26T16:31:00','2025-11-26T17:17:00',1,'open','2025-11-24T16:31:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (59,70,47,15,'2025-11-18T09:37:00','2025-11-18T10:21:00',1,'matched','2025-11-14T09:37:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (60,1002,94,60,'2025-11-27T09:28:00','2025-11-27T09:56:00',2,'open','2025-11-24T09:28:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (61,89,18,82,'2025-11-01T09:16:00','2025-11-01T10:08:00',1,'matched','2025-10-29T09:16:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (62,11,58,77,'2025-11-12T15:25:00','2025-11-12T16:09:00',2,'cancelled','2025-11-07T15:25:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (63,73,70,5,'2025-11-05T17:57:00','2025-11-05T18:41:00',2,'matched','2025-11-05T17:57:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (64,15,99,32,'2025-11-24T16:22:00','2025-11-24T16:42:00',2,'matched','2025-11-20T16:22:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (65,71,97,43,'2025-11-09T07:00:00','2025-11-09T07:16:00',1,'open','2025-11-04T07:00:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (66,94,72,75,'2025-11-26T15:14:00','2025-11-26T15:52:00',1,'open','2025-11-21T15:14:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (67,90,20,45,'2025-10-14T13:06:00','2025-10-14T13:34:00',1,'cancelled','2025-10-14T13:06:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (68,17,107,94,'2025-10-22T11:19:00','2025-10-22T11:50:00',1,'matched','2025-10-18T11:19:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (69,84,45,61,'2025-10-19T13:46:00','2025-10-19T14:40:00',1,'open','2025-10-17T13:46:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (70,1004,104,91,'2025-10-20T06:54:00','2025-10-20T07:25:00',2,'open','2025-10-19T06:54:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (71,39,67,23,'2025-11-17T08:21:00','2025-11-17T09:06:00',1,'cancelled','2025-11-16T08:21:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (72,71,81,24,'2025-10-24T16:42:00','2025-10-24T17:19:00',1,'open','2025-10-20T16:42:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (73,32,58,76,'2025-11-09T12:00:00','2025-11-09T12:55:00',1,'open','2025-11-04T12:00:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (74,9,74,14,'2025-11-16T08:23:00','2025-11-16T08:53:00',1,'open','2025-11-14T08:23:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (75,88,68,82,'2025-11-02T15:24:00','2025-11-02T16:13:00',1,'matched','2025-10-31T15:24:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (76,63,109,59,'2025-11-21T08:52:00','2025-11-21T09:45:00',2,'matched','2025-11-17T08:52:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (77,12,87,18,'2025-11-02T07:15:00','2025-11-02T07:44:00',1,'open','2025-10-28T07:15:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (78,24,66,50,'2025-11-08T15:08:00','2025-11-08T15:54:00',1,'open','2025-11-05T15:08:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (79,96,89,83,'2025-10-24T14:10:00','2025-10-24T14:51:00',1,'open','2025-10-22T14:10:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (80,77,80,7,'2025-11-04T06:31:00','2025-11-04T06:49:00',1,'matched','2025-10-31T06:31:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (81,90,84,64,'2025-11-08T16:31:00','2025-11-08T17:07:00',2,'matched','2025-11-05T16:31:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (82,27,11,73,'2025-10-14T18:48:00','2025-10-14T19:12:00',1,'open','2025-10-13T18:48:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (83,96,37,22,'2025-11-11T15:47:00','2025-11-11T16:46:00',1,'open','2025-11-11T15:47:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (84,105,15,35,'2025-11-16T19:23:00','2025-11-16T20:07:00',1,'matched','2025-11-13T19:23:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (85,97,28,55,'2025-10-19T17:53:00','2025-10-19T18:44:00',1,'open','2025-10-19T17:53:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (86,77,34,45,'2025-10-18T13:55:00','2025-10-18T14:12:00',1,'open','2025-10-13T13:55:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (87,62,111,91,'2025-11-21T16:27:00','2025-11-21T16:47:00',2,'open','2025-11-20T16:27:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (88,33,104,8,'2025-11-18T11:34:00','2025-11-18T12:01:00',2,'open','2025-11-16T11:34:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (89,51,38,74,'2025-10-30T19:32:00','2025-10-30T20:25:00',1,'open','2025-10-27T19:32:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (90,8,36,111,'2025-11-23T08:44:00','2025-11-23T09:02:00',1,'open','2025-11-21T08:44:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (91,39,98,108,'2025-11-25T12:31:00','2025-11-25T12:50:00',2,'matched','2025-11-20T12:31:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (92,73,49,58,'2025-10-17T16:50:00','2025-10-17T17:49:00',1,'open','2025-10-12T16:50:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (93,47,89,48,'2025-11-11T13:20:00','2025-11-11T14:07:00',1,'matched','2025-11-06T13:20:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (94,13,94,59,'2025-11-22T16:44:00','2025-11-22T17:16:00',1,'matched','2025-11-19T16:44:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (95,34,55,106,'2025-10-26T13:17:00','2025-10-26T13:47:00',1,'open','2025-10-22T13:17:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (96,22,73,110,'2025-11-13T18:21:00','2025-11-13T19:14:00',1,'open','2025-11-08T18:21:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (97,79,3,16,'2025-10-23T13:29:00','2025-10-23T13:39:00',1,'open','2025-10-18T13:29:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (98,22,84,39,'2025-10-23T19:59:00','2025-10-23T20:26:00',1,'matched','2025-10-21T19:59:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (99,12,48,86,'2025-11-23T08:03:00','2025-11-23T08:38:00',2,'open','2025-11-18T08:03:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (100,37,55,84,'2025-10-18T17:06:00','2025-10-18T17:16:00',1,'open','2025-10-17T17:06:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (101,35,67,88,'2025-11-10T06:00:00','2025-11-10T06:54:00',1,'matched','2025-11-07T06:00:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (102,22,62,10,'2025-10-27T12:05:00','2025-10-27T13:01:00',1,'open','2025-10-25T12:05:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (103,52,18,49,'2025-11-30T19:08:00','2025-11-30T19:59:00',2,'open','2025-11-26T19:08:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (104,102,2,79,'2025-11-23T08:28:00','2025-11-23T09:00:00',2,'open','2025-11-18T08:28:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (105,25,53,79,'2025-11-25T13:55:00','2025-11-25T14:18:00',1,'matched','2025-11-24T13:55:00'); +INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (106,18,76,94,'2025-11-06T11:27:00','2025-11-06T11:57:00',1,'open','2025-11-01T11:27:00'); -- RIDE_MATCH INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (1,3,3,1,11.71,'completed','2025-10-13T13:30:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (2,4,7,1,7.95,'cancelled','2025-10-13T06:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (3,8,16,2,14.77,'confirmed','2025-10-15T02:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (4,9,16,1,15.16,'no_show','2025-10-13T08:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (5,15,2,1,11.01,'completed','2025-10-13T16:45:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (6,21,3,1,10.23,'no_show','2025-10-13T23:45:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (7,7,13,2,11.43,'confirmed','2025-11-24T19:34:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (8,9,37,1,17.37,'completed','2025-11-16T09:34:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (9,10,75,2,9.17,'completed','2025-11-28T14:07:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (10,11,17,2,9.73,'completed','2025-10-19T14:02:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (11,13,60,2,9.77,'completed','2025-10-27T19:58:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (12,14,56,1,17.15,'cancelled','2025-10-22T16:33:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (13,15,56,1,11.57,'completed','2025-11-09T14:31:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (14,16,95,1,12.99,'confirmed','2025-11-02T17:26:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (15,19,45,1,8.37,'cancelled','2025-10-18T12:46:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (16,20,25,2,19.18,'cancelled','2025-11-30T15:46:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (17,22,80,2,8.17,'confirmed','2025-11-27T05:25:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (18,24,66,1,13.54,'completed','2025-11-28T09:48:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (19,25,30,2,8.73,'confirmed','2025-11-29T04:01:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (20,27,95,1,12.23,'confirmed','2025-11-13T17:41:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (21,28,91,2,17.34,'no_show','2025-11-28T13:31:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (22,31,92,1,20.35,'completed','2025-10-14T04:34:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (23,32,59,2,13.04,'confirmed','2025-10-14T12:52:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (24,33,9,2,10.57,'cancelled','2025-10-22T09:12:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (25,35,60,1,12.77,'cancelled','2025-10-18T14:54:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (26,36,71,1,17.93,'confirmed','2025-10-16T14:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (27,37,39,1,17.32,'confirmed','2025-11-15T18:02:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (28,38,94,2,25.99,'cancelled','2025-10-17T12:59:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (29,40,1,2,12.32,'confirmed','2025-10-28T11:53:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (30,41,89,1,16.37,'no_show','2025-10-21T18:44:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (31,43,95,1,13.34,'confirmed','2025-10-14T12:17:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (32,45,12,2,13.9,'confirmed','2025-10-18T08:09:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (33,46,30,1,16.37,'completed','2025-10-19T05:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (34,47,17,2,11.9,'completed','2025-10-24T09:28:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (35,48,73,1,10.34,'completed','2025-11-17T06:38:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (36,49,34,1,16.04,'no_show','2025-10-18T14:57:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (37,52,35,1,10.83,'no_show','2025-10-25T12:06:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (38,54,20,1,9.85,'completed','2025-11-20T15:12:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (39,55,69,1,12.08,'cancelled','2025-11-26T13:31:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (40,56,34,1,16.05,'confirmed','2025-11-18T07:37:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (41,59,60,2,8.24,'completed','2025-11-12T13:25:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (42,60,9,2,14.75,'confirmed','2025-11-05T16:57:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (43,61,75,2,21.45,'completed','2025-11-24T12:22:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (44,62,60,1,7.95,'cancelled','2025-11-09T07:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (45,63,100,1,14.26,'confirmed','2025-11-26T12:14:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (46,64,81,1,12.78,'confirmed','2025-10-14T09:06:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (47,65,10,1,4.91,'confirmed','2025-10-22T08:19:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (48,67,61,2,11.4,'no_show','2025-10-20T06:54:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (49,68,74,1,13.28,'completed','2025-11-17T05:21:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (50,69,88,1,8.99,'confirmed','2025-10-24T15:42:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (51,70,58,1,19.77,'confirmed','2025-11-09T08:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (52,73,71,1,18.27,'confirmed','2025-10-24T16:43:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (53,74,26,1,11.56,'cancelled','2025-11-02T06:15:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (54,80,101,2,11.17,'confirmed','2025-11-13T10:32:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (55,81,50,1,8.34,'confirmed','2025-11-30T02:48:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (56,82,104,1,17.96,'confirmed','2025-11-23T15:15:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (57,83,42,2,14.81,'confirmed','2025-10-28T08:51:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (58,84,102,1,21.02,'confirmed','2025-10-21T17:13:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (59,85,103,1,14.92,'no_show','2025-10-23T10:45:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (60,86,59,2,19.89,'completed','2025-11-08T16:03:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (61,87,66,2,22.47,'completed','2025-11-02T15:15:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (62,92,23,1,11.08,'confirmed','2025-11-03T19:43:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (63,94,62,1,10.44,'no_show','2025-10-23T18:59:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (64,96,23,1,14.12,'cancelled','2025-10-18T15:06:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (65,97,19,1,11.98,'no_show','2025-11-10T06:00:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (66,101,85,1,8.39,'confirmed','2025-11-10T17:13:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (67,102,21,1,8.54,'completed','2025-11-02T15:08:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (68,104,72,1,11.34,'no_show','2025-11-07T08:41:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (69,105,76,1,14.94,'confirmed','2025-11-20T05:33:00'); -INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (70,106,94,1,11.15,'completed','2025-11-08T07:34:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (2,7,71,1,13.45,'completed','2025-10-13T15:13:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (3,12,67,1,10.31,'confirmed','2025-10-27T06:29:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (4,13,7,1,11.66,'cancelled','2025-11-24T11:39:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (5,14,15,1,10.93,'completed','2025-11-10T15:43:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (6,15,2,1,15.52,'no_show','2025-11-16T08:48:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (7,16,78,2,11.92,'completed','2025-11-18T08:36:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (8,19,9,1,9.11,'confirmed','2025-11-02T17:26:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (9,21,37,1,20.19,'completed','2025-11-17T11:08:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (10,22,52,1,10.71,'completed','2025-11-05T03:34:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (11,23,15,2,11.5,'completed','2025-11-30T14:46:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (12,25,63,2,14.92,'completed','2025-11-27T09:25:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (13,26,56,1,15.63,'cancelled','2025-11-23T11:19:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (14,27,56,1,8.51,'completed','2025-11-28T13:48:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (15,28,95,1,15.18,'confirmed','2025-10-27T17:09:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (16,31,66,2,15.7,'cancelled','2025-11-04T06:08:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (17,32,17,1,19.58,'cancelled','2025-10-17T19:42:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (18,34,104,1,12.54,'completed','2025-10-14T04:34:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (19,36,14,2,19.0,'completed','2025-10-22T09:12:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (20,37,30,2,12.59,'confirmed','2025-10-18T11:25:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (21,40,95,1,13.63,'confirmed','2025-10-19T20:33:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (22,41,61,1,14.37,'no_show','2025-10-15T19:04:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (23,44,78,2,10.37,'confirmed','2025-11-29T06:50:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (24,45,10,1,13.86,'confirmed','2025-11-09T09:53:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (25,46,103,1,14.6,'confirmed','2025-10-14T13:17:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (26,47,60,1,10.62,'cancelled','2025-10-24T14:37:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (27,48,71,1,12.01,'confirmed','2025-11-27T05:05:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (28,49,39,1,19.81,'confirmed','2025-11-02T09:06:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (29,50,64,1,18.53,'cancelled','2025-10-19T04:05:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (30,52,1,2,12.32,'confirmed','2025-11-08T08:49:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (31,53,89,1,15.09,'no_show','2025-11-25T12:10:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (32,55,95,1,15.9,'confirmed','2025-11-22T09:38:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (33,57,8,1,20.12,'confirmed','2025-11-20T15:12:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (34,58,30,1,18.15,'completed','2025-11-26T14:31:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (35,59,96,1,10.57,'confirmed','2025-11-18T08:37:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (36,61,76,1,17.57,'completed','2025-11-01T08:16:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (37,62,53,2,19.43,'no_show','2025-11-12T12:25:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (38,65,35,1,15.48,'no_show','2025-11-09T06:00:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (39,67,20,1,12.77,'completed','2025-10-14T11:06:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (40,68,69,1,15.07,'cancelled','2025-10-22T08:19:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (41,69,34,1,22.67,'confirmed','2025-10-19T11:46:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (42,72,42,1,9.38,'completed','2025-10-24T14:42:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (43,73,7,1,11.0,'confirmed','2025-11-09T11:00:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (44,74,52,1,17.27,'completed','2025-11-16T04:23:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (45,75,60,1,5.27,'cancelled','2025-11-02T15:24:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (46,76,44,2,11.43,'cancelled','2025-11-21T06:52:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (47,79,76,1,13.15,'cancelled','2025-10-24T14:10:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (48,81,83,2,13.71,'confirmed','2025-11-08T14:31:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (49,82,60,1,4.91,'no_show','2025-10-14T15:48:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (50,84,4,1,13.76,'completed','2025-11-16T17:23:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (51,85,24,1,10.6,'completed','2025-10-19T16:53:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (52,86,80,1,15.38,'completed','2025-10-18T10:55:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (53,87,104,2,13.1,'confirmed','2025-11-21T13:27:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (54,88,36,2,17.37,'cancelled','2025-11-18T10:34:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (55,94,70,1,15.35,'confirmed','2025-11-22T12:44:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (56,95,50,1,9.81,'confirmed','2025-10-26T09:17:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (57,96,104,1,16.45,'confirmed','2025-11-13T15:21:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (58,97,99,1,11.63,'confirmed','2025-10-23T12:29:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (59,98,85,1,11.05,'completed','2025-10-23T19:59:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (60,101,103,1,13.01,'no_show','2025-11-10T05:00:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (61,102,76,1,14.0,'completed','2025-10-27T10:05:00'); +INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (62,104,68,2,18.31,'completed','2025-11-23T04:28:00'); -- RATING INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (1,1,2,12,5,'Friendly','2025-10-13T15:30:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (3,8,9,4,5,'Great ride','2025-10-13T04:45:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (4,8,94,59,1,'Car was clean','2025-11-16T12:34:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (5,9,33,53,5,'Helpful with bags','2025-11-30T06:07:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (6,10,51,85,5,'Would ride again','2025-10-20T00:02:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (7,11,31,48,5,'On time','2025-10-28T16:58:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (8,13,37,53,3,'Great ride','2025-11-11T12:31:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (9,18,94,78,4,'On time','2025-11-29T14:48:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (10,22,96,48,5,'Driver was late','2025-10-14T09:34:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (11,33,76,53,4,'Driver was late','2025-10-19T06:00:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (12,34,26,85,5,'Would ride again','2025-10-24T20:28:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (13,38,31,54,5,'Car was clean','2025-11-21T16:12:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (14,41,22,48,5,'Helpful with bags','2025-11-14T02:25:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (15,43,26,53,4,'Friendly driver','2025-11-25T17:22:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (16,49,52,70,4,'On time','2025-11-17T07:21:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (17,60,25,59,2,'Great ride','2025-11-09T14:03:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (18,61,52,78,5,'Driver was late','2025-11-02T20:15:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (19,67,76,49,3,'Great ride','2025-11-03T13:08:00'); -INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (20,70,76,100,4,'Would ride again','2025-11-10T05:34:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (2,2,5,55,1,'Car was clean','2025-10-14T11:13:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (3,5,14,66,5,'Great ride','2025-11-11T06:43:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (4,7,37,5,5,'Would ride again','2025-11-18T09:36:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (5,9,101,16,4,'Helpful with bags','2025-11-17T20:08:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (6,10,12,1001,5,'Helpful with bags','2025-11-06T00:34:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (7,11,1004,66,5,'Driver was late','2025-12-01T00:46:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (8,12,39,101,4,'Helpful with bags','2025-11-28T09:25:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (9,18,84,1003,2,'Would ride again','2025-10-15T18:34:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (10,19,15,43,5,'Friendly driver','2025-10-22T20:12:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (11,34,56,53,5,'Driver was late','2025-11-27T22:31:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (12,36,89,27,5,'Car was clean','2025-11-03T05:16:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (13,42,71,98,5,'Driver was late','2025-10-26T04:42:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (14,44,9,1001,5,'Driver was late','2025-11-18T01:23:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (15,50,105,6,5,'Great ride','2025-11-18T14:23:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (16,52,77,4,1,'Car was clean','2025-10-18T13:55:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (17,59,22,49,5,'Helpful with bags','2025-10-25T11:59:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (18,61,22,27,5,'Would ride again','2025-10-27T20:05:00'); +INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (19,62,102,43,5,'On time','2025-11-24T01:28:00'); + +-- MESSAGE_THREAD +INSERT INTO MESSAGE_THREAD (thread_id,user1_id,user2_id,ride_match_id,created_at) VALUES (1,1,2,NULL,'2025-11-30 19:25:00'); + +-- MESSAGE +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (1,1,2,'Hey, are we still on for 5:30 PM?','2025-11-30 03:03:42'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (2,1,1,'Yes, I will be there in 10 minutes.','2025-11-30 03:03:42'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (3,1,2,'Hey Quincy, are we still on for 5:30 PM?','2025-11-30 19:29:48'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (4,1,1,'Yes absolutely! Leaving now.','2025-11-30 19:29:48'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (5,1,2,'Perfect, see you soon!','2025-11-30 19:29:48'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (6,1,1,'Hey Abdul, do you still need a ride tomorrow?','2025-11-30 19:29:51'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (7,1,2,'Yes please! Around 9 AM if possible.','2025-11-30 19:29:51'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (8,1,1,'I ll be there.','2025-11-30 19:29:51'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (9,1,2,'Got you!','2025-11-30 19:29:51'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (10,1,1,'Thanks for the ride earlier!','2025-11-30 19:29:55'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (11,1,2,'Anytime man!','2025-11-30 19:29:55'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (12,1,1,'Left you a 5-star rating too :)','2025-11-30 19:29:55'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (13,1,2,'Hey bro, heading out soon?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (14,1,1,'Yeah give me 5 minutes.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (15,1,2,'Bet, take your time.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (16,1,1,'Traffic is crazy today.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (17,1,2,'Fr bro, Drexel roads are wild.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (18,1,1,'Want to grab food after?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (19,1,2,'m down for halal cart.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (20,1,1,'Say less.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (21,1,2,'Almost downstairs.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (22,1,1,'I see you.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (23,1,2,'Don t forget the aux today.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (24,1,1,'Haha alright.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (25,1,2,'Let s go.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (26,1,1,'On my way now.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (27,1,2,'You driving today?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (28,1,1,'Yeah I got it.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (29,1,2,'Fire.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (30,1,1,'Good morning! Still need a ride?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (31,1,2,'Yes please! Appreciate you.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (32,1,1,'Leaving in 10.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (33,1,2,'ll meet you outside.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (34,1,1,'Perfect.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (35,1,2,'Did you do the homework for CS 281?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (36,1,1,'Barely, that class is wild.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (37,1,2,'Facts bro.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (38,1,1,'ll quiz you in the car lol.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (39,1,2,'Bet.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (40,1,1,'m by the curb.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (41,1,2,'Coming now.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (42,1,1,'You want coffee?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (43,1,2,'Nah I m good, thanks.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (44,1,1,'Alright see you.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (45,1,2,'Almost there.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (46,1,1,'Thanks again for covering me last ride bro.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (47,1,2,'No problem at all.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (48,1,1,'You good for later today?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (49,1,2,'Yep, same time as usual.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (50,1,1,'Bet.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (51,1,2,'You wanna stop at Wawa after?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (52,1,1,'Always bro.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (53,1,2,'Haha say less.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (54,1,1,'You see the game last night?','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (55,1,2,'Bro Embiid is insane.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (56,1,1,'MVP season.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (57,1,2,'100%.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (58,1,1,'Heading out now.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (59,1,2,'m already outside.','2025-11-30 19:32:38'); +INSERT INTO MESSAGE (message_id,thread_id,sender_id,body,sent_at) VALUES (60,1,1,'Coming.','2025-11-30 19:32:38'); diff --git a/database/ran_sql_cmds_in_script.sql b/database/ran_sql_cmds_in_script.sql new file mode 100644 index 0000000..942e005 --- /dev/null +++ b/database/ran_sql_cmds_in_script.sql @@ -0,0 +1,173 @@ +-- ===================================================================== +-- SQL used at runtime by the Find-My-Ride application +-- ===================================================================== + +-- --------------------------------------------------------------------- +-- Source: app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidAuthRepository.kt +-- --------------------------------------------------------------------- + +-- Login: check email + password +SELECT user_id, email, username, role +FROM "USER" +WHERE email = ? AND password_hash = ?; + +-- Check if email already exists (used in registration & login) +SELECT 1 FROM "USER" WHERE email = ? LIMIT 1; + +-- Register new user (default role = 'rider') +INSERT INTO "USER"(email, username, password_hash, role) +VALUES(?, ?, ?, 'rider'); + +-- (Second use of the same existence check) +SELECT 1 FROM "USER" WHERE email = ? LIMIT 1; + + + +-- --------------------------------------------------------------------- +-- Source: app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidMessagesRepository.kt +-- Was used for debugging errors in script execution +-- --------------------------------------------------------------------- + +-- Seed message threads (demo data) +INSERT INTO MESSAGE_THREAD (thread_id, user1_id, user2_id) +VALUES + (1, 1, 2), + (2, 1, 3), + (3, 1, 4); + +-- Seed messages - Thread 1: Abdul <-> Quincy +INSERT INTO MESSAGE (thread_id, sender_id, body) VALUES + (1, 1, 'Hey Quincy, are we still on for 5:30 PM?'), + (1, 2, 'Yes! I''ll be there in 10 minutes.'), + (1, 1, 'Perfect, see you soon.'); + +-- Seed messages - Thread 2: Abdul <-> Ame +INSERT INTO MESSAGE (thread_id, sender_id, body) VALUES + (2, 3, 'Hey Abdul, do you still need a ride tomorrow?'), + (2, 1, 'Yeah! Morning around 9 would be amazing.'), + (2, 3, 'Got you, I''ll swing by then.'); + +-- Seed messages - Thread 3: Abdul <-> Kennan +INSERT INTO MESSAGE (thread_id, sender_id, body) VALUES + (3, 1, 'Thanks again for the last ride!'), + (3, 4, 'No problem, happy to help.'), + (3, 1, 'I left you a 5-star rating too :)'); + +-- Load all message threads for the logged-in user (with last message preview) +SELECT + t.thread_id, + CASE + WHEN t.user1_id = ? THEN u2.username + ELSE u1.username + END AS contact_name, + COALESCE(last_msg.body, 'No messages yet') AS last_message, + last_msg.sent_at AS last_sent_at +FROM MESSAGE_THREAD t + JOIN "USER" u1 ON t.user1_id = u1.user_id + JOIN "USER" u2 ON t.user2_id = u2.user_id + LEFT JOIN MESSAGE last_msg ON last_msg.message_id = ( + SELECT m.message_id + FROM MESSAGE m + WHERE m.thread_id = t.thread_id + ORDER BY m.sent_at DESC, m.message_id DESC + LIMIT 1 + ) +WHERE t.user1_id = ? OR t.user2_id = ? +ORDER BY + (last_sent_at IS NULL), + last_sent_at DESC, + t.thread_id DESC; + +-- Load all messages inside a specific thread +SELECT message_id, sender_id, body, sent_at +FROM MESSAGE +WHERE thread_id = ? +ORDER BY sent_at ASC, message_id ASC; + +-- Insert a new chat message +INSERT INTO MESSAGE(thread_id, sender_id, body) +VALUES (?, ?, ?); + +-- Get sent_at for a specific message (used after insert) +SELECT sent_at +FROM MESSAGE +WHERE message_id = ?; + + + +-- --------------------------------------------------------------------- +-- Source: app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidProfileRepository.kt +-- --------------------------------------------------------------------- + +-- Load main profile info for the logged-in user +SELECT username, email, phone_number, rating_avg +FROM "USER" +WHERE user_id = ? + LIMIT 1; + +-- Load all vehicles owned by logged-in user +SELECT vehicle_id, make, model, color, plate, seats_total, year, fun_fact +FROM "VEHICLE" +WHERE owner_user_id = ? +ORDER BY vehicle_id; + +-- Update profile fields (username, email, phone number) +UPDATE "USER" +SET username = ?, email = ?, phone_number = ? +WHERE user_id = ?; + +-- Get all vehicle IDs for current user (used to compute inserts/updates/deletes) +SELECT vehicle_id +FROM "VEHICLE" +WHERE owner_user_id = ?; + +-- Update an existing vehicle +UPDATE "VEHICLE" +SET make = ?, model = ?, color = ?, plate = ?, + seats_total = ?, year = ?, fun_fact = ? +WHERE vehicle_id = ?; + +-- Insert a new vehicle +INSERT INTO "VEHICLE"( + vehicle_id, owner_user_id, make, model, color, + plate, seats_total, year, fun_fact +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + +-- Delete a vehicle +DELETE FROM "VEHICLE" WHERE vehicle_id = ?; + + + +-- --------------------------------------------------------------------- +-- Source: app/composeApp/src/androidMain/kotlin/com/example/demo/AndroidRideRepository.kt +-- --------------------------------------------------------------------- + +-- Load open ride offers, joined with location names for origin/destination +SELECT + o.offer_id, + o.driver_id, + o.vehicle_id, + lo_from.name AS from_name, + lo_to.name AS to_name, + o.depart_at, + o.seats_available, + o.price_base +FROM RIDE_OFFER o + JOIN LOCATION lo_from ON o.original_location_id = lo_from.location_id + JOIN LOCATION lo_to ON o.dest_location_id = lo_to.location_id +WHERE o.status = 'open' +ORDER BY o.depart_at ASC; + + + +-- --------------------------------------------------------------------- +-- Source: app/composeApp/src/androidMain/kotlin/com/example/demo/RideShareDbHelper.kt +-- (Legacy/demo local table not tied to main project schema but just included here for completeness) +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS rides( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pickup TEXT NOT NULL, + dropoff TEXT NOT NULL, + ride_time TEXT NOT NULL +); diff --git a/database/schema.sql b/database/schema.sql index e925393..0e47d49 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -1,6 +1,6 @@ -PRAGMA foreign_keys = ON; -- Enforces foreign key constraints --- USER Table For Users +PRAGMA foreign_keys = ON; + CREATE TABLE "USER" ( user_id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, @@ -12,7 +12,6 @@ CREATE TABLE "USER" ( created_at TEXT NOT NULL DEFAULT (datetime('now')) ); --- VEHICLE Table for Vehicles CREATE TABLE "VEHICLE" ( vehicle_id INTEGER PRIMARY KEY, owner_user_id INTEGER NOT NULL, @@ -21,19 +20,17 @@ CREATE TABLE "VEHICLE" ( color TEXT, plate TEXT NOT NULL UNIQUE, seats_total INTEGER NOT NULL CHECK (seats_total BETWEEN 1 AND 8), - year INTEGER CHECK (year BETWEEN 1900 AND 2100), -- We did 2100 because why not keeping it future proof - fun_fact TEXT, -- Just for fun to add some personality for each vehicle + year INTEGER CHECK (year BETWEEN 1900 AND 2100), + fun_fact TEXT, FOREIGN KEY (owner_user_id) REFERENCES "USER"(user_id) ); --- LOCATION Table for Locations CREATE TABLE "LOCATION" ( location_id INTEGER PRIMARY KEY, name TEXT NOT NULL, address TEXT NOT NULL ); --- RIDE_OFFER Table for Ride Offers CREATE TABLE "RIDE_OFFER" ( offer_id INTEGER PRIMARY KEY, driver_id INTEGER NOT NULL, @@ -52,7 +49,6 @@ CREATE TABLE "RIDE_OFFER" ( FOREIGN KEY (dest_location_id) REFERENCES "LOCATION"(location_id) ); --- RIDE_REQUEST Table for Ride Requests CREATE TABLE "RIDE_REQUEST" ( request_id INTEGER PRIMARY KEY, rider_id INTEGER NOT NULL, @@ -69,7 +65,6 @@ CREATE TABLE "RIDE_REQUEST" ( CHECK (latest_pickup IS NULL OR latest_pickup >= earliest_pickup) ); --- RIDE_MATCH Table for Matches between Requests and Offers CREATE TABLE "RIDE_MATCH" ( match_id INTEGER PRIMARY KEY, request_id INTEGER NOT NULL, @@ -83,7 +78,6 @@ CREATE TABLE "RIDE_MATCH" ( UNIQUE (request_id, offer_id) ); --- RATING Table for User Ratings CREATE TABLE "RATING" ( rating_id INTEGER PRIMARY KEY, match_id INTEGER NOT NULL, @@ -98,3 +92,25 @@ CREATE TABLE "RATING" ( CHECK (from_user_id <> to_user_id), UNIQUE (match_id, from_user_id, to_user_id) ); + +CREATE TABLE "MESSAGE_THREAD" ( + thread_id INTEGER PRIMARY KEY, + user1_id INTEGER NOT NULL, + user2_id INTEGER NOT NULL, + ride_match_id INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (user1_id) REFERENCES "USER"(user_id), + FOREIGN KEY (user2_id) REFERENCES "USER"(user_id), + FOREIGN KEY (ride_match_id) REFERENCES "RIDE_MATCH"(match_id), + CHECK (user1_id <> user2_id) +); + +CREATE TABLE "MESSAGE" ( + message_id INTEGER PRIMARY KEY, + thread_id INTEGER NOT NULL, + sender_id INTEGER NOT NULL, + body TEXT NOT NULL, + sent_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (thread_id) REFERENCES "MESSAGE_THREAD"(thread_id), + FOREIGN KEY (sender_id) REFERENCES "USER"(user_id) +); diff --git a/demo/.idea/workspace.xml b/demo/.idea/workspace.xml new file mode 100644 index 0000000..3b00bca --- /dev/null +++ b/demo/.idea/workspace.xml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/BlackBoard_Deliverables/Deliverable_4/live_demo/Live_Demo.webm b/docs/BlackBoard_Deliverables/Deliverable_4/live_demo/Live_Demo.webm new file mode 100644 index 0000000..24d05ad Binary files /dev/null and b/docs/BlackBoard_Deliverables/Deliverable_4/live_demo/Live_Demo.webm differ diff --git a/docs/BlackBoard_Deliverables/Deliverable_4/personal_contributions/Mustafa-Individual Deliverable 04.pdf b/docs/BlackBoard_Deliverables/Deliverable_4/personal_contributions/Mustafa-Individual Deliverable 04.pdf new file mode 100644 index 0000000..fc27a5e Binary files /dev/null and b/docs/BlackBoard_Deliverables/Deliverable_4/personal_contributions/Mustafa-Individual Deliverable 04.pdf differ diff --git a/docs/BlackBoard_Deliverables/Deliverable_4/personal_contributions/Samii_Individual_Deliverable.pdf b/docs/BlackBoard_Deliverables/Deliverable_4/personal_contributions/Samii_Individual_Deliverable.pdf new file mode 100644 index 0000000..3deaa7e Binary files /dev/null and b/docs/BlackBoard_Deliverables/Deliverable_4/personal_contributions/Samii_Individual_Deliverable.pdf differ diff --git a/docs/BlackBoard_Deliverables/Deliverable_4/personal_reflections/Final Reflection.pdf b/docs/BlackBoard_Deliverables/Deliverable_4/personal_reflections/Final Reflection.pdf new file mode 100644 index 0000000..2a83dac Binary files /dev/null and b/docs/BlackBoard_Deliverables/Deliverable_4/personal_reflections/Final Reflection.pdf differ diff --git a/docs/BlackBoard_Deliverables/Deliverable_4/personal_reflections/Samii_Reflection.pdf b/docs/BlackBoard_Deliverables/Deliverable_4/personal_reflections/Samii_Reflection.pdf new file mode 100644 index 0000000..493da6b Binary files /dev/null and b/docs/BlackBoard_Deliverables/Deliverable_4/personal_reflections/Samii_Reflection.pdf differ diff --git a/docs/BlackBoard_Deliverables/Deliverable_4/presentation/FindMyRide Final Presentation.pptx b/docs/BlackBoard_Deliverables/Deliverable_4/presentation/FindMyRide Final Presentation.pptx new file mode 100644 index 0000000..1e29579 Binary files /dev/null and b/docs/BlackBoard_Deliverables/Deliverable_4/presentation/FindMyRide Final Presentation.pptx differ diff --git a/docs/dev_journal/Kennan.md b/docs/dev_journal/Kennan.md index a772d0d..01e91bf 100644 --- a/docs/dev_journal/Kennan.md +++ b/docs/dev_journal/Kennan.md @@ -1,3 +1,52 @@ +# 11/20/2025 + +Experienced a bunch of issues with trying to build and run the app. + +Solution: Deleted the .gradle folder in the app subdirectory and forced it to be rebuilt. + +# 11/16/2025 + +## Development Strategy + +As we team we focused and came up with the strategy with 3 Phases. + +We are following Three Phase Strategy: + +UI Development -> UI Connection Development -> Frontend to Backend Database Connection + +And [ChatGPT](https://chatgpt.com/share/691a8a83-69c0-800e-be1c-aa304a8a901f) verified this is the correct process that modern companies use. + +Following paging structure with react where we have folders for our features. + +## Importing Icons + +You should use your own SVG/XML files if you want to import icons onto the UI for the screens. + + 1. Get your icons - Download the icons you want as .svg or .xml (Android Vector) files. (Sites like Heroicons or Phosphor Icons are good sources). + + 2. Place them in the resources folder Go to composeApp/src/commonMain/composeResources/drawable. Paste your files there (e.g., ic_calendar.xml, ic_location.xml). + + 3. Update the code to use painterResource + +EXAMPLE CALL using painterResource: +icon = painterResource(Res.drawable.ic_calendar) + +#### 11/21/2025 Update: ANDROID DOES NOT SUPPORT SVG FILES + +Here's how to convert them to XML before use: + + 1. In the Project view (left panel), right-click on your commonMain/composeResources/drawable folder. + + 2. Select New > Vector Asset. + + 3. In the "Asset Type" section, choose Local file (SVG, PSD). + + 4. Click the folder icon next to Path and select your original SVG file. + + 5. Click Next and then Finish. + +This will create a new .xml file in that folder. Delete the old .svg file so there is no confusion. + # 11/2/2025 - Initialize the SQLite Database diff --git a/docs/dev_journal/Samii.md b/docs/dev_journal/Samii.md index 9d5d8dd..f3482a9 100644 --- a/docs/dev_journal/Samii.md +++ b/docs/dev_journal/Samii.md @@ -1,3 +1,141 @@ +# 11/16/2025 + +## Development Strategy + +As we team we focused and came up with the strategy with 3 Phases. + +We are following Three Phase Strategy: + +UI Development -> UI Connection Development -> Frontend to Backend Database Connection + +And [ChatGPT](https://chatgpt.com/share/691a8a83-69c0-800e-be1c-aa304a8a901f) verified this is the correct process that modern companies use. + +Following paging structure with react where we have folders for our features. + +- I'm make the structure + +```txt +feature: + - auth/ # all authentication related stuff + - rides/ # all main ride related stuff + - .../ # insert other features +``` + +- I added a thing to allow colors to be used all over by every file in this project. + - I think Ken is also asking me about my hex color irl so idk how to think about that. /s + +Now need to focus on getting the code for login page to make sense. + +- I asked a question but he left on delivered. + +## KMP Notes: + +### @Composable +- `@Composable` marks this as a composable fucntion - describing UI in Compose + - annotation in Jetpack Compose (and Compose Multiplatform) + - marks a function as part of the UI tree: + - Tells the compiler "this function describes teh UI, not normal code" + - It gets compiled into a declarative UI node + - Compose knows when to recompse it when states change + - Describes how its drawn on Android, iOS, DeskTop, and Web + - Compose can optimize it, skip re-rendering, or redraw specific parts. + - Without it, function is just normal Kotlin function; Compose cannot treat it as UI. + +### @Composable Function Signature +Example Code: +```kotlin +@Composable +fun LoginScreen( + onLoginClick: (email: String, password: string) -> Unit = { _, _ -> }, + onForgotPasswordClick: () -> Unit = {}, + onSignUpClick: () -> Unit = {} +) { + //... +} +``` +- `@Composable`: Marks this as a composable function -> describes UI in Compose. +- `fun LoginScreen(..)`: This is the login screen component. +- `onLoginClick`: A callback that takes an email and password. Default will be an empty lambda that does nothing +- `onForgotPasswordClick`: Callback when "Forgot password?" is clicked, default does nothing. +- `onSignUpClick`: Callback when "Sign Up now" is clicked, default is nothing. +- The defaults make it easy to preview or use this composable without wiring up real logic immediately. +- `_` == means "I don't care about this input" + +#### WHY it looks weird +- The function parameters are functions -- NOT Integers, Strings, or Booleans. + +Example of a normal param: +```kotlin +fun doSomething(name: String) {} +``` + +Kotlin also allows: +```kotlin +fun doSomething(callback: () -> Unit) + +// Which means callback is a function with no parameters that return nothing. +``` + +#### Why do this? +Because your UI composable should NOT contain business logic. + +Example: +Inside your screen you will have buttons like: +```kotlin +Button(onClick = { onLoginClick(email, password) }) { //... } +``` + +But the actual logic (like Firebase login, Drexel API login, etc.) will come from parent screen, not this UI. + +### Local state (email & password) +Example: +```kotlin +var email by remember { mutableStateOf("") +var password by remember { mutableStateOf("") } +``` +- `remember { mutableStateOf("") }`: Create state that Compose tracks and remember between recompositions. + +- Ken said I'm not the right type of asian so I'm not invited to the asian friendsgiving + +# 11/10/2025 + +Tried developing more not working + +- SDK broken nothing works. +- Kennan said something weird about my skin-color. + +# 10/5/2025 + +- Developing Kotlin Multiplatform + +Got it to set up. + +I went to this page to download the plugin + +[Plugin Link](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform?_gl=1%2A5nmlkh%2A_gcl_aw%2AR0NMLjE3NTk2ODk2MzkuQ2owS0NRandyb2pIQmhEZEFSSXNBSmRFSl9jbzlqd2wtaE01ZUlSM3RsbnQ4OWZlRGZjREx3MGNhT3AyYTQ1MlFwa3dyUVJfZWV3SzNUZ2FBc3hGRUFMd193Y0I.%2A_gcl_au%2AMTU1OTY1NjY0OC4xNzU5NTEyNjU4LjQ2NTY5OTIyOS4xNzU5Njg5OTU3LjE3NTk2ODk5NTY.%2AFPAU%2AMjk4MjQwNjQwLjE3NTk1MTI2Nzk.%2A_ga%2AMTk1NjQ5NDYzMC4xNzU5NTEyNjU5%2A_ga_9J976DJZ68%2AczE3NTk2ODk0NjMkbzIkZzEkdDE3NTk2OTAxNTgkajU5JGwwJGgw) + +Ignore the rating ;-) + +I downloaded the zip package + +Nvm tried pressing button computer crashed using some middle zip file they afftered. + +# 10/3/2025 + +## Pipeline Stuff + +- Create first pipeline to work off of. + - Simple and only focuses on creating test cases for the files we start with. + +- Updating README so it will be easy for us to follow professional standards when it comes to commits and etc. + +- Trying this new pipeline format that adds this weekly security check. + - It's new so I'm excited to see if I did it right and what I have done, so let's hope for the best. + +## Creating Kotlin Base for Multiplatform + +[Documentation Link](https://www.jetbrains.com/help/kotlin-multiplatform-dev/quickstart.html#set-up-the-environment) + # 11/2/2025 - Initialize the SQLite Database diff --git a/identifier.sqlite b/identifier.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/script_generation/generate_seed.py b/script_generation/generate_seed.py deleted file mode 100644 index 920e60f..0000000 --- a/script_generation/generate_seed.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env python3 -""" -generate_seed.py -Generates sql/seed_full.sql with realistic synthetic data for Find-My-Ride. -""" - -import os, random, datetime -from pathlib import Path - -random.seed(42) - -OUT_DIR = Path(".") -OUT_DIR.mkdir(parents=True, exist_ok=True) -SQL_PATH = OUT_DIR / "populate.sql" - -# --- keep existing sample rows --- -users_existing = [ - # (user_id, full_name_or_username, email, phone, role, rating_avg, created_at) - (1, "abdul_bookwala", "abdul.bookwala1@example.edu", "8569861234", "driver", 4.72, "2025-10-02T00:00:00"), - (2, "quincy_lu", "quincy.lu2@example.edu", "8569862334", "driver", 4.67, "2025-10-09T00:00:00"), - (3, "ame_shabuse", "ame.shabuse3@example.edu", "8569861334", "rider", 4.55, "2025-10-02T00:00:00"), - (4, "ame_lu", "ame.lu4@example.edu", "8569831234", "both", 4.54, "2025-10-10T00:00:00"), - (5, "kennan_shajid", "kennan.shajid5@example.edu", "8569861234", "both", 4.70, "2025-10-01T00:00:00"), -] - -vehicles_existing = [ - # (vehicle_id, owner_user_id, make, model, color, plate, seats_total, year, fun_fact) - (1,2,"Tesla","Model Y","Blue","NJ-7909",7,2025,"n/a"), - (2,5,"Tesla","Model 3","Gray","NJ-6737",5,2024,"n/a"), - (3,6,"Toyota","Camry","Silver","NJ-8767",4,2015,"n/a"), - (4,8,"Chevrolet","Malibu","Red","NJ-8301",5,2021,"n/a"), - (5,9,"Toyota","Camry","Blue","NJ-6823",4,2024,"n/a"), - (6,14,"Honda","Civic","White","NJ-7519",5,2022,"n/a"), -] - -locations_existing = [ - (1,"Drexel Main Building","3141 Chestnut St"), - (2,"Korman Center","3220-26 Woodland Walk"), - (3,"University Crossings","3175 JFK Blvd"), - (4,"30th Street Station","2955 Market St"), - (5,"Queen Lane Campus","2900 Queen Ln"), - (6,"Vidas Athletic Complex","43rd & Powelton"), - (7,"Cira Green","129 S 30th St"), - (8,"Wawa 34th Market","3400 Market St"), -] - -ride_offers_existing = [ - # (offer_id,driver_id,vehicle_id,origin_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) - (1,5,3,3,8,"2025-10-16T01:45:00",2,4.17,0.72,"closed","2025-10-15T01:45:00"), - (2,1,6,6,10,"2025-10-15T18:45:00",1,5.31,0.86,"closed","2025-10-14T18:45:00"), - (3,12,1,2,3,"2025-10-14T13:00:00",2,4.63,1.13,"open","2025-10-13T13:00:00"), - (4,6,2,3,8,"2025-10-14T17:45:00",3,8.52,0.9,"closed","2025-10-13T17:45:00"), -] - -ride_requests_existing = [ - # (request_id, rider_id, pickup_location_id, dropoff_location_id, earliest_pickup, latest_pickup, seats_needed, status, created_at) - (1,11,7,10,"2025-10-14T19:05:00","2025-10-14T19:45:00",1,"cancelled","2025-10-13T19:15:00"), - (2,10,6,8,"2025-10-14T13:15:00","2025-10-14T13:25:00",1,"open","2025-10-13T13:15:00"), - (3,7,1,9,"2025-10-14T07:20:00","2025-10-14T07:40:00",1,"matched","2025-10-13T07:30:00"), - (4,13,6,7,"2025-10-13T23:50:00","2025-10-14T00:30:00",2,"matched","2025-10-13T00:00:00"), - (5,10,7,2,"2025-10-15T21:45:00","2025-10-15T21:55:00",1,"open","2025-10-14T21:45:00"), - (6,11,3,2,"2025-10-15T03:00:00","2025-10-15T03:10:00",1,"cancelled","2025-10-14T03:00:00"), -] - -matches_existing = [ - # (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) - (1,3,3,1,11.71,"completed","2025-10-13T13:30:00"), - (2,4,7,1,7.95,"cancelled","2025-10-13T06:00:00"), - (3,8,16,2,14.77,"confirmed","2025-10-15T02:00:00"), - (4,9,16,1,15.16,"no_show","2025-10-13T08:00:00"), - (5,15,2,1,11.01,"completed","2025-10-13T16:45:00"), - (6,21,3,1,10.23,"no_show","2025-10-13T23:45:00"), -] - -ratings_existing = [ - # (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) - (1,1,2,12,5,"Friendly","2025-10-13T15:30:00"), - (3,8,9,4,5,"Great ride","2025-10-13T04:45:00"), -] - -# --- generation parameters --- -EXTRA = 100 # number of rows to add per table -start_user_id = max(u[0] for u in users_existing) + 1 # 6 -start_vehicle_id = max((v[0] for v in vehicles_existing), default=0) + 1 -start_location_id = max(l[0] for l in locations_existing) + 1 -start_offer_id = max(o[0] for o in ride_offers_existing) + 1 -start_request_id = max(r[0] for r in ride_requests_existing) + 1 -start_match_id = max(m[0] for m in matches_existing) + 1 -start_rating_id = max(r[0] for r in ratings_existing) + 1 - -first_names = ["Alex","Jordan","Taylor","Morgan","Cameron","Riley","Casey","Jamie","Avery","Sam","Sydney","Charlie","Drew","Logan","Peyton","Harper","Blake","Quinn","Rowan","Elliot","Maria","Jamal","Priya","Diego","Lina","Marcus"] -last_names = ["Smith","Johnson","Williams","Brown","Jones","Miller","Davis","Garcia","Rodriguez","Wilson","Martinez","Anderson","Taylor","Thomas","Hernandez","Moore","Martin","Jackson","Thompson","White"] -car_makes_models = [("Toyota","Camry"),("Honda","Civic"),("Tesla","Model 3"),("Tesla","Model Y"),("Chevrolet","Malibu"),("Hyundai","Elantra"),("Ford","Focus"),("Nissan","Altima")] -colors = ["Blue","Gray","Silver","Red","Black","White","Green","Gold","Maroon"] -roles = ["rider","driver","both"] - -# helper to produce ISO timestamps in Oct 2025 window -def rand_datetime_oct13_to_nov30(): - base = datetime.datetime(2025,10,13) - delta = datetime.timedelta(days=random.randint(0,48), hours=random.randint(0,23), minutes=random.randint(0,59)) - return (base + delta).isoformat(timespec="seconds") - -# --- generate users --- -users_generated = [] -for i in range(EXTRA): - uid = start_user_id + i - fn = random.choice(first_names) - ln = random.choice(last_names) - username = f"{fn.lower()}.{ln.lower()}{uid}" - email = f"{fn.lower()}.{ln.lower()}{uid}@drexel.edu" - phone = f"215{random.randint(2000000,9999999)}" - role = random.choices(roles, weights=[0.45,0.40,0.15], k=1)[0] - rating_avg = round(max(2.5, min(5.0, random.gauss(4.4,0.4))),2) - created_at = (datetime.datetime(2025,10,1) + datetime.timedelta(days=random.randint(0,60))).isoformat(timespec="seconds") - users_generated.append((uid, email, username, "x", phone, role, rating_avg, created_at)) - -users_all = users_existing + users_generated - -# --- generate locations --- -locations_generated = [] -for i in range(EXTRA): - lid = start_location_id + i - name = f"Philly Point {lid}" - address = f"{random.randint(100,3999)} Market St" - locations_generated.append((lid, name, address)) -locations_all = locations_existing + locations_generated - -# --- generate vehicles (assign to drivers/both) --- -driver_candidates = [u for u in users_all if u[5] in ("driver","both")] -# ensure enough drivers by converting some generated users if needed -if len(driver_candidates) < 40: - for j in range(30): - idx = j % len(users_generated) - u = users_generated[idx] - users_generated[idx] = (u[0], u[1], u[2], u[3], u[4], "driver", u[6], u[7]) - users_all = users_existing + users_generated - driver_candidates = [u for u in users_all if u[5] in ("driver","both")] - -vehicles_generated = [] -for i in range(EXTRA): - vid = start_vehicle_id + i - owner = random.choice(driver_candidates)[0] - make, model = random.choice(car_makes_models) - color = random.choice(colors) - plate = f"NJ-{1000 + vid:04d}" - seats = random.choice([4,4,5,5,6]) - year = random.randint(2010,2025) - fun_fact = random.choice(["n/a","student vehicle","rideshare-ready","garage kept"]) - vehicles_generated.append((vid, owner, make, model, color, plate, seats, year, fun_fact)) -vehicles_all = vehicles_existing + vehicles_generated - -# --- generate ride_offers --- -offers_generated = [] -for i in range(EXTRA): - oid = start_offer_id + i - driver = random.choice(driver_candidates)[0] - # pick a vehicle by this owner or random - owned = [v for v in vehicles_all if v[1] == driver] - vehicle_id = random.choice(owned)[0] if owned else random.choice(vehicles_all)[0] - origin = random.choice(locations_all)[0] - dest = random.choice(locations_all)[0] - if origin == dest: - dest = (dest % (start_location_id + EXTRA - 1)) + 1 - depart_at = rand_datetime_oct13_to_nov30() - seats_available = random.choice([1,1,2,2,3,4]) - price_base = round(random.uniform(3.0,9.0),2) - price_per_mile = round(random.uniform(0.5,1.6),2) - status = random.choices(["open","closed"], weights=[0.6,0.4])[0] - created_at = (datetime.datetime.fromisoformat(depart_at) - datetime.timedelta(days=random.randint(0,7))).isoformat(timespec="seconds") - offers_generated.append((oid, driver, vehicle_id, origin, dest, depart_at, seats_available, price_base, price_per_mile, status, created_at)) -offers_all = ride_offers_existing + offers_generated - -# --- generate ride_requests --- -riders = [u for u in users_all if u[5] in ("rider","both")] -requests_generated = [] -for i in range(EXTRA): - rid = start_request_id + i - rider = random.choice(riders)[0] - pickup = random.choice(locations_all)[0] - dropoff = random.choice(locations_all)[0] - if pickup == dropoff: - dropoff = (dropoff % (start_location_id + EXTRA - 1)) + 1 - earliest_dt = datetime.datetime(2025,10,13) + datetime.timedelta(days=random.randint(0,48), hours=random.randint(6,20), minutes=random.randint(0,59)) - latest_dt = earliest_dt + datetime.timedelta(minutes=random.randint(10,60)) - seats_needed = random.choice([1,1,2]) - status = random.choices(["open","matched","cancelled"], weights=[0.55,0.35,0.10])[0] - created_at = (earliest_dt - datetime.timedelta(days=random.randint(0,5))).isoformat(timespec="seconds") - requests_generated.append((rid, rider, pickup, dropoff, earliest_dt.isoformat(timespec="seconds"), latest_dt.isoformat(timespec="seconds"), seats_needed, status, created_at)) -requests_all = ride_requests_existing + requests_generated - -# --- generate matches by trying to align requests to offers --- -matches_generated = [] -match_id = start_match_id -# simple matching: randomly attempt to pair some requests to offers -for req in requests_generated: - if random.random() < 0.6: # 60% of generated requests get a match - # pick a candidate offer where seats >= seats_needed - candidates = [o for o in offers_all if o[6] >= req[6]] - if not candidates: - continue - offer = random.choice(candidates) - seats_booked = min(offer[6], req[6]) - miles = random.uniform(2.0,12.0) - price_total = round(offer[7] + offer[8]*miles, 2) if isinstance(offer[7], float) else round(offer[7] + offer[8]*miles,2) - state = random.choices(["confirmed","completed","cancelled","no_show"], weights=[0.4,0.35,0.15,0.10])[0] - matched_at = (datetime.datetime.fromisoformat(req[4]) - datetime.timedelta(hours=random.randint(0,4))).isoformat(timespec="seconds") - matches_generated.append((match_id, req[0], offer[0], seats_booked, price_total, state, matched_at)) - match_id += 1 - -matches_all = matches_existing + matches_generated - -# --- generate ratings for completed matches --- -ratings_generated = [] -rating_id = start_rating_id -for m in matches_generated: - if m[5] == "completed" and random.random() < 0.85: - # find from_user (rider) and to_user (driver) - req = next((r for r in requests_all if r[0] == m[1]), None) - offer = next((o for o in offers_all if o[0] == m[2]), None) - if not req or not offer: - continue - from_user = req[1] - to_user = offer[1] - if from_user == to_user: - continue - stars = random.choices([5,4,3,2,1], weights=[0.6,0.25,0.08,0.04,0.03])[0] - comment = random.choice(["Great ride","Friendly driver","On time","Would ride again","Car was clean","Driver was late","Helpful with bags"]) - created_at = (datetime.datetime.fromisoformat(m[6]) + datetime.timedelta(hours=random.randint(1,48))).isoformat(timespec="seconds") - ratings_generated.append((rating_id, m[0], from_user, to_user, stars, comment, created_at)) - rating_id += 1 - -# --- Clean up ratings to ensure no self-ratings or duplicates --- -cleaned_ratings = [] -seen_pairs = set() -for r in ratings_generated: - rid, match_id, from_user, to_user, stars, comment, created_at = r - if from_user == to_user: - continue - pair_key = (from_user, to_user) - if pair_key in seen_pairs: - continue - seen_pairs.add(pair_key) - cleaned_ratings.append(r) -ratings_generated = cleaned_ratings - -ratings_all = ratings_existing + ratings_generated - -# --- write SQL file --- -def sql_quote(val): - if val is None: - return "NULL" - if isinstance(val, str): - return "'" + val.replace("'", "''") + "'" - return str(val) - -with open(SQL_PATH, "w", encoding="utf-8") as f: - f.write("-- USERS\n") - for u in users_all: - if len(u) == 7: - # older sample format (id, fullname, email, phone, role, rating, created_at) - uid, uname, email, phone, role, rating, created = u if len(u) == 7 else (u[0], u[2], u[1], u[3], u[4], u[5], u[6]) - # if the sample stored full name, ensure username is simple - # but our existing list is normalized so treat generically: - f.write("INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s);\n" % ( - sql_quote(uid), sql_quote(email), sql_quote(uname), sql_quote("x"), sql_quote(phone), sql_quote(role), sql_quote(rating), sql_quote(created) - )) - else: - # generated entries: (uid, email, username, pw, phone, role, rating, created_at) - uid, email, uname, pw, phone, role, rating, created = u - f.write("INSERT INTO USER (user_id,email,username,password_hash,phone_number,role,rating_avg,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s);\n" % ( - sql_quote(uid), sql_quote(email), sql_quote(uname), sql_quote(pw), sql_quote(phone), sql_quote(role), sql_quote(rating), sql_quote(created) - )) - - f.write("\n-- VEHICLE\n") - for v in vehicles_all: - f.write("INSERT INTO VEHICLE (vehicle_id,owner_user_id,make,model,color,plate,seats_total,year,fun_fact) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s);\n" % tuple(sql_quote(x) for x in v)) - - f.write("\n-- LOCATION\n") - for loc in locations_all: - f.write("INSERT INTO LOCATION (location_id,name,address) VALUES (%s,%s,%s);\n" % (sql_quote(loc[0]), sql_quote(loc[1]), sql_quote(loc[2]))) - - f.write("\n-- RIDE_OFFER\n") - for o in offers_all: - f.write("INSERT INTO RIDE_OFFER (offer_id,driver_id,vehicle_id,original_location_id,dest_location_id,depart_at,seats_available,price_base,price_per_mile,status,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);\n" % ( - sql_quote(o[0]), sql_quote(o[1]), sql_quote(o[2]), sql_quote(o[3]), sql_quote(o[4]), sql_quote(o[5]), sql_quote(o[6]), sql_quote(o[7]), sql_quote(o[8]), sql_quote(o[9]), sql_quote(o[10]) - )) - - f.write("\n-- RIDE_REQUEST\n") - for r in requests_all: - f.write("INSERT INTO RIDE_REQUEST (request_id,rider_id,pickup_location_id,dropoff_location_id,earliest_pickup,latest_pickup,seats_needed,status,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s);\n" % ( - sql_quote(r[0]), sql_quote(r[1]), sql_quote(r[2]), sql_quote(r[3]), sql_quote(r[4]), sql_quote(r[5]), sql_quote(r[6]), sql_quote(r[7]), sql_quote(r[8]) - )) - - f.write("\n-- RIDE_MATCH\n") - for m in matches_all: - f.write("INSERT INTO RIDE_MATCH (match_id,request_id,offer_id,seats_booked,price_total,state,matched_at) VALUES (%s,%s,%s,%s,%s,%s,%s);\n" % ( - sql_quote(m[0]), sql_quote(m[1]), sql_quote(m[2]), sql_quote(m[3]), sql_quote(m[4]), sql_quote(m[5]), sql_quote(m[6]) - )) - - f.write("\n-- RATING\n") - for rt in ratings_all: - f.write("INSERT INTO RATING (rating_id,match_id,from_user_id,to_user_id,stars,comment,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s);\n" % ( - sql_quote(rt[0]), sql_quote(rt[1]), sql_quote(rt[2]), sql_quote(rt[3]), sql_quote(rt[4]), sql_quote(rt[5]), sql_quote(rt[6]) - )) - -print("Wrote SQL to:", SQL_PATH) -print("Row counts (approx): USERS=%d VEHICLE=%d LOCATION=%d RIDE_OFFER=%d RIDE_REQUEST=%d RIDE_MATCH=%d RATING=%d" % ( - len(users_all), len(vehicles_all), len(locations_all), len(offers_all), len(requests_all), len(matches_all), len(ratings_all) -)) \ No newline at end of file