diff --git a/TODO b/TODO index f67a3ca..70eb2e3 100644 --- a/TODO +++ b/TODO @@ -3,11 +3,11 @@ functionality: refactor: (done) extract file opener from settings activity (done) extract zip parser from settings activity - (done) make use of Config.kt + make use of Config.kt (maybe move consts here?) fix: app rotation with placed point ui: - (done) proper file loading menu + proper file loading menu translation usage test: khm: diff --git a/app/src/main/java/com/mirenkov/ktheightmap/Config.kt b/app/src/main/java/com/mirenkov/ktheightmap/Config.kt new file mode 100644 index 0000000..4d04d8b --- /dev/null +++ b/app/src/main/java/com/mirenkov/ktheightmap/Config.kt @@ -0,0 +1,8 @@ +package com.mirenkov.ktheightmap + +class Config { + companion object { + const val MAP_START_OFFSET_X = -646.65625F + const val MAP_START_OFFSET_Y = -1157.2814F + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt b/app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt index 4566413..73a4326 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt @@ -13,9 +13,7 @@ import com.nareshchocha.filepickerlibrary.models.FilePickerResult class FileLoader { companion object { var filePath: MutableState = mutableStateOf(null) - val launcherActive = mutableStateOf(false) fun filePickerResult(result: FilePickerResult): String? { - launcherActive.value = false if (result.errorMessage != null) { Log.e(TAG, result.errorMessage!!) return null diff --git a/app/src/main/java/com/mirenkov/ktheightmap/Global.kt b/app/src/main/java/com/mirenkov/ktheightmap/Global.kt deleted file mode 100644 index 42d8821..0000000 --- a/app/src/main/java/com/mirenkov/ktheightmap/Global.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.mirenkov.ktheightmap - -import android.util.Log -import java.io.FileInputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipException -import java.util.zip.ZipInputStream - -class Global { - companion object { - const val MAP_START_OFFSET_X = -646.65625F - const val MAP_START_OFFSET_Y = -1157.2814F - - fun fileIsValidZip(file: String): Boolean { - ZipInputStream(FileInputStream(file)).use { - return it.nextEntry != null - } - } - - fun zipContainsSQLite(file: String): Boolean { - ZipInputStream(FileInputStream(file)).use { - var entry: ZipEntry? = it.nextEntry - while (entry != null) { - if (entry.name.endsWith(".sqlitedb")) - return true - entry = it.nextEntry - } - } - return false - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt index 5134a03..d691183 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt @@ -29,9 +29,12 @@ import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle +import com.mirenkov.ktheightmap.Config.Companion.MAP_START_OFFSET_X +import com.mirenkov.ktheightmap.Config.Companion.MAP_START_OFFSET_Y import com.mirenkov.ktheightmap.parser.KhmParser import kotlin.math.absoluteValue import kotlin.math.ceil +import kotlin.math.floor import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt diff --git a/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt b/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt index 873c328..dc7c39e 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt @@ -13,16 +13,13 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -35,8 +32,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel -import com.mirenkov.ktheightmap.Global.Companion.fileIsValidZip -import com.mirenkov.ktheightmap.Global.Companion.zipContainsSQLite import com.mirenkov.ktheightmap.parser.KhmParser import com.mirenkov.ktheightmap.parser.SasJPEGParser import com.mirenkov.ktheightmap.parser.SasSQLiteParser @@ -71,78 +66,50 @@ class SettingsActivity : ComponentActivity() { fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher) { val coroutineScope = rememberCoroutineScope() - val filePath by remember { FileLoader.filePath } - val pathIsNotNull = filePath != null - val pathIsValidKhm = pathIsNotNull && KhmParser.isFileValid(filePath!!) - val pathIsValidZip = pathIsNotNull && fileIsValidZip(filePath!!) - val pathContainsSQLite = pathIsValidZip && zipContainsSQLite(filePath!!) - - var launcherActive by remember { FileLoader.launcherActive } - var showLoading by remember { mutableStateOf(false) } - val ctx = LocalContext.current - - var enableClearTiles by remember { mutableStateOf(vm.repository.containsTiles()) } - var enableClearHeight by remember { mutableStateOf(KhmParser.fileExists(ctx)) } - val loadToast = Toast.makeText(ctx, "File successfully loaded.", Toast.LENGTH_LONG) val clearTilesToast = Toast.makeText(ctx, "Tiles removed from database.", Toast.LENGTH_LONG) val clearHeightToast = Toast.makeText(ctx, "Height data removed.", Toast.LENGTH_LONG) - - val selectedFileText = if (pathIsNotNull) filePath!!.split('/').last() else "none" - val fileSelectorRow = @Composable () { - Row(Modifier.fillMaxWidth()) { - Button({ launcher.launch(null); launcherActive = true }) { Text("Select") } - Text("Selected file: $selectedFileText", Modifier.padding(8.dp, 0.dp).align(Alignment.CenterVertically)) - } + Row(Modifier.fillMaxWidth()) { + Button({ launcher.launch(null) }) { Text("Select") } + Text("Selected file: none", Modifier.padding(8.dp, 0.dp).align(Alignment.CenterVertically)) + } } - KtHeightMapTheme { - Scaffold(floatingActionButton = { if (showLoading || launcherActive) CircularProgressIndicator() }) { paddingValues -> + Scaffold { paddingValues -> Column(Modifier.padding(paddingValues)) { LabeledSection("Select file:", Modifier.padding(24.dp, 8.dp)) { fileSelectorRow() } LabeledSection("Load file as...", Modifier.padding(24.dp, 8.dp)) { Button({ filePath?.let { - showLoading = true coroutineScope.launch(Dispatchers.IO) { SasSQLiteParser.processZip(it, vm.repository, ctx) - enableClearTiles = true - coroutineScope.launch(Dispatchers.Main) { loadToast.show(); showLoading = false } + coroutineScope.launch(Dispatchers.Main) { loadToast.show() } } - } }, enabled = pathContainsSQLite) { Text("SQLite (.zip)") } + } }) { Text("SQLite (.zip)") } Button({ filePath?.let { - showLoading = true coroutineScope.launch(Dispatchers.IO) { SasJPEGParser.processZip(it, vm.repository) - enableClearTiles = true - coroutineScope.launch(Dispatchers.Main) { loadToast.show(); showLoading = false } + coroutineScope.launch(Dispatchers.Main) { loadToast.show() } } - } }, enabled = pathIsValidZip && !pathContainsSQLite) { Text("JPEG (.zip)") } + } }) { Text("JPEG (.zip)") } Button({ filePath?.let { - showLoading = true - coroutineScope.launch(Dispatchers.IO) { - KhmParser.load(it, ctx) - enableClearHeight = true - coroutineScope.launch(Dispatchers.Main) { loadToast.show(); showLoading = false } - } - } }, enabled = pathIsValidKhm) { Text("Height data (.khm)") } + KhmParser.load(it, ctx) + loadToast.show() + } }) { Text("Height data (.khm)") } } LabeledSection("Clear...", Modifier.padding(24.dp, 8.dp)) { Button({ coroutineScope.launch { - showLoading = true vm.repository.clearTiles() - enableClearTiles = false - coroutineScope.launch(Dispatchers.Main) { clearTilesToast.show(); showLoading = false } - } }, enabled = enableClearTiles) { Text("Tile data") } + clearTilesToast.show() + } }) { Text("Tile data") } Button({ KhmParser.clear(ctx) - enableClearHeight = false clearHeightToast.show() - }, enabled = enableClearHeight) { Text("Height data") } + }) { Text("Height data") } } } } diff --git a/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt b/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt index e4ca4e2..028d931 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/TileRepository.kt @@ -25,8 +25,4 @@ class TileRepository(private val tileDao: TileDao) { fun getMaxLevel(): Int { return coroutineScope.future(Dispatchers.IO){ tileDao.maxLevel() }.join() ?: 2 } - - fun containsTiles(): Boolean { - return coroutineScope.future(Dispatchers.IO){ tileDao.maxLevel() }.join() != null - } } \ No newline at end of file diff --git a/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt b/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt index 2bb07e7..9d35050 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/TileViewModel.kt @@ -7,8 +7,8 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel -import com.mirenkov.ktheightmap.Global.Companion.MAP_START_OFFSET_X -import com.mirenkov.ktheightmap.Global.Companion.MAP_START_OFFSET_Y +import com.mirenkov.ktheightmap.Config.Companion.MAP_START_OFFSET_X +import com.mirenkov.ktheightmap.Config.Companion.MAP_START_OFFSET_Y class TileViewModel(application: Application): ViewModel() { val repository: TileRepository diff --git a/app/src/main/java/com/mirenkov/ktheightmap/parser/KhmParser.kt b/app/src/main/java/com/mirenkov/ktheightmap/parser/KhmParser.kt index e22cf5b..35801e8 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/parser/KhmParser.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/parser/KhmParser.kt @@ -3,7 +3,6 @@ package com.mirenkov.ktheightmap.parser import android.content.Context import java.io.DataInputStream import java.io.FileInputStream -import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -22,48 +21,27 @@ class KhmParser { const val HEIGHT_FILE: String = "height.khm" fun load(filePath: String, ctx: Context) { - if (fileExists(ctx)) { + if (ctx.getFileStreamPath(HEIGHT_FILE).exists()) { return } val inp = FileInputStream(filePath) - - if (checkHeader(inp)) - ctx.openFileOutput(HEIGHT_FILE, Context.MODE_PRIVATE).use { - it.write(inp.readBytes()) - } + ctx.openFileOutput(HEIGHT_FILE, Context.MODE_PRIVATE).use { + it.write(inp.readBytes()) + } inp.close() } - fun isFileValid(filePath: String): Boolean { - FileInputStream(filePath).use { - return checkHeader(it) - } - } - fun clear(ctx: Context) { - if (fileExists(ctx)) { + if (ctx.getFileStreamPath(HEIGHT_FILE).exists()) { assert(ctx.deleteFile(HEIGHT_FILE)) } } - fun fileExists(ctx: Context): Boolean { - return (ctx.getFileStreamPath(HEIGHT_FILE).exists()) - } - private fun getOffset(header: HeightInfo, x: Int, y: Int): Int { return (x * header.width + y) * 2 } - // NOTE: this function only checks if values are in accepted ranges - private fun checkHeader(fis: FileInputStream): Boolean { - header = null - readHeader(DataInputStream(fis)) - return abs(header!!.latPerValue) < abs(header!!.minLat) && - abs(header!!.lonPerValue) < abs(header!!.minLon) && - header!!.width > 0 && header!!.height > 0 - } - private fun readHeader(dis: DataInputStream): HeightInfo { if (header == null) { header = with(dis) { @@ -92,7 +70,7 @@ class KhmParser { } fun getHeight(lon: Float, lat: Float, ctx: Context): UShort { - if (!fileExists(ctx)) + if (!ctx.getFileStreamPath(HEIGHT_FILE).exists()) return 0u.toUShort() val dis = DataInputStream(ctx.openFileInput(HEIGHT_FILE)) dis.use {