diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
new file mode 100644
index 0000000..6321c42
--- /dev/null
+++ b/.github/workflows/android.yml
@@ -0,0 +1,31 @@
+name: Android CI
+on:
+ push:
+ branches: [ main, mvp-android ]
+ paths:
+ - 'android/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - 'android/**'
+
+jobs:
+ android-build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+
+ - name: Setup Gradle
+ uses: gradle/gradle-build-action@v2
+
+ - name: Build and Test
+ run: |
+ cd android
+ chmod +x gradlew
+ ./gradlew build test
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 82f9275..b9af20a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,3 +160,75 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
+
+# Android
+*.iml
+.gradle/
+local.properties
+.idea/
+.DS_Store
+build/
+captures/
+.externalNativeBuild
+.cxx
+
+# Keystore files
+*.jks
+*.keystore
+!android/keystore/debug.keystore
+
+# Android Studio
+*.iws
+*.ipr
+.idea/
+out/
+
+# Gradle
+.gradle/
+build/
+gradle-app.setting
+!gradle-wrapper.jar
+.gradletasknamecache
+
+# Generated files
+bin/
+gen/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/caches
+
+# VS Code
+.vscode/
+
+# Sonar
+.sonar/
+.scannerwork/
+
+# Test reports
+app/build/reports/
+app/build/test-results/
+
+# Detekt
+build/reports/detekt/
+
+# KtLint
+build/reports/ktlint/
+.gradle/
+
+# Android Studio
+.idea/
\ No newline at end of file
diff --git a/README.md b/README.md
index dde1e6d..758e59b 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,10 @@
# RunOn
+## Project Status
+
[](https://github.com/fleXRPL/RunOn/actions/workflows/build.yml)
+[](https://github.com/fleXRPL/RunOn/actions/workflows/android.yml)
[](https://sonarcloud.io/summary/new_code?id=fleXRPL_RunOn)
[](https://sonarcloud.io/summary/new_code?id=fleXRPL_RunOn)
[](https://sonarcloud.io/summary/new_code?id=fleXRPL_RunOn)
@@ -24,6 +27,7 @@ RunOn helps runners find and participate in local running events by providing:
## Architecture
### Backend
+
- **APIs**:
- Google Search API for event discovery
- Google Calendar API for event management
@@ -33,6 +37,7 @@ RunOn helps runners find and participate in local running events by providing:
- SonarCloud quality gates
### Android App
+
- **Language**: Kotlin
- **UI Framework**: Jetpack Compose
- **Key Features**:
@@ -40,23 +45,50 @@ RunOn helps runners find and participate in local running events by providing:
- Direct Google Calendar integration
- Google Sign-in
+## Project Structure
+
+```bash
+RunOn/
+├── .github/
+│ └── workflows/
+│ └── build.yml # CI/CD pipeline
+│ └── android.yml # Android CI/CD pipeline
+├── android/ # Android mobile app
+│ ├── app/ # Main Android application
+│ └── docs/ # Android documentation
+├── backend/ # Core functionality
+│ ├── functions/ # Business logic
+│ ├── models/ # Data models
+│ ├── tests/ # Test suite
+│ └── scripts/ # Development tools
+```
+
## Development Setup
-1. Clone the repository:
+### Android Setup
+
```bash
+# Clone repository
git clone https://github.com/fleXRPL/RunOn.git
-cd RunOn
+cd RunOn/android
+
+# Run Android setup script
+chmod +x scripts/setup.sh
+bash scripts/setup.sh
```
-2. Install dependencies:
+### Backend Setup
+
```bash
-cd backend
+# Clone repository
+git clone https://github.com/fleXRPL/RunOn.git
+cd RunOn/backend
+
+# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt
-```
-3. Run tests:
-```bash
+# Run tests and checks
bash scripts/format_and_lint.sh
```
@@ -69,7 +101,8 @@ bash scripts/format_and_lint.sh
1. Ensure all tests pass with 100% coverage
2. Follow PEP 8 style guide
-3. Run `format_and_lint.sh` before committing
+3. Run `format_and_lint.sh` before committing to backend
+4. Run `format_and_lint.sh` before committing to android
## License
diff --git a/android/README.md b/android/README.md
index 9e0a587..146f285 100644
--- a/android/README.md
+++ b/android/README.md
@@ -1,103 +1,84 @@
-# RunOn Android Application
+# RunOn! Android App
-## Overview
+## MVP Features
-The RunOn Android application provides a native mobile interface for discovering and managing running events, with seamless calendar integration and user management features.
+- Event discovery integration
+- Google Calendar sync
+- Clean architecture implementation
## Project Structure
-```
+```bash
android/
├── app/
│ ├── src/
│ │ ├── main/
-│ │ │ ├── kotlin/com/flexrpl/runon/
-│ │ │ │ ├── activities/ # Main UI activities
-│ │ │ │ ├── fragments/ # UI fragments
-│ │ │ │ ├── models/ # Data models
-│ │ │ │ ├── network/ # API client and services
-│ │ │ │ ├── services/ # Background services
-│ │ │ │ └── utils/ # Utility classes
-│ │ │ └── res/ # Resources
-│ │ └── test/ # Unit tests
-│ ├── build.gradle # App-level build config
-│ └── proguard-rules.pro # ProGuard rules
-├── gradle/ # Gradle wrapper
-├── build.gradle # Project-level build config
-└── settings.gradle # Project settings
+│ │ │ ├── kotlin/
+│ │ │ │ └── com/flexrpl/runon/
+│ │ │ │ ├── data/ # Data layer
+│ │ │ │ ├── domain/ # Business logic
+│ │ │ │ └── ui/ # Presentation layer
+│ │ │ └── res/ # Resources
+│ │ └── test/ # Unit tests
+│ └── build.gradle.kts
+└── build.gradle.kts
```
-## Technology Stack
-
-- **Language**: Kotlin
-- **UI Framework**: Jetpack Compose
-- **Architecture**: MVVM with Clean Architecture
-- **Dependencies**:
- - Android Architecture Components
- - Retrofit for API communication
- - Room for local storage
- - Hilt for dependency injection
- - Material Design 3
-
## Development Setup
-1. Install Android Studio (latest version)
-2. Install JDK 17 or later
-3. Clone the repository
-4. Open the project in Android Studio
-5. Sync Gradle files
-6. Run the application
-
-## Building and Running
+### Quick Start
```bash
-# Build debug variant
-./gradlew assembleDebug
-
-# Run tests
-./gradlew test
-
-# Install on connected device
-./gradlew installDebug
+# From project root
+./android/scripts/setup.sh
```
-## Testing
+The setup script will:
+
+- Install required tools (via Homebrew)
+- Initialize Gradle
+- Set up permissions
+- Run initial build
+
+### Required Software
+
+- Android Studio Hedgehog | 2023.1.1
+- JDK 17
+
+## Core Dependencies
+
+```kotlin
+dependencies {
+ // Core Android
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
+
+ // UI
+ implementation("androidx.activity:activity-compose:1.8.2")
+ implementation(platform("androidx.compose:compose-bom:2024.01.00"))
+
+ // Testing
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+}
+```
-- Unit Tests: `./gradlew test`
-- Instrumentation Tests: `./gradlew connectedAndroidTest`
-- UI Tests: `./gradlew connectedCheck`
+## Getting Started
-## CI/CD
+1. Clone the repository
+2. Open in Android Studio
+3. Sync Gradle files
+4. Run tests
+5. Build and run
-The project uses GitHub Actions for continuous integration and deployment:
+## Testing Requirements
-- Automated builds
-- Unit test execution
-- Code quality checks
-- Release management
+- Unit tests for all business logic
+- UI tests for critical paths
+- Integration tests for API communication
## Code Style
-The project follows the official Kotlin style guide and Android best practices:
-
-- Kotlin style guide
-- Android architecture components patterns
-- Material Design guidelines
-
-## Security
-
-- SSL pinning for API communication
-- Secure storage for user credentials
-- ProGuard optimization and obfuscation
-
-## Contributing
-
-1. Fork the repository
-2. Create a feature branch
-3. Commit your changes
-4. Push to the branch
-5. Create a Pull Request
-
-## License
-
-This project is licensed under the terms of the [LICENSE](../LICENSE) file in the root directory.
+- Follow Kotlin coding conventions
+- Use Compose best practices
+- Maintain clean architecture separation
diff --git a/android/android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt b/android/android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt
new file mode 100644
index 0000000..666380f
--- /dev/null
+++ b/android/android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt
@@ -0,0 +1,9 @@
+package com.flexrpl.runon.domain.model
+
+data class Event(
+ val id: String,
+ val title: String,
+ val description: String,
+ val date: String,
+ val location: String
+)
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
new file mode 100644
index 0000000..de19b2d
--- /dev/null
+++ b/android/app/build.gradle.kts
@@ -0,0 +1,82 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+ id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
+}
+
+android {
+ namespace = "com.flexrpl.runon"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.flexrpl.runon"
+ minSdk = 26
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildFeatures {
+ compose = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.8"
+ }
+
+ signingConfigs {
+ getByName("debug") {
+ storeFile = file("../keystore/debug.keystore")
+ storePassword = "android"
+ keyAlias = "androiddebugkey"
+ keyPassword = "android"
+ }
+ }
+
+ buildTypes {
+ debug {
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+}
+
+dependencies {
+ // Core Android
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
+ implementation("androidx.activity:activity-compose:1.8.2")
+
+ // Compose (minimal set)
+ implementation(platform("androidx.compose:compose-bom:2024.01.00"))
+ implementation("androidx.compose.ui:ui")
+ implementation("androidx.compose.material3:material3")
+ implementation("androidx.compose.ui:ui-tooling-preview")
+
+ // Basic Testing
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
+
+ // Debug tooling
+ debugImplementation("androidx.compose.ui:ui-tooling")
+
+ // Coroutines
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
+}
+
+// Basic KtLint configuration
+ktlint {
+ android.set(true)
+ verbose.set(true)
+}
diff --git a/android/app/config/detekt.yml b/android/app/config/detekt.yml
new file mode 100644
index 0000000..31f9b88
--- /dev/null
+++ b/android/app/config/detekt.yml
@@ -0,0 +1,11 @@
+complexity:
+ LongMethod:
+ threshold: 60
+ TooManyFunctions:
+ thresholdInFiles: 20
+ thresholdInClasses: 20
+ thresholdInInterfaces: 20
+
+style:
+ MaxLineLength:
+ maxLineLength: 120
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c318295
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/flexrpl/runon/MainActivity.kt b/android/app/src/main/kotlin/com/flexrpl/runon/MainActivity.kt
new file mode 100644
index 0000000..56c4c42
--- /dev/null
+++ b/android/app/src/main/kotlin/com/flexrpl/runon/MainActivity.kt
@@ -0,0 +1,36 @@
+package com.flexrpl.runon
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.flexrpl.runon.data.repository.EventRepositoryImpl
+import com.flexrpl.runon.ui.EventScreen
+import com.flexrpl.runon.ui.EventViewModel
+
+class MainActivity : ComponentActivity() {
+ private val viewModel by lazy {
+ val repository = EventRepositoryImpl()
+ ViewModelProvider(this, EventViewModelFactory(repository))[EventViewModel::class.java]
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ EventScreen(viewModel)
+ }
+ }
+}
+
+class EventViewModelFactory(
+ private val repository: EventRepositoryImpl
+) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ if (modelClass.isAssignableFrom(EventViewModel::class.java)) {
+ return EventViewModel(repository) as T
+ }
+ throw IllegalArgumentException("Unknown ViewModel class")
+ }
+}
diff --git a/android/app/src/main/kotlin/com/flexrpl/runon/data/repository/EventRepository.kt b/android/app/src/main/kotlin/com/flexrpl/runon/data/repository/EventRepository.kt
new file mode 100644
index 0000000..aa6eaaa
--- /dev/null
+++ b/android/app/src/main/kotlin/com/flexrpl/runon/data/repository/EventRepository.kt
@@ -0,0 +1,8 @@
+package com.flexrpl.runon.data.repository
+
+import com.flexrpl.runon.domain.model.Event
+import kotlinx.coroutines.flow.Flow
+
+interface EventRepository {
+ fun getEvents(): Flow>
+}
diff --git a/android/app/src/main/kotlin/com/flexrpl/runon/data/repository/EventRepositoryImpl.kt b/android/app/src/main/kotlin/com/flexrpl/runon/data/repository/EventRepositoryImpl.kt
new file mode 100644
index 0000000..b695397
--- /dev/null
+++ b/android/app/src/main/kotlin/com/flexrpl/runon/data/repository/EventRepositoryImpl.kt
@@ -0,0 +1,14 @@
+package com.flexrpl.runon.data.repository
+
+import com.flexrpl.runon.domain.model.Event
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+class EventRepositoryImpl : EventRepository {
+ override fun getEvents(): Flow> = flowOf(
+ listOf(
+ Event("1", "Morning Run", "5K run in the park", "2024-02-14", "Central Park"),
+ Event("2", "Evening Jog", "Easy 3K", "2024-02-15", "Riverside")
+ )
+ )
+}
diff --git a/android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt b/android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt
new file mode 100644
index 0000000..666380f
--- /dev/null
+++ b/android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt
@@ -0,0 +1,9 @@
+package com.flexrpl.runon.domain.model
+
+data class Event(
+ val id: String,
+ val title: String,
+ val description: String,
+ val date: String,
+ val location: String
+)
diff --git a/android/app/src/main/kotlin/com/flexrpl/runon/ui/EventScreen.kt b/android/app/src/main/kotlin/com/flexrpl/runon/ui/EventScreen.kt
new file mode 100644
index 0000000..1b482c1
--- /dev/null
+++ b/android/app/src/main/kotlin/com/flexrpl/runon/ui/EventScreen.kt
@@ -0,0 +1,58 @@
+package com.flexrpl.runon.ui
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.flexrpl.runon.domain.model.Event
+
+@Composable
+fun EventScreen(viewModel: EventViewModel) {
+ Surface(color = MaterialTheme.colorScheme.background) {
+ val events by viewModel.events.collectAsState(initial = emptyList())
+ EventList(events = events)
+ }
+}
+
+@Composable
+private fun EventList(events: List) {
+ LazyColumn {
+ items(events) { event ->
+ EventCard(event = event)
+ }
+ }
+}
+
+@Composable
+private fun EventCard(event: Event) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp)
+ ) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text(
+ text = event.title,
+ style = MaterialTheme.typography.titleMedium
+ )
+ Text(
+ text = event.date,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ Text(
+ text = event.location,
+ style = MaterialTheme.typography.bodySmall
+ )
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/com/flexrpl/runon/ui/EventViewModel.kt b/android/app/src/main/kotlin/com/flexrpl/runon/ui/EventViewModel.kt
new file mode 100644
index 0000000..2872fbe
--- /dev/null
+++ b/android/app/src/main/kotlin/com/flexrpl/runon/ui/EventViewModel.kt
@@ -0,0 +1,11 @@
+package com.flexrpl.runon.ui
+
+import androidx.lifecycle.ViewModel
+import com.flexrpl.runon.data.repository.EventRepository
+import kotlinx.coroutines.flow.StateFlow
+
+class EventViewModel(
+ private val repository: EventRepository
+) : ViewModel() {
+ val events = repository.getEvents()
+}
diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..5527bf5
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap/ic_launcher.xml b/android/app/src/main/res/mipmap/ic_launcher.xml
new file mode 100644
index 0000000..f3f7fbe
--- /dev/null
+++ b/android/app/src/main/res/mipmap/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c7f5192
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #FF6200EE
+ #FFFFFF
+ #FFFFFF
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..87f2117
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ RunOn
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..372780d
--- /dev/null
+++ b/android/app/src/main/res/values/themes.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/test/kotlin/com/flexrpl/runon/EventViewModelTest.kt b/android/app/src/test/kotlin/com/flexrpl/runon/EventViewModelTest.kt
new file mode 100644
index 0000000..4d2df67
--- /dev/null
+++ b/android/app/src/test/kotlin/com/flexrpl/runon/EventViewModelTest.kt
@@ -0,0 +1,49 @@
+package com.flexrpl.runon
+
+import com.flexrpl.runon.data.repository.EventRepository
+import com.flexrpl.runon.domain.model.Event
+import com.flexrpl.runon.ui.EventViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class EventViewModelTest {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val repository: EventRepository = mock()
+ private lateinit var viewModel: EventViewModel
+ private val testEvents = listOf(
+ Event("1", "Test Event", "Description", "2024-02-14", "Location")
+ )
+
+ @Before
+ fun setup() {
+ whenever(repository.getEvents()).thenReturn(flowOf(testEvents))
+ Dispatchers.setMain(testDispatcher)
+ viewModel = EventViewModel(repository)
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `events are loaded from repository`() = testScope.runTest {
+ val actual = viewModel.events.first()
+ assertEquals(testEvents, actual)
+ }
+}
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644
index 0000000..143568b
--- /dev/null
+++ b/android/build.gradle.kts
@@ -0,0 +1,15 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+plugins {
+ id("com.android.application") version "8.2.2" apply false
+ kotlin("android") version "1.9.22" apply false
+}
+
+tasks.register("clean", Delete::class) {
+ delete(layout.buildDirectory)
+}
\ No newline at end of file
diff --git a/android/docs/DEVELOPMENT.md b/android/docs/DEVELOPMENT.md
new file mode 100644
index 0000000..857341e
--- /dev/null
+++ b/android/docs/DEVELOPMENT.md
@@ -0,0 +1,168 @@
+# Android Development Guide
+
+## Project Overview
+
+RunOn is an Android application built with modern Android development tools and practices:
+
+- Kotlin as the programming language
+- Jetpack Compose for UI
+- Coroutines and Flow for asynchronous operations
+- Material3 for design
+
+## Directory Structure
+
+```bash
+android/
+├── app/ # Main application module
+│ ├── src/
+│ │ ├── main/
+│ │ │ ├── kotlin/ # Kotlin source files
+│ │ │ │ └── com/flexrpl/runon/
+│ │ │ │ ├── data/ # Data layer (repositories)
+│ │ │ │ ├── domain/ # Domain layer (models)
+│ │ │ │ └── ui/ # UI layer (screens, viewmodels)
+│ │ │ └── res/ # Android resources
+│ │ └── test/ # Unit tests
+│ └── build.gradle.kts # App-level build configuration
+├── gradle/ # Gradle wrapper files
+├── scripts/ # Development scripts
+└── build.gradle.kts # Project-level build configuration
+```
+
+## Setup Instructions
+
+### Prerequisites
+
+1. JDK 17
+2. macOS (M1/ARM64 compatible)
+3. Android Studio (for emulator and interactive development)
+4. Git
+
+### First-Time Setup
+
+1. Clone and setup:
+
+ ```bash
+ git clone https://github.com/flexrpl/RunOn.git
+ cd RunOn
+ ./android/scripts/setup.sh # Sets up build environment
+ ```
+
+2. Configure emulator in Android Studio:
+ - Open Android Studio
+ - Tools → Device Manager
+ - Create Device (Pixel 5, API 34)
+ - Start emulator
+
+3. Build and install from command line:
+
+ ```bash
+ cd android
+ ./gradlew installDebug
+ ```
+
+### Development Workflow
+
+1. **Command Line Tasks**:
+ - Building: `./gradlew build`
+ - Testing: `./gradlew test`
+ - Formatting: `./scripts/format_and_lint.sh`
+ - Installing: `./gradlew installDebug`
+
+2. **Android Studio Tasks**:
+ - Code editing
+ - Debugging
+ - UI development
+ - Resource management
+
+### 1. Code Organization
+
+- **data/repository/**: Data access layer
+ - `EventRepository.kt`: Interface defining data operations
+ - `EventRepositoryImpl.kt`: Implementation with mock data
+- **domain/model/**: Business models
+ - `Event.kt`: Core data model
+- **ui/**: User interface components
+ - `EventScreen.kt`: Main UI composition
+ - `EventViewModel.kt`: Manages UI state
+
+### 2. Making Changes
+
+1. Create a feature branch:
+
+ ```bash
+ git checkout -b feature/your-feature-name
+ ```
+
+2. Write code following our conventions:
+ - Use Kotlin idioms
+ - Follow Material3 design guidelines
+ - Write tests for business logic
+
+3. Format your code:
+
+ ```bash
+ ./scripts/format_and_lint.sh
+ ```
+
+ This runs:
+ - ktlint for Kotlin style
+
+4. Run tests:
+
+ ```bash
+ ./gradlew test
+ ```
+
+### 3. Testing
+
+The project includes:
+
+- Unit tests with JUnit and MockK
+- Basic UI tests with Compose testing
+- Coroutines testing utilities
+
+### 4. Manual Testing
+
+1. Start the emulator:
+
+ ```bash
+ $ANDROID_HOME/emulator/emulator -avd RunOnEmulator &
+ ```
+
+2. Install the app:
+
+ ```bash
+ cd android
+ ./gradlew installDebug
+ ```
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Emulator Issues**
+ - First boot can take several minutes
+ - Check nohup.out for emulator logs
+ - Try rerunning dev_setup.sh
+
+2. **Build Failures**
+ - Check Java version: `java -version`
+ - Verify ANDROID_HOME is set
+ - Run `./gradlew clean build --info`
+
+3. **Test Failures**
+ - Check test logs in build/reports/tests
+ - Run specific test: `./gradlew test --tests TestName`
+
+### Getting Help
+
+- Check existing GitHub issues
+- Review this documentation
+- Ask in team discussions
+
+## Additional Resources
+
+- [Kotlin Style Guide](https://kotlinlang.org/docs/coding-conventions.html)
+- [Compose Documentation](https://developer.android.com/jetpack/compose)
+- [Material3 Design](https://m3.material.io/)
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..97f494e
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,3 @@
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+kotlin.code.style=official
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..a4b76b9
Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cea7a79
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/android/gradlew b/android/gradlew
new file mode 100755
index 0000000..f3b75f3
--- /dev/null
+++ b/android/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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# 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, 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" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# 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/android/gradlew.bat b/android/gradlew.bat
new file mode 100644
index 0000000..9b42019
--- /dev/null
+++ b/android/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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+: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/android/keystore/debug.keystore b/android/keystore/debug.keystore
new file mode 100644
index 0000000..ddc569e
Binary files /dev/null and b/android/keystore/debug.keystore differ
diff --git a/android/nohup.out b/android/nohup.out
new file mode 100644
index 0000000..890e174
--- /dev/null
+++ b/android/nohup.out
@@ -0,0 +1,9 @@
+INFO | Android emulator version 35.2.10.0 (build_id 12414864) (CL:N/A)
+INFO | Graphics backend: gfxstream
+WARNING | Failed to process .ini file /Users/garotconklin/.android/avd/RunOnEmulator.avd/config.ini for reading.
+WARNING | Failed to process .ini file /Users/garotconklin/.android/avd/RunOnEmulator.avd/config.ini for reading.
+PANIC: CPU Architecture 'arm' is not supported by the QEMU2 emulator, (the classic engine is deprecated!)INFO | Android emulator version 35.2.10.0 (build_id 12414864) (CL:N/A)
+INFO | Graphics backend: gfxstream
+WARNING | Failed to process .ini file /Users/garotconklin/.android/avd/RunOnEmulator.avd/config.ini for reading.
+WARNING | Failed to process .ini file /Users/garotconklin/.android/avd/RunOnEmulator.avd/config.ini for reading.
+PANIC: CPU Architecture 'arm' is not supported by the QEMU2 emulator, (the classic engine is deprecated!)
\ No newline at end of file
diff --git a/android/scripts/dev_setup.sh b/android/scripts/dev_setup.sh
new file mode 100755
index 0000000..4a88374
--- /dev/null
+++ b/android/scripts/dev_setup.sh
@@ -0,0 +1,165 @@
+#!/bin/bash
+
+set -e
+
+echo "🚀 Setting up development environment..."
+
+# Function to check if a command exists
+command_exists() {
+ command -v "$1" >/dev/null 2>&1
+}
+
+# Check for cleanup flag
+if [ "$1" = "--clean" ]; then
+ echo "🧹 Cleaning up existing emulator..."
+ rm -rf ~/.android/avd/RunOnEmulator*
+ rm -f ~/.android/avd/RunOnEmulator.ini
+ echo "✅ Cleanup complete"
+ exit 0
+fi
+
+# Set ANDROID_HOME if not set
+if [ -z "$ANDROID_HOME" ]; then
+ export ANDROID_HOME="/Users/$USER/Library/Android/sdk"
+ echo "Setting ANDROID_HOME to $ANDROID_HOME"
+ echo "export ANDROID_HOME=$ANDROID_HOME" >> ~/.zshrc
+fi
+
+# Set ANDROID_SDK_ROOT (required by emulator)
+if [ -z "$ANDROID_SDK_ROOT" ]; then
+ export ANDROID_SDK_ROOT="$ANDROID_HOME"
+ echo "Setting ANDROID_SDK_ROOT to $ANDROID_SDK_ROOT"
+ echo "export ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> ~/.zshrc
+fi
+
+# Verify Android SDK location
+if [ ! -d "$ANDROID_HOME" ]; then
+ echo "Android SDK not found at $ANDROID_HOME, attempting to install..."
+ if ! command_exists sdkmanager; then
+ echo "Installing Android Command Line Tools..."
+ brew install --cask android-commandlinetools
+ fi
+fi
+
+# Add Android SDK tools to PATH
+export PATH="$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin"
+
+echo "📱 Setting up Android emulator..."
+
+# Install required packages
+echo "Installing system images and emulator..."
+yes | sdkmanager --licenses > /dev/null 2>&1
+echo "Installing Android SDK components..."
+echo "Installing platform tools..."
+sdkmanager "platform-tools"
+
+echo "Installing emulator..."
+sdkmanager "emulator"
+
+echo "Installing system image..."
+mkdir -p "$ANDROID_SDK_ROOT/system-images/android-34/google_apis/arm64-v8a"
+sdkmanager --verbose "system-images;android-34;google_apis;arm64-v8a"
+
+echo "Installing platform and build tools..."
+sdkmanager "platforms;android-34" "build-tools;34.0.0"
+
+# Accept the system image license
+yes | sdkmanager --licenses
+
+# Verify system image installation
+if [ ! -d "$ANDROID_SDK_ROOT/system-images/android-34/google_apis/arm64-v8a" ]; then
+ echo "Checking system image installation..."
+ sdkmanager --list_installed | grep "system-images;android-34"
+ echo "❌ System image installation failed"
+ exit 1
+fi
+
+# Create AVD if it doesn't exist
+if ! avdmanager list avd | grep -q "RunOnEmulator"; then
+ echo "Creating Android Virtual Device..."
+
+ # Clean up any existing AVD files
+ echo "Cleaning up any existing AVD files..."
+ rm -rf ~/.android/avd/RunOnEmulator*
+ rm -f ~/.android/avd/RunOnEmulator.ini
+
+ # Create base config directory
+ mkdir -p ~/.android/avd
+
+ # Create AVD with minimal configuration
+ echo "no" | avdmanager create avd \
+ -n "RunOnEmulator" \
+ -k "system-images;android-34;google_apis;arm64-v8a" \
+ --force
+
+ # Create a fresh config.ini specifically for M1
+ cat > ~/.android/avd/RunOnEmulator.avd/config.ini << 'EOL'
+hw.cpu.arch=arm64
+hw.cpu.ncore=4
+image.sysdir.1=system-images/android-34/google_apis/arm64-v8a/
+tag.display=Google APIs
+tag.id=google_apis
+AvdId=RunOnEmulator
+EOL
+
+ # Create hardware config
+ cat > ~/.android/avd/RunOnEmulator.avd/hardware-qemu.ini << 'EOL'
+hw.ramSize=4096
+hw.gpu.enabled=yes
+hw.gpu.mode=auto
+hw.keyboard=yes
+EOL
+else
+ echo "✓ RunOnEmulator AVD already exists"
+fi
+
+# Check if emulator is running
+if ! pgrep -f "RunOnEmulator" > /dev/null; then
+ echo "Starting emulator..."
+ # Start emulator in background
+ nohup $ANDROID_HOME/emulator/emulator \
+ -avd RunOnEmulator \
+ -no-window \
+ -gpu host \
+ -accel auto &
+
+ # Wait for emulator to boot
+ echo "Waiting for emulator to boot..."
+ echo "This might take a few minutes on first boot..."
+
+ # Add timeout (5 minutes)
+ TIMEOUT=300
+ COUNTER=0
+
+ $ANDROID_HOME/platform-tools/adb wait-for-device
+
+ # Additional wait for full boot
+ echo -n "Waiting for system boot: "
+ while [ "$($ANDROID_HOME/platform-tools/adb shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
+ echo -n "."
+ sleep 2
+ COUNTER=$((COUNTER + 2))
+ if [ $COUNTER -ge $TIMEOUT ]; then
+ echo "\n❌ Emulator boot timed out after ${TIMEOUT} seconds"
+ echo "Try running the script again or check the emulator logs"
+ exit 1
+ fi
+ done
+ echo " Done!"
+else
+ echo "✓ Emulator already running"
+fi
+
+echo "��� Building and installing app..."
+cd "$(dirname "$0")/.."
+./gradlew installDebug
+
+echo "✅ Development environment setup complete!"
+echo "The RunOn app should now be installed on the emulator"
+echo ""
+echo "To manually start the emulator in the future:"
+echo "$ANDROID_HOME/emulator/emulator -avd RunOnEmulator &"
+echo ""
+echo "To reinstall the app:"
+echo "cd android"
+echo "./gradlew installDebug"
\ No newline at end of file
diff --git a/android/scripts/fix_formatting.sh b/android/scripts/fix_formatting.sh
new file mode 100755
index 0000000..fab3073
--- /dev/null
+++ b/android/scripts/fix_formatting.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# Exit on error
+set -e
+
+echo "🔧 Fixing file formatting..."
+
+# Find all Kotlin files
+find . -name "*.kt" -type f | while read -r file; do
+ # Ensure file ends with newline
+ if [ "$(tail -c1 "$file" | xxd -p)" != "0a" ]; then
+ echo "" >> "$file"
+ echo "✓ Added newline to $file"
+ fi
+
+ # Remove trailing whitespace
+ sed -i '' 's/[[:space:]]*$//' "$file"
+ echo "✓ Removed trailing spaces from $file"
+done
+
+echo "✅ Formatting fixes complete!"
\ No newline at end of file
diff --git a/android/scripts/format_and_lint.sh b/android/scripts/format_and_lint.sh
new file mode 100755
index 0000000..2810381
--- /dev/null
+++ b/android/scripts/format_and_lint.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -e
+
+# Change to the android directory (parent of scripts directory)
+cd "$(dirname "$0")/.."
+
+echo "🧹 Running code formatting..."
+
+# Run ktlint
+echo "Running ktlint..."
+./gradlew ktlintFormat
+
+echo "✅ Code formatting complete!"
\ No newline at end of file
diff --git a/android/scripts/setup.sh b/android/scripts/setup.sh
new file mode 100755
index 0000000..23a66b9
--- /dev/null
+++ b/android/scripts/setup.sh
@@ -0,0 +1,198 @@
+#!/bin/bash
+
+# Exit on error
+set -e
+
+echo "🚀 Setting up Android development environment..."
+
+# Function to check if a command exists
+command_exists() {
+ command -v "$1" >/dev/null 2>&1
+}
+
+# Check Java version
+echo "📝 Checking Java version..."
+# Check if Java is installed and is version 17
+if ! command_exists java || ! java -version 2>&1 | grep -q "version \"17"; then
+ echo "Installing JDK 17..."
+ brew install openjdk@17
+ # Only create symlink if it doesn't exist
+ if [ ! -L "/Library/Java/JavaVirtualMachines/openjdk-17.jdk" ]; then
+ sudo ln -sfn $(brew --prefix)/opt/openjdk@17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk
+ fi
+fi
+
+# Check if Homebrew is installed
+if ! command_exists brew; then
+ echo "Installing Homebrew..."
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+fi
+
+# Install required tools
+echo "📦 Installing required tools..."
+if ! command_exists gradle; then
+ echo "Installing Gradle..."
+ brew install gradle
+else
+ echo "✓ Gradle already installed"
+fi
+
+if ! command_exists kotlin; then
+ echo "Installing Kotlin..."
+ brew install kotlin
+else
+ echo "✓ Kotlin already installed"
+fi
+
+# Ensure using Java 17
+export JAVA_HOME=$(/usr/libexec/java_home -v 17)
+echo "Using Java from: $JAVA_HOME"
+
+# Install Android Command Line Tools
+echo "📱 Setting up Android SDK..."
+if ! command_exists sdkmanager; then
+ echo "Installing Android Command Line Tools..."
+ brew install --cask android-commandlinetools
+else
+ echo "✓ Android Command Line Tools already installed"
+fi
+
+# Set ANDROID_HOME and ANDROID_SDK_ROOT
+export ANDROID_HOME="/Users/$USER/Library/Android/sdk"
+export ANDROID_SDK_ROOT="$ANDROID_HOME"
+echo "export ANDROID_HOME=$ANDROID_HOME" >> ~/.zshrc
+echo "export ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> ~/.zshrc
+echo "export PATH=\$PATH:\$ANDROID_HOME/tools:\$ANDROID_HOME/platform-tools" >> ~/.zshrc
+
+# Accept licenses and install required SDK components
+yes | sdkmanager --licenses
+echo "Installing Android SDK components..."
+sdkmanager --install \
+ "platform-tools" \
+ "platforms;android-34" \
+ "build-tools;34.0.0" \
+ "emulator" \
+ "system-images;android-34;google_apis;arm64-v8a"
+
+# Verify installations
+for component in "platform-tools" "platforms;android-34" "build-tools;34.0.0" "emulator" "system-images;android-34;google_apis;arm64-v8a"; do
+ if ! sdkmanager --list | grep -q "$component"; then
+ echo "❌ Failed to install $component"
+ exit 1
+ fi
+done
+
+# Create local.properties
+echo "sdk.dir=$ANDROID_HOME" > android/local.properties
+
+# Create gradle.properties with required settings
+echo "android.useAndroidX=true" > android/gradle.properties
+echo "org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8" >> android/gradle.properties
+echo "kotlin.code.style=official" >> android/gradle.properties
+
+# Simplify directory creation
+mkdir -p android/app/src/main/res/{mipmap,values,drawable}
+mkdir -p android/app/src/main/kotlin/com/flexrpl/runon/{data/repository,domain/model,ui}
+
+# Generate debug keystore
+if [ ! -f "android/keystore/debug.keystore" ]; then
+ echo "🔐 Generating debug keystore..."
+ keytool -genkey -v \
+ -keystore android/keystore/debug.keystore \
+ -storepass android \
+ -alias androiddebugkey \
+ -keypass android \
+ -keyalg RSA \
+ -keysize 2048 \
+ -validity 10000 \
+ -dname "CN=Android Debug,O=Android,C=US" \
+ -noprompt
+else
+ echo "✓ Debug keystore already exists"
+fi
+
+# Create launcher icon
+if [ ! -f "android/app/src/main/res/mipmap/ic_launcher.xml" ]; then
+ cat > android/app/src/main/res/mipmap/ic_launcher.xml << 'EOL'
+
+
+
+
+
+EOL
+fi
+
+if [ ! -f "android/app/src/main/res/values/colors.xml" ]; then
+ cat > android/app/src/main/res/values/colors.xml << 'EOL'
+
+
+ #FF6200EE
+ #FFFFFF
+ #FFFFFF
+
+EOL
+fi
+
+# Navigate to android directory
+cd "$(dirname "$0")/.."
+
+# Initialize Gradle wrapper
+echo "🔧 Setting up Gradle wrapper..."
+gradle wrapper
+
+# Make scripts executable
+echo "🔑 Setting permissions..."
+chmod +x gradlew
+chmod +x scripts/*
+
+# Create format_and_lint script if it doesn't exist
+mkdir -p scripts
+if [ ! -f "scripts/format_and_lint.sh" ]; then
+ cat > scripts/format_and_lint.sh << 'EOL'
+#!/bin/bash
+
+set -e
+
+# Change to the android directory (parent of scripts directory)
+cd "$(dirname "$0")/.."
+
+echo "🧹 Running code formatting..."
+
+# Run ktlint
+echo "Running ktlint..."
+./gradlew ktlintFormat
+
+echo "✅ Code formatting complete!"
+EOL
+fi
+
+# Run formatting and linting first
+echo "🧹 Running formatting..."
+bash scripts/format_and_lint.sh
+
+# Run initial setup
+echo "🏗️ Running initial build..."
+./gradlew build
+
+echo "✅ Setup complete! You can now run:"
+echo "cd android"
+echo "./scripts/format_and_lint.sh"
+
+# Create basic project structure
+echo "📁 Creating project structure..."
+
+# Create Event model
+if [ ! -f "android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt" ]; then
+ mkdir -p "android/app/src/main/kotlin/com/flexrpl/runon/domain/model"
+ cat > "android/app/src/main/kotlin/com/flexrpl/runon/domain/model/Event.kt" << 'EOL'
+package com.flexrpl.runon.domain.model
+
+data class Event(
+ val id: String,
+ val title: String,
+ val description: String,
+ val date: String,
+ val location: String
+)
+EOL
+fi
\ No newline at end of file
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
new file mode 100644
index 0000000..c322e2f
--- /dev/null
+++ b/android/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "RunOn"
+include(":app")
\ No newline at end of file
diff --git a/backend/README.md b/backend/README.md
index a85769c..9cbb57d 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -10,24 +10,27 @@ Simple backend services for the RunOn Android application.
## Setup
1. Install dependencies:
+
```bash
pip install -r requirements.txt
pip install -r requirements-dev.txt
```
2. Run tests:
+
```bash
bash scripts/format_and_lint.sh
```
## Project Structure
-```
+
+```bash
backend/
├── functions/ # Core functionality
│ ├── event_discovery/ # Event search
│ └── calendar_sync/ # Calendar integration
-├── models/ # Data models
-└── tests/ # Test suite (100% coverage)
+├── models/ # Data models
+└── tests/ # Test suite (100% coverage)
```
## Contributing
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..0627431
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("com.android.tools.build:gradle:8.2.2")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
+ }
+}
\ No newline at end of file