From 2b821d8204e7736285136ff8d3d2c1db02a176ec Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Fri, 7 Nov 2025 12:50:34 +0300 Subject: [PATCH] SettingsActivity refactor --- TODO | 4 +- .../com/mirenkov/ktheightmap/FileLoader.kt | 31 +++++ .../com/mirenkov/ktheightmap/MapCanvas.kt | 1 + .../mirenkov/ktheightmap/SettingsActivity.kt | 120 ++++-------------- .../ktheightmap/{ => parser}/KhmParser.kt | 8 +- .../ktheightmap/parser/SasJPEGParser.kt | 49 +++++++ 6 files changed, 117 insertions(+), 96 deletions(-) create mode 100644 app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt rename app/src/main/java/com/mirenkov/ktheightmap/{ => parser}/KhmParser.kt (96%) create mode 100644 app/src/main/java/com/mirenkov/ktheightmap/parser/SasJPEGParser.kt diff --git a/TODO b/TODO index 612015c..70eb2e3 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,8 @@ functionality: (done) sasplanet zipped sqlitedbs parser refactor: - extract file opener from settings activity - extract zip parser from settings activity + (done) extract file opener from settings activity + (done) extract zip parser from settings activity make use of Config.kt (maybe move consts here?) fix: app rotation with placed point diff --git a/app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt b/app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt new file mode 100644 index 0000000..73a4326 --- /dev/null +++ b/app/src/main/java/com/mirenkov/ktheightmap/FileLoader.kt @@ -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 = 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 { + return registerForActivityResult(FilePickerResultContracts.PickDocumentFile()) + { result -> FileLoader.filePath.value = filePickerResult(result) } +} \ 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 13f4faa..ba9325a 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/MapCanvas.kt @@ -27,6 +27,7 @@ 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.parser.KhmParser import kotlin.math.absoluteValue import kotlin.math.floor import kotlin.math.pow diff --git a/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt b/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt index 64c3e66..b868ad0 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/SettingsActivity.kt @@ -2,7 +2,6 @@ package com.mirenkov.ktheightmap import android.app.Application import android.os.Bundle -import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -14,50 +13,26 @@ import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner 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.ui.theme.KtHeightMapTheme -import com.nareshchocha.filepickerlibrary.FilePickerResultContracts import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig -import com.nareshchocha.filepickerlibrary.models.FilePickerResult import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -import java.io.FileInputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream class SettingsActivity : ComponentActivity() { - - companion object { - var filePath: MutableState = 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?) { super.onCreate(savedInstanceState) - val launcher = registerForActivityResult(FilePickerResultContracts.PickDocumentFile()) - { result -> filePath.value = filePickerResult(result) } + + val launcher = registerFileLoader() enableEdgeToEdge() setContent { @@ -77,79 +52,38 @@ class SettingsActivity : ComponentActivity() { @Composable fun SettingsMain(vm: TileViewModel, launcher: ActivityResultLauncher) { val coroutineScope = rememberCoroutineScope() - val filePath by remember { SettingsActivity.filePath } + val filePath by remember { FileLoader.filePath } val ctx = LocalContext.current + val toast = Toast.makeText(ctx, "File parsed.", Toast.LENGTH_LONG) KtHeightMapTheme { Column(Modifier.fillMaxSize() .safeDrawingPadding()) { - Button( - onClick = { launcher.launch(null) } - ) { 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 = { + Button({ launcher.launch(null) }) { Text("Select file") } + Button({ filePath?.let { coroutineScope.launch(Dispatchers.IO) { 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") } + Button({ + filePath?.let { + coroutineScope.launch(Dispatchers.IO) { + SasJPEGParser.processZip(it, vm.repository) + coroutineScope.launch(Dispatchers.Main) { toast.show() } } } - } - ) { Text("Load SAS") } + }, + ) { 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") } } } -} - -suspend fun processZip(filePath: String?): List = coroutineScope { - val list: MutableList = mutableListOf() - filePath?.let { - ZipInputStream(FileInputStream(it)).use { zipInputStream -> - var entry = zipInputStream.nextEntry - while (true) { - 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 - } - } - } - 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) } \ No newline at end of file diff --git a/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt b/app/src/main/java/com/mirenkov/ktheightmap/parser/KhmParser.kt similarity index 96% rename from app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt rename to app/src/main/java/com/mirenkov/ktheightmap/parser/KhmParser.kt index 68b6a76..efc8bc9 100644 --- a/app/src/main/java/com/mirenkov/ktheightmap/KhmParser.kt +++ b/app/src/main/java/com/mirenkov/ktheightmap/parser/KhmParser.kt @@ -1,4 +1,4 @@ -package com.mirenkov.ktheightmap +package com.mirenkov.ktheightmap.parser import android.content.Context import java.io.DataInputStream @@ -32,6 +32,12 @@ class KhmParser { 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 { return (x * header.width + y) * 2 } diff --git a/app/src/main/java/com/mirenkov/ktheightmap/parser/SasJPEGParser.kt b/app/src/main/java/com/mirenkov/ktheightmap/parser/SasJPEGParser.kt new file mode 100644 index 0000000..d1dc8c4 --- /dev/null +++ b/app/src/main/java/com/mirenkov/ktheightmap/parser/SasJPEGParser.kt @@ -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) + } + } +} \ No newline at end of file