Full Code of nizarmah/igatha for AI

main 5b3a4f3be121 cached
128 files
294.1 KB
70.1k tokens
1 requests
Download .txt
Showing preview only (331K chars total). Download the full file or copy to clipboard to get everything.
Repository: nizarmah/igatha
Branch: main
Commit: 5b3a4f3be121
Files: 128
Total size: 294.1 KB

Directory structure:
gitextract_sbi5etug/

├── LICENSE
├── PRIVACY.md
├── README.md
├── android/
│   ├── .gitignore
│   ├── .gitkeep
│   ├── .idea/
│   │   ├── .gitignore
│   │   ├── .name
│   │   ├── AndroidProjectSystem.xml
│   │   ├── compiler.xml
│   │   ├── deploymentTargetSelector.xml
│   │   ├── deviceManager.xml
│   │   ├── gradle.xml
│   │   ├── inspectionProfiles/
│   │   │   └── Project_Default.xml
│   │   ├── kotlinc.xml
│   │   ├── migrations.xml
│   │   ├── misc.xml
│   │   ├── runConfigurations.xml
│   │   └── vcs.xml
│   ├── app/
│   │   ├── .gitignore
│   │   ├── build.gradle.kts
│   │   ├── proguard-rules.pro
│   │   └── src/
│   │       ├── androidTest/
│   │       │   └── java/
│   │       │       └── com/
│   │       │           └── nizarmah/
│   │       │               └── igatha/
│   │       │                   └── ExampleInstrumentedTest.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── java/
│   │       │   │   └── com/
│   │       │   │       └── nizarmah/
│   │       │   │           └── igatha/
│   │       │   │               ├── Constants.kt
│   │       │   │               ├── IgathaApp.kt
│   │       │   │               ├── MainActivity.kt
│   │       │   │               ├── model/
│   │       │   │               │   └── Device.kt
│   │       │   │               ├── sensor/
│   │       │   │               │   ├── AcceleratorSensor.kt
│   │       │   │               │   ├── BarometerSensor.kt
│   │       │   │               │   ├── GyroscopeSensor.kt
│   │       │   │               │   └── SensorType.kt
│   │       │   │               ├── service/
│   │       │   │               │   ├── DisasterDetectionService.kt
│   │       │   │               │   ├── DisasterDetector.kt
│   │       │   │               │   ├── DisasterEventBus.kt
│   │       │   │               │   ├── EmergencyManager.kt
│   │       │   │               │   ├── FeedbackWorker.kt
│   │       │   │               │   ├── ProximityScanner.kt
│   │       │   │               │   ├── SOSBeacon.kt
│   │       │   │               │   ├── SOSService.kt
│   │       │   │               │   └── SirenPlayer.kt
│   │       │   │               ├── ui/
│   │       │   │               │   ├── component/
│   │       │   │               │   │   ├── FeedbackButtonView.kt
│   │       │   │               │   │   ├── PermissionHandler.kt
│   │       │   │               │   │   ├── PersistentBanner.kt
│   │       │   │               │   │   └── Section.kt
│   │       │   │               │   ├── screen/
│   │       │   │               │   │   ├── ContentScreen.kt
│   │       │   │               │   │   ├── FeedbackFormScreen.kt
│   │       │   │               │   │   └── SettingsScreen.kt
│   │       │   │               │   ├── theme/
│   │       │   │               │   │   ├── Color.kt
│   │       │   │               │   │   ├── Theme.kt
│   │       │   │               │   │   └── Type.kt
│   │       │   │               │   └── view/
│   │       │   │               │       ├── ContentView.kt
│   │       │   │               │       ├── DeviceDetailView.kt
│   │       │   │               │       ├── DeviceListView.kt
│   │       │   │               │       ├── DeviceRowView.kt
│   │       │   │               │       ├── FeedbackFormView.kt
│   │       │   │               │       └── SettingsView.kt
│   │       │   │               ├── util/
│   │       │   │               │   ├── PermissionsHelper.kt
│   │       │   │               │   ├── PermissionsManager.kt
│   │       │   │               │   └── SettingsManager.kt
│   │       │   │               └── viewmodel/
│   │       │   │                   ├── ContentViewModel.kt
│   │       │   │                   ├── FeedbackFormViewModel.kt
│   │       │   │                   ├── FeedbackFormViewModelFactory.kt
│   │       │   │                   ├── SettingsViewModel.kt
│   │       │   │                   └── SettingsViewModelFactory.kt
│   │       │   └── res/
│   │       │       ├── drawable/
│   │       │       │   ├── ic_im_okay.xml
│   │       │       │   ├── ic_launcher_background.xml
│   │       │       │   ├── ic_launcher_foreground.xml
│   │       │       │   ├── ic_need_help.xml
│   │       │       │   ├── ic_notification.xml
│   │       │       │   ├── ic_notification_alerting.xml
│   │       │       │   ├── ic_notification_signaling.xml
│   │       │       │   └── ic_stop_sos.xml
│   │       │       ├── mipmap-anydpi-v26/
│   │       │       │   ├── ic_launcher.xml
│   │       │       │   └── ic_launcher_round.xml
│   │       │       ├── values/
│   │       │       │   ├── colors.xml
│   │       │       │   ├── ic_launcher_background.xml
│   │       │       │   ├── strings.xml
│   │       │       │   └── themes.xml
│   │       │       └── xml/
│   │       │           ├── backup_rules.xml
│   │       │           └── data_extraction_rules.xml
│   │       └── test/
│   │           └── java/
│   │               └── com/
│   │                   └── nizarmah/
│   │                       └── igatha/
│   │                           └── ExampleUnitTest.kt
│   ├── build.gradle.kts
│   ├── gradle/
│   │   ├── libs.versions.toml
│   │   └── wrapper/
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── gradle.properties
│   ├── gradlew
│   ├── gradlew.bat
│   └── settings.gradle.kts
├── fastlane/
│   └── metadata/
│       └── android/
│           ├── de/
│           │   ├── full_description.txt
│           │   └── short_description.txt
│           └── en-US/
│               ├── full_description.txt
│               └── short_description.txt
└── ios/
    ├── Igatha/
    │   ├── AppDelegate.swift
    │   ├── Assets.xcassets/
    │   │   ├── AccentColor.colorset/
    │   │   │   └── Contents.json
    │   │   ├── AppIcon.appiconset/
    │   │   │   └── Contents.json
    │   │   └── Contents.json
    │   ├── Constants.swift
    │   ├── Igatha.entitlements
    │   ├── IgathaApp.swift
    │   ├── Info.plist
    │   ├── Models/
    │   │   └── Device.swift
    │   ├── Preview Content/
    │   │   └── Preview Assets.xcassets/
    │   │       └── Contents.json
    │   ├── Sensors/
    │   │   ├── AccelerometerSensor.swift
    │   │   ├── BarometerSensor.swift
    │   │   ├── GyroscopeSensor.swift
    │   │   └── SensorType.swift
    │   ├── Services/
    │   │   ├── DeepLinkHandler.swift
    │   │   ├── DisasterDetector.swift
    │   │   ├── EmergencyManager.swift
    │   │   ├── LocationManager.swift
    │   │   ├── NotificationManager.swift
    │   │   ├── ProximityScanner.swift
    │   │   ├── SOSBeacon.swift
    │   │   └── SirenPlayer.swift
    │   ├── ViewModels/
    │   │   ├── ContentViewModel.swift
    │   │   ├── FeedbackFormViewModel.swift
    │   │   └── SettingsViewModel.swift
    │   └── Views/
    │       ├── ContentView.swift
    │       ├── DeviceDetailView.swift
    │       ├── DeviceListView.swift
    │       ├── DeviceRowView.swift
    │       ├── FeedbackButtonView.swift
    │       ├── FeedbackFormView.swift
    │       └── SettingsView.swift
    └── Igatha.xcodeproj/
        ├── project.pbxproj
        ├── project.xcworkspace/
        │   └── contents.xcworkspacedata
        └── xcuserdata/
            └── nizarmah.xcuserdatad/
                └── xcschemes/
                    └── xcschememanagement.plist

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Nizar Mahmoud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: PRIVACY.md
================================================
# Privacy Policy

**Last Updated:** November 1, 2024

## Introduction

**Igatha** is committed to protecting your privacy. This Privacy Policy outlines how the app functions and the measures we take to ensure your information remains secure.

## How Igatha Works

### 1. Sensor Data
- **Accelerometer Readings:** Used locally for detecting sudden movements to identify potential emergencies.
- **Gyroscope Data:** Utilized locally to determine device orientation, aiding in emergency detection.
- **Barometer Readings:** Monitors pressure changes locally to assist in disaster detection.
- **Data Handling:** All sensor data is processed locally on your device and immediately discarded. No data is transmitted off your device.

### 2. Bluetooth
- **Bluetooth Low Energy (BLE) Signals:** Used locally for broadcasting SOS beacons and scanning for nearby signals.
- **Signal Strength:** Assessed locally to estimate the distance of detected signals.
- **Temporary Device Identifiers:** Uses the first 8 characters of UUIDs for temporary identification purposes.
- **Data Handling:** No persistent storage of detected devices. All Bluetooth interactions are handled locally without data retention or transmission.

### 3. Location Services
- **Background Location Permission:** Required to enable sensor monitoring for automated disaster detection on iOS.
- **Bluetooth Scanning Permission:** Required to enable bluetooth scanning for nearby devices on Android 11  and lower.
- **Data Handling:** We do **not** transmit or store GPS coordinates or perform location tracking. Location permissions are solely used to facilitate sensor-based emergency detection locally on your device.

### 4. Foreground Services
- **Background Permission:** Required to enable sensor monitoring for automated disaster detection and signaling SOS in the background on Android.

## Data Privacy

- **Local Processing:** All data processing occurs on your device. No data is transmitted over the internet.
- **No Data Collection:** Data is not transmitted off your device or persisted between app sessions. Once processed, it is immediately discarded.
- **No Third-Party Integration:** Igatha does not use any analytics tools, tracking mechanisms, or third-party SDKs.
- **No Cloud Services:** There is no cloud integration or external servers involved.

## Permissions Used

- **Bluetooth:** Enables local SOS beacon transmission and detection.
- **Location:** Required for background sensor operations to facilitate automated emergency detection on iOS. Required for bluetooth scanning on Android 11 and lower.
- **Motion/Sensors:** Access to device motion and sensors for local disaster detection.
- **Foreground Services:** Required to enable sensor monitoring for automated disaster detection and signaling SOS in the background on Android.

## Open Source Commitment

Igatha is open-sourced under the [GNU General Public License v3.0](https://github.com/nizarmah/igatha/blob/main/LICENSE). You can review, modify, and contribute to the source code at [github.com/nizarmah/igatha](https://github.com/nizarmah/igatha).

## Contact Us

If you have any questions or concerns about this Privacy Policy, please contact us at [nizarmah@hotmail.com](mailto:nizarmah@hotmail.com).


================================================
FILE: README.md
================================================
# Igatha

Igatha is an open-source SOS signaling and recovery app designed for war zones and disaster areas, enabling offline emergency communication when traditional networks fail.

## Status

- **iOS**: [v1.0](https://apps.apple.com/us/app/igatha/id6737691452)
- **Android**: [v1.0](https://play.google.com/store/apps/details?id=com.nizarmah.igatha)

## Quickstart

1. Install the app using the links above.
1. Open the app and grant the necessary permissions.

## How to use Igatha

### Sending SOS signals (distress mode)

#### Manual signaling

1. Open Igatha.
1. Ensure bluetooth is enabled.
1. Tap "_Send SOS_".

#### Automatic signaling

1. Open Igatha.
1. Tap the gear icon (top right).
1. Enable "_Disaster Detection_".

With disaster detection, Igatha will now run in the background, monitoring your device's sensors.
When a potential disaster is detected, you'll receive an "_Are you okay?_" notification:
* If you respond with "_Need help_" or don't respond in 2 minutes, it will automatically broadcast an SOS.
* If you respond with "_I'm okay_", it will ignore the event.

### Helping others (recovery mode)

If you're safe and want to help others:

1. Open Igatha.
1. Ensure bluetooth is enabled (on Android 11 or lower, also enable Location).
1. Check "_People seeking help_".
1. Move towards locations where displayed distances decrease.
1. Listen carefully for audible sirens.

## How Igatha works

### Bluetooth low energy (BLE)

Igatha uses Bluetooth Low Energy (BLE) to:
1. Broadcast SOS signals.
1. Scan for nearby SOS broadcasts.
1. Estimate approximate distance to the signal source based on signal strength.

No internet or GPS is required, preventing signal jamming or manipulation.

### SOS signal composition

The SOS signal combines:
1. BLE advertisement: broadcasts a pseudonymized identifier.
1. Audible siren: generated via device speakers to help responders locate you.

Responders can toggle additional signals, like flashlight or vibration, remotely. (planned feature)

### Disaster detection sensors

Igatha detects disasters using device sensors:
1. Accelerometer: measures sudden motion changes.
1. Gyroscope: detect orientation and rotation shifts.
1. Barometer (if available): detects atmospheric pressure changes, reducing false positives.

Disaster detection triggers when multiple sensors simultaneously detect abrupt changes.

Location permissions are required for "_Disaster Detection_".

## Battery usage

Igatha minimizes battery use by leveraging BLE and optimized sensor monitoring.

The app can continuously broadcast for extended periods during emergencies.

## Limitations

### Early stage

* This is a Minimum Viable Product (MVP) with significant room for improvement
* Testing has been limited to controlled environments
* While not guaranteed to work in all scenarios, it provides a potential lifeline where no alternatives exist

### Signal range

* BLE range: typically 10-30 meters indoors, further outdoors, limited by rubble and building materials.
* Optional extensions: Third-party BLE receivers can extend range significantly.

## Why open source?

Igatha is open-sourced for:

1. **Transparency**: In crisis situations, people need to trust the tools they use. Open source allows anyone to verify the app's security and privacy measures.

2. **Accessibility**: Making the code freely available ensures the technology can be used, studied, and adapted by anyone who needs it.

3. **Community Impact**: War and disaster response tools should be community resources, not commercial products. Open sourcing enables collaborative improvement and adaptation for different crisis scenarios.

## Contributing

Contributions are vital for improving Igatha:
* Testing and bug reports
* Documentation
* Translations
* Feature enhancements
* Code optimization
* Security reviews
* Distribution

To contribute, open an issue or submit a pull request.

## Privacy & security

* Completely offline; no data collection or internet connectivity.
* Uses pseudonymized identifiers for privacy.

## Contact

For questions, suggestions, or feedback, please open an issue in the repository.


================================================
FILE: android/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties


================================================
FILE: android/.gitkeep
================================================


================================================
FILE: android/.idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml


================================================
FILE: android/.idea/.name
================================================
Igatha

================================================
FILE: android/.idea/AndroidProjectSystem.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="AndroidProjectSystem">
    <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
  </component>
</project>

================================================
FILE: android/.idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <bytecodeTargetLevel target="21" />
  </component>
</project>

================================================
FILE: android/.idea/deploymentTargetSelector.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="deploymentTargetSelector">
    <selectionStates>
      <SelectionState runConfigName="app">
        <option name="selectionMode" value="DROPDOWN" />
      </SelectionState>
    </selectionStates>
  </component>
</project>

================================================
FILE: android/.idea/deviceManager.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="DeviceTable">
    <option name="columnSorters">
      <list>
        <ColumnSorterState>
          <option name="column" value="Name" />
          <option name="order" value="ASCENDING" />
        </ColumnSorterState>
      </list>
    </option>
  </component>
</project>

================================================
FILE: android/.idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleMigrationSettings" migrationVersion="1" />
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="testRunner" value="CHOOSE_PER_TEST" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
          </set>
        </option>
        <option name="resolveExternalAnnotations" value="false" />
      </GradleProjectSettings>
    </option>
  </component>
</project>

================================================
FILE: android/.idea/inspectionProfiles/Project_Default.xml
================================================
<component name="InspectionProjectProfileManager">
  <profile version="1.0">
    <option name="myName" value="Project Default" />
    <inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
      <option name="composableFile" value="true" />
    </inspection_tool>
    <inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
    </inspection_tool>
    <inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
    </inspection_tool>
    <inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
    </inspection_tool>
    <inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
    <inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
    </inspection_tool>
    <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
      <option name="composableFile" value="true" />
      <option name="previewFile" value="true" />
    </inspection_tool>
  </profile>
</component>

================================================
FILE: android/.idea/kotlinc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="KotlinJpsPluginSettings">
    <option name="version" value="2.0.0" />
  </component>
</project>

================================================
FILE: android/.idea/migrations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectMigrations">
    <option name="MigrateToGradleLocalJavaHome">
      <set>
        <option value="$PROJECT_DIR$" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: android/.idea/misc.xml
================================================
<project version="4">
  <component name="ExternalStorageConfigurationManager" enabled="true" />
  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>

================================================
FILE: android/.idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
        <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
        <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
        <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
        <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
        <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
        <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
        <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: android/.idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
  </component>
</project>

================================================
FILE: android/app/.gitignore
================================================
/build

================================================
FILE: android/app/build.gradle.kts
================================================
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)

    id("kotlin-parcelize")
}

android {
    namespace = "com.nizarmah.igatha"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.nizarmah.igatha"
        minSdk = 26
        targetSdk = 35
        versionCode = 2
        versionName = "1.1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
    buildFeatures {
        compose = true
    }

    dependenciesInfo {
        // Exclude dependency block from APKs for open source compliance.
        // @see https://android.izzysoft.de/articles/named/iod-scan-apkchecks#blobs
        includeInApk = false
        // Include dependency block in AAB for playstore compliance.
        // @see https://developer.android.com/build/dependencies#dependency-info-play
        includeInBundle = true
    }
}

dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)

    implementation(libs.androidx.navigation.compose)

    implementation(libs.gson)

    implementation(libs.androidx.work.runtime.ktx)

    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
}


================================================
FILE: android/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

================================================
FILE: android/app/src/androidTest/java/com/nizarmah/igatha/ExampleInstrumentedTest.kt
================================================
package com.nizarmah.igatha

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.nizarmah.igatha", appContext.packageName)
    }
}

================================================
FILE: android/app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- FeedbackFormViewModel - Required for Google Forms submission -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- ProximityScanner | SOSBeacon -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    <!-- ProximityScanner -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <!-- See: https://source.android.com/docs/core/connect/bluetooth/ble#location-scanning -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
        android:maxSdkVersion="30" />

    <!-- SOSBeacon -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    <!-- SirenPlayer -->
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

    <!-- SOSService | DisasterDetectionService -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />

    <!-- See: https://developer.android.com/develop/ui/views/notifications/notification-permission -->
    <!-- See: com.nizarmah.igatha.util.PermissionsHelper -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"
        android:minSdkVersion="33" />

    <application
        android:name=".IgathaApp"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Igatha"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.Igatha">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <!-- Deep link handling -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="igatha" />
            </intent-filter>
        </activity>

        <service
            android:name=".service.DisasterDetectionService"
            android:enabled="true"
            android:exported="false"
            android:foregroundServiceType="health"
            android:stopWithTask="false" />

        <service
            android:name=".service.SOSService"
            android:enabled="true"
            android:exported="false"
            android:foregroundServiceType="health"
            android:stopWithTask="false" />
    </application>

</manifest>

================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/Constants.kt
================================================
package com.nizarmah.igatha

import android.os.ParcelUuid

object Constants {
    val SOS_BEACON_SERVICE_UUID: ParcelUuid = ParcelUuid.fromString("00001802-0000-1000-8000-00805F9B34FB") // 1802

    // update interval for sensor readings in microseconds
    const val SENSOR_UPDATE_INTERVAL: Int = 100_000
    // threshold for sudden changes in linear acceleration
    // 3.0 g ~= dropping your phone on a hard surface
    const val SENSOR_ACCELERATION_THRESHOLD: Double = 3.0
    // threshold for sudden changes in rotation
    // 6.0 r/s ~= almost a full rotation in 1 second
    const val SENSOR_ROTATION_THRESHOLD: Double = 6.0
    // threshold for sudden changes in atmospheric pressure
    // 0.1 kPa ~= altitude change of approx. 8 to 12 meters
    const val SENSOR_PRESSURE_THRESHOLD: Double = 0.1

    // key for shared preferences collection
    const val SHARED_PREFERENCES_KEY: String = "com.nizarmah.igatha.preferences"
    // key for disaster detector enabled setting in preferences
    const val DISASTER_DETECTION_ENABLED_KEY: String = "disasterDetectionEnabled"
    // time window for temporally correlating sensor readings
    // if all thresholds exceed in 1.5s then we have an incident
    const val DISASTER_TEMPORAL_CORRELATION_TIME_WINDOW: Double = 1.5
    // grace period (seconds) before an incident response is triggered
    const val DISASTER_RESPONSE_GRACE_PERIOD: Double = 120.0

    // percentage of how much a new value should affect the old value
    const val RSSI_EXPONENTIAL_MOVING_AVERAGE_SMOOTHING_FACTOR: Double = 0.18

    const val DISASTER_MONITORING_NOTIFICATION_ID: Int = 1
    const val DISASTER_MONITORING_NOTIFICATION_KEY: String = "DISASTER_MONITORING"
    const val DISASTER_RESPONSE_NOTIFICATION_ID: Int = 2
    const val DISASTER_RESPONSE_NOTIFICATION_KEY: String = "DISASTER_RESPONSE"
    const val DISTRESS_ACTIVE_NOTIFICATION_ID: Int = 3
    const val DISTRESS_ACTIVE_NOTIFICATION_KEY: String = "DISTRESS_ACTIVE"

    const val FEEDBACK_NOTIFICATION_ID: Int = 4
    const val FEEDBACK_NOTIFICATION_KEY: String = "FEEDBACK_REQUEST"

    const val ACTION_IGNORE_ALERT: String = "com.nizarmah.igatha.actions.IGNORE_ALERT"
    const val ACTION_START_SOS: String = "com.nizarmah.igatha.actions.START_SOS"
    const val ACTION_STOP_SOS: String = "com.nizarmah.igatha.actions.STOP_SOS"

    // Deep links
    object DeepLink {
        const val SCHEME = "igatha"

        object Settings {
            const val VALUE = "settings"
        }
    }

    // Notification constants
    object Notifications {
        object Feedback {
            const val ID = "feedbackRequest"
            val LINK = DeepLink.Settings

            // Delay before notification is shown (3 days in seconds)
            const val TRIGGER_DELAY: Long = 3 * 24 * 60 * 60

            // Key for timestamp of when feedback request was scheduled
            const val TIMESTAMP_KEY = "feedbackScheduledTimestamp"
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/IgathaApp.kt
================================================
package com.nizarmah.igatha

import android.app.Application
import android.content.SharedPreferences
import com.nizarmah.igatha.util.PermissionsManager
import com.nizarmah.igatha.util.SettingsManager
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.ExistingWorkPolicy
import java.util.concurrent.TimeUnit
import com.nizarmah.igatha.service.FeedbackWorker
import androidx.core.content.edit

class IgathaApp : Application() {
    override fun onCreate() {
        super.onCreate()

        SettingsManager.init(this)
        PermissionsManager.init(this)

        var sharedPrefs = getSharedPreferences(
            Constants.SHARED_PREFERENCES_KEY,
            MODE_PRIVATE
        )

        var workManager = WorkManager.getInstance(this)

        scheduleFeedbackNotification(
            PermissionsManager.notificationsPermitted.value,
            sharedPrefs,
            workManager
        )
    }

    // scheduleFeedbackNotification is used to schedule the feedback notification.
    private fun scheduleFeedbackNotification(
        notificationsPermitted: Boolean,
        sharedPrefs: SharedPreferences,
        workManager: WorkManager
    ) {
        if (
            // Check if notifications are not permitted
            !notificationsPermitted ||
            // Check if feedback notification was already scheduled
            sharedPrefs.contains(Constants.Notifications.Feedback.TIMESTAMP_KEY)
        ) {
            return
        }

        // Schedule feedback notification once using WorkManager
        val feedbackWorkRequest = OneTimeWorkRequestBuilder<FeedbackWorker>()
            .setInitialDelay(
                Constants.Notifications.Feedback.TRIGGER_DELAY,
                TimeUnit.SECONDS
            )
            .build()

        // Enqueue the work request
        workManager
            .enqueueUniqueWork(
                Constants.Notifications.Feedback.ID,
                ExistingWorkPolicy.KEEP,
                feedbackWorkRequest
            )

        // Mark feedback notification as scheduled
        sharedPrefs.edit {
            putLong(
                Constants.Notifications.Feedback.TIMESTAMP_KEY,
                System.currentTimeMillis()
            )
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/MainActivity.kt
================================================
package com.nizarmah.igatha

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.nizarmah.igatha.ui.component.PermissionHandler
import com.nizarmah.igatha.ui.screen.ContentScreen
import com.nizarmah.igatha.ui.theme.IgathaTheme
import com.nizarmah.igatha.util.PermissionsManager

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            IgathaTheme(
                // Disable dynamic wallpaper-based theming
                // so our static red primary is used instead
                dynamicColor = false
            ) {
                MainScreen()
            }
        }
    }

    override fun onResume() {
        super.onResume()
        PermissionsManager.refreshPermissions(this)
    }
}

@Composable
fun MainScreen() {
    Scaffold(
        bottomBar = {
            PermissionHandler(
                modifier = Modifier.fillMaxWidth()
            )
        },
        content = { paddingValues ->
            ContentScreen(
                modifier = Modifier
                    .padding(paddingValues)
                    .fillMaxSize()
            )
        }
    )
}

@Preview(showBackground = true)
@Composable
fun MainActivityPreview() {
    MainActivity()
}

================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/model/Device.kt
================================================
package com.nizarmah.igatha.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.util.*
import kotlin.math.pow
import kotlin.math.roundToInt
import com.nizarmah.igatha.Constants

fun UUID.shortName(): String {
    return this.toString().substring(0, 8).uppercase()
}

@Parcelize
data class Device(
    val id: String,
    val shortName: String,
    var rssi: Double,
    var lastSeen: Date = Date()
) : Parcelable {
    constructor(
        id: UUID,
        rssi: Double,
        lastSeen: Date = Date()
    ) : this(
        id.toString().uppercase(),
        id.shortName(),
        rssi,
        lastSeen
    )

    fun update(rssi: Double, lastSeen: Date = Date()) {
        val oldRSSI = this.rssi
        val newRSSI = rssi

        // smoothing factor
        val alpha = Constants.RSSI_EXPONENTIAL_MOVING_AVERAGE_SMOOTHING_FACTOR

        // smoothen the RSSI with exponential moving average
        val smoothedRSSI = alpha * newRSSI + (1 - alpha) * oldRSSI

        this.rssi = smoothedRSSI
        this.lastSeen = lastSeen
    }

    fun estimateDistance(pathLossExponent: PathLossExponent = PathLossExponent.URBAN): Double {
        // 1 meter ~= -59.0 RSSI
        val txPower = -59.0

        // path-loss exponent
        val n: Double = pathLossExponent.value

        val distance = 10.0.pow((txPower - rssi) / (10.0 * n))

        // round for simplicity
        return (distance * 1000.0).roundToInt() / 1000.0
    }
}

enum class PathLossExponent(val value: Double) {
    // path-loss exponent (n)

    // free open spaces
    // n = 2.0
    FREE_SPACE(2.0),

    // indoor environments
    // n = 3.0
    INDOOR(3.0),

    // dense urban environments
    // n = 4.0
    URBAN(4.0)
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/sensor/AcceleratorSensor.kt
================================================
package com.nizarmah.igatha.sensor

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlin.math.sqrt
import com.nizarmah.igatha.sensor.Sensor as InternalSensor

class AcceleratorSensor(
    context: Context,
    override val threshold: Double, // Threshold in g's
    private val updateInterval: Int // in microseconds
) : InternalSensor, SensorEventListener {
    private val sensorManager: SensorManager =
        context.getSystemService(Context.SENSOR_SERVICE) as SensorManager

    override val sensor: Sensor? =
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)

    override val sensorType: SensorType = SensorType.ACCELEROMETER

    override val isAvailable: Boolean
        get() = sensor != null

    private val _events = MutableSharedFlow<SensorCapturedEvent>(
        replay = 0,
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    override val events: SharedFlow<SensorCapturedEvent> = _events.asSharedFlow()

    override fun startUpdates() {
        if (!isAvailable) {
            Log.w(TAG, "Accelerometer sensor not available")
            return
        }

        sensor?.let { accelerometer ->
            sensorManager.registerListener(
                this,
                accelerometer,
                updateInterval
            )
            Log.d(TAG, "AcceleratorSensor: started updates")
        }
    }

    override fun stopUpdates() {
        sensorManager.unregisterListener(this)
        Log.d(TAG, "AcceleratorSensor: stopped updates")
    }

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type != Sensor.TYPE_ACCELEROMETER) return

        val x = event.values[0]
        val y = event.values[1]
        val z = event.values[2]

        // Normalize acceleration to g's
        val totalAcceleration = sqrt(x * x + y * y + z * z) / SensorManager.GRAVITY_EARTH

        if (totalAcceleration > threshold) {
            val sensorEvent = SensorCapturedEvent(
                sensorType = sensorType,
                eventTime = event.timestamp
            )

            _events.tryEmit(sensorEvent)
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
        // Not used in this implementation
    }

    companion object {
        private const val TAG = "AcceleratorSensor"
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/sensor/BarometerSensor.kt
================================================
package com.nizarmah.igatha.sensor

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlin.math.abs
import com.nizarmah.igatha.sensor.Sensor as InternalSensor

class BarometerSensor(
    context: Context,
    override val threshold: Double,
    private val updateInterval: Int // in microseconds
) : InternalSensor, SensorEventListener {
    private val sensorManager: SensorManager =
        context.getSystemService(Context.SENSOR_SERVICE) as SensorManager

    override val sensor: Sensor? =
        sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE)

    override val sensorType: SensorType = SensorType.BAROMETER

    override val isAvailable: Boolean
        get() = sensor != null

    private var initialPressure: Float? = null

    private val _events = MutableSharedFlow<SensorCapturedEvent>(
        replay = 0,
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    override val events: SharedFlow<SensorCapturedEvent> = _events.asSharedFlow()

    override fun startUpdates() {
        if (!isAvailable) {
            Log.w(TAG, "Barometer sensor not available")
            return
        }

        initialPressure = null

        sensor?.let { barometer ->
            sensorManager.registerListener(
                this,
                barometer,
                updateInterval
            )
            Log.d(TAG, "BarometerSensor: started updates")
        }
    }

    override fun stopUpdates() {
        sensorManager.unregisterListener(this)
        initialPressure = null
        Log.d(TAG, "BarometerSensor: stopped updates")
    }

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type != Sensor.TYPE_PRESSURE) return

        val pressure = event.values[0] // pressure in hPa (millibar)

        if (initialPressure == null) {
            initialPressure = pressure
            return
        }

        val pressureChange = abs(pressure - (initialPressure ?: return))

        if (pressureChange > threshold) {
            val sensorEvent = SensorCapturedEvent(
                sensorType = sensorType,
                eventTime = event.timestamp
            )
            _events.tryEmit(sensorEvent)
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
        // Not used in this implementation
    }

    companion object {
        private const val TAG = "BarometerSensor"
    }
}

================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/sensor/GyroscopeSensor.kt
================================================
package com.nizarmah.igatha.sensor

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlin.math.sqrt
import com.nizarmah.igatha.sensor.Sensor as InternalSensor

class GyroscopeSensor(
    context: Context,
    override val threshold: Double, // Threshold in rad/s
    private val updateInterval: Int // in microseconds
) : InternalSensor, SensorEventListener {
    private val sensorManager: SensorManager =
        context.getSystemService(Context.SENSOR_SERVICE) as SensorManager

    override val sensor: Sensor? =
        sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)

    override val sensorType: SensorType = SensorType.GYROSCOPE

    override val isAvailable: Boolean
        get() = sensor != null

    private val _events = MutableSharedFlow<SensorCapturedEvent>(
        replay = 0,
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    override val events: SharedFlow<SensorCapturedEvent> = _events.asSharedFlow()

    private var filteredRotationRate = 0.0
    private val alpha = 0.8 // Low-pass filter coefficient

    override fun startUpdates() {
        if (!isAvailable) {
            Log.w(TAG, "Gyroscope sensor not available")
            return
        }

        sensor?.let { gyroscope ->
            sensorManager.registerListener(
                this,
                gyroscope,
                updateInterval
            )
            Log.d(TAG, "GyroscopeSensor: started updates")
        }
    }

    override fun stopUpdates() {
        sensorManager.unregisterListener(this)
        Log.d(TAG, "GyroscopeSensor: stopped updates")
    }

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type != Sensor.TYPE_GYROSCOPE) return

        val x = event.values[0]
        val y = event.values[1]
        val z = event.values[2]

        val rawRotationRate = sqrt(x * x + y * y + z * z)

        // Apply low-pass filter
        filteredRotationRate = alpha * filteredRotationRate + (1 - alpha) * rawRotationRate

        if (filteredRotationRate > threshold) {
            val sensorEvent = SensorCapturedEvent(
                sensorType = sensorType,
                eventTime = event.timestamp
            )
            _events.tryEmit(sensorEvent)
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
        // Not used in this implementation
    }

    companion object {
        private const val TAG = "GyroscopeSensor"
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/sensor/SensorType.kt
================================================
package com.nizarmah.igatha.sensor

import android.hardware.Sensor
import kotlinx.coroutines.flow.SharedFlow

enum class SensorType {
    ACCELEROMETER,
    GYROSCOPE,
    BAROMETER
}

interface AnySensor {
    val isAvailable: Boolean

    fun startUpdates()
    fun stopUpdates()
}

interface Sensor : AnySensor {
    val sensor: Sensor?
    val threshold: Double
    val sensorType: SensorType

    // A shared flow to emit events when the threshold is exceeded
    val events: SharedFlow<SensorCapturedEvent>
}

data class SensorCapturedEvent(
    val sensorType: SensorType,
    val eventTime: Long
)


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/DisasterDetectionService.kt
================================================
package com.nizarmah.igatha.service

import android.app.*
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.R
import kotlinx.coroutines.*

class DisasterDetectionService : Service() {

    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    private lateinit var emergencyManager: EmergencyManager

    private var confirmationJob: Job? = null

    override fun onCreate() {
        super.onCreate()

        // Initialize inside onCreate because context is not available earlier
        emergencyManager = EmergencyManager.getInstance(applicationContext)
        val started = emergencyManager.startDetector()

        // If the detector did not start, stop the service
        if (!started) {
            stopSelf()
        }

        // Start the service in the foreground with a low-priority notification
        val notification = createNotification()
        startForeground(Constants.DISASTER_MONITORING_NOTIFICATION_ID, notification)

        // Observe disaster detection events
        scope.launch {
            DisasterEventBus.disasterDetectedFlow.collect {
                onDisasterDetected()
            }
        }
    }

    private fun onDisasterDetected() {
        // Show notification to the user
        showDisasterDetectedNotification()

        // Start confirmation timer
        startConfirmationTimer()
    }

    private fun showDisasterDetectedNotification() {
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        val channelId = createHighPriorityNotificationChannel()

        // Intent for "I'm Okay" action
        val imOkayIntent = Intent(this, DisasterDetectionService::class.java).apply {
            action = Constants.ACTION_IGNORE_ALERT
        }
        val imOkayPendingIntent = PendingIntent.getService(
            this, 0, imOkayIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Intent for "Need Help" action
        val needHelpIntent = Intent(this, DisasterDetectionService::class.java).apply {
            action = Constants.ACTION_START_SOS
        }
        val needHelpPendingIntent = PendingIntent.getService(
            this, 1, needHelpIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Build the notification
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_notification_alerting)
            .setContentTitle("Are you okay?")
            .setContentText("Detected a possible disaster.")
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setCategory(NotificationCompat.CATEGORY_ALARM)
            .setAutoCancel(true)
            .addAction(R.drawable.ic_im_okay, "I'm Okay", imOkayPendingIntent)
            .addAction(R.drawable.ic_need_help, "Need Help", needHelpPendingIntent)

        // Show the notification
        notificationManager.notify(Constants.DISASTER_RESPONSE_NOTIFICATION_ID, notificationBuilder.build())
    }

    private fun startConfirmationTimer() {
        confirmationJob?.cancel()
        confirmationJob = scope.launch {
            delay((Constants.DISASTER_RESPONSE_GRACE_PERIOD * 1000).toLong())
            startSOSService()
        }
    }

    private fun cancelConfirmationTimer() {
        confirmationJob?.cancel()
        confirmationJob = null
    }

    private fun startSOSService() {
        val sosIntent = Intent(this, SOSService::class.java)
        startForegroundService(sosIntent)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        cancelConfirmationTimer()
        if (intent?.action != null) {
            // Dismiss the alert notification
            val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.cancel(Constants.DISASTER_RESPONSE_NOTIFICATION_ID)
        }

        when (intent?.action) {
            Constants.ACTION_START_SOS -> {
                // Start SOS immediately
                startSOSService()
            }
        }

        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        // Clean up resources
        cancelConfirmationTimer()
        emergencyManager.stopDetector()
        scope.cancel()
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    private fun createNotification(): Notification {
        val channelId = createNotificationChannel()

        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setContentTitle("Monitoring for disasters")
            .setSmallIcon(R.drawable.ic_notification)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setOngoing(true)

        return notificationBuilder.build()
    }

    private fun createNotificationChannel(): String {
        val channelId = Constants.DISASTER_MONITORING_NOTIFICATION_KEY
        val channelName = "Disaster Detection Service"
        val channel = NotificationChannel(
            channelId,
            channelName,
            NotificationManager.IMPORTANCE_LOW
        )
        val notificationManager = getSystemService(NotificationManager::class.java)
        notificationManager.createNotificationChannel(channel)
        return channelId
    }

    private fun createHighPriorityNotificationChannel(): String {
        val channelId = Constants.DISASTER_RESPONSE_NOTIFICATION_KEY
        val channelName = "Emergency Alerts"
        val channel = NotificationChannel(
            channelId,
            channelName,
            NotificationManager.IMPORTANCE_HIGH
        )
        channel.enableVibration(true)
        channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
        val notificationManager = getSystemService(NotificationManager::class.java)
        notificationManager.createNotificationChannel(channel)
        return channelId
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/DisasterDetector.kt
================================================
package com.nizarmah.igatha.service

import android.content.Context
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.sensor.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.nizarmah.igatha.sensor.Sensor as InternalSensor

class DisasterDetector(
    context: Context,
    accelerationThreshold: Double,
    rotationThreshold: Double,
    pressureThreshold: Double,
    private val eventTimeWindow: Long // in milliseconds
) {

    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    private val eventTimes = mutableMapOf<SensorType, Long>()

    private val accelerometerSensor = AcceleratorSensor(
        context,
        threshold = accelerationThreshold,
        updateInterval = Constants.SENSOR_UPDATE_INTERVAL
    )
    private val gyroscopeSensor = GyroscopeSensor(
        context,
        threshold = rotationThreshold,
        updateInterval = Constants.SENSOR_UPDATE_INTERVAL
    )
    private val barometerSensor = BarometerSensor(
        context,
        threshold = pressureThreshold,
        updateInterval = Constants.SENSOR_UPDATE_INTERVAL
    )

    private val _isAvailable = MutableStateFlow(true)
    val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()

    private val _isActive = MutableStateFlow(false)
    val isActive: StateFlow<Boolean> = _isActive.asStateFlow()

    fun startDetection() {
        scope.launch {
            collectSensorEvents(accelerometerSensor)
        }
        scope.launch {
            collectSensorEvents(gyroscopeSensor)
        }
        scope.launch {
            collectSensorEvents(barometerSensor)
        }

        _isActive.value = true
    }

    fun stopDetection() {
        _isActive.value = false

        accelerometerSensor.stopUpdates()
        gyroscopeSensor.stopUpdates()
        barometerSensor.stopUpdates()
        scope.coroutineContext.cancelChildren()
    }

    private suspend fun collectSensorEvents(sensor: InternalSensor) {
        sensor.startUpdates()
        sensor.events.collect { event ->
            eventTimes[event.sensorType] = System.currentTimeMillis()
            checkForIncident()
        }
    }

    private fun checkForIncident() {
        val currentTime = System.currentTimeMillis()

        if (
            (barometerSensor.isAvailable &&
                eventTimes.size < 3) ||
            // Some android devices don't have barometers
            (!barometerSensor.isAvailable &&
                eventTimes.size < 2)
        ) return

        if (
            (barometerSensor.isAvailable &&
                eventTimes.values.all { currentTime - it <= eventTimeWindow }) ||
            // If no barometer, apply aggressive filtering
            (!barometerSensor.isAvailable &&
                eventTimes.values.all { currentTime - it <= eventTimeWindow / 3.2 })
        ) {
            // Apply more aggressive filtering if barometer is missing
            if (
                !barometerSensor.isAvailable &&
                eventTimes.values.all { currentTime - it > eventTimeWindow / 2 }
            ) {
                return
            }

            // Disaster detected
            scope.launch {
                DisasterEventBus.emitDisasterDetected()
            }
            eventTimes.clear()
        }
    }

    fun deinit() {
        stopDetection()
        scope.cancel()
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/DisasterEventBus.kt
================================================
package com.nizarmah.igatha.service

import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow

object DisasterEventBus {
    private val _disasterDetectedFlow = MutableSharedFlow<Unit>(replay = 0)
    val disasterDetectedFlow: SharedFlow<Unit> = _disasterDetectedFlow

    suspend fun emitDisasterDetected() {
        _disasterDetectedFlow.emit(Unit)
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/EmergencyManager.kt
================================================
package com.nizarmah.igatha.service

import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.*
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.util.PermissionsManager
import com.nizarmah.igatha.util.SettingsManager
import kotlinx.coroutines.cancel

class EmergencyManager private constructor(context: Context) {
    private val appContext = context.applicationContext

    companion object {
        @Volatile
        private var instance: EmergencyManager? = null

        fun getInstance(ctx: Context?): EmergencyManager {
            val appCtx = ctx?.applicationContext
                ?: throw IllegalStateException("EmergencyManager requested with null Context")

            return instance ?: synchronized(this) {
                instance ?: EmergencyManager(appCtx).also { instance = it }
            }
        }
    }

    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    // Components initialization
    private val sosBeacon = SOSBeacon(appContext)
    private val sirenPlayer = SirenPlayer(appContext)
    private val disasterDetector = DisasterDetector(
        context = appContext,
        accelerationThreshold = Constants.SENSOR_ACCELERATION_THRESHOLD,
        rotationThreshold = Constants.SENSOR_ROTATION_THRESHOLD,
        pressureThreshold = Constants.SENSOR_PRESSURE_THRESHOLD,
        eventTimeWindow = Constants.DISASTER_TEMPORAL_CORRELATION_TIME_WINDOW.toLong()
    )

    // State management
    val isSOSAvailable: StateFlow<Boolean> = combine(
        sosBeacon.isAvailable,
        sirenPlayer.isAvailable,
        PermissionsManager.sosPermitted
    ) { beacon, siren, permitted ->
        beacon && siren && permitted
    }.stateIn(scope, SharingStarted.Eagerly, false)

    val isSOSActive: StateFlow<Boolean> = combine(
        sosBeacon.isActive,
        sirenPlayer.isActive
    ) { beacon, siren ->
        beacon || siren
    }.stateIn(scope, SharingStarted.Eagerly, false)

    val isDetectorAvailable: StateFlow<Boolean> = combine(
        disasterDetector.isAvailable,
        isSOSAvailable,
        PermissionsManager.disasterDetectionPermitted
    ) { detector, sos, permitted ->
        detector && sos && permitted
    }.stateIn(scope, SharingStarted.Eagerly, false)
    val isDetectorEnabled: StateFlow<Boolean> = combine(
        isDetectorAvailable,
        SettingsManager.disasterDetectionEnabled
    ) { available, enabled ->
        available && enabled
    }.stateIn(scope, SharingStarted.Eagerly, false)
    val isDetectorActive: StateFlow<Boolean> = disasterDetector.isActive

    // Core functionality
    fun startSOS() {
        if (!isSOSAvailable.value || isSOSActive.value) return

        sosBeacon.startBroadcasting()
        sirenPlayer.startSiren()
    }

    fun stopSOS() {
        sosBeacon.stopBroadcasting()
        sirenPlayer.stopSiren()
    }

    fun startDetector(): Boolean {
        if (!isDetectorEnabled.value) return false

        if (isDetectorActive.value) return true

        disasterDetector.startDetection()
        return true
    }

    fun stopDetector() {
        disasterDetector.stopDetection()
    }

    fun deinit() {
        stopSOS()
        stopDetector()
        sirenPlayer.deinit()
        sosBeacon.deinit()
        disasterDetector.deinit()
        scope.cancel()
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/FeedbackWorker.kt
================================================
package com.nizarmah.igatha.service

import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.annotation.RequiresPermission
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.R

class FeedbackWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
    @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
    override suspend fun doWork(): Result {
        val channel = createNotificationChannel()

        val notificationManager =
            applicationContext.getSystemService(NotificationManager::class.java)

        notificationManager?.createNotificationChannel(channel)

        val notification = sendFeedbackNotification()

        // Display the notification
        NotificationManagerCompat.from(applicationContext)
            .notify(Constants.FEEDBACK_NOTIFICATION_ID, notification)

        return Result.success()
    }

    // createNotificationChannel creates or updates the notification channel for feedback
    private fun createNotificationChannel(): NotificationChannel {
        val channelId = Constants.FEEDBACK_NOTIFICATION_KEY
        val channelName = "Feedback Requests"
        val channel = NotificationChannel(
            channelId,
            channelName,
            NotificationManager.IMPORTANCE_DEFAULT
        )

        return channel
    }

    // sendFeedbackNotification builds and displays the feedback notification
    private fun sendFeedbackNotification(): Notification {
        val channelId = Constants.FEEDBACK_NOTIFICATION_KEY

        // Build deep link intent using the feedback notification link constant
        val contentUri = Uri.Builder()
            .scheme(Constants.DeepLink.SCHEME)
            .authority(Constants.Notifications.Feedback.LINK.VALUE)
            .build()

        // Build the pending intent
        val intent = Intent(Intent.ACTION_VIEW, contentUri)
        val pendingIntent = PendingIntent.getActivity(
            applicationContext,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Build the notification
        val notification = NotificationCompat.Builder(applicationContext, channelId)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle("Tell us why you use Igatha")
            .setContentText("Help us improve it for you and others")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setAutoCancel(true)
            .setContentIntent(pendingIntent)
            .build()

        return notification
    }
}

================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/ProximityScanner.kt
================================================
package com.nizarmah.igatha.service

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.model.Device
import java.util.Date
import java.util.UUID

class ProximityScanner(private val context: Context) {
    private val _isActive = MutableStateFlow(false)
    val isActive: StateFlow<Boolean> = _isActive.asStateFlow()

    private val _isAvailable = MutableStateFlow(false)
    val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()

    private val _scannedDevices = MutableStateFlow<Device?>(null)
    val scannedDevices: StateFlow<Device?> = _scannedDevices.asStateFlow()

    private var bluetoothLeScanner: BluetoothLeScanner? = null

    private val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            super.onScanResult(callbackType, result)

            val macAddress = result.device.address
            val uuid = UUID.nameUUIDFromBytes(macAddress.toByteArray())

            val device = Device(
                id = uuid,
                rssi = result.rssi.toDouble(),
                lastSeen = Date()
            )

            _scannedDevices.value = device
        }

        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            Log.e(TAG, "Scanning failed with error: $errorCode")
            stopScanning()
        }
    }

    private val bluetoothStateReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (BluetoothAdapter.ACTION_STATE_CHANGED != intent?.action) return

            updateAvailability()
        }
    }

    init {
        initialize()

        // Register for Bluetooth state changes
        val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        context.registerReceiver(bluetoothStateReceiver, filter)
    }

    fun deinit() {
        context.unregisterReceiver(bluetoothStateReceiver)
        stopScanning()
    }

    private fun initialize() {
        val bluetoothAdapter = getBluetoothAdapter()
        if (bluetoothAdapter == null) {
            _isAvailable.value = false
            return
        }

        bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
        updateAvailability()
    }

    fun startScanning() {
        if (!_isAvailable.value || _isActive.value) {
            return
        }

        val scanFilters = listOf(
            ScanFilter.Builder()
                // TODO: Handle iOS overflow for bg advertisement
                // See: http://www.davidgyoungtech.com/2020/05/07/hacking-the-overflow-area
                .setServiceUuid(Constants.SOS_BEACON_SERVICE_UUID)
                .build()
        )

        val scanSettings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()

        try {
            bluetoothLeScanner?.startScan(scanFilters, scanSettings, scanCallback)
            _isActive.value = true
        } catch (e: SecurityException) {
            Log.e(TAG, "SecurityException in startScanning: ${e.message}")
            _isActive.value = false
        }
    }

    fun stopScanning() {
        if (!_isAvailable.value || !_isActive.value) return

        try {
            bluetoothLeScanner?.stopScan(scanCallback)
        } catch (e: SecurityException) {
            Log.e(TAG, "SecurityException in stopScanning: ${e.message}")
        }

        _isActive.value = false
    }

    private fun updateAvailability() {
        val bluetoothAdapter = getBluetoothAdapter()
        if (bluetoothAdapter == null) {
            _isAvailable.value = false
            return
        }

        try {
            _isAvailable.value = bluetoothAdapter.isEnabled
            if (!_isAvailable.value && _isActive.value) {
                stopScanning()
            }
        } catch (e: SecurityException) {
            _isAvailable.value = false
            Log.e(TAG, "SecurityException in updateAvailability: ${e.message}")
        }
    }

    private fun getBluetoothAdapter(): BluetoothAdapter? {
        val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        return bluetoothManager.adapter
    }

    companion object {
        private const val TAG = "ProximityScanner"
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/SOSBeacon.kt
================================================
package com.nizarmah.igatha.service

import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.content.ContextCompat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.nizarmah.igatha.Constants

class SOSBeacon(private val context: Context) {
    // StateFlows to expose state changes
    private val _isActive = MutableStateFlow(false)
    val isActive: StateFlow<Boolean> = _isActive.asStateFlow()

    private val _isAvailable = MutableStateFlow(false)
    val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()

    private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null

    private val advertiseCallback = object : AdvertiseCallback() {
        override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
            super.onStartSuccess(settingsInEffect)

            _isActive.value = true
        }

        override fun onStartFailure(errorCode: Int) {
            super.onStartFailure(errorCode)

            _isActive.value = false
        }
    }

    private val bluetoothStateReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (BluetoothAdapter.ACTION_STATE_CHANGED != intent?.action) return

            updateAvailability()
        }
    }

    init {
        initialize()

        // Register the Bluetooth state receiver
        val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        context.registerReceiver(bluetoothStateReceiver, filter)
    }

    fun deinit() {
        context.unregisterReceiver(bluetoothStateReceiver)

        stopBroadcasting()
    }

    private fun initialize() {
        val bluetoothAdapter = getBluetoothAdapter()
        if (bluetoothAdapter == null) {
            _isAvailable.value = false
            return
        }

        bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser

        updateAvailability()
    }

    fun startBroadcasting() {
        if (!_isAvailable.value || _isActive.value) {
            return
        }

        val settings = AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
            .setConnectable(false)
            .setTimeout(0)
            .build()

        val advertiseData = AdvertiseData.Builder()
            .addServiceUuid(Constants.SOS_BEACON_SERVICE_UUID)
            .setIncludeDeviceName(false)
            .build()

        try {
            bluetoothLeAdvertiser?.startAdvertising(settings, advertiseData, advertiseCallback)
        } catch (e: SecurityException) {
            _isActive.value = false

            Log.e(TAG, "SecurityException in startBroadcasting: ${e.message}")
        }
    }

    fun stopBroadcasting() {
        try {
            bluetoothLeAdvertiser?.stopAdvertising(advertiseCallback)
        } catch (e: SecurityException) {
            Log.e(TAG, "SecurityException in stopBroadcasting: ${e.message}")
        }

        _isActive.value = false
    }

    private fun updateAvailability() {
        val bluetoothAdapter = getBluetoothAdapter()
        if (bluetoothAdapter == null) {
            _isAvailable.value = false
            return
        }

        val isEnabled: Boolean
        val isSupported: Boolean

        try {
            isEnabled = bluetoothAdapter.isEnabled
            isSupported = bluetoothAdapter.isMultipleAdvertisementSupported
        } catch (e: SecurityException) {
            _isAvailable.value = false

            Log.e(TAG, "SecurityException in updateAvailability: ${e.message}")

            return
        }

        _isAvailable.value = isEnabled && isSupported
        if (!_isAvailable.value && _isActive.value) {
            stopBroadcasting()
        }
    }

    private fun getBluetoothAdapter(): BluetoothAdapter? {
        if (!hasBluetoothPermissions()) {
            Log.e(TAG, "Missing Bluetooth permissions")
            return null
        }

        val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        val bluetoothAdapter = bluetoothManager.adapter

        if (bluetoothAdapter == null) {
            Log.e(TAG, "Bluetooth is not supported on this device")
            return null
        }

        return bluetoothAdapter
    }

    private fun hasBluetoothPermissions(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            // For Android 12 and above
            ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_ADVERTISE) == PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED
        } else {
            // For below Android 12
            true // Permissions are granted at install time
        }
    }

    companion object {
        private const val TAG = "SOSBeacon"
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/SOSService.kt
================================================
package com.nizarmah.igatha.service

import android.app.*
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.R
import kotlinx.coroutines.*

class SOSService : Service() {

    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    private lateinit var emergencyManager: EmergencyManager

    override fun onCreate() {
        super.onCreate()

        // Initialize inside onCreate because context is not available earlier
        emergencyManager = EmergencyManager.getInstance(applicationContext)

        startSOS()
    }

    private fun startSOS() {
        emergencyManager.startSOS()

        // Show notification with "Stop SOS" action
        showSOSNotification()
    }

    private fun stopSOS() {
        emergencyManager.stopSOS()

        // Cancel SOS notification
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.cancel(Constants.DISTRESS_ACTIVE_NOTIFICATION_ID)

        // Stop the service
        stopSelf()
    }

    private fun showSOSNotification() {
        val channelId = createNotificationChannel()

        // Intent to stop SOS
        val stopSOSIntent = Intent(this, SOSService::class.java).apply {
            action = Constants.ACTION_STOP_SOS
        }
        val stopSOSPendingIntent = PendingIntent.getService(
            this, 0, stopSOSIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Build the notification
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_notification_signaling)
            .setContentTitle("Broadcasting SOS")
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setOngoing(true)
            .addAction(R.drawable.ic_stop_sos, "Stop", stopSOSPendingIntent)

        // Start the service in the foreground
        startForeground(Constants.DISTRESS_ACTIVE_NOTIFICATION_ID, notificationBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            Constants.ACTION_STOP_SOS -> {
                // Stop SOS procedures
                stopSOS()
            }
        }

        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        // Clean up resources
        stopSOS()
        scope.cancel()
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    private fun createNotificationChannel(): String {
        val channelId = Constants.DISTRESS_ACTIVE_NOTIFICATION_KEY
        val channelName = "SOS Service"
        val channel = NotificationChannel(
            channelId,
            channelName,
            NotificationManager.IMPORTANCE_HIGH
        )
        val notificationManager = getSystemService(NotificationManager::class.java)
        notificationManager.createNotificationChannel(channel)
        return channelId
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/service/SirenPlayer.kt
================================================
package com.nizarmah.igatha.service

import android.content.Context
import android.media.*
import android.util.Log
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlin.math.PI
import kotlin.math.sin

class SirenPlayer(context: Context) {
    // StateFlows for isActive and isAvailable
    private val _isActive = MutableStateFlow(false)
    val isActive: StateFlow<Boolean> = _isActive.asStateFlow()

    private val _isAvailable = MutableStateFlow(false)
    val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()

    private var audioTrack: AudioTrack? = null
    private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

    init {
        // assume supported
        _isAvailable.value = true
    }

    fun deinit() {
        stopSiren()
    }

    fun startSiren() {
        if (!_isAvailable.value || _isActive.value) return

        try {
            val audioAttributes = AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .build()

            val sampleRate = 44100
            val buffer = createSirenBuffer()

            audioTrack = AudioTrack.Builder()
                .setAudioAttributes(audioAttributes)
                .setAudioFormat(
                    AudioFormat.Builder()
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                        .setSampleRate(sampleRate)
                        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                        .build()
                )
                .setTransferMode(AudioTrack.MODE_STATIC)
                .setBufferSizeInBytes(buffer.size * 2)
                .build()

            // Route audio to built-in speaker
            audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
            audioManager.isSpeakerphoneOn = true

            val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
            val speakerDevice = devices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
            if (speakerDevice != null) {
                audioTrack?.preferredDevice = speakerDevice
            } else {
                Log.e(TAG, "Speaker device not found")
            }

            audioTrack?.let { track ->
                track.write(buffer, 0, buffer.size)
                track.setLoopPoints(0, buffer.size / track.channelCount, -1)
                track.play()
                _isActive.value = true
                Log.d(TAG, "Siren started")
            } ?: run {
                _isAvailable.value = false
                Log.e(TAG, "Failed to create AudioTrack")
            }
        } catch (e: Exception) {
            _isAvailable.value = false
            Log.e(TAG, "Exception in startSiren: ${e.message}", e)
        }
    }

    fun stopSiren() {
        try {
            // Reset audio mode and routing
            audioManager.mode = AudioManager.MODE_NORMAL
            audioManager.isSpeakerphoneOn = false

            audioTrack?.let { track ->
                track.stop()
                track.release()
                Log.d(TAG, "Siren stopped")
            }
            audioTrack = null
            _isActive.value = false
        } catch (e: Exception) {
            Log.e(TAG, "Exception in stopSiren: ${e.message}", e)
        }
    }

    private fun createSirenBuffer(): ShortArray {
        return try {
            val duration = 2.0 // seconds
            val sampleRate = 44100 // Hz
            val totalSamples = (sampleRate * duration).toInt()
            val buffer = ShortArray(totalSamples)

            val frequencyStart = 600.0f // Hz
            val frequencyEnd = 1200.0f // Hz
            val amplitude = 0.8f // Volume (0.0 to 1.0)

            for (sampleIndex in 0 until totalSamples) {
                val t = sampleIndex / sampleRate.toFloat()

                // Modulate frequency to create siren effect
                val modulation = sin(PI.toFloat() * t / duration.toFloat())

                val frequency = frequencyStart + modulation * (frequencyEnd - frequencyStart)
                val sample = sin(2.0f * PI.toFloat() * frequency * t) * amplitude

                // Convert to 16-bit PCM value
                val pcmValue = (sample * Short.MAX_VALUE).toInt().toShort()
                buffer[sampleIndex] = pcmValue
            }

            buffer
        } catch (e: Exception) {
            Log.e(TAG, "Exception in createSirenBuffer: ${e.message}", e)
            _isAvailable.value = false
            ShortArray(0)
        }
    }

    companion object {
        private const val TAG = "SirenPlayer"
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/component/FeedbackButtonView.kt
================================================
package com.nizarmah.igatha.ui.component

import androidx.compose.foundation.background
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.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
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.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nizarmah.igatha.ui.theme.IgathaTheme
import com.nizarmah.igatha.ui.theme.colors

/**
 * A styled button for navigation to the feedback form.
 * Matches the iOS design with gradient background and heart icon.
 */
@Composable
fun FeedbackButtonView(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    // Create a pink-to-purple gradient to match iOS
    val gradient = Brush.linearGradient(
        colors = listOf(
            MaterialTheme.colors.pink.copy(alpha = 0.6f),
            MaterialTheme.colors.purple.copy(alpha = 0.6f)
        ),
        start = Offset.Zero,
        end = Offset.Infinite
    )

    Surface(
        modifier = modifier
            .fillMaxWidth()
            .clip(RoundedCornerShape(4.dp))
            .clickable { onClick() },
        shadowElevation = 4.dp
    ) {
        Row(
            modifier = Modifier
                .background(gradient)
                .padding(vertical = 16.dp, horizontal = 20.dp)
                .fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(16.dp)
            ) {
                // Heart outline icon
                Icon(
                    imageVector = Icons.Outlined.FavoriteBorder,
                    contentDescription = "Feedback",
                    modifier = Modifier.size(30.dp),
                    tint = Color.White
                )

                // Text content
                Column(
                    verticalArrangement = Arrangement.spacedBy(4.dp)
                ) {
                    Text(
                        text = "Tell us why you use Igatha",
                        style = MaterialTheme.typography.titleMedium,
                        fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,
                        color = Color.White
                    )

                    Text(
                        text = "It helps us make it more reliable.",
                        style = MaterialTheme.typography.bodyMedium,
                        color = Color.White.copy(alpha = 0.9f)
                    )
                }
            }

            // Chevron
            Icon(
                imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
                contentDescription = "Go to feedback form",
                tint = Color.White
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun FeedbackButtonViewPreview() {
    IgathaTheme {
        Box(
            modifier = Modifier.padding(16.dp)
        ) {
            FeedbackButtonView(
                onClick = {}
            )
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/component/PermissionHandler.kt
================================================
package com.nizarmah.igatha.ui.component

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.nizarmah.igatha.util.PermissionsHelper
import com.nizarmah.igatha.util.PermissionsManager

@Composable
fun PermissionHandler(
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    val permissionsGranted by PermissionsManager.permissionsGranted.collectAsState()

    // Launcher to request multiple permissions
    val permissionLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestMultiplePermissions(),
        onResult = { permissions ->
            PermissionsManager.refreshPermissions(context)
        }
    )

    // Check Bluetooth status and prompt if disabled
    val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
    var isBluetoothEnabled by remember { mutableStateOf(bluetoothAdapter?.isEnabled == true) }

    // Request permissions on first launch
    LaunchedEffect(Unit) {
        if (!permissionsGranted) {
            permissionLauncher.launch(
                PermissionsHelper.getSOSPermissions()
                        + PermissionsHelper.getProximityScanPermissions()
                        + PermissionsHelper.getDisasterDetectionPermissions()
            )
        }
    }

    // Monitor Bluetooth state changes
    DisposableEffect(Unit) {
        val receiver = object : android.content.BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
                    val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
                    isBluetoothEnabled = state == BluetoothAdapter.STATE_ON
                }
            }
        }

        val filter = android.content.IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        context.registerReceiver(receiver, filter)

        // Lifecycle observer for onResume
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_RESUME) {
                // Refresh permissions and Bluetooth status
                PermissionsManager.refreshPermissions(context)
                isBluetoothEnabled = bluetoothAdapter?.isEnabled == true
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            context.unregisterReceiver(receiver)
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    Column(
        modifier = modifier
    ) {
        if (!permissionsGranted) {
            PersistentBanner(
                message = "Permissions are missing.",
                actionLabel = "Settings",
                onActionClick = {
                    // Open app settings
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                        data = Uri.fromParts("package", context.packageName, null)
                        flags = Intent.FLAG_ACTIVITY_NEW_TASK
                    }

                    context.startActivity(intent)
                }
            )
        } else if (!isBluetoothEnabled) {
            PersistentBanner(
                message = "Bluetooth is required.",
                actionLabel = "Enable",
                onActionClick = {
                    // Open bluetooth settings
                    val intent = Intent(Settings.ACTION_BLUETOOTH_SETTINGS).apply {
                        flags = Intent.FLAG_ACTIVITY_NEW_TASK
                    }

                    context.startActivity(intent)
                }
            )
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/component/PersistentBanner.kt
================================================
package com.nizarmah.igatha.ui.component

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.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun PersistentBanner(
    message: String,
    actionLabel: String,
    onActionClick: () -> Unit,
    backgroundColor: Color = Color(0xFF323232),
    messageColor: Color = Color.White,
    actionTextColor: Color = Color(0xFFBB86FC)
) {
    Surface(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 0.dp, vertical = 0.dp), // Minimal external padding
        color = backgroundColor, // Directly set the background color
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp, vertical = 8.dp), // Internal padding for content
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Text(
                text = message,
                style = MaterialTheme.typography.bodyMedium,
                color = messageColor
            )
            TextButton(onClick = onActionClick) {
                Text(
                    text = actionLabel,
                    color = actionTextColor
                )
            }
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/component/Section.kt
================================================
package com.nizarmah.igatha.ui.component

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun Section(
    header: String? = null,
    footer: String? = null,
    padding: Modifier = Modifier.padding(vertical = 8.dp),
    content: @Composable () -> Unit,
) {
    Column(modifier = padding) {
        header?.let {
            SectionHeader(text = it)
        }

        Surface(
            color = MaterialTheme.colorScheme.surface,
            shape = MaterialTheme.shapes.medium,
            modifier = Modifier
                .padding(horizontal = 16.dp)
                .padding(vertical = 0.dp)
        ) {
            Column {
                content()
            }
        }

        footer?.let {
            SectionFooter(text = it)
        }
    }
}

@Composable
fun SectionHeader(text: String) {
    Text(
        text = text.uppercase(),
        style = MaterialTheme.typography.bodySmall.copy(
            lineHeight = MaterialTheme.typography.bodySmall.fontSize.times(1.4f)
        ),
        color = MaterialTheme.colorScheme.secondary,
        modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
    )
}

@Composable
fun SectionFooter(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.bodySmall.copy(
            lineHeight = MaterialTheme.typography.bodySmall.fontSize.times(1.4f)
        ),
        color = MaterialTheme.colorScheme.secondary,
        modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
    )
}

@Composable
fun SectionItem(
    content: @Composable RowScope.() -> Unit
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 12.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        content()
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/screen/ContentScreen.kt
================================================
package com.nizarmah.igatha.ui.screen

import android.net.Uri
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import com.google.gson.Gson
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.model.Device
import com.nizarmah.igatha.ui.view.ContentView
import com.nizarmah.igatha.ui.view.DeviceDetailView
import com.nizarmah.igatha.viewmodel.ContentViewModel

@Composable
fun ContentScreen(modifier: Modifier) {
    val navController = rememberNavController()
    val viewModel: ContentViewModel = viewModel()

    val isSOSAvailable by viewModel.isSOSAvailable.collectAsState()
    val isSOSActive by viewModel.isSOSActive.collectAsState()
    val activeAlert by viewModel.activeAlert.collectAsState()
    val devices by viewModel.devices.collectAsState()

    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = "home",
        enterTransition = {
            slideIntoContainer(
                towards = AnimatedContentTransitionScope.SlideDirection.Left,
                animationSpec = tween(300)
            )
        },
        exitTransition = {
            slideOutOfContainer(
                towards = AnimatedContentTransitionScope.SlideDirection.Left,
                animationSpec = tween(300)
            )
        },
        popEnterTransition = {
            slideIntoContainer(
                towards = AnimatedContentTransitionScope.SlideDirection.Right,
                animationSpec = tween(300)
            )
        },
        popExitTransition = {
            slideOutOfContainer(
                towards = AnimatedContentTransitionScope.SlideDirection.Right,
                animationSpec = tween(300)
            )
        }
    ) {
        composable("home") {
            ContentView(
                isSOSAvailable = isSOSAvailable,
                isSOSActive = isSOSActive,
                devices = devices,
                activeAlert = activeAlert,
                onSOSClick = {
                    if (isSOSActive) {
                        viewModel.stopSOS()
                    } else {
                        viewModel.showSOSConfirmation()
                    }
                },
                onConfirmSOS = {
                    viewModel.dismissAlert()
                    viewModel.startSOS()
                },
                onDismissAlert = {
                    viewModel.dismissAlert()
                },
                onSettingsClick = {
                    navController.navigate("settings")
                },
                onDeviceClick = { device ->
                    val deviceJson = Gson().toJson(device)
                    navController.navigate("device/$deviceJson")
                }
            )
        }

        composable(
            route = "settings",
            deepLinks = listOf(
                navDeepLink {
                    uriPattern = Uri.Builder()
                        .scheme(Constants.DeepLink.SCHEME)
                        .authority(Constants.DeepLink.Settings.VALUE)
                        .build().toString()
                }
            )
        ) {
            SettingsScreen(
                onBackClick = {
                    navController.popBackStack()
                },
                onFeedbackClick = {
                    navController.navigate("feedback")
                }
            )
        }

        composable("feedback") {
            FeedbackFormScreen(
                onNavigateBack = {
                    navController.popBackStack()
                }
            )
        }

        composable(
            route = "device/{device}",
            arguments = listOf(
                navArgument("device") {
                    type = NavType.StringType
                }
            )
        ) { backStackEntry ->
            val deviceJson = backStackEntry.arguments?.getString("device")
            val device = Gson().fromJson(deviceJson, Device::class.java)

            DeviceDetailView(
                device = device,
                onBackClick = {
                    navController.popBackStack()
                }
            )
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/screen/FeedbackFormScreen.kt
================================================
package com.nizarmah.igatha.ui.screen

import android.app.Application
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import com.nizarmah.igatha.ui.view.FeedbackFormView
import com.nizarmah.igatha.viewmodel.FeedbackFormViewModel
import com.nizarmah.igatha.viewmodel.FeedbackFormViewModelFactory
import com.nizarmah.igatha.viewmodel.FormState
import com.nizarmah.igatha.viewmodel.SubmissionResult
import kotlinx.coroutines.launch

/**
 * Feedback form screen to understand why people use Igatha.
 * This screen is responsible for coordination between the view and viewmodel.
 */
@Composable
fun FeedbackFormScreen(
    onNavigateBack: () -> Unit
) {
    // Create ViewModel with Factory
    val application = LocalContext.current.applicationContext as Application
    val viewModel: FeedbackFormViewModel = viewModel(
        factory = FeedbackFormViewModelFactory(application)
    )

    // Collect state from ViewModel
    val formState by viewModel.formState.collectAsState()
    val submissionResult by viewModel.submissionResult.collectAsState()
    val hasCustomUsage by viewModel.hasCustomUsage.collectAsState()
    val customUsage by viewModel.customUsage.collectAsState()
    val ideas by viewModel.ideas.collectAsState()
    val email by viewModel.email.collectAsState()

    // For actions that need coroutine scope
    val scope = rememberCoroutineScope()

    // Use the FeedbackFormView from ui.view
    FeedbackFormView(
        formState = formState,
        customUsage = customUsage,
        ideas = ideas,
        email = email,
        hasCustomUsage = hasCustomUsage,
        isUsageReasonSelected = { viewModel.isUsageReasonSelected(it) },
        onUsageReasonToggle = { viewModel.toggleUsageReason(it) },
        onCustomUsageChange = { viewModel.updateCustomUsage(it) },
        onIdeasChange = { viewModel.updateIdeas(it) },
        onEmailChange = { viewModel.updateEmail(it) },
        onSubmit = {
            if (formState == FormState.Idle) {
                scope.launch {
                    viewModel.submit()
                }
            }
        },
        onBackClick = onNavigateBack
    )

    // Alert dialogs for submission results
    submissionResult?.let { result ->
        when (result) {
            is SubmissionResult.Success -> {
                AlertDialog(
                    onDismissRequest = {
                        viewModel.dismissAlert()
                        onNavigateBack()
                    },
                    title = { Text("Thank you!") },
                    text = { Text("Your feedback helps us improve Igatha.") },
                    confirmButton = {
                        TextButton(
                            onClick = {
                                viewModel.dismissAlert()
                                onNavigateBack()
                            }
                        ) {
                            Text("Done", color = MaterialTheme.colorScheme.primary)
                        }
                    }
                )
            }
            is SubmissionResult.Error -> {
                AlertDialog(
                    onDismissRequest = { viewModel.dismissAlert() },
                    title = { Text("Error") },
                    text = { Text(result.message) },
                    confirmButton = {
                        TextButton(onClick = { viewModel.dismissAlert() }) {
                            Text("OK", color = MaterialTheme.colorScheme.primary)
                        }
                    }
                )
            }
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/screen/SettingsScreen.kt
================================================
package com.nizarmah.igatha.ui.screen

import android.app.Application
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import com.nizarmah.igatha.ui.view.SettingsView
import com.nizarmah.igatha.viewmodel.SettingsViewModel
import com.nizarmah.igatha.viewmodel.SettingsViewModelFactory

@Composable
fun SettingsScreen(
    onBackClick: () -> Unit,
    onFeedbackClick: () -> Unit
) {
    val context = LocalContext.current.applicationContext as Application
    val viewModel: SettingsViewModel = viewModel(factory = SettingsViewModelFactory(context))
    val disasterDetectionEnabled by viewModel.disasterDetectionEnabled.collectAsState()
    val isDisasterDetectionAvailable by viewModel.isDisasterDetectionAvailable.collectAsState()

    SettingsView(
        disasterDetectionEnabled = disasterDetectionEnabled,
        isDisasterDetectionAvailable = isDisasterDetectionAvailable,
        onDisasterDetectionEnabledChanged = { enabled ->
            viewModel.setDisasterDetectionEnabled(enabled)
        },
        onBackClick = onBackClick,
        onFeedbackClick = onFeedbackClick
    )
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/theme/Color.kt
================================================
package com.nizarmah.igatha.ui.theme

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color

val Gray = Color(0xFF8E8E93)

// iOS colors
// @see https://developer.apple.com/design/human-interface-guidelines/color#iOS-iPadOS-system-colors

val RedLight = Color(255, 59, 48)
val RedDark = Color(255, 69, 58)

val PinkLight = Color(255, 45, 85)
val PinkDark = Color(255, 55, 95)

val PurpleLight = Color(175, 82, 222)
val PurpleDark = Color(191, 90, 242)

/**
 * Data class representing iOS color palette with light/dark variants
 */
data class ColorScheme(
    val red: Color = RedLight,
    val pink: Color = PinkLight,
    val purple: Color = PurpleLight
)

/**
 * Light mode iOS colors
 */
val LightColors = ColorScheme(
    red = RedLight,
    pink = PinkLight,
    purple = PurpleLight
)

/**
 * Dark mode iOS colors
 */
val DarkColors = ColorScheme(
    red = RedDark,
    pink = PinkDark,
    purple = PurpleDark
)

/**
 * CompositionLocal to provide iOS colors down the tree
 */
val LocalColors = staticCompositionLocalOf { LightColors }

/**
 * Extension property for accessing iOS colors from MaterialTheme
 */
val MaterialTheme.colors: ColorScheme
    @Composable
    @ReadOnlyComposable
    get() = LocalColors.current


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/theme/Theme.kt
================================================
package com.nizarmah.igatha.ui.theme

import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext

private val BaseLightColorScheme = lightColorScheme()

private val BaseDarkColorScheme = darkColorScheme()

private val LightColorScheme = BaseLightColorScheme.copy(
    primary = RedLight,
)

private val DarkColorScheme = BaseDarkColorScheme.copy(
    primary = RedDark,
)

@Composable
fun IgathaTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    // Select the appropriate iOS colors based on theme
    val colors = if (darkTheme) DarkColors else LightColors

    // Provide both Material and iOS colors down the tree
    CompositionLocalProvider(
        LocalColors provides colors
    ) {
        MaterialTheme(
            colorScheme = colorScheme,
            typography = Typography,
            content = content
        )
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/theme/Type.kt
================================================
package com.nizarmah.igatha.ui.theme

import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// Set of Material typography styles to start with
val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    )
    /* Other default text styles to override
    titleLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 22.sp,
        lineHeight = 28.sp,
        letterSpacing = 0.sp
    ),
    labelSmall = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 11.sp,
        lineHeight = 16.sp,
        letterSpacing = 0.5.sp
    )
    */
)

================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/view/ContentView.kt
================================================
package com.nizarmah.igatha.ui.view

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nizarmah.igatha.ui.theme.Gray
import com.nizarmah.igatha.ui.theme.IgathaTheme
import com.nizarmah.igatha.model.Device
import com.nizarmah.igatha.viewmodel.AlertType
import java.util.Date
import java.util.UUID

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ContentView(
    isSOSAvailable: Boolean,
    isSOSActive: Boolean,
    devices: List<Device>,
    activeAlert: AlertType?,
    onSOSClick: () -> Unit,
    onConfirmSOS: () -> Unit,
    onDismissAlert: () -> Unit,
    onSettingsClick: () -> Unit,
    onDeviceClick: (Device) -> Unit
) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = {},
                actions = {
                    IconButton(onClick = onSettingsClick) {
                        Icon(
                            imageVector = Icons.Default.Settings,
                            contentDescription = "Settings"
                        )
                    }
                },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.surfaceContainer
                )
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .padding(paddingValues)
                .fillMaxSize()
        ) {
            Box(
                modifier = Modifier
                    .weight(1f)
                    .fillMaxWidth()
            ) {
                DeviceListView(
                    devices = devices,
                    onDeviceClick = onDeviceClick
                )
            }

            SOSButton(
                isSOSAvailable = isSOSAvailable,
                isSOSActive = isSOSActive,
                onSOSClick = onSOSClick
            )
        }
    }

    // Handle alerts
    activeAlert?.let { alert ->
        when (alert) {
            is AlertType.SOSConfirmation -> {
                AlertDialog(
                    onDismissRequest = onDismissAlert,
                    title = { Text("Are you sure?") },
                    text = {
                        Text(
                            text = "This will broadcast your location and start a loud siren.",
                            lineHeight = MaterialTheme.typography.bodyMedium.fontSize.times(1.4f)
                        )
                    },
                    confirmButton = {
                        TextButton(
                            onClick = onConfirmSOS
                        ) {
                            Text("Yes")
                        }
                    },
                    dismissButton = {
                        TextButton(
                            onClick = onDismissAlert
                        ) {
                            Text("Cancel")
                        }
                    }
                )
            }
        }
    }
}

@Composable
fun SOSButton(
    isSOSAvailable: Boolean = true,
    isSOSActive: Boolean = false,
    onSOSClick: () -> Unit = {}
) {
    Button(
        onClick = onSOSClick,
        enabled = isSOSAvailable,
        modifier = Modifier
            .padding(16.dp)
            .fillMaxWidth()
            .height(50.dp)
            .alpha(if (isSOSAvailable) 1f else 0.75f),
        shape = RoundedCornerShape(10.dp),
        colors = ButtonDefaults.buttonColors(
            containerColor = when {
                isSOSActive -> Gray
                else -> MaterialTheme.colorScheme.primary
            },
            contentColor = Color.White,
            disabledContainerColor = MaterialTheme.colorScheme.primary,
            disabledContentColor = Color.White
        )
    ) {
        Text(
            text = when {
                !isSOSAvailable -> "SOS Unavailable"
                isSOSActive -> "Stop SOS"
                else -> "Send SOS"
            },
            fontWeight = FontWeight.Bold
        )
    }
}

@Preview(showBackground = true)
@Composable
fun ContentViewPreview() {
    IgathaTheme {
        ContentView(
            isSOSAvailable = true,
            isSOSActive = false,
            devices = listOf(
                Device(
                    id = UUID.randomUUID(),
                    rssi = -75.0,
                    lastSeen = Date()
                ),
                Device(
                    id = UUID.randomUUID(),
                    rssi = -85.0,
                    lastSeen = Date()
                )
            ),
            activeAlert = null,
            onSOSClick = {},
            onConfirmSOS = {},
            onDismissAlert = {},
            onSettingsClick = {},
            onDeviceClick = { device -> }
        )
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/view/DeviceDetailView.kt
================================================
package com.nizarmah.igatha.ui.view

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.sharp.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.nizarmah.igatha.model.Device
import com.nizarmah.igatha.ui.theme.IgathaTheme
import com.nizarmah.igatha.ui.component.Section
import com.nizarmah.igatha.ui.component.SectionItem
import java.util.Date
import java.util.Locale
import java.util.UUID

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DeviceDetailView(
    device: Device,
    onBackClick: () -> Unit
) {
    val timeSinceLastSeen = remember(device.lastSeen) {
        calculateTimeSinceLastSeen(device.lastSeen)
    }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Device Details") },
                navigationIcon = {
                    IconButton(onClick = onBackClick) {
                        Icon(
                            imageVector = Icons.AutoMirrored.Sharp.ArrowBack,
                            contentDescription = "Go back"
                        )
                    }
                },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.surfaceContainer
                )
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .padding(paddingValues)
                .fillMaxSize()
                .background(MaterialTheme.colorScheme.surfaceContainer)
        ) {
            LazyColumn {
                item {
                    Section(
                        header = "Identity",
                        footer = "Identity is pseudonymized for privacy."
                    ) {
                        SectionItem {
                            Text(text = "Name", style = MaterialTheme.typography.bodyMedium)

                            Spacer(modifier = Modifier.padding(4.dp))

                            Text(
                                text = device.shortName,
                                style = MaterialTheme.typography.bodyMedium,
                                color = MaterialTheme.colorScheme.onSurfaceVariant
                            )
                        }

                        HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))

                        SectionItem {
                            Text(text = "ID", style = MaterialTheme.typography.bodyMedium)

                            Spacer(modifier = Modifier.padding(4.dp))

                            Text(
                                text = device.id,
                                style = MaterialTheme.typography.bodyMedium,
                                color = MaterialTheme.colorScheme.secondary,
                                fontFamily = FontFamily.Monospace,
                                textAlign = TextAlign.End,
                                modifier = Modifier.fillMaxWidth(),
                                letterSpacing = (-0.5).sp
                            )
                        }
                    }
                }
                item {
                    Section(
                        header = "Location",
                        footer = "Location is limited by used tech. Direction is not available. Distance is approximate and varies due to signal fluctuations. It is for general guidance only."
                    ) {
                        SectionItem {
                            Text(text = "Distance", style = MaterialTheme.typography.bodyMedium)

                            Spacer(modifier = Modifier.padding(4.dp))

                            Text(
                                text = String.format(
                                    Locale.getDefault(),
                                    "%.1f meters away",
                                    device.estimateDistance()
                                ),
                                style = MaterialTheme.typography.bodyMedium,
                                color = MaterialTheme.colorScheme.onSurfaceVariant,
                                fontFamily = FontFamily.Monospace
                            )
                        }
                    }
                }
                item {
                    Section(
                        header = "Status",
                        footer = "Status shows if the device is active and in range."
                    ) {
                        SectionItem {
                            Text(text = "Last Seen", style = MaterialTheme.typography.bodyMedium)

                            Spacer(modifier = Modifier.padding(4.dp))

                            Text(
                                text = timeSinceLastSeen,
                                style = MaterialTheme.typography.bodyMedium,
                                color = MaterialTheme.colorScheme.onSurfaceVariant
                            )
                        }
                    }
                }
            }
        }
    }
}

fun calculateTimeSinceLastSeen(lastSeen: Date): String {
    val now = Date()
    val diff = now.time - lastSeen.time
    val seconds = diff / 1000
    val minutes = seconds / 60
    val hours = minutes / 60
    val days = hours / 24
    val weeks = days / 7

    return when {
        seconds < 60 -> "$seconds second${if (seconds != 1L) "s" else ""} ago"
        minutes < 60 -> "$minutes minute${if (minutes != 1L) "s" else ""} ago"
        hours < 24 -> "$hours hour${if (hours != 1L) "s" else ""} ago"
        days < 7 -> "$days day${if (days != 1L) "s" else ""} ago"
        else -> "$weeks week${if (weeks != 1L) "s" else ""} ago"
    }
}

@Preview(showBackground = true)
@Composable
fun DeviceDetailViewPreview() {
    IgathaTheme {
        DeviceDetailView(
            device = Device(
                id = UUID.randomUUID(),
                rssi = -40.0
            ),
            onBackClick = {
                // do nothing
            }
        )
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/view/DeviceListView.kt
================================================
package com.nizarmah.igatha.ui.view

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nizarmah.igatha.model.Device
import com.nizarmah.igatha.ui.component.Section
import com.nizarmah.igatha.ui.theme.IgathaTheme
import java.util.Date
import java.util.UUID

@Composable
fun DeviceListView(devices: List<Device>, onDeviceClick: (Device) -> Unit) {
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.surfaceContainer)
    ) {
        item {
            Section (
                header = "People seeking help",
                footer = "Note: Distance is approximate and varies due to signal fluctuations. It is for general guidance only. Proximity scanning requires location on Android 11 or lower."
            ) {
                if (devices.isEmpty()) {
                    Text(
                        "No devices found nearby.",
                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(16.dp)
                    )
                } else {
                    devices.forEachIndexed { index, device ->
                        DeviceRowView(
                            device = device,
                            onClick = { onDeviceClick(device) }
                        )

                        if (index < devices.size - 1) {
                            HorizontalDivider(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(horizontal = 16.dp),
                                color = MaterialTheme.colorScheme.outlineVariant
                            )
                        }
                    }
                }
            }
        }
    }
}

@Composable
@Preview(showBackground = true)
fun DeviceListViewPreview() {
    val mockDevices = listOf(
        Device(id = UUID.randomUUID(), rssi = -40.0),
        Device(id = UUID.randomUUID(), rssi = -60.0, lastSeen = Date(System.currentTimeMillis() - 600_000)),
        Device(id = UUID.randomUUID(), rssi = -75.0),
        Device(id = UUID.randomUUID(), rssi = -85.0)
    )

    IgathaTheme {
        DeviceListView(devices = mockDevices) {}
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/view/DeviceRowView.kt
================================================
package com.nizarmah.igatha.ui.view

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.sharp.KeyboardArrowRight
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nizarmah.igatha.model.Device
import com.nizarmah.igatha.ui.theme.IgathaTheme
import java.util.*

@Composable
fun DeviceRowView(device: Device, onClick: () -> Unit = {}) {
    val isStale = device.lastSeen.before(
        Date(System.currentTimeMillis() - 300_000)
    ) // 5 minutes ago

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onClick() }
            .padding(16.dp)
            .alpha(if (isStale) 0.4f else 1.0f),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Icon(
            imageVector = Icons.Default.Person,
            contentDescription = "Device Icon",
            tint = MaterialTheme.colorScheme.onSecondary,
            modifier = Modifier
                .size(40.dp)
                .background(
                    color = MaterialTheme.colorScheme.secondary,
                    shape = CircleShape
                )
                .padding(5.dp)
        )
        Spacer(modifier = Modifier.width(16.dp))
        Column(
            modifier = Modifier.weight(1f)
        ) {
            Text(
                text = device.shortName,
                style = MaterialTheme.typography.titleMedium,
                color = MaterialTheme.colorScheme.onSurface
            )

            Spacer(modifier = Modifier.height(4.dp))

            Text(
                text = String.format(
                    Locale.getDefault(),
                    "%.1f meters away",
                    device.estimateDistance()),
                style = MaterialTheme.typography.bodySmall,
                fontFamily = FontFamily.Monospace,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
        }
        Icon(
            imageVector = Icons.AutoMirrored.Sharp.KeyboardArrowRight,
            contentDescription = "See device details",
            tint = MaterialTheme.colorScheme.onSurfaceVariant
        )
    }
}

@Composable
@Preview(showBackground = true)
fun DeviceRowViewPreview() {
    val previewDevice = Device(
        id = UUID.randomUUID(),
        rssi = -60.0
    )

    IgathaTheme {
        DeviceRowView(device = previewDevice)
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/view/FeedbackFormView.kt
================================================
package com.nizarmah.igatha.ui.view

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.sharp.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nizarmah.igatha.ui.component.Section
import com.nizarmah.igatha.ui.theme.IgathaTheme
import com.nizarmah.igatha.viewmodel.FormState
import com.nizarmah.igatha.viewmodel.UsageReason

@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun FeedbackFormView(
    formState: FormState,
    customUsage: String,
    ideas: String,
    email: String,
    hasCustomUsage: Boolean,
    isUsageReasonSelected: (UsageReason) -> Boolean,
    onUsageReasonToggle: (UsageReason) -> Unit,
    onCustomUsageChange: (String) -> Unit,
    onIdeasChange: (String) -> Unit,
    onEmailChange: (String) -> Unit,
    onSubmit: () -> Unit,
    onBackClick: () -> Unit
) {
    // Focus and keyboard management
    val focusManager = LocalFocusManager.current
    val keyboardController = LocalSoftwareKeyboardController.current

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Share Feedback") },
                navigationIcon = {
                    IconButton(onClick = onBackClick) {
                        Icon(
                            imageVector = Icons.AutoMirrored.Sharp.ArrowBack,
                            contentDescription = "Go back"
                        )
                    }
                },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.surfaceContainer
                )
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .padding(paddingValues)
                .fillMaxSize()
                .background(MaterialTheme.colorScheme.surfaceContainer)
        ) {
            LazyColumn {
                // Usage reasons section
                item {
                    Section(
                        header = "Why do you use Igatha?",
                        footer = "Select all that apply."
                    ) {
                        Column {
                            // Usage reason items
                            UsageReason.allCases.forEachIndexed { index, reason ->
                                UsageReasonItem(
                                    reason = reason,
                                    isSelected = isUsageReasonSelected(reason),
                                    onToggle = { onUsageReasonToggle(reason) }
                                )

                                // Add divider between items (except for the last one)
                                if (index < UsageReason.allCases.size - 1) {
                                    HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant)
                                }
                            }

                            // Custom reason text field (conditional)
                            if (hasCustomUsage) {
                                HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant)
                                TextField(
                                    value = customUsage,
                                    onValueChange = onCustomUsageChange,
                                    modifier = Modifier.fillMaxWidth(),
                                    placeholder = { Text("Please describe") },
                                    colors = TextFieldDefaults.colors(
                                        unfocusedContainerColor = MaterialTheme.colorScheme.surface,
                                        focusedContainerColor = MaterialTheme.colorScheme.surface,
                                        unfocusedIndicatorColor = MaterialTheme.colorScheme.surfaceVariant,
                                        focusedIndicatorColor = MaterialTheme.colorScheme.primary
                                    ),
                                    singleLine = true,
                                    keyboardOptions = KeyboardOptions(
                                        capitalization = KeyboardCapitalization.Sentences,
                                        imeAction = ImeAction.Next
                                    ),
                                    keyboardActions = KeyboardActions(
                                        onNext = { focusManager.moveFocus(FocusDirection.Down) }
                                    )
                                )
                            }
                        }
                    }
                }

                // Ideas section
                item {
                    Section(
                        header = "What would make Igatha more helpful?",
                        footer = "Optional. If you have any ideas, don't hesitate."
                    ) {
                        TextField(
                            value = ideas,
                            onValueChange = onIdeasChange,
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(120.dp),
                            colors = TextFieldDefaults.colors(
                                unfocusedContainerColor = MaterialTheme.colorScheme.surface,
                                focusedContainerColor = MaterialTheme.colorScheme.surface,
                                unfocusedIndicatorColor = MaterialTheme.colorScheme.surfaceVariant,
                                focusedIndicatorColor = MaterialTheme.colorScheme.primary
                            ),
                            keyboardOptions = KeyboardOptions(
                                capitalization = KeyboardCapitalization.Sentences,
                                imeAction = ImeAction.Next
                            ),
                            keyboardActions = KeyboardActions(
                                onNext = { focusManager.moveFocus(FocusDirection.Down) }
                            )
                        )
                    }
                }

                // Email section
                item {
                    Section(
                        header = "Your email",
                        footer = "Optional. We'll only contact you for clarifications."
                    ) {
                        TextField(
                            value = email,
                            onValueChange = onEmailChange,
                            modifier = Modifier.fillMaxWidth(),
                            placeholder = { Text("Email address") },
                            colors = TextFieldDefaults.colors(
                                unfocusedContainerColor = MaterialTheme.colorScheme.surface,
                                focusedContainerColor = MaterialTheme.colorScheme.surface,
                                unfocusedIndicatorColor = MaterialTheme.colorScheme.surfaceVariant,
                                focusedIndicatorColor = MaterialTheme.colorScheme.primary
                            ),
                            keyboardOptions = KeyboardOptions(
                                keyboardType = KeyboardType.Email,
                                capitalization = KeyboardCapitalization.None,
                                imeAction = ImeAction.Done
                            ),
                            keyboardActions = KeyboardActions(
                                onDone = {
                                    keyboardController?.hide()
                                    focusManager.clearFocus()
                                }
                            ),
                            singleLine = true
                        )
                    }
                }

                // Submit button section
                item {
                    Section(
                        padding = Modifier.padding(vertical = 16.dp)
                    ) {
                        Button(
                            onClick = {
                                keyboardController?.hide()
                                focusManager.clearFocus()
                                onSubmit()
                            },
                            modifier = Modifier
                                .fillMaxWidth(),
                            enabled = formState == FormState.Idle,
                            shape = RoundedCornerShape(8.dp),
                            colors = ButtonDefaults.buttonColors(
                                containerColor = MaterialTheme.colorScheme.surface,
                                contentColor = MaterialTheme.colorScheme.primary
                            ),
                            contentPadding = PaddingValues(16.dp)
                        ) {
                            Row(
                                horizontalArrangement = Arrangement.SpaceBetween,
                                verticalAlignment = Alignment.CenterVertically,
                                modifier = Modifier.fillMaxWidth()
                            ) {
                                Text(
                                    text = "Submit",
                                    style = MaterialTheme.typography.bodyLarge
                                )

                                // Show progress indicator if submitting
                                if (formState == FormState.Submitting) {
                                    CircularProgressIndicator(
                                        modifier = Modifier.size(20.dp),
                                        strokeWidth = 2.dp
                                    )
                                }
                            }
                        }
                    }
                }

                // Spacer
                item {
                    Spacer(modifier = Modifier.height(16.dp))
                }
            }
        }
    }
}

@Composable
private fun UsageReasonItem(
    reason: UsageReason,
    isSelected: Boolean,
    onToggle: () -> Unit
) {
    // State that tracks the visual selection state, initially set to the actual selection state
    val (visuallySelected, setVisuallySelected) = androidx.compose.runtime.remember(isSelected) {
        androidx.compose.runtime.mutableStateOf(isSelected)
    }

    // Update the visual state when the actual selection state changes
    androidx.compose.runtime.LaunchedEffect(isSelected) {
        setVisuallySelected(isSelected)
    }

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable {
                // Immediately update visual state for responsive feedback
                setVisuallySelected(!visuallySelected)
                // Then trigger the actual model update
                onToggle()
            }
            .padding(vertical = 12.dp, horizontal = 16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        // Reason content
        Column(modifier = Modifier.weight(1f)) {
            Text(
                text = reason.displayString,
                style = MaterialTheme.typography.bodyLarge
            )

            // Add spacing between title and example
            Spacer(modifier = Modifier.height(4.dp))

            // Subtext
            reason.exampleString?.let { example ->
                Text(
                    text = example,
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                    lineHeight = MaterialTheme.typography.bodySmall.fontSize.times(1.4f)
                )
            }
        }

        // Checkmark for selected reasons - use visuallySelected for immediate feedback
        if (visuallySelected) {
            Icon(
                imageVector = Icons.Default.Check,
                contentDescription = "Selected",
                tint = MaterialTheme.colorScheme.primary
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun FeedbackFormViewPreview() {
    IgathaTheme {
        FeedbackFormView(
            formState = FormState.Idle,
            customUsage = "I use it for my personal safety",
            ideas = "Would be nice to have offline maps",
            email = "user@example.com",
            hasCustomUsage = true,
            isUsageReasonSelected = { it in setOf(UsageReason.DisasterPreparedness, UsageReason.Other) },
            onUsageReasonToggle = {},
            onCustomUsageChange = {},
            onIdeasChange = {},
            onEmailChange = {},
            onSubmit = {},
            onBackClick = {}
        )
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/ui/view/SettingsView.kt
================================================
package com.nizarmah.igatha.ui.view

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.sharp.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nizarmah.igatha.ui.component.FeedbackButtonView
import com.nizarmah.igatha.ui.component.Section
import com.nizarmah.igatha.ui.component.SectionItem
import com.nizarmah.igatha.ui.theme.IgathaTheme

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsView(
    disasterDetectionEnabled: Boolean,
    isDisasterDetectionAvailable: Boolean,
    onDisasterDetectionEnabledChanged: (Boolean) -> Unit,
    onBackClick: () -> Unit,
    onFeedbackClick: () -> Unit = {}
) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Settings") },
                navigationIcon = {
                    IconButton(onClick = onBackClick) {
                        Icon(
                            imageVector = Icons.AutoMirrored.Sharp.ArrowBack,
                            contentDescription = "Go back"
                        )
                    }
                },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.surfaceContainer
                )
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .padding(paddingValues)
                .fillMaxSize()
                .background(MaterialTheme.colorScheme.surfaceContainer)
        ) {
            LazyColumn {
                item {
                    Section(
                        header = "Background Services",
                        footer = "Services might require additional permissions."
                    ) {
                        SectionItem {
                            Column(modifier = Modifier.weight(1f)) {
                                Text(
                                    text = "Disaster Detection",
                                    style = MaterialTheme.typography.bodyLarge
                                )

                                Spacer(modifier = Modifier.height(4.dp))

                                Text(
                                    text = "Detects disasters and sends SOS when the app is not in use. This may increase battery consumption.",
                                    style = MaterialTheme.typography.bodySmall,
                                    color = MaterialTheme.colorScheme.secondary,
                                    lineHeight = MaterialTheme.typography.bodySmall.fontSize.times(1.4f)
                                )
                            }

                            Spacer(modifier = Modifier.padding(2.dp))

                            Switch(
                                checked = disasterDetectionEnabled,
                                onCheckedChange = onDisasterDetectionEnabledChanged,
                                enabled = isDisasterDetectionAvailable
                            )
                        }
                    }
                }

                item {
                    Section(
                        header = "Feedback",
                        footer = "Your feedback helps us improve Igatha, for everyone."
                    ) {
                        Box(modifier = Modifier.padding(horizontal = 0.dp)) {
                            FeedbackButtonView(
                                onClick = onFeedbackClick
                            )
                        }
                    }
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun SettingsViewPreview() {
    IgathaTheme {
        SettingsView(
            disasterDetectionEnabled = true,
            isDisasterDetectionAvailable = true,
            onDisasterDetectionEnabledChanged = {},
            onBackClick = {}
        )
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/util/PermissionsHelper.kt
================================================
package com.nizarmah.igatha.util

import android.Manifest
import android.os.Build
import kotlin.collections.plus

object PermissionsHelper {
    fun getNotificationsPermissions(): Array<String> {
        var permissions = emptyArray<String>()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            permissions += arrayOf(
                Manifest.permission.POST_NOTIFICATIONS
            )
        }

        return permissions
    }

    fun getSOSPermissions(): Array<String> {
        var permissions = arrayOf(
            // SOS Beacon
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
            // Siren Player
            Manifest.permission.MODIFY_AUDIO_SETTINGS
        )

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            permissions += arrayOf(
                // SOS Beacon
                Manifest.permission.BLUETOOTH_ADVERTISE
            )
        }

        permissions += getServicePermissions()

        return permissions
    }

    fun getProximityScanPermissions(): Array<String> {
        var permissions = arrayOf(
            // ProximityScanner
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
        )

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            permissions += arrayOf(
                // ProximityScanner
                Manifest.permission.BLUETOOTH_SCAN,
                Manifest.permission.BLUETOOTH_CONNECT,
            )
        }

        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
            permissions += arrayOf(
                // ProximityScanner
                Manifest.permission.ACCESS_COARSE_LOCATION
            )
        }

        return permissions
    }

    fun getDisasterDetectionPermissions(): Array<String> {
        var permissions = emptyArray<String>()

        permissions += getServicePermissions()

        return permissions
    }

    private fun getServicePermissions(): Array<String> {
        var permissions = emptyArray<String>()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            permissions += arrayOf(
                Manifest.permission.FOREGROUND_SERVICE
            )
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            permissions += arrayOf(
                Manifest.permission.HIGH_SAMPLING_RATE_SENSORS
            )
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            permissions += arrayOf(
                Manifest.permission.FOREGROUND_SERVICE_HEALTH
            )
        }

        // Foreground services need a notification
        // So, check notification permissions as well
        permissions += getNotificationsPermissions()

        return permissions
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/util/PermissionsManager.kt
================================================
package com.nizarmah.igatha.util

import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn

object PermissionsManager {
    private val _notificationsPermitted = MutableStateFlow(false)
    val notificationsPermitted: StateFlow<Boolean> = _notificationsPermitted.asStateFlow()

    private val _sosPermitted = MutableStateFlow(false)
    val sosPermitted: StateFlow<Boolean> = _sosPermitted.asStateFlow()

    private val _disasterDetectionPermitted = MutableStateFlow(false)
    val disasterDetectionPermitted: StateFlow<Boolean> = _disasterDetectionPermitted.asStateFlow()

    private val _proximityScanPermitted = MutableStateFlow(false)
    val proximityScanPermitted: StateFlow<Boolean> = _proximityScanPermitted.asStateFlow()

    @OptIn(DelicateCoroutinesApi::class)
    val permissionsGranted: StateFlow<Boolean> = combine(
        sosPermitted,
        disasterDetectionPermitted,
        proximityScanPermitted,
        notificationsPermitted
    ) { sos, disaster, proximity, notifications ->
        sos && disaster && proximity && notifications
    }.stateIn(GlobalScope, SharingStarted.Eagerly, false)

    // Initialize by checking current permissions
    fun init(context: Context) {
        refreshPermissions(context)
    }

    // Function to refresh the current permission state
    fun refreshPermissions(context: Context) {
        _notificationsPermitted.value = hasPermissions(context, PermissionsHelper.getNotificationsPermissions())
        _sosPermitted.value = hasPermissions(context, PermissionsHelper.getSOSPermissions())
        _proximityScanPermitted.value = hasPermissions(context, PermissionsHelper.getProximityScanPermissions())
        _disasterDetectionPermitted.value = hasPermissions(context, PermissionsHelper.getDisasterDetectionPermissions())
    }

    // Helper function to check permissions
    private fun hasPermissions(context: Context, permissions: Array<String>): Boolean {
        return permissions.all { permission ->
            ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
        }
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/util/SettingsManager.kt
================================================
package com.nizarmah.igatha.util

import android.content.Context
import android.content.SharedPreferences
import com.nizarmah.igatha.Constants
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

object SettingsManager {
    private val _disasterDetectionEnabled = MutableStateFlow(true)
    val disasterDetectionEnabled: StateFlow<Boolean> = _disasterDetectionEnabled.asStateFlow()

    private lateinit var sharedPreferences: SharedPreferences

    fun init(context: Context) {
        sharedPreferences = getSharedPreferences(context)

        // Initialize the StateFlow with the current value
        val enabled = sharedPreferences.getBoolean(Constants.DISASTER_DETECTION_ENABLED_KEY, true)
        _disasterDetectionEnabled.value = enabled

        // Listen for changes in SharedPreferences
        sharedPreferences.registerOnSharedPreferenceChangeListener { prefs, key ->
            if (key == Constants.DISASTER_DETECTION_ENABLED_KEY) {
                val newValue = prefs.getBoolean(key, true)
                _disasterDetectionEnabled.value = newValue
            }
        }
    }

    fun setDisasterDetectionEnabled(enabled: Boolean) {
        _disasterDetectionEnabled.value = enabled

        sharedPreferences.edit().putBoolean(
            Constants.DISASTER_DETECTION_ENABLED_KEY, enabled).apply()
    }

    private fun getSharedPreferences(context: Context): SharedPreferences {
        return context.getSharedPreferences(
            Constants.SHARED_PREFERENCES_KEY,
            Context.MODE_PRIVATE
        )
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/viewmodel/ContentViewModel.kt
================================================
package com.nizarmah.igatha.viewmodel

import android.app.Application
import android.content.Intent
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.nizarmah.igatha.Constants
import com.nizarmah.igatha.model.Device
import com.nizarmah.igatha.service.DisasterDetectionService
import com.nizarmah.igatha.service.EmergencyManager
import com.nizarmah.igatha.service.ProximityScanner
import com.nizarmah.igatha.service.SOSService
import com.nizarmah.igatha.util.PermissionsManager
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

class ContentViewModel(app: Application) : AndroidViewModel(app) {

    private val emergencyManager = EmergencyManager.getInstance(getApplication())
    private val proximityScanner = ProximityScanner(getApplication())

    // SOS state
    val isSOSAvailable: StateFlow<Boolean> = emergencyManager.isSOSAvailable
    val isSOSActive: StateFlow<Boolean> = emergencyManager.isSOSActive

    // ProximityScanner state
    val isProximityScanAvailable: StateFlow<Boolean> = combine(
        proximityScanner.isAvailable,
        PermissionsManager.proximityScanPermitted
    ) { isAvailable, isPermitted ->
        isAvailable && isPermitted
    }.stateIn(viewModelScope, SharingStarted.Eagerly, false)

    // Active alert state
    private val _activeAlert = MutableStateFlow<AlertType?>(null)
    val activeAlert: StateFlow<AlertType?> = _activeAlert.asStateFlow()

    private val _devicesMap = MutableStateFlow<Map<String, Device>>(emptyMap())
    val devices: StateFlow<List<Device>> = _devicesMap.asStateFlow()
        .map { devicesMap ->
            devicesMap.values.sortedByDescending { it.rssi }
        }
        .stateIn(
            viewModelScope,
            SharingStarted.Eagerly,
            emptyList()
        )

    init {
        // Observe the disaster detection availability
        viewModelScope.launch {
            emergencyManager.isDetectorEnabled.collect { available ->
                if (available) {
                    startDisasterDetectionService()
                } else {
                    stopDisasterDetectionService()
                }
            }
        }

        // Observe proximity scanner's scanned devices
        viewModelScope.launch {
            proximityScanner.scannedDevices.collect { device ->
                device?.let { updateDevice(it) }
            }
        }

        // Start or stop the scanner based on availability
        viewModelScope.launch {
            isProximityScanAvailable.collect { available ->
                if (available) {
                    proximityScanner.startScanning()
                } else {
                    proximityScanner.stopScanning()
                }
            }
        }
    }

    private fun startDisasterDetectionService() {
        val context = getApplication<Application>()
        val serviceIntent = Intent(context, DisasterDetectionService::class.java)
        context.startForegroundService(serviceIntent)
    }

    private fun stopDisasterDetectionService() {
        val context = getApplication<Application>()
        val serviceIntent = Intent(context, DisasterDetectionService::class.java)
        context.stopService(serviceIntent)
    }

    private fun startSOSService() {
        val context = getApplication<Application>()
        val serviceIntent = Intent(context, SOSService::class.java)
        context.startForegroundService(serviceIntent)
    }

    private fun stopSOSService() {
        val context = getApplication<Application>()
        val serviceIntent = Intent(context, SOSService::class.java).apply {
            action = Constants.ACTION_STOP_SOS
        }
        context.startService(serviceIntent)
    }

    fun startSOS() {
        if (!isSOSAvailable.value || isSOSActive.value) {
            return
        }

        startSOSService()
    }

    fun stopSOS() {
        stopSOSService()
    }

    fun showSOSConfirmation() {
        if (!isSOSAvailable.value || isSOSActive.value) {
            return
        }

        _activeAlert.value = AlertType.SOSConfirmation
    }

    fun dismissAlert() {
        _activeAlert.value = null
    }

    private fun updateDevice(device: Device) {
        _devicesMap.update { currentMap ->
            val updatedMap = currentMap.toMutableMap()
            updatedMap[device.id] = device
            updatedMap
        }
    }

    override fun onCleared() {
        super.onCleared()
        proximityScanner.deinit()
    }
}

sealed class AlertType(id: Int) {
    object SOSConfirmation : AlertType(1)
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/viewmodel/FeedbackFormViewModel.kt
================================================
package com.nizarmah.igatha.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.util.UUID

/**
 * FeedbackFormViewModel handles all logic for the feedback form view.
 */
class FeedbackFormViewModel(
    app: Application,
    private val feedbackService: FeedbackService = UsageFeedbackGoogleForm(Dispatchers.IO)
) : AndroidViewModel(app) {

    // Form state
    private val _formState = MutableStateFlow<FormState>(FormState.Idle)
    val formState: StateFlow<FormState> = _formState.asStateFlow()

    // Submission result
    private val _submissionResult = MutableStateFlow<SubmissionResult?>(null)
    val submissionResult: StateFlow<SubmissionResult?> = _submissionResult.asStateFlow()

    // Form data
    private val _usageReasons = MutableStateFlow<Set<UsageReason>>(emptySet())

    // Form bindings
    private val _customUsage = MutableStateFlow("")
    val customUsage: StateFlow<String> = _customUsage.asStateFlow()

    private val _ideas = MutableStateFlow("")
    val ideas: StateFlow<String> = _ideas.asStateFlow()

    private val _email = MutableStateFlow("")
    val email: StateFlow<String> = _email.asStateFlow()

    // Checks if the user selected the "other" usage reason
    val hasCustomUsage: StateFlow<Boolean> = MutableStateFlow(false).apply {
        viewModelScope.launch {
            _usageReasons.collect { reasons ->
                value = reasons.contains(UsageReason.Other)
            }
        }
    }

    // Updates the form data
    fun updateCustomUsage(value: String) {
        _customUsage.value = value
    }

    fun updateIdeas(value: String) {
        _ideas.value = value
    }

    fun updateEmail(value: String) {
        _email.value = value
    }

    // Checks if the usage reason is selected
    fun isUsageReasonSelected(reason: UsageReason): Boolean {
        return _usageReasons.value.contains(reason)
    }

    // Toggles the usage reason
    fun toggleUsageReason(reason: UsageReason) {
        val currentReasons = _usageReasons.value.toMutableSet()
        if (currentReasons.contains(reason)) {
            currentReasons.remove(reason)
        } else {
            currentReasons.add(reason)
        }
        _usageReasons.value = currentReasons
    }

    // Dismisses submission result alert
    fun dismissAlert() {
        _submissionResult.value = null
    }

    // Validates the form and returns the error if it's invalid
    fun validateForm(): String? {
        // Form is valid if at least one usage reason is selected
        if (_usageReasons.value.isEmpty()) {
            return "Please select at least one usage reason."
        }

        // If "other" is selected, custom usage should not be empty
        if (_usageReasons.value.contains(UsageReason.Other) &&
            _customUsage.value.trim().isEmpty()) {
            return "Please specify why you chose 'other'."
        }

        return null
    }

    // Submits the feedback form
    fun submit() {
        // Validate the form and return the error if it's invalid
        val errorMessage = validateForm()
        if (errorMessage != null) {
            _submissionResult.value = SubmissionResult.Error(errorMessage)
            return
        }

        // Update form state to submitting
        _formState.value = FormState.Submitting

        // Create a feedback object
        val feedback = Feedback(
            usageReasons = _usageReasons.value,
            customUsage = _customUsage.value,
            ideas = _ideas.value,
            email = _email.value
        )

        // Submit the form asynchronously
        viewModelScope.launch {
            try {
                // Try to submit the form
                feedbackService.submit(feedback)

                // Show the success alert
                _submissionResult.value = SubmissionResult.Success
            } catch (e: Exception) {
                // Show the error alert with a more descriptive message
                val errorMessage = when {
                    e.message.isNullOrBlank() -> "Connection error. Check your internet connection."
                    else -> e.message
                }

                // Generate a truncated reference ID
                val refId = UUID.randomUUID().toString().substring(0, 8)

                // Log the error with full details for troubleshooting (shows in logcat)
                // TODO: Replace local logging with an error reporting service
                android.util.Log.e(
                    "FeedbackForm",
                    "Error submitting form - Ref: $refId - Details: $errorMessage",
                    e
                )

                _submissionResult.value = SubmissionResult.Error(
                    "Your feedback could not be submitted. Please try again later. (Ref: $refId)"
                )
            } finally {
                // Update form state to idle
                _formState.value = FormState.Idle
            }
        }
    }
}

/**
 * Interface for feedback services to abstract the submission process.
 */
interface FeedbackService {
    suspend fun submit(feedback: Feedback)
}

/**
 * UsageFeedbackGoogleForm has the submission logic for https://forms.gle/rcu3MZjPYww7Fbnh7.
 * This class performs network operations in a coroutine context.
 */
class UsageFeedbackGoogleForm(private val ioDispatcher: CoroutineDispatcher) : FeedbackService {
    // Form URL for the POST request
    private val formUrl = "https://docs.google.com/forms/u/0/d/e/1FAIpQLSdCdNYIaPcg2-eAs1Mlvwoa6P5Ijqfdb1hmWlaA-poIKpMDtQ/formResponse"

    // Feedback field identifier
    private val feedbackField = "entry.457989095"

    // Submits the feedback form
    override suspend fun submit(feedback: Feedback) = withContext(ioDispatcher) {
        // Create a URL connection
        val url = URL(formUrl)
        val connection = url.openConnection() as HttpURLConnection

        try {
            // Set up the connection
            connection.requestMethod = "POST"
            connection.doOutput = true
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
            connection.connectTimeout = 15000 // 15 seconds timeout
            connection.readTimeout = 15000 // 15 seconds timeout

            // Create the form data
            val postData = "${feedbackField}=${URLEncoder.encode(feedback.toJson(), "UTF-8")}"

            // Send the request
            connection.outputStream.use { os ->
                val input = postData.toByteArray(Charsets.UTF_8)
                os.write(input, 0, input.size)
            }

            // Check the response code
            val responseCode = connection.responseCode
            if (responseCode != HttpURLConnection.HTTP_OK && responseCode != HttpURLConnection.HTTP_MOVED_TEMP) {
                throw Exception("HTTP error: $responseCode")
            }
        } catch (e: Exception) {
            throw Exception("Network error: ${e.message ?: e.javaClass.simpleName}")
        } finally {
            connection.disconnect()
        }
    }
}

/**
 * Feedback stores the form data and converts it to a JSON string.
 */
data class Feedback(
    val usageReasons: Set<UsageReason>,
    val customUsage: String,
    val ideas: String,
    val email: String
) {
    // Constant form data
    private val device = "android"
    private val key = "android-usage-feedback-v1.0.0"

    // Converts the form data to a JSON string
    fun toJson(): String {
        val jsonObject = JSONObject().apply {
            put("usage", JSONObject().apply {
                put("reasons", usageReasons.map { it.displayString })
                put("custom", customUsage)
            })
            put("ideas", ideas)
            put("email", email)
            put("device", device)
            put("key", key)
        }

        return jsonObject.toString()
    }
}

/**
 * FormState stores the state of the form.
 */
sealed class FormState {
    object Idle : FormState()
    object Submitting : FormState()
}

/**
 * SubmissionResult stores the result of the form submission.
 */
sealed class SubmissionResult {
    object Success : SubmissionResult()
    data class Error(val message: String) : SubmissionResult()
}

/**
 * UsageReason stores all usage reasons and their examples.
 */
enum class UsageReason {
    DisasterPreparedness,
    AdventureTravel,
    Caregiving,
    WorkplaceSafety,
    RegionalConflict,
    Other;

    // Display string is the primary text for the usage reason
    val displayString: String
        get() = when (this) {
            DisasterPreparedness -> "Disaster Preparedness"
            AdventureTravel -> "Adventure & Travel"
            Caregiving -> "Caregiving"
            WorkplaceSafety -> "Workplace Safety"
            RegionalConflict -> "Regional Conflict or Instability"
            Other -> "Other"
        }

    // Example string is the subtext for the usage reason
    val exampleString: String?
        get() = when (this) {
            DisasterPreparedness -> "e.g., earthquakes, floods, conflicts"
            AdventureTravel -> "e.g., hiking, biking, remote trips"
            Caregiving -> "e.g., monitoring elderly or dependents"
            WorkplaceSafety -> "e.g., construction, mining, field jobs"
            RegionalConflict -> "e.g., war, civil unrest, political instability"
            Other -> "Please specify below"
        }

    companion object {
        // All cases for iteration
        val allCases: List<UsageReason> = entries.toList()
    }
}

/* Notes

 [1]:
    I am aware that the Google form can be spammed.
    I care most about emails, and this helps me reach users directly.
    I made sure there's no long term or financial damage from spam.
    This cheap implementation to get emails here outweighs any other.

 */


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/viewmodel/FeedbackFormViewModelFactory.kt
================================================
package com.nizarmah.igatha.viewmodel

import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

/**
 * Factory for creating a FeedbackFormViewModel with a constructor that takes an application.
 */
class FeedbackFormViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(FeedbackFormViewModel::class.java)) {
            return FeedbackFormViewModel(application) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/viewmodel/SettingsViewModel.kt
================================================
package com.nizarmah.igatha.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import com.nizarmah.igatha.util.SettingsManager
import kotlinx.coroutines.flow.StateFlow
import com.nizarmah.igatha.service.EmergencyManager

class SettingsViewModel(app: Application) : AndroidViewModel(app) {
    val disasterDetectionEnabled: StateFlow<Boolean> = SettingsManager.disasterDetectionEnabled
    val isDisasterDetectionAvailable: StateFlow<Boolean> = EmergencyManager.getInstance(getApplication()).isDetectorAvailable

    fun setDisasterDetectionEnabled(enabled: Boolean) {
        SettingsManager.setDisasterDetectionEnabled(enabled)
    }
}


================================================
FILE: android/app/src/main/java/com/nizarmah/igatha/viewmodel/SettingsViewModelFactory.kt
================================================
package com.nizarmah.igatha.viewmodel

import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class SettingsViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(SettingsViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return SettingsViewModel(application) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}


================================================
FILE: android/app/src/main/res/drawable/ic_im_okay.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
      
    <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
    
</vector>


================================================
FILE: android/app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path
        android:fillColor="#3DDC84"
        android:pathData="M0,0h108v108h-108z" />
    <path
        android:fillColor="#00000000"
        android:pathData="M9,0L9,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,0L19,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,0L29,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,0L39,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,0L49,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,0L59,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,0L69,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,0L79,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M89,0L89,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M99,0L99,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,9L108,9"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,19L108,19"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,29L108,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,39L108,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,49L108,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,59L108,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,69L108,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,79L108,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,89L108,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,99L108,99"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,29L89,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,39L89,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,49L89,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,59L89,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,69L89,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,79L89,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,19L29,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,19L39,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,19L49,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,19L59,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,19L69,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,19L79,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
</vector>


================================================
FILE: android/app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="#FFFFFF">
  <group android:scaleX="0.522"
      android:scaleY="0.522"
      android:translateX="5.736"
      android:translateY="5.736">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M20.79,9.23l-2,-3.46l-4.79,2.77l0,-5.54l-4,0l0,5.54l-4.79,-2.77l-2,3.46l4.79,2.77l-4.79,2.77l2,3.46l4.79,-2.77l0,5.54l4,0l0,-5.54l4.79,2.77l2,-3.46l-4.79,-2.77z"/>
  </group>
</vector>


================================================
FILE: android/app/src/main/res/drawable/ic_need_help.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
      
    <path android:fillColor="@android:color/white" android:pathData="M13.5,7h-3c-1.1,0 -2,0.9 -2,2v6c0,1.1 0.9,2 2,2h3c1.1,0 2,-0.9 2,-2V9C15.5,7.9 14.6,7 13.5,7zM13.5,15h-3V9h3V15zM1,15h4v-2H3c-1.1,0 -2,-0.9 -2,-2V9c0,-1.1 0.9,-2 2,-2h4v2H3v2h2c1.1,0 2,0.9 2,2v2c0,1.1 -0.9,2 -2,2H1V15zM17,15h4v-2h-2c-1.1,0 -2,-0.9 -2,-2V9c0,-1.1 0.9,-2 2,-2h4v2h-4v2h2c1.1,0 2,0.9 2,2v2c0,1.1 -0.9,2 -2,2h-4V15z"/>
    
</vector>


================================================
FILE: android/app/src/main/res/drawable/ic_notification.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
      
    <path android:fillColor="@android:color/white" android:pathData="M20.79,9.23l-2,-3.46l-4.79,2.77l0,-5.54l-4,0l0,5.54l-4.79,-2.77l-2,3.46l4.79,2.77l-4.79,2.77l2,3.46l4.79,-2.77l0,5.54l4,0l0,-5.54l4.79,2.77l2,-3.46l-4.79,-2.77z"/>
    
</vector>


================================================
FILE: android/app/src/main/res/drawable/ic_notification_alerting.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
      
    <path android:fillColor="@android:color/white" android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z"/>
    
</vector>


================================================
FILE: android/app/src/main/res/drawable/ic_notification_signaling.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
      
    <path android:fillColor="@android:color/white" android:pathData="M12,9c-3.15,0 -6,2.41 -6,6.15c0,2.49 2,5.44 6,8.85c4,-3.41 6,-6.36 6,-8.85C18,11.41 15.15,9 12,9zM12,16.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5S12.83,16.5 12,16.5zM12,4c1.93,0 3.68,0.78 4.95,2.05l-1.41,1.41C14.63,6.56 13.38,6 12,6S9.37,6.56 8.46,7.46L7.05,6.05C8.32,4.78 10.07,4 12,4zM19.78,3.23l-1.41,1.41C16.74,3.01 14.49,2 12.01,2S7.27,3.01 5.64,4.63L4.22,3.22C6.22,1.23 8.97,0 12.01,0S17.79,1.23 19.78,3.23z"/>
    
</vector>


================================================
FILE: android/app/src/main/res/drawable/ic_stop_sos.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
      
    <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10C22,6.48 17.52,2 12,2zM16,16H8V8h8V16z"/>
    
</vector>


================================================
FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
    <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

================================================
FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
    <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

================================================
FILE: android/app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
</resources>

================================================
FILE: android/app/src/main/res/values/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="ic_launcher_background">#FF3B30</color>
</resources>

================================================
FILE: android/app/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name">Igatha</string>
</resources>

================================================
FILE: android/app/src/main/res/values/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="Theme.Igatha" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

================================================
FILE: android/app/src/main/res/xml/backup_rules.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
   Sample backup rules file; uncomment and customize as necessary.
   See https://developer.android.com/guide/topics/data/autobackup
   for details.
   Note: This file is ignored for devices older that API 31
   See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
    <!--
   <include domain="sharedpref" path="."/>
   <exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

================================================
FILE: android/app/src/main/res/xml/data_extraction_rules.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
   Sample data extraction rules file; uncomment and customize as necessary.
   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
   for details.
-->
<data-extraction-rules>
    <cloud-backup>
        <!-- TODO: Use <include> and <exclude> to control what is backed up.
        <include .../>
        <exclude .../>
        -->
    </cloud-backup>
    <!--
    <device-transfer>
        <include .../>
        <exclude .../>
    </device-transfer>
    -->
</data-extraction-rules>

================================================
FILE: android/app/src/test/java/com/nizarmah/igatha/ExampleUnitTest.kt
================================================
package com.nizarmah.igatha

import org.junit.Test

import org.junit.Assert.*

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}

================================================
FILE: android/build.gradle.kts
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    alias(libs.plugins.kotlin.compose) apply false
}

================================================
FILE: android/gradle/libs.versions.toml
================================================
[versions]
agp = "8.9.2"
gson = "2.10.1"
kotlin = "2.0.0"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.1"
navigationCompose = "2.8.9"
composeBom = "2025.04.01"
workRuntimeKtx = "2.10.1"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }



================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: android/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

================================================
FILE: android/gradlew
================================================
#!/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, 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
Download .txt
gitextract_sbi5etug/

├── LICENSE
├── PRIVACY.md
├── README.md
├── android/
│   ├── .gitignore
│   ├── .gitkeep
│   ├── .idea/
│   │   ├── .gitignore
│   │   ├── .name
│   │   ├── AndroidProjectSystem.xml
│   │   ├── compiler.xml
│   │   ├── deploymentTargetSelector.xml
│   │   ├── deviceManager.xml
│   │   ├── gradle.xml
│   │   ├── inspectionProfiles/
│   │   │   └── Project_Default.xml
│   │   ├── kotlinc.xml
│   │   ├── migrations.xml
│   │   ├── misc.xml
│   │   ├── runConfigurations.xml
│   │   └── vcs.xml
│   ├── app/
│   │   ├── .gitignore
│   │   ├── build.gradle.kts
│   │   ├── proguard-rules.pro
│   │   └── src/
│   │       ├── androidTest/
│   │       │   └── java/
│   │       │       └── com/
│   │       │           └── nizarmah/
│   │       │               └── igatha/
│   │       │                   └── ExampleInstrumentedTest.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── java/
│   │       │   │   └── com/
│   │       │   │       └── nizarmah/
│   │       │   │           └── igatha/
│   │       │   │               ├── Constants.kt
│   │       │   │               ├── IgathaApp.kt
│   │       │   │               ├── MainActivity.kt
│   │       │   │               ├── model/
│   │       │   │               │   └── Device.kt
│   │       │   │               ├── sensor/
│   │       │   │               │   ├── AcceleratorSensor.kt
│   │       │   │               │   ├── BarometerSensor.kt
│   │       │   │               │   ├── GyroscopeSensor.kt
│   │       │   │               │   └── SensorType.kt
│   │       │   │               ├── service/
│   │       │   │               │   ├── DisasterDetectionService.kt
│   │       │   │               │   ├── DisasterDetector.kt
│   │       │   │               │   ├── DisasterEventBus.kt
│   │       │   │               │   ├── EmergencyManager.kt
│   │       │   │               │   ├── FeedbackWorker.kt
│   │       │   │               │   ├── ProximityScanner.kt
│   │       │   │               │   ├── SOSBeacon.kt
│   │       │   │               │   ├── SOSService.kt
│   │       │   │               │   └── SirenPlayer.kt
│   │       │   │               ├── ui/
│   │       │   │               │   ├── component/
│   │       │   │               │   │   ├── FeedbackButtonView.kt
│   │       │   │               │   │   ├── PermissionHandler.kt
│   │       │   │               │   │   ├── PersistentBanner.kt
│   │       │   │               │   │   └── Section.kt
│   │       │   │               │   ├── screen/
│   │       │   │               │   │   ├── ContentScreen.kt
│   │       │   │               │   │   ├── FeedbackFormScreen.kt
│   │       │   │               │   │   └── SettingsScreen.kt
│   │       │   │               │   ├── theme/
│   │       │   │               │   │   ├── Color.kt
│   │       │   │               │   │   ├── Theme.kt
│   │       │   │               │   │   └── Type.kt
│   │       │   │               │   └── view/
│   │       │   │               │       ├── ContentView.kt
│   │       │   │               │       ├── DeviceDetailView.kt
│   │       │   │               │       ├── DeviceListView.kt
│   │       │   │               │       ├── DeviceRowView.kt
│   │       │   │               │       ├── FeedbackFormView.kt
│   │       │   │               │       └── SettingsView.kt
│   │       │   │               ├── util/
│   │       │   │               │   ├── PermissionsHelper.kt
│   │       │   │               │   ├── PermissionsManager.kt
│   │       │   │               │   └── SettingsManager.kt
│   │       │   │               └── viewmodel/
│   │       │   │                   ├── ContentViewModel.kt
│   │       │   │                   ├── FeedbackFormViewModel.kt
│   │       │   │                   ├── FeedbackFormViewModelFactory.kt
│   │       │   │                   ├── SettingsViewModel.kt
│   │       │   │                   └── SettingsViewModelFactory.kt
│   │       │   └── res/
│   │       │       ├── drawable/
│   │       │       │   ├── ic_im_okay.xml
│   │       │       │   ├── ic_launcher_background.xml
│   │       │       │   ├── ic_launcher_foreground.xml
│   │       │       │   ├── ic_need_help.xml
│   │       │       │   ├── ic_notification.xml
│   │       │       │   ├── ic_notification_alerting.xml
│   │       │       │   ├── ic_notification_signaling.xml
│   │       │       │   └── ic_stop_sos.xml
│   │       │       ├── mipmap-anydpi-v26/
│   │       │       │   ├── ic_launcher.xml
│   │       │       │   └── ic_launcher_round.xml
│   │       │       ├── values/
│   │       │       │   ├── colors.xml
│   │       │       │   ├── ic_launcher_background.xml
│   │       │       │   ├── strings.xml
│   │       │       │   └── themes.xml
│   │       │       └── xml/
│   │       │           ├── backup_rules.xml
│   │       │           └── data_extraction_rules.xml
│   │       └── test/
│   │           └── java/
│   │               └── com/
│   │                   └── nizarmah/
│   │                       └── igatha/
│   │                           └── ExampleUnitTest.kt
│   ├── build.gradle.kts
│   ├── gradle/
│   │   ├── libs.versions.toml
│   │   └── wrapper/
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── gradle.properties
│   ├── gradlew
│   ├── gradlew.bat
│   └── settings.gradle.kts
├── fastlane/
│   └── metadata/
│       └── android/
│           ├── de/
│           │   ├── full_description.txt
│           │   └── short_description.txt
│           └── en-US/
│               ├── full_description.txt
│               └── short_description.txt
└── ios/
    ├── Igatha/
    │   ├── AppDelegate.swift
    │   ├── Assets.xcassets/
    │   │   ├── AccentColor.colorset/
    │   │   │   └── Contents.json
    │   │   ├── AppIcon.appiconset/
    │   │   │   └── Contents.json
    │   │   └── Contents.json
    │   ├── Constants.swift
    │   ├── Igatha.entitlements
    │   ├── IgathaApp.swift
    │   ├── Info.plist
    │   ├── Models/
    │   │   └── Device.swift
    │   ├── Preview Content/
    │   │   └── Preview Assets.xcassets/
    │   │       └── Contents.json
    │   ├── Sensors/
    │   │   ├── AccelerometerSensor.swift
    │   │   ├── BarometerSensor.swift
    │   │   ├── GyroscopeSensor.swift
    │   │   └── SensorType.swift
    │   ├── Services/
    │   │   ├── DeepLinkHandler.swift
    │   │   ├── DisasterDetector.swift
    │   │   ├── EmergencyManager.swift
    │   │   ├── LocationManager.swift
    │   │   ├── NotificationManager.swift
    │   │   ├── ProximityScanner.swift
    │   │   ├── SOSBeacon.swift
    │   │   └── SirenPlayer.swift
    │   ├── ViewModels/
    │   │   ├── ContentViewModel.swift
    │   │   ├── FeedbackFormViewModel.swift
    │   │   └── SettingsViewModel.swift
    │   └── Views/
    │       ├── ContentView.swift
    │       ├── DeviceDetailView.swift
    │       ├── DeviceListView.swift
    │       ├── DeviceRowView.swift
    │       ├── FeedbackButtonView.swift
    │       ├── FeedbackFormView.swift
    │       └── SettingsView.swift
    └── Igatha.xcodeproj/
        ├── project.pbxproj
        ├── project.xcworkspace/
        │   └── contents.xcworkspacedata
        └── xcuserdata/
            └── nizarmah.xcuserdatad/
                └── xcschemes/
                    └── xcschememanagement.plist
Condensed preview — 128 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (325K chars).
[
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2025 Nizar Mahmoud\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "PRIVACY.md",
    "chars": 3255,
    "preview": "# Privacy Policy\n\n**Last Updated:** November 1, 2024\n\n## Introduction\n\n**Igatha** is committed to protecting your privac"
  },
  {
    "path": "README.md",
    "chars": 4135,
    "preview": "# Igatha\n\nIgatha is an open-source SOS signaling and recovery app designed for war zones and disaster areas, enabling of"
  },
  {
    "path": "android/.gitignore",
    "chars": 225,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
  },
  {
    "path": "android/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "android/.idea/.gitignore",
    "chars": 47,
    "preview": "# Default ignored files\n/shelf/\n/workspace.xml\n"
  },
  {
    "path": "android/.idea/.name",
    "chars": 6,
    "preview": "Igatha"
  },
  {
    "path": "android/.idea/AndroidProjectSystem.xml",
    "chars": 212,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AndroidProjectSystem\">\n    <option name="
  },
  {
    "path": "android/.idea/compiler.xml",
    "chars": 169,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <bytecodeTar"
  },
  {
    "path": "android/.idea/deploymentTargetSelector.xml",
    "chars": 301,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"deploymentTargetSelector\">\n    <selectio"
  },
  {
    "path": "android/.idea/deviceManager.xml",
    "chars": 351,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"DeviceTable\">\n    <option name=\"columnSo"
  },
  {
    "path": "android/.idea/gradle.xml",
    "chars": 757,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleMigrationSettings\" migrationVersio"
  },
  {
    "path": "android/.idea/inspectionProfiles/Project_Default.xml",
    "chars": 3600,
    "preview": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project De"
  },
  {
    "path": "android/.idea/kotlinc.xml",
    "chars": 175,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"KotlinJpsPluginSettings\">\n    <option na"
  },
  {
    "path": "android/.idea/migrations.xml",
    "chars": 254,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectMigrations\">\n    <option name=\"Mi"
  },
  {
    "path": "android/.idea/misc.xml",
    "chars": 409,
    "preview": "<project version=\"4\">\n  <component name=\"ExternalStorageConfigurationManager\" enabled=\"true\" />\n  <component name=\"Proje"
  },
  {
    "path": "android/.idea/runConfigurations.xml",
    "chars": 964,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": "android/.idea/vcs.xml",
    "chars": 183,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "android/app/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "android/app/build.gradle.kts",
    "chars": 2278,
    "preview": "plugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.kotlin.android)\n    alias(libs.plugins.kotl"
  },
  {
    "path": "android/app/proguard-rules.pro",
    "chars": 750,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "android/app/src/androidTest/java/com/nizarmah/igatha/ExampleInstrumentedTest.kt",
    "chars": 665,
    "preview": "package com.nizarmah.igatha\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.ru"
  },
  {
    "path": "android/app/src/main/AndroidManifest.xml",
    "chars": 3405,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:to"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/Constants.kt",
    "chars": 2953,
    "preview": "package com.nizarmah.igatha\n\nimport android.os.ParcelUuid\n\nobject Constants {\n    val SOS_BEACON_SERVICE_UUID: ParcelUui"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/IgathaApp.kt",
    "chars": 2285,
    "preview": "package com.nizarmah.igatha\n\nimport android.app.Application\nimport android.content.SharedPreferences\nimport com.nizarmah"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/MainActivity.kt",
    "chars": 1767,
    "preview": "package com.nizarmah.igatha\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activit"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/model/Device.kt",
    "chars": 1735,
    "preview": "package com.nizarmah.igatha.model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport java.util.*\nim"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/sensor/AcceleratorSensor.kt",
    "chars": 2697,
    "preview": "package com.nizarmah.igatha.sensor\n\nimport android.content.Context\nimport android.hardware.Sensor\nimport android.hardwar"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/sensor/BarometerSensor.kt",
    "chars": 2771,
    "preview": "package com.nizarmah.igatha.sensor\n\nimport android.content.Context\nimport android.hardware.Sensor\nimport android.hardwar"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/sensor/GyroscopeSensor.kt",
    "chars": 2825,
    "preview": "package com.nizarmah.igatha.sensor\n\nimport android.content.Context\nimport android.hardware.Sensor\nimport android.hardwar"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/sensor/SensorType.kt",
    "chars": 606,
    "preview": "package com.nizarmah.igatha.sensor\n\nimport android.hardware.Sensor\nimport kotlinx.coroutines.flow.SharedFlow\n\nenum class"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/DisasterDetectionService.kt",
    "chars": 6103,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.app.*\nimport android.content.Intent\nimport android.os.IBinder\nimport"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/DisasterDetector.kt",
    "chars": 3488,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.content.Context\nimport com.nizarmah.igatha.Constants\nimport com.niza"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/DisasterEventBus.kt",
    "chars": 393,
    "preview": "package com.nizarmah.igatha.service\n\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.Sha"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/EmergencyManager.kt",
    "chars": 3448,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.content.Context\nimport kotlinx.coroutines.CoroutineScope\nimport kotl"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/FeedbackWorker.kt",
    "chars": 3000,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.Manifest\nimport android.app.Notification\nimport android.app.Notifica"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/ProximityScanner.kt",
    "chars": 4907,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.bluetooth.BluetoothAdapter\nimport android.bluetooth.BluetoothManager"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/SOSBeacon.kt",
    "chars": 5385,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.Manifest\nimport android.bluetooth.BluetoothAdapter\nimport android.bl"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/SOSService.kt",
    "chars": 3102,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.app.*\nimport android.content.Intent\nimport android.os.IBinder\nimport"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/service/SirenPlayer.kt",
    "chars": 4812,
    "preview": "package com.nizarmah.igatha.service\n\nimport android.content.Context\nimport android.media.*\nimport android.util.Log\nimpor"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/component/FeedbackButtonView.kt",
    "chars": 3789,
    "preview": "package com.nizarmah.igatha.ui.component\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundati"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/component/PermissionHandler.kt",
    "chars": 4411,
    "preview": "package com.nizarmah.igatha.ui.component\n\nimport android.bluetooth.BluetoothAdapter\nimport android.bluetooth.BluetoothMa"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/component/PersistentBanner.kt",
    "chars": 1531,
    "preview": "package com.nizarmah.igatha.ui.component\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/component/Section.kt",
    "chars": 2398,
    "preview": "package com.nizarmah.igatha.ui.component\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose."
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/screen/ContentScreen.kt",
    "chars": 4700,
    "preview": "package com.nizarmah.igatha.ui.screen\n\nimport android.net.Uri\nimport androidx.compose.animation.AnimatedContentTransitio"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/screen/FeedbackFormScreen.kt",
    "chars": 3840,
    "preview": "package com.nizarmah.igatha.ui.screen\n\nimport android.app.Application\nimport androidx.compose.material3.AlertDialog\nimpo"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/screen/SettingsScreen.kt",
    "chars": 1279,
    "preview": "package com.nizarmah.igatha.ui.screen\n\nimport android.app.Application\nimport androidx.compose.runtime.Composable\nimport "
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/theme/Color.kt",
    "chars": 1416,
    "preview": "package com.nizarmah.igatha.ui.theme\n\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Co"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/theme/Theme.kt",
    "chars": 1739,
    "preview": "package com.nizarmah.igatha.ui.theme\n\nimport android.os.Build\nimport androidx.compose.foundation.isSystemInDarkTheme\nimp"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/theme/Type.kt",
    "chars": 988,
    "preview": "package com.nizarmah.igatha.ui.theme\n\nimport androidx.compose.material3.Typography\nimport androidx.compose.ui.text.TextS"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/view/ContentView.kt",
    "chars": 5435,
    "preview": "package com.nizarmah.igatha.ui.view\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.layo"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/view/DeviceDetailView.kt",
    "chars": 7119,
    "preview": "package com.nizarmah.igatha.ui.view\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.la"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/view/DeviceListView.kt",
    "chars": 2637,
    "preview": "package com.nizarmah.igatha.ui.view\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.la"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/view/DeviceRowView.kt",
    "chars": 2902,
    "preview": "package com.nizarmah.igatha.ui.view\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.cl"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/view/FeedbackFormView.kt",
    "chars": 13870,
    "preview": "package com.nizarmah.igatha.ui.view\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.cl"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/ui/view/SettingsView.kt",
    "chars": 4223,
    "preview": "package com.nizarmah.igatha.ui.view\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.la"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/util/PermissionsHelper.kt",
    "chars": 2840,
    "preview": "package com.nizarmah.igatha.util\n\nimport android.Manifest\nimport android.os.Build\nimport kotlin.collections.plus\n\nobject"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/util/PermissionsManager.kt",
    "chars": 2495,
    "preview": "package com.nizarmah.igatha.util\n\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/util/SettingsManager.kt",
    "chars": 1626,
    "preview": "package com.nizarmah.igatha.util\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport com.niz"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/viewmodel/ContentViewModel.kt",
    "chars": 4596,
    "preview": "package com.nizarmah.igatha.viewmodel\n\nimport android.app.Application\nimport android.content.Intent\nimport androidx.life"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/viewmodel/FeedbackFormViewModel.kt",
    "chars": 10290,
    "preview": "package com.nizarmah.igatha.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport "
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/viewmodel/FeedbackFormViewModelFactory.kt",
    "chars": 676,
    "preview": "package com.nizarmah.igatha.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.ViewModel\nimport android"
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/viewmodel/SettingsViewModel.kt",
    "chars": 671,
    "preview": "package com.nizarmah.igatha.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport "
  },
  {
    "path": "android/app/src/main/java/com/nizarmah/igatha/viewmodel/SettingsViewModelFactory.kt",
    "chars": 570,
    "preview": "package com.nizarmah.igatha.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.ViewModel\nimport android"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_im_okay.xml",
    "chars": 406,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 5606,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:wi"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_launcher_foreground.xml",
    "chars": 609,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_need_help.xml",
    "chars": 612,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_notification.xml",
    "chars": 444,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_notification_alerting.xml",
    "chars": 484,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_notification_signaling.xml",
    "chars": 722,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_stop_sos.xml",
    "chars": 383,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 337,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 337,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "android/app/src/main/res/values/colors.xml",
    "chars": 378,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purpl"
  },
  {
    "path": "android/app/src/main/res/values/ic_launcher_background.xml",
    "chars": 120,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#FF3B30</color>\n</resources>"
  },
  {
    "path": "android/app/src/main/res/values/strings.xml",
    "chars": 68,
    "preview": "<resources>\n    <string name=\"app_name\">Igatha</string>\n</resources>"
  },
  {
    "path": "android/app/src/main/res/values/themes.xml",
    "chars": 148,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.Igatha\" parent=\"android:Theme.Material.Light."
  },
  {
    "path": "android/app/src/main/res/xml/backup_rules.xml",
    "chars": 478,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See htt"
  },
  {
    "path": "android/app/src/main/res/xml/data_extraction_rules.xml",
    "chars": 551,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n "
  },
  {
    "path": "android/app/src/test/java/com/nizarmah/igatha/ExampleUnitTest.kt",
    "chars": 343,
    "preview": "package com.nizarmah.igatha\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which wil"
  },
  {
    "path": "android/build.gradle.kts",
    "chars": 269,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    alias("
  },
  {
    "path": "android/gradle/libs.versions.toml",
    "chars": 2148,
    "preview": "[versions]\nagp = \"8.9.2\"\ngson = \"2.10.1\"\nkotlin = \"2.0.0\"\ncoreKtx = \"1.16.0\"\njunit = \"4.13.2\"\njunitVersion = \"1.2.1\"\nesp"
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "chars": 338,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionSha256Sum=20f1b1176237254a6fc204d8434196fa1"
  },
  {
    "path": "android/gradle.properties",
    "chars": 1346,
    "preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
  },
  {
    "path": "android/gradlew",
    "chars": 8729,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "android/gradlew.bat",
    "chars": 2966,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "android/settings.gradle.kts",
    "chars": 530,
    "preview": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\."
  },
  {
    "path": "fastlane/metadata/android/de/full_description.txt",
    "chars": 2360,
    "preview": "<p><i>Igatha</i> ist eine Offline-SOS-App für Notfälle, wenn Kommunikationsnetzwerke ausfallen.</p><p><i>Igatha:</i> Ein"
  },
  {
    "path": "fastlane/metadata/android/de/short_description.txt",
    "chars": 41,
    "preview": "SOS-Signalisierung und -Wiederherstellung"
  },
  {
    "path": "fastlane/metadata/android/en-US/full_description.txt",
    "chars": 2060,
    "preview": "<p><i>Igatha</i> is an offline SOS app designed for emergencies when communication networks fail.</p><p><i>Igatha:</i> A"
  },
  {
    "path": "fastlane/metadata/android/en-US/short_description.txt",
    "chars": 28,
    "preview": "SOS Signaling &amp; Recovery"
  },
  {
    "path": "ios/Igatha/AppDelegate.swift",
    "chars": 1839,
    "preview": "//\n//  AppDelegate.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 14/10/2024.\n//\n\nimport SwiftUI\nimport UserNotific"
  },
  {
    "path": "ios/Igatha/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 223,
    "preview": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"platform\" : \"universal\",\n        \"reference\" : \"systemRedColor\"\n      "
  },
  {
    "path": "ios/Igatha/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 9796,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"AppIcon-20@2x.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n  "
  },
  {
    "path": "ios/Igatha/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "ios/Igatha/Constants.swift",
    "chars": 2518,
    "preview": "//\n//  Constants.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 06/10/2024.\n//\n\nimport CoreBluetooth\n\nstruct Consta"
  },
  {
    "path": "ios/Igatha/Igatha.entitlements",
    "chars": 249,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "ios/Igatha/IgathaApp.swift",
    "chars": 297,
    "preview": "//\n//  IgathaApp.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 05/10/2024.\n//\n\nimport SwiftUI\n\n@main\nstruct Igatha"
  },
  {
    "path": "ios/Igatha/Info.plist",
    "chars": 2109,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "ios/Igatha/Models/Device.swift",
    "chars": 2051,
    "preview": "//\n//  Models.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 06/10/2024.\n//\n\nimport Foundation\nimport CoreBluetooth"
  },
  {
    "path": "ios/Igatha/Preview Content/Preview Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "ios/Igatha/Sensors/AccelerometerSensor.swift",
    "chars": 1774,
    "preview": "//\n//  AccelerometerSensor.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 12/10/2024.\n//\n\nimport Foundation\nimport "
  },
  {
    "path": "ios/Igatha/Sensors/BarometerSensor.swift",
    "chars": 1712,
    "preview": "//\n//  BarometerSensor.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 12/10/2024.\n//\n\nimport Foundation\nimport Core"
  },
  {
    "path": "ios/Igatha/Sensors/GyroscopeSensor.swift",
    "chars": 1723,
    "preview": "//\n//  GyroscopeSensor.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 12/10/2024.\n//\n\nimport Foundation\nimport Core"
  },
  {
    "path": "ios/Igatha/Sensors/SensorType.swift",
    "chars": 593,
    "preview": "//\n//  SensorType.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 12/10/2024.\n//\n\nimport Foundation\n\nenum SensorType"
  },
  {
    "path": "ios/Igatha/Services/DeepLinkHandler.swift",
    "chars": 1330,
    "preview": "//\n//  DeepLinkHandler.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 22/04/2025.\n//\n\nimport Foundation\nimport Swif"
  },
  {
    "path": "ios/Igatha/Services/DisasterDetector.swift",
    "chars": 4366,
    "preview": "//\n//  DisasterDetector.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 12/10/2024.\n//\n\nimport Foundation\n\nclass Dis"
  },
  {
    "path": "ios/Igatha/Services/EmergencyManager.swift",
    "chars": 8479,
    "preview": "//\n//  EmergencyManager.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 14/10/2024.\n//\n\nimport SwiftUI\nimport UserNo"
  },
  {
    "path": "ios/Igatha/Services/LocationManager.swift",
    "chars": 2856,
    "preview": "//\n//  LocationManager.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 17/10/2024.\n//\n\nimport Foundation\nimport Core"
  },
  {
    "path": "ios/Igatha/Services/NotificationManager.swift",
    "chars": 4362,
    "preview": "//\n//  NotificationManager.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 22/04/2025.\n//\n\nimport Foundation\nimport "
  },
  {
    "path": "ios/Igatha/Services/ProximityScanner.swift",
    "chars": 2393,
    "preview": "//\n//  ProximityScanner.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 06/10/2024.\n//\n\nimport Foundation\nimport Cor"
  },
  {
    "path": "ios/Igatha/Services/SOSBeacon.swift",
    "chars": 2644,
    "preview": "//\n//  SOSBeacon.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 11/10/2024.\n//\n\nimport Foundation\nimport CoreBlueto"
  },
  {
    "path": "ios/Igatha/Services/SirenPlayer.swift",
    "chars": 5251,
    "preview": "//\n//  SirenPlayer.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 12/10/2024.\n//\n\nimport AVFoundation\n\nclass SirenP"
  },
  {
    "path": "ios/Igatha/ViewModels/ContentViewModel.swift",
    "chars": 3468,
    "preview": "//\n//  ViewModel.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 14/10/2024.\n//\n\nimport SwiftUI\n\nclass ContentViewMo"
  },
  {
    "path": "ios/Igatha/ViewModels/FeedbackFormViewModel.swift",
    "chars": 9261,
    "preview": "//\n//  FeedbackFormViewModel.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 20/04/2025.\n//\n\nimport SwiftUI\nimport F"
  },
  {
    "path": "ios/Igatha/ViewModels/SettingsViewModel.swift",
    "chars": 650,
    "preview": "//\n//  SettingsViewModel.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 17/10/2024.\n//\n\nimport SwiftUI\n\nclass Setti"
  },
  {
    "path": "ios/Igatha/Views/ContentView.swift",
    "chars": 3623,
    "preview": "//\n//  ContentView.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 05/10/2024.\n//\n\nimport SwiftUI\n\nstruct ContentVie"
  },
  {
    "path": "ios/Igatha/Views/DeviceDetailView.swift",
    "chars": 5638,
    "preview": "//\n//  DeviceDetailView.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 13/10/2024.\n//\n\nimport SwiftUI\n\nstruct Devic"
  },
  {
    "path": "ios/Igatha/Views/DeviceListView.swift",
    "chars": 1812,
    "preview": "//\n//  DeviceListView.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 13/10/2024.\n//\n\nimport SwiftUI\n\nstruct DeviceL"
  },
  {
    "path": "ios/Igatha/Views/DeviceRowView.swift",
    "chars": 1904,
    "preview": "//\n//  DeviceRowView.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 13/10/2024.\n//\n\nimport SwiftUI\n\nstruct DeviceRo"
  },
  {
    "path": "ios/Igatha/Views/FeedbackButtonView.swift",
    "chars": 2029,
    "preview": "//\n//  FeedbackRowView.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 20/04/2025.\n//\n\nimport SwiftUI\n\nstruct Feedba"
  },
  {
    "path": "ios/Igatha/Views/FeedbackFormView.swift",
    "chars": 4558,
    "preview": "//\n//  FeedbackFormView.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 20/04/2025.\n//\n\nimport SwiftUI\n\n// Feedback "
  },
  {
    "path": "ios/Igatha/Views/SettingsView.swift",
    "chars": 1611,
    "preview": "//\n//  SettingsView.swift\n//  Igatha\n//\n//  Created by Nizar Mahmoud on 14/10/2024.\n//\n\nimport SwiftUI\n\nstruct SettingsV"
  },
  {
    "path": "ios/Igatha.xcodeproj/project.pbxproj",
    "chars": 15015,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 77;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "ios/Igatha.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "ios/Igatha.xcodeproj/xcuserdata/nizarmah.xcuserdatad/xcschemes/xcschememanagement.plist",
    "chars": 341,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the nizarmah/igatha GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 128 files (294.1 KB), approximately 70.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!