Finished refactoring the old SettingsScreenController.kt

This commit is contained in:
Nacho 2025-01-18 12:05:10 +01:00
parent 542072c238
commit 23e233227a
6 changed files with 74 additions and 120 deletions

View File

@ -25,9 +25,7 @@ import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenController
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -57,8 +55,6 @@ class MainActivity : ComponentActivity() {
registerFileActivityResult() registerFileActivityResult()
val application = applicationContext as VBHelper val application = applicationContext as VBHelper
val settingsScreenController = SettingsScreenController.Factory(this, ApkSecretsImporter(), application.container.dataStoreSecretsRepository)
.buildSettingScreenHandlers()
val scanScreenController = ScanScreenControllerImpl( val scanScreenController = ScanScreenControllerImpl(
application.container.dataStoreSecretsRepository.secretsFlow, application.container.dataStoreSecretsRepository.secretsFlow,
this::handleReceivedNfcCharacter, this::handleReceivedNfcCharacter,
@ -66,13 +62,13 @@ class MainActivity : ComponentActivity() {
this::registerActivityLifecycleListener, this::registerActivityLifecycleListener,
this::unregisterActivityLifecycleListener this::unregisterActivityLifecycleListener
) )
val newSettingsScreenController = NewSettingsScreenControllerImpl(this) val settingsScreenController = SettingsScreenControllerImpl(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
VBHelperTheme { VBHelperTheme {
MainApplication(settingsScreenController, scanScreenController, newSettingsScreenController) MainApplication(scanScreenController, settingsScreenController)
} }
} }
Log.i("MainActivity", "Activity onCreated") Log.i("MainActivity", "Activity onCreated")
@ -192,16 +188,14 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
private fun MainApplication( private fun MainApplication(
settingsScreenController: SettingsScreenController,
scanScreenController: ScanScreenControllerImpl, scanScreenController: ScanScreenControllerImpl,
newSettingsScreenController: NewSettingsScreenControllerImpl settingsScreenController: SettingsScreenControllerImpl
) { ) {
AppNavigation( AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers( applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController, settingsScreenController,
scanScreenController, scanScreenController,
newSettingsScreenController
), ),
onClickImportCard = { onClickImportCard = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {

View File

@ -15,15 +15,13 @@ import com.github.nacabaro.vbhelper.screens.ItemsScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenController
import com.github.nacabaro.vbhelper.screens.SpriteViewer import com.github.nacabaro.vbhelper.screens.SpriteViewer
import com.github.nacabaro.vbhelper.screens.StorageScreen import com.github.nacabaro.vbhelper.screens.StorageScreen
import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
data class AppNavigationHandlers( data class AppNavigationHandlers(
val settingsScreenController: SettingsScreenController, val settingsScreenController: SettingsScreenControllerImpl,
val scanScreenController: ScanScreenControllerImpl, val scanScreenController: ScanScreenControllerImpl,
val newSettingsScreenController: NewSettingsScreenControllerImpl
) )
@Composable @Composable
@ -76,7 +74,6 @@ fun AppNavigation(
SettingsScreen( SettingsScreen(
navController = navController, navController = navController,
settingsScreenController = applicationNavigationHandlers.settingsScreenController, settingsScreenController = applicationNavigationHandlers.settingsScreenController,
newSettingsScreenController = applicationNavigationHandlers.newSettingsScreenController,
onClickImportCard = onClickImportCard, onClickImportCard = onClickImportCard,
) )
} }

View File

@ -1,8 +0,0 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.net.Uri
interface NewSettingsScreenController {
fun onClickOpenDirectory()
fun onClickImportDatabase()
}

View File

@ -1,9 +1,5 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -22,14 +18,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.nacabaro.vbhelper.navigation.NavigationItems
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
navController: NavController, navController: NavController,
settingsScreenController: SettingsScreenController, settingsScreenController: SettingsScreenControllerImpl,
newSettingsScreenController: NewSettingsScreenControllerImpl,
onClickImportCard: () -> Unit onClickImportCard: () -> Unit
) { ) {
Scaffold ( Scaffold (
@ -52,14 +45,14 @@ fun SettingsScreen(
) { ) {
SettingsSection("NFC Communication") SettingsSection("NFC Communication")
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") { SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
settingsScreenController.apkFilePickLauncher.launch(arrayOf("*/*")) settingsScreenController.onClickImportApk()
} }
SettingsSection("Data management") SettingsSection("Data management")
SettingsEntry(title = "Export data", description = "Export application database") { SettingsEntry(title = "Export data", description = "Export application database") {
newSettingsScreenController.onClickOpenDirectory() settingsScreenController.onClickOpenDirectory()
} }
SettingsEntry(title = "Import data", description = "Import application database") { SettingsEntry(title = "Import data", description = "Import application database") {
newSettingsScreenController.onClickImportDatabase() settingsScreenController.onClickImportDatabase()
} }
SettingsSection("DiM/BEm management") SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard) SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard)
@ -71,12 +64,6 @@ fun SettingsScreen(
} }
} }
fun buildFilePickLauncher(activity: ComponentActivity, onItemPicked: (Uri?) -> Unit): ActivityResultLauncher<Array<String>> {
return activity.registerForActivityResult(ActivityResultContracts.OpenDocument()) {
onItemPicked.invoke(it)
}
}
@Composable @Composable
fun SettingsEntry( fun SettingsEntry(
title: String, title: String,

View File

@ -1,71 +0,0 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.net.Uri
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.source.SecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsRepository
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
data class SettingsScreenController(val apkFilePickLauncher: ActivityResultLauncher<Array<String>>) {
class Factory(private val componentActivity: ComponentActivity, private val secretsImporter: SecretsImporter, private val secretsRepository: SecretsRepository) {
fun buildSettingScreenHandlers(): SettingsScreenController {
return SettingsScreenController(
apkFilePickLauncher = buildFilePickerActivityLauncher(this::importApk)
)
}
private fun buildFilePickerActivityLauncher(onResult : (Uri?) ->Unit): ActivityResultLauncher<Array<String>> {
return componentActivity.registerForActivityResult(ActivityResultContracts.OpenDocument()) {
onResult.invoke(it)
}
}
private fun importApk(uri: Uri?) {
if(uri == null) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "APK Import Cancelled", Toast.LENGTH_SHORT)
.show()
}
return
}
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
componentActivity.contentResolver.openInputStream(uri).use {
if(it == null) {
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Selected file is empty!",
Toast.LENGTH_SHORT
).show()
}
return@launch
}
var secrets: Secrets? = null
try {
secrets = secretsImporter.importSecrets(it)
} catch (e: Exception) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Secrets import failed. Please only select the official Vital Arena App 2.1.0 APK.", Toast.LENGTH_SHORT).show()
}
return@launch
}
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
secretsRepository.updateSecrets(secrets)
}.invokeOnCompletion {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Secrets successfully imported. Connections with devices are now possible.", Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}

View File

@ -10,24 +10,33 @@ import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsRepository
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
class NewSettingsScreenControllerImpl( class SettingsScreenControllerImpl(
private val context: ComponentActivity, private val context: ComponentActivity,
): NewSettingsScreenController { ): SettingsScreenController {
private val roomDbName = "internalDb"
private val filePickerLauncher: ActivityResultLauncher<String> private val filePickerLauncher: ActivityResultLauncher<String>
private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>> private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>>
private val filePickerApk: ActivityResultLauncher<Array<String>>
private val secretsImporter: SecretsImporter = ApkSecretsImporter()
private val application = context.applicationContext as VBHelper
private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository
init { init {
filePickerLauncher = context.registerForActivityResult( filePickerLauncher = context.registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream") ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
exportDatabase("internalDb", uri) exportDatabase(uri)
} else { } else {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT) Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT)
@ -40,13 +49,25 @@ class NewSettingsScreenControllerImpl(
ActivityResultContracts.OpenDocument() ActivityResultContracts.OpenDocument()
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
importDatabase("internalDb", uri) importDatabase(uri)
} else { } else {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show() Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show()
} }
} }
} }
filePickerApk = context.registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
if (uri != null) {
importApk(uri)
} else {
context.runOnUiThread {
Toast.makeText(context, "APK import cancelled", Toast.LENGTH_SHORT).show()
}
}
}
} }
override fun onClickOpenDirectory() { override fun onClickOpenDirectory() {
@ -57,10 +78,13 @@ class NewSettingsScreenControllerImpl(
filePickerOpenerLauncher.launch(arrayOf("application/octet-stream")) filePickerOpenerLauncher.launch(arrayOf("application/octet-stream"))
} }
private fun exportDatabase(roomDbName: String, destinationUri: Uri) { override fun onClickImportApk() {
filePickerApk.launch(arrayOf("*/*"))
}
private fun exportDatabase(destinationUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) { context.lifecycleScope.launch(Dispatchers.IO) {
try { try {
val application = context.applicationContext as VBHelper
val dbFile = File(context.getDatabasePath(roomDbName).absolutePath) val dbFile = File(context.getDatabasePath(roomDbName).absolutePath)
if (!dbFile.exists()) { if (!dbFile.exists()) {
throw IllegalStateException("Database file does not exist!") throw IllegalStateException("Database file does not exist!")
@ -88,11 +112,9 @@ class NewSettingsScreenControllerImpl(
} }
} }
private fun importDatabase(roomDbName: String, sourceUri: Uri) { private fun importDatabase(sourceUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) { context.lifecycleScope.launch(Dispatchers.IO) {
try { try {
var application = context.applicationContext as VBHelper
if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) { if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show()
@ -153,4 +175,37 @@ class NewSettingsScreenControllerImpl(
} }
outputStream.flush() outputStream.flush()
} }
private fun importApk(uri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
context.contentResolver.openInputStream(uri).use {
if(it == null) {
context.runOnUiThread {
Toast.makeText(
context,
"Selected file is empty!",
Toast.LENGTH_SHORT
).show()
}
return@launch
}
val secrets: Secrets?
try {
secrets = secretsImporter.importSecrets(it)
} catch (e: Exception) {
context.runOnUiThread {
Toast.makeText(context, "Secrets import failed. Please only select the official Vital Arena App 2.1.0 APK.", Toast.LENGTH_SHORT).show()
}
return@launch
}
context.lifecycleScope.launch(Dispatchers.IO) {
secretsRepository.updateSecrets(secrets)
}.invokeOnCompletion {
context.runOnUiThread {
Toast.makeText(context, "Secrets successfully imported. Connections with devices are now possible.", Toast.LENGTH_SHORT).show()
}
}
}
}
}
} }