From c5bf6da79ee26b9d973f8270c8ebb31e42157f9c Mon Sep 17 00:00:00 2001 From: 4aiman Date: Thu, 21 May 2026 01:10:15 +0300 Subject: [PATCH] Android: always refresh seed DB from assets when size differs Old logic: copy only if file doesn't exist. App updates with newer seed DB were ignored because the old file was already there. Now compares file size with asset size and overwrites on mismatch. --- .../main/java/dev/baldav/app/MainActivity.kt | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt 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() + } +}