Settings menu completed

This commit is contained in:
Alexey 2025-11-11 15:48:17 +03:00
commit 9ceb3787c2
8 changed files with 90 additions and 32 deletions

View file

@ -1,8 +0,0 @@
package com.mirenkov.ktheightmap
class Config {
companion object {
const val MAP_START_OFFSET_X = -646.65625F
const val MAP_START_OFFSET_Y = -1157.2814F
}
}

View file

@ -13,7 +13,9 @@ import com.nareshchocha.filepickerlibrary.models.FilePickerResult
class FileLoader {
companion object {
var filePath: MutableState<String?> = 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

View file

@ -0,0 +1,32 @@
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
}
}
}

View file

@ -29,12 +29,9 @@ 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

View file

@ -13,6 +13,7 @@ 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
@ -34,6 +35,8 @@ 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
@ -68,8 +71,16 @@ class SettingsActivity : ComponentActivity() {
fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher<DocumentFilePickerConfig?>) {
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()) }
@ -82,44 +93,50 @@ fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher<DocumentFil
val selectedFileText = if (pathIsNotNull) filePath!!.split('/').last() else "none"
val fileSelectorRow = @Composable () {
Row(Modifier.fillMaxWidth()) {
Button({ launcher.launch(null) }) { Text("Select") }
Text("Selected file: $selectedFileText", Modifier.padding(8.dp, 0.dp).align(Alignment.CenterVertically))
}
Row(Modifier.fillMaxWidth()) {
Button({ launcher.launch(null); launcherActive = true }) { Text("Select") }
Text("Selected file: $selectedFileText", Modifier.padding(8.dp, 0.dp).align(Alignment.CenterVertically))
}
}
KtHeightMapTheme {
Scaffold { paddingValues ->
Scaffold(floatingActionButton = { if (showLoading || launcherActive) CircularProgressIndicator() }) { 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() }
coroutineScope.launch(Dispatchers.Main) { loadToast.show(); showLoading = false }
}
} }, enabled = pathIsNotNull) { Text("SQLite (.zip)") }
} }, enabled = pathContainsSQLite) { 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() }
coroutineScope.launch(Dispatchers.Main) { loadToast.show(); showLoading = false }
}
} }, enabled = pathIsNotNull) { Text("JPEG (.zip)") }
} }, enabled = pathIsValidZip && !pathContainsSQLite) { Text("JPEG (.zip)") }
Button({ filePath?.let {
KhmParser.load(it, ctx)
enableClearHeight = true
loadToast.show()
} }, enabled = pathIsNotNull) { Text("Height data (.khm)") }
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)") }
}
LabeledSection("Clear...", Modifier.padding(24.dp, 8.dp)) {
Button({ coroutineScope.launch {
showLoading = true
vm.repository.clearTiles()
enableClearTiles = false
clearTilesToast.show()
coroutineScope.launch(Dispatchers.Main) { clearTilesToast.show(); showLoading = false }
} }, enabled = enableClearTiles) { Text("Tile data") }
Button({
KhmParser.clear(ctx)

View file

@ -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.Config.Companion.MAP_START_OFFSET_X
import com.mirenkov.ktheightmap.Config.Companion.MAP_START_OFFSET_Y
import com.mirenkov.ktheightmap.Global.Companion.MAP_START_OFFSET_X
import com.mirenkov.ktheightmap.Global.Companion.MAP_START_OFFSET_Y
class TileViewModel(application: Application): ViewModel() {
val repository: TileRepository

View file

@ -3,6 +3,7 @@ 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
@ -26,12 +27,20 @@ class KhmParser {
}
val inp = FileInputStream(filePath)
ctx.openFileOutput(HEIGHT_FILE, Context.MODE_PRIVATE).use {
it.write(inp.readBytes())
}
if (checkHeader(inp))
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)) {
assert(ctx.deleteFile(HEIGHT_FILE))
@ -46,6 +55,15 @@ class KhmParser {
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) {