diff --git a/src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt b/src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt new file mode 100644 index 0000000..0cf3364 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt @@ -0,0 +1,151 @@ +package dev.baldav.app + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.FileObserver +import android.util.Log +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import java.io.File + +class MainActivity : TauriActivity() { + private var commandObserver: FileObserver? = null + private var pendingSaveContent: String? = null + + private val saveFileLauncher = registerForActivityResult( + ActivityResultContracts.CreateDocument("application/gpx+xml") + ) { uri: Uri? -> + if (uri != null && pendingSaveContent != null) { + try { + contentResolver.openOutputStream(uri)?.use { it.write(pendingSaveContent!!.toByteArray()) } + Log.d("BalDav", "File saved to $uri") + } catch (e: Exception) { + Log.e("BalDav", "Failed to save file", e) + } + } + pendingSaveContent = null + } + + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + + // Rust app_data_dir() on Android = app's root data dir (filesDir.parentFile) + val appDataDir = filesDir.parentFile ?: filesDir + Log.d("BalDav", "App data dir: ${appDataDir.absolutePath}") + + // Create command file if not exists (FileObserver needs it) + val cmdFile = File(appDataDir, "gps_command.txt") + if (!cmdFile.exists()) cmdFile.createNewFile() + + setupCommandWatcher(appDataDir) + processGpsCommand(appDataDir) + Log.d("BalDav", "Command watcher started on ${appDataDir.absolutePath}") + + // Copy seed DB from assets to filesDir (Rust can't read Android assets directly). + // Always overwrite — APK updates may contain a newer seed DB. + try { + val seedDest = File(filesDir, "products_seed.db") + val assetSize = assets.open("frontend/data/products_seed.db").use { it.available().toLong() } + if (!seedDest.exists() || seedDest.length() != assetSize) { + assets.open("frontend/data/products_seed.db").use { input -> + seedDest.outputStream().use { output -> + input.copyTo(output) + } + } + Log.d("BalDav", "Seed DB copied to ${seedDest.absolutePath} (${seedDest.length()} bytes)") + } + } catch (e: Exception) { + Log.e("BalDav", "Failed to copy seed DB", e) + } + + val decorView = window.decorView + ViewCompat.setOnApplyWindowInsetsListener(decorView) { view, insets -> + val statusBar = insets.getInsets(WindowInsetsCompat.Type.statusBars()) + val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val density = resources.displayMetrics.density + val top = (statusBar.top / density).toInt() + val bottom = (navBar.bottom / density).toInt() + val left = (navBar.left / density).toInt() + val right = (navBar.right / density).toInt() + try { + val insetsFile = File(filesDir, "safe_insets.json") + insetsFile.writeText("""{"top":$top,"bottom":$bottom,"left":$left,"right":$right}""") + Log.d("BalDav", "Safe insets: top=$top bottom=$bottom left=$left right=$right") + } catch (e: Exception) { + Log.e("BalDav", "Failed to write safe insets", e) + } + ViewCompat.onApplyWindowInsets(view, insets) + } + } + + private fun setupCommandWatcher(appDataDir: File) { + commandObserver = object : FileObserver(appDataDir.absolutePath, CREATE or CLOSE_WRITE or MODIFY or MOVED_TO) { + override fun onEvent(event: Int, path: String?) { + if (path == "gps_command.txt") { + Log.d("BalDav", "Command file event: $event") + runOnUiThread { processGpsCommand(appDataDir) } + } + } + } + commandObserver?.startWatching() + Log.d("BalDav", "FileObserver started on ${appDataDir.absolutePath}") + } + + private fun processGpsCommand(appDataDir: File) { + val cmdFile = File(appDataDir, "gps_command.txt") + if (!cmdFile.exists()) return + val cmd = cmdFile.readText().trim() + if (cmd.isEmpty()) return + cmdFile.writeText("") // Clear but don't delete (FileObserver needs the file) + + Log.d("BalDav", "Processing GPS command: $cmd") + + when { + cmd.startsWith("start") -> { + val parts = cmd.split(":") + val minSpeed = parts.getOrNull(1)?.toFloatOrNull() ?: 0.42f + val maxAcc = parts.getOrNull(2)?.toFloatOrNull() ?: 20f + val minDist = parts.getOrNull(3)?.toFloatOrNull() ?: 1f + Log.d("BalDav", "Starting GPS service: speed=$minSpeed acc=$maxAcc dist=$minDist") + val intent = Intent(this, GpsRecorderService::class.java).apply { + putExtra(GpsRecorderService.EXTRA_MIN_SPEED, minSpeed) + putExtra(GpsRecorderService.EXTRA_MAX_ACC, maxAcc) + putExtra(GpsRecorderService.EXTRA_MIN_DIST, minDist) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(intent) + } else { + startService(intent) + } + } + cmd == "stop" -> { + Log.d("BalDav", "Stopping GPS service") + val intent = Intent(this, GpsRecorderService::class.java) + intent.action = GpsRecorderService.ACTION_STOP + startService(intent) + } + cmd.startsWith("save:") -> { + // save:filename.gpx — read content from save_content.txt, show SAF picker + val filename = cmd.substringAfter("save:") + val contentFile = File(appDataDir, "save_content.txt") + if (contentFile.exists()) { + pendingSaveContent = contentFile.readText() + contentFile.delete() + Log.d("BalDav", "Opening save dialog for $filename (${pendingSaveContent?.length} bytes)") + saveFileLauncher.launch(filename) + } + } + } + } + + override fun onDestroy() { + commandObserver?.stopWatching() + super.onDestroy() + } +}