SettingsActivity refactor

This commit is contained in:
Alexey 2025-11-07 12:50:34 +03:00
commit 2b821d8204
6 changed files with 117 additions and 96 deletions

4
TODO
View file

@ -1,8 +1,8 @@
functionality: functionality:
(done) sasplanet zipped sqlitedbs parser (done) sasplanet zipped sqlitedbs parser
refactor: refactor:
extract file opener from settings activity (done) extract file opener from settings activity
extract zip parser from settings activity (done) extract zip parser from settings activity
make use of Config.kt (maybe move consts here?) make use of Config.kt (maybe move consts here?)
fix: fix:
app rotation with placed point app rotation with placed point

View file

@ -0,0 +1,31 @@
package com.mirenkov.ktheightmap
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import com.mirenkov.ktheightmap.FileLoader.Companion.filePickerResult
import com.nareshchocha.filepickerlibrary.FilePickerResultContracts
import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig
import com.nareshchocha.filepickerlibrary.models.FilePickerResult
class FileLoader {
companion object {
var filePath: MutableState<String?> = mutableStateOf(null)
fun filePickerResult(result: FilePickerResult): String? {
if (result.errorMessage != null) {
Log.e(TAG, result.errorMessage!!)
return null
} else {
val filePath = result.selectedFilePath
return filePath
}
}
}
}
fun ComponentActivity.registerFileLoader(): ActivityResultLauncher<DocumentFilePickerConfig?> {
return registerForActivityResult(FilePickerResultContracts.PickDocumentFile())
{ result -> FileLoader.filePath.value = filePickerResult(result) }
}

View file

@ -27,6 +27,7 @@ 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.parser.KhmParser
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.floor import kotlin.math.floor
import kotlin.math.pow import kotlin.math.pow

View file

@ -2,7 +2,6 @@ package com.mirenkov.ktheightmap
import android.app.Application import android.app.Application
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -14,50 +13,26 @@ import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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.parser.KhmParser
import com.mirenkov.ktheightmap.parser.SasJPEGParser
import com.mirenkov.ktheightmap.parser.SasSQLiteParser import com.mirenkov.ktheightmap.parser.SasSQLiteParser
import com.mirenkov.ktheightmap.ui.theme.KtHeightMapTheme import com.mirenkov.ktheightmap.ui.theme.KtHeightMapTheme
import com.nareshchocha.filepickerlibrary.FilePickerResultContracts
import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig
import com.nareshchocha.filepickerlibrary.models.FilePickerResult
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.FileInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
class SettingsActivity : ComponentActivity() { class SettingsActivity : ComponentActivity() {
companion object {
var filePath: MutableState<String?> = mutableStateOf(null)
}
fun filePickerResult(result: FilePickerResult): String? {
if (result.errorMessage != null) {
Log.e(TAG, result.errorMessage ?: "")
return null
} else {
val filePath = result.selectedFilePath
filePath?.let {
if (it.endsWith(".zip") || it.endsWith(".khm"))
return filePath
}
return null
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val launcher = registerForActivityResult(FilePickerResultContracts.PickDocumentFile())
{ result -> filePath.value = filePickerResult(result) } val launcher = registerFileLoader()
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
@ -77,79 +52,38 @@ class SettingsActivity : ComponentActivity() {
@Composable @Composable
fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher<DocumentFilePickerConfig?>) { fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher<DocumentFilePickerConfig?>) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val filePath by remember { SettingsActivity.filePath } val filePath by remember { FileLoader.filePath }
val ctx = LocalContext.current val ctx = LocalContext.current
val toast = Toast.makeText(ctx, "File parsed.", Toast.LENGTH_LONG)
KtHeightMapTheme { KtHeightMapTheme {
Column(Modifier.fillMaxSize() Column(Modifier.fillMaxSize()
.safeDrawingPadding()) { .safeDrawingPadding()) {
Button( Button({ launcher.launch(null) }) { Text("Select file") }
onClick = { launcher.launch(null) } Button({
) { Text(text = "Select database") }
Button(
onClick = { coroutineScope.launch {
processZip(filePath).forEach {
with(vm.repository) {
val t = getTile(it.x, it.y, it.level)
if (t == null) {
pushTile(it)
}
}
}
} },
) { Text(text = "Load .zip") }
Button(
onClick = { coroutineScope.launch { vm.repository.clearTiles() } },
) { Text(text = "Clear database") }
Button(
onClick = { filePath?.let {
KhmParser.load(it, ctx)
} },
) { Text(text = "Load .khm") }
Button(
onClick = {
filePath?.let { filePath?.let {
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
SasSQLiteParser.processZip(it, vm.repository, ctx) SasSQLiteParser.processZip(it, vm.repository, ctx)
coroutineScope.launch(Dispatchers.Main) { Toast.makeText(ctx, "File parsed.", Toast.LENGTH_LONG).show() } coroutineScope.launch(Dispatchers.Main) { toast.show() }
} }
} } }
} ) { Text("Load SQLite") }
) { Text("Load SAS") } Button({
}
}
}
suspend fun processZip(filePath: String?): List<Tile> = coroutineScope {
val list: MutableList<Tile> = mutableListOf()
filePath?.let { filePath?.let {
ZipInputStream(FileInputStream(it)).use { zipInputStream -> coroutineScope.launch(Dispatchers.IO) {
var entry = zipInputStream.nextEntry SasJPEGParser.processZip(it, vm.repository)
while (true) { coroutineScope.launch(Dispatchers.Main) { toast.show() }
if (entry.name.endsWith(".jpg")) {
Log.i(TAG, entry.name)
val tile = processEntry(zipInputStream, entry)
list.add(tile)
}
entry = zipInputStream.nextEntry
if (entry == null)
break
} }
} }
},
) { Text("Load JPEG") }
Button({
filePath?.let {
KhmParser.load(it, ctx)
toast.show()
} },
) { Text("Load KHM") }
Button({ coroutineScope.launch { vm.repository.clearTiles() } }) { Text("Clear tiles") }
Button({ KhmParser.clear(ctx) }) { Text("Clear KHM") }
}
} }
return@coroutineScope list
}
fun processEntry(zis: ZipInputStream, entry: ZipEntry): Tile {
val ba = ByteArray(entry.size.toInt())
var offset = 0
var size = zis.read(ba)
while (size > 0) {
offset += size
size = zis.read(ba, offset, ba.size - offset)
}
val regex = Regex("(?<=z)(\\d+)|(?<=x)(\\d+)|(?<=y)(\\d+)")
val ( level, x, y ) = regex.findAll(entry.name).map { it.value.toInt() }.toList()
val base64 = ba.toBase64()
return Tile(x, y, level, base64)
} }

View file

@ -1,4 +1,4 @@
package com.mirenkov.ktheightmap package com.mirenkov.ktheightmap.parser
import android.content.Context import android.content.Context
import java.io.DataInputStream import java.io.DataInputStream
@ -32,6 +32,12 @@ class KhmParser {
inp.close() inp.close()
} }
fun clear(ctx: Context) {
if (ctx.getFileStreamPath(HEIGHT_FILE).exists()) {
assert(ctx.deleteFile(HEIGHT_FILE))
}
}
private fun getOffset(header: HeightInfo, x: Int, y: Int): Int { private fun getOffset(header: HeightInfo, x: Int, y: Int): Int {
return (x * header.width + y) * 2 return (x * header.width + y) * 2
} }

View file

@ -0,0 +1,49 @@
package com.mirenkov.ktheightmap.parser
import android.util.Log
import com.mirenkov.ktheightmap.TAG
import com.mirenkov.ktheightmap.Tile
import com.mirenkov.ktheightmap.TileRepository
import com.mirenkov.ktheightmap.toBase64
import java.io.FileInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
class SasJPEGParser {
companion object {
fun processZip(filePath: String?, repo: TileRepository) {
filePath?.let {
ZipInputStream(FileInputStream(it)).use { zipInputStream ->
var entry = zipInputStream.nextEntry
while (true) {
if (entry.name.endsWith(".jpg")) {
Log.i(TAG, entry.name)
processEntry(zipInputStream, entry, repo)
}
entry = zipInputStream.nextEntry
if (entry == null)
break
}
}
}
}
private fun processEntry(zis: ZipInputStream, entry: ZipEntry, repo: TileRepository) {
val ba = ByteArray(entry.size.toInt())
var offset = 0
var size = zis.read(ba)
while (size > 0) {
offset += size
size = zis.read(ba, offset, ba.size - offset)
}
val regex = Regex("(?<=z)(\\d+)|(?<=x)(\\d+)|(?<=y)(\\d+)")
val ( level, x, y ) = regex.findAll(entry.name).map { it.value.toInt() }.toList()
val base64 = ba.toBase64()
val tile = Tile(x, y, level, base64)
val checkedTile = repo.getTile(tile.x, tile.y, tile.level)
if (checkedTile == null)
repo.pushTile(tile)
}
}
}