Add OVERWATCH v0.1.0 — full detection engine + polish
Phase 1 (BLE), Phase 2 (WiFi BSSID/SSID), Phase 3 (DeFlock map proximity), Phase 4 (Waze live POLICE alerts), and Phase 5 polish all wired through one DetectionStore. Confidence engine scores 0-100; UI maps to 4-tier circle. Polish: - Stylized two-line app title: [DЯΣΛMMΛKΣЯ] // 0VΣЯW4TCH - Modal bottom sheet for source drill-down (tap circle) - Settings screen: per-source toggles, proximity sliders, theme select - SharedPreferences-backed Settings with StateFlow exposure - DetectionService respects per-source toggles at start time - Scanners read proximity overrides via supplier lambdas README documents all sources, architecture, build steps, permissions, and the legal disclaimer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package org.soulstone.overwatch
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.soulstone.overwatch.data.settings.Settings
|
||||
import org.soulstone.overwatch.service.DetectionService
|
||||
import org.soulstone.overwatch.ui.MainScreen
|
||||
import org.soulstone.overwatch.ui.SettingsScreen
|
||||
import org.soulstone.overwatch.ui.theme.OverwatchTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private val requiredPermissions: Array<String>
|
||||
get() = buildList {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
add(Manifest.permission.BLUETOOTH_SCAN)
|
||||
add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
} else {
|
||||
add(Manifest.permission.BLUETOOTH)
|
||||
add(Manifest.permission.BLUETOOTH_ADMIN)
|
||||
}
|
||||
// Location is needed pre-S for BLE, pre-T for WiFi scan results,
|
||||
// and for Phase 3 DeFlock proximity.
|
||||
add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
add(Manifest.permission.NEARBY_WIFI_DEVICES)
|
||||
add(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}.toTypedArray()
|
||||
|
||||
private val permissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { result ->
|
||||
permissionsGranted.value = result.all { it.value }
|
||||
}
|
||||
|
||||
private val permissionsGranted = androidx.compose.runtime.mutableStateOf(false)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
permissionsGranted.value = checkAllPermissions()
|
||||
val settings = Settings.get(this)
|
||||
|
||||
setContent {
|
||||
val themeMode by settings.themeMode.collectAsState()
|
||||
OverwatchTheme(mode = themeMode) {
|
||||
var screen by remember { mutableStateOf(Screen.MAIN) }
|
||||
|
||||
when (screen) {
|
||||
Screen.MAIN -> {
|
||||
val running by DetectionService.running.collectAsState()
|
||||
val events by DetectionService.store.events.collectAsState()
|
||||
val threat by DetectionService.store.threatLevel.collectAsState()
|
||||
val maxScore by DetectionService.store.maxScore.collectAsState()
|
||||
val granted by permissionsGranted
|
||||
|
||||
MainScreen(
|
||||
running = running,
|
||||
threat = threat,
|
||||
score = maxScore,
|
||||
events = events,
|
||||
canStart = granted || running,
|
||||
permissionMessage = if (!granted) "Bluetooth, WiFi + location permissions required" else null,
|
||||
onStartStop = {
|
||||
if (running) {
|
||||
DetectionService.stop(this)
|
||||
} else {
|
||||
if (granted) {
|
||||
DetectionService.start(this)
|
||||
} else {
|
||||
permissionLauncher.launch(requiredPermissions)
|
||||
}
|
||||
}
|
||||
},
|
||||
onOpenSettings = { screen = Screen.SETTINGS }
|
||||
)
|
||||
}
|
||||
Screen.SETTINGS -> {
|
||||
SettingsScreen(
|
||||
settings = settings,
|
||||
onBack = { screen = Screen.MAIN }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
permissionsGranted.value = checkAllPermissions()
|
||||
}
|
||||
|
||||
private fun checkAllPermissions(): Boolean =
|
||||
requiredPermissions.all {
|
||||
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private enum class Screen { MAIN, SETTINGS }
|
||||
}
|
||||
Reference in New Issue
Block a user