Settings menu completed
This commit is contained in:
parent
d30721cdf6
commit
9ceb3787c2
8 changed files with 90 additions and 32 deletions
4
TODO
4
TODO
|
|
@ -3,11 +3,11 @@ functionality:
|
|||
refactor:
|
||||
(done) extract file opener from settings activity
|
||||
(done) extract zip parser from settings activity
|
||||
make use of Config.kt (maybe move consts here?)
|
||||
(done) make use of Config.kt
|
||||
fix:
|
||||
app rotation with placed point
|
||||
ui:
|
||||
proper file loading menu
|
||||
(done) proper file loading menu
|
||||
translation usage
|
||||
test:
|
||||
khm:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
32
app/src/main/java/com/mirenkov/ktheightmap/Global.kt
Normal file
32
app/src/main/java/com/mirenkov/ktheightmap/Global.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue