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:
|
refactor:
|
||||||
(done) extract file opener from settings activity
|
(done) extract file opener from settings activity
|
||||||
(done) extract zip parser 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:
|
fix:
|
||||||
app rotation with placed point
|
app rotation with placed point
|
||||||
ui:
|
ui:
|
||||||
proper file loading menu
|
(done) proper file loading menu
|
||||||
translation usage
|
translation usage
|
||||||
test:
|
test:
|
||||||
khm:
|
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 {
|
class FileLoader {
|
||||||
companion object {
|
companion object {
|
||||||
var filePath: MutableState<String?> = mutableStateOf(null)
|
var filePath: MutableState<String?> = mutableStateOf(null)
|
||||||
|
val launcherActive = mutableStateOf(false)
|
||||||
fun filePickerResult(result: FilePickerResult): String? {
|
fun filePickerResult(result: FilePickerResult): String? {
|
||||||
|
launcherActive.value = false
|
||||||
if (result.errorMessage != null) {
|
if (result.errorMessage != null) {
|
||||||
Log.e(TAG, result.errorMessage!!)
|
Log.e(TAG, result.errorMessage!!)
|
||||||
return null
|
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.rememberTextMeasurer
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.withStyle
|
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 com.mirenkov.ktheightmap.parser.KhmParser
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.floor
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -34,6 +35,8 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
|
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
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.KhmParser
|
||||||
import com.mirenkov.ktheightmap.parser.SasJPEGParser
|
import com.mirenkov.ktheightmap.parser.SasJPEGParser
|
||||||
import com.mirenkov.ktheightmap.parser.SasSQLiteParser
|
import com.mirenkov.ktheightmap.parser.SasSQLiteParser
|
||||||
|
|
@ -68,8 +71,16 @@ class SettingsActivity : ComponentActivity() {
|
||||||
fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher<DocumentFilePickerConfig?>) {
|
fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher<DocumentFilePickerConfig?>) {
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val filePath by remember { FileLoader.filePath }
|
val filePath by remember { FileLoader.filePath }
|
||||||
val pathIsNotNull = filePath != null
|
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
|
val ctx = LocalContext.current
|
||||||
|
|
||||||
var enableClearTiles by remember { mutableStateOf(vm.repository.containsTiles()) }
|
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 selectedFileText = if (pathIsNotNull) filePath!!.split('/').last() else "none"
|
||||||
|
|
||||||
val fileSelectorRow = @Composable () {
|
val fileSelectorRow = @Composable () {
|
||||||
Row(Modifier.fillMaxWidth()) {
|
Row(Modifier.fillMaxWidth()) {
|
||||||
Button({ launcher.launch(null) }) { Text("Select") }
|
Button({ launcher.launch(null); launcherActive = true }) { Text("Select") }
|
||||||
Text("Selected file: $selectedFileText", Modifier.padding(8.dp, 0.dp).align(Alignment.CenterVertically))
|
Text("Selected file: $selectedFileText", Modifier.padding(8.dp, 0.dp).align(Alignment.CenterVertically))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KtHeightMapTheme {
|
KtHeightMapTheme {
|
||||||
Scaffold { paddingValues ->
|
Scaffold(floatingActionButton = { if (showLoading || launcherActive) CircularProgressIndicator() }) { paddingValues ->
|
||||||
Column(Modifier.padding(paddingValues)) {
|
Column(Modifier.padding(paddingValues)) {
|
||||||
LabeledSection("Select file:", Modifier.padding(24.dp, 8.dp)) {
|
LabeledSection("Select file:", Modifier.padding(24.dp, 8.dp)) {
|
||||||
fileSelectorRow()
|
fileSelectorRow()
|
||||||
}
|
}
|
||||||
LabeledSection("Load file as...", Modifier.padding(24.dp, 8.dp)) {
|
LabeledSection("Load file as...", Modifier.padding(24.dp, 8.dp)) {
|
||||||
Button({ filePath?.let {
|
Button({ filePath?.let {
|
||||||
|
showLoading = true
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
SasSQLiteParser.processZip(it, vm.repository, ctx)
|
SasSQLiteParser.processZip(it, vm.repository, ctx)
|
||||||
enableClearTiles = true
|
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 {
|
Button({ filePath?.let {
|
||||||
|
showLoading = true
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
SasJPEGParser.processZip(it, vm.repository)
|
SasJPEGParser.processZip(it, vm.repository)
|
||||||
enableClearTiles = true
|
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 {
|
Button({ filePath?.let {
|
||||||
KhmParser.load(it, ctx)
|
showLoading = true
|
||||||
enableClearHeight = true
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
loadToast.show()
|
KhmParser.load(it, ctx)
|
||||||
} }, enabled = pathIsNotNull) { Text("Height data (.khm)") }
|
enableClearHeight = true
|
||||||
|
coroutineScope.launch(Dispatchers.Main) { loadToast.show(); showLoading = false }
|
||||||
|
}
|
||||||
|
} }, enabled = pathIsValidKhm) { Text("Height data (.khm)") }
|
||||||
}
|
}
|
||||||
LabeledSection("Clear...", Modifier.padding(24.dp, 8.dp)) {
|
LabeledSection("Clear...", Modifier.padding(24.dp, 8.dp)) {
|
||||||
Button({ coroutineScope.launch {
|
Button({ coroutineScope.launch {
|
||||||
|
showLoading = true
|
||||||
vm.repository.clearTiles()
|
vm.repository.clearTiles()
|
||||||
enableClearTiles = false
|
enableClearTiles = false
|
||||||
clearTilesToast.show()
|
coroutineScope.launch(Dispatchers.Main) { clearTilesToast.show(); showLoading = false }
|
||||||
} }, enabled = enableClearTiles) { Text("Tile data") }
|
} }, enabled = enableClearTiles) { Text("Tile data") }
|
||||||
Button({
|
Button({
|
||||||
KhmParser.clear(ctx)
|
KhmParser.clear(ctx)
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.mirenkov.ktheightmap.Config.Companion.MAP_START_OFFSET_X
|
import com.mirenkov.ktheightmap.Global.Companion.MAP_START_OFFSET_X
|
||||||
import com.mirenkov.ktheightmap.Config.Companion.MAP_START_OFFSET_Y
|
import com.mirenkov.ktheightmap.Global.Companion.MAP_START_OFFSET_Y
|
||||||
|
|
||||||
class TileViewModel(application: Application): ViewModel() {
|
class TileViewModel(application: Application): ViewModel() {
|
||||||
val repository: TileRepository
|
val repository: TileRepository
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.mirenkov.ktheightmap.parser
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
@ -26,12 +27,20 @@ class KhmParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
val inp = FileInputStream(filePath)
|
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()
|
inp.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isFileValid(filePath: String): Boolean {
|
||||||
|
FileInputStream(filePath).use {
|
||||||
|
return checkHeader(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun clear(ctx: Context) {
|
fun clear(ctx: Context) {
|
||||||
if (fileExists(ctx)) {
|
if (fileExists(ctx)) {
|
||||||
assert(ctx.deleteFile(HEIGHT_FILE))
|
assert(ctx.deleteFile(HEIGHT_FILE))
|
||||||
|
|
@ -46,6 +55,15 @@ class KhmParser {
|
||||||
return (x * header.width + y) * 2
|
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 {
|
private fun readHeader(dis: DataInputStream): HeightInfo {
|
||||||
if (header == null) {
|
if (header == null) {
|
||||||
header = with(dis) {
|
header = with(dis) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue