From 42dd87f0c4b7940d35ee273e292d9d084b2c22ec Mon Sep 17 00:00:00 2001 From: Nacho Date: Sat, 18 Jan 2025 00:45:16 +0100 Subject: [PATCH 1/3] Data export working During export the database will be closed, otherwise the WAL will ruin the export. data will be saved as a .vbhelper file, then the app will be closed to avoid making any changes to the DB For import, we check if the extension is .vbhelper, otherwise we can corrupt the database, IMPORTANT. Next we delete everything related to RoomDB and we swap it with the new files. Finally the app will be closed to ensure RoomDB is running on the new DB. Finally, I still have to reintegrate the importApk functionality with the NewSettingsScreenController and get rid of the old one. --- .../github/nacabaro/vbhelper/MainActivity.kt | 23 ++- .../vbhelper/navigation/AppNavigation.kt | 15 +- .../NewSettingsScreenController.kt | 8 + .../NewSettingsScreenControllerImpl.kt | 152 ++++++++++++++++++ .../{ => settingsScreen}/SettingsScreen.kt | 12 +- .../SettingsScreenController.kt | 2 +- 6 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt rename app/src/main/java/com/github/nacabaro/vbhelper/screens/{ => settingsScreen}/SettingsScreen.kt (83%) rename app/src/main/java/com/github/nacabaro/vbhelper/screens/{ => settingsScreen}/SettingsScreenController.kt (98%) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt b/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt index 033f574..deae7b2 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt @@ -21,12 +21,12 @@ import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.domain.characters.Card import com.github.nacabaro.vbhelper.domain.Sprites import com.github.nacabaro.vbhelper.domain.characters.Character -import com.github.nacabaro.vbhelper.domain.characters.Dex import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl -import com.github.nacabaro.vbhelper.screens.SettingsScreenController +import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl +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.utils.DeviceType @@ -64,14 +64,15 @@ class MainActivity : ComponentActivity() { this::handleReceivedNfcCharacter, this, this::registerActivityLifecycleListener, - this::unregisterActivityLifecycleListener) - + this::unregisterActivityLifecycleListener + ) + val newSettingsScreenController = NewSettingsScreenControllerImpl(this) super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { VBHelperTheme { - MainApplication(settingsScreenController, scanScreenController) + MainApplication(settingsScreenController, scanScreenController, newSettingsScreenController) } } Log.i("MainActivity", "Activity onCreated") @@ -190,10 +191,18 @@ class MainActivity : ComponentActivity() { } @Composable - private fun MainApplication(settingsScreenController: SettingsScreenController, scanScreenController: ScanScreenControllerImpl) { + private fun MainApplication( + settingsScreenController: SettingsScreenController, + scanScreenController: ScanScreenControllerImpl, + newSettingsScreenController: NewSettingsScreenControllerImpl + ) { AppNavigation( - applicationNavigationHandlers = AppNavigationHandlers(settingsScreenController, scanScreenController), + applicationNavigationHandlers = AppNavigationHandlers( + settingsScreenController, + scanScreenController, + newSettingsScreenController + ), onClickImportCard = { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt b/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt index 6f5fb75..b0138b5 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt @@ -1,6 +1,5 @@ package com.github.nacabaro.vbhelper.navigation -import android.util.Log import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -15,12 +14,17 @@ import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen import com.github.nacabaro.vbhelper.screens.ItemsScreen import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl -import com.github.nacabaro.vbhelper.screens.SettingsScreen -import com.github.nacabaro.vbhelper.screens.SettingsScreenController +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.StorageScreen +import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl -data class AppNavigationHandlers(val settingsScreenController: SettingsScreenController, val scanScreenController: ScanScreenControllerImpl) +data class AppNavigationHandlers( + val settingsScreenController: SettingsScreenController, + val scanScreenController: ScanScreenControllerImpl, + val newSettingsScreenController: NewSettingsScreenControllerImpl +) @Composable fun AppNavigation( @@ -72,7 +76,8 @@ fun AppNavigation( SettingsScreen( navController = navController, settingsScreenController = applicationNavigationHandlers.settingsScreenController, - onClickImportCard = onClickImportCard + newSettingsScreenController = applicationNavigationHandlers.newSettingsScreenController, + onClickImportCard = onClickImportCard, ) } composable(NavigationItems.Viewer.route) { diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt new file mode 100644 index 0000000..612406b --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt @@ -0,0 +1,8 @@ +package com.github.nacabaro.vbhelper.screens.settingsScreen + +import android.net.Uri + +interface NewSettingsScreenController { + fun onClickOpenDirectory() + fun onClickImportDatabase() +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt new file mode 100644 index 0000000..53e8515 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt @@ -0,0 +1,152 @@ +package com.github.nacabaro.vbhelper.screens.settingsScreen + +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import android.net.Uri +import android.provider.OpenableColumns +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import com.github.nacabaro.vbhelper.di.VBHelper +import kotlinx.coroutines.Dispatchers +import java.io.File +import java.io.InputStream +import java.io.OutputStream + + +class NewSettingsScreenControllerImpl( + private val context: ComponentActivity, +): NewSettingsScreenController { + private val filePickerLauncher: ActivityResultLauncher + private val filePickerOpenerLauncher: ActivityResultLauncher> + + init { + filePickerLauncher = context.registerForActivityResult( + ActivityResultContracts.CreateDocument("application/octet-stream") + ) { uri -> + if (uri != null) { + exportDatabase("internalDb", uri) + } else { + context.runOnUiThread { + Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT) + .show() + } + } + } + + filePickerOpenerLauncher = context.registerForActivityResult( + ActivityResultContracts.OpenDocument() + ) { uri -> + if (uri != null) { + importDatabase("internalDb", uri) + } else { + context.runOnUiThread { + Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show() + } + } + } + } + + override fun onClickOpenDirectory() { + filePickerLauncher.launch("My application data.vbhelper") + } + + override fun onClickImportDatabase() { + filePickerOpenerLauncher.launch(arrayOf("application/octet-stream")) + } + + private fun exportDatabase(roomDbName: String, destinationUri: Uri) { + context.lifecycleScope.launch(Dispatchers.IO) { + try { + val application = context.applicationContext as VBHelper + val dbFile = File(context.getDatabasePath(roomDbName).absolutePath) + if (!dbFile.exists()) { + throw IllegalStateException("Database file does not exist!") + } + + application.container.db.close() + + context.contentResolver.openOutputStream(destinationUri)?.use { outputStream -> + dbFile.inputStream().use { inputStream -> + copyFile(inputStream, outputStream) + } + } ?: throw IllegalArgumentException("Unable to open destination Uri for writing") + + context.runOnUiThread { + Toast.makeText(context, "Database exported successfully!", Toast.LENGTH_SHORT).show() + Toast.makeText(context, "Closing application to avoid changes.", Toast.LENGTH_LONG).show() + context.finishAffinity() + } + } catch (e: Exception) { + Log.e("ScanScreenController", "Error exporting database $e") + context.runOnUiThread { + Toast.makeText(context, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show() + } + } + } + } + + private fun importDatabase(roomDbName: String, sourceUri: Uri) { + context.lifecycleScope.launch(Dispatchers.IO) { + try { + if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) { + context.runOnUiThread { + Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show() + } + return@launch + } + + val dbPath = context.getDatabasePath(roomDbName) + val shmFile = File(dbPath.parent, "$roomDbName-shm") + val walFile = File(dbPath.parent, "$roomDbName-wal") + + // Delete existing database files + if (dbPath.exists()) dbPath.delete() + if (shmFile.exists()) shmFile.delete() + if (walFile.exists()) walFile.delete() + + val dbFile = File(dbPath.absolutePath) + + context.contentResolver.openInputStream(sourceUri)?.use { inputStream -> + dbFile.outputStream().use { outputStream -> + copyFile(inputStream, outputStream) + } + } ?: throw IllegalArgumentException("Unable to open source Uri for reading") + + context.runOnUiThread { + Toast.makeText(context, "Database imported successfully!", Toast.LENGTH_SHORT).show() + Toast.makeText(context, "Reopen the app to finish import process!", Toast.LENGTH_LONG).show() + context.finishAffinity() + } + } catch (e: Exception) { + Log.e("ScanScreenController", "Error importing database $e") + context.runOnUiThread { + Toast.makeText(context, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show() + } + } + } + } + + private fun getFileNameFromUri(uri: Uri): String? { + var fileName: String? = null + val cursor = context.contentResolver.query(uri, null, null, null, null) + cursor?.use { + if (it.moveToFirst()) { + val nameIndex = it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME) + fileName = it.getString(nameIndex) + } + } + return fileName + } + + private fun copyFile(inputStream: InputStream, outputStream: OutputStream) { + val buffer = ByteArray(1024) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + outputStream.write(buffer, 0, bytesRead) + } + outputStream.flush() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt similarity index 83% rename from app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreen.kt rename to app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt index 104cded..42b6e8d 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt @@ -1,4 +1,4 @@ -package com.github.nacabaro.vbhelper.screens +package com.github.nacabaro.vbhelper.screens.settingsScreen import android.net.Uri import androidx.activity.ComponentActivity @@ -22,11 +22,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.github.nacabaro.vbhelper.components.TopBanner +import com.github.nacabaro.vbhelper.navigation.AppNavigation +import com.github.nacabaro.vbhelper.navigation.NavigationItems @Composable fun SettingsScreen( navController: NavController, settingsScreenController: SettingsScreenController, + newSettingsScreenController: NewSettingsScreenControllerImpl, onClickImportCard: () -> Unit ) { Scaffold ( @@ -51,6 +54,13 @@ fun SettingsScreen( SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") { settingsScreenController.apkFilePickLauncher.launch(arrayOf("*/*")) } + SettingsSection("Data management") + SettingsEntry(title = "Export data", description = "Export application database") { + newSettingsScreenController.onClickOpenDirectory() + } + SettingsEntry(title = "Import data", description = "Import application database") { + newSettingsScreenController.onClickImportDatabase() + } SettingsSection("DiM/BEm management") SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard) SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreenController.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt similarity index 98% rename from app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreenController.kt rename to app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt index 4afb3d1..6f1f41a 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreenController.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt @@ -1,4 +1,4 @@ -package com.github.nacabaro.vbhelper.screens +package com.github.nacabaro.vbhelper.screens.settingsScreen import android.net.Uri import android.widget.Toast From 542072c2387c770e4893a48f8ff7530f3b7ec266 Mon Sep 17 00:00:00 2001 From: Nacho Date: Sat, 18 Jan 2025 01:05:45 +0100 Subject: [PATCH 2/3] Forgot to close DB before importing process This will clear the database lock file before closing the application, ensuring that during the second startup the application does not see anything weird with the data. Not closing the database before import can lead to weird behavior. --- .../screens/settingsScreen/NewSettingsScreenControllerImpl.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt index 53e8515..dc03802 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt @@ -91,6 +91,8 @@ class NewSettingsScreenControllerImpl( private fun importDatabase(roomDbName: String, sourceUri: Uri) { context.lifecycleScope.launch(Dispatchers.IO) { try { + var application = context.applicationContext as VBHelper + if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) { context.runOnUiThread { Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show() @@ -98,6 +100,8 @@ class NewSettingsScreenControllerImpl( return@launch } + application.container.db.close() + val dbPath = context.getDatabasePath(roomDbName) val shmFile = File(dbPath.parent, "$roomDbName-shm") val walFile = File(dbPath.parent, "$roomDbName-wal") From 23e233227a92b9403ce57e0ec9a0f0d1f1b82cd8 Mon Sep 17 00:00:00 2001 From: Nacho Date: Sat, 18 Jan 2025 12:05:10 +0100 Subject: [PATCH 3/3] Finished refactoring the old SettingsScreenController.kt --- .../github/nacabaro/vbhelper/MainActivity.kt | 14 +--- .../vbhelper/navigation/AppNavigation.kt | 7 +- .../NewSettingsScreenController.kt | 8 -- .../screens/settingsScreen/SettingsScreen.kt | 21 +----- .../SettingsScreenController.kt | 71 ------------------ ...mpl.kt => SettingsScreenControllerImpl.kt} | 73 ++++++++++++++++--- 6 files changed, 74 insertions(+), 120 deletions(-) delete mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt delete mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt rename app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/{NewSettingsScreenControllerImpl.kt => SettingsScreenControllerImpl.kt} (68%) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt b/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt index deae7b2..569662b 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt @@ -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.navigation.AppNavigationHandlers import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl -import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl -import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenController -import com.github.nacabaro.vbhelper.source.ApkSecretsImporter +import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme import com.github.nacabaro.vbhelper.utils.DeviceType import kotlinx.coroutines.flow.MutableStateFlow @@ -57,8 +55,6 @@ class MainActivity : ComponentActivity() { registerFileActivityResult() val application = applicationContext as VBHelper - val settingsScreenController = SettingsScreenController.Factory(this, ApkSecretsImporter(), application.container.dataStoreSecretsRepository) - .buildSettingScreenHandlers() val scanScreenController = ScanScreenControllerImpl( application.container.dataStoreSecretsRepository.secretsFlow, this::handleReceivedNfcCharacter, @@ -66,13 +62,13 @@ class MainActivity : ComponentActivity() { this::registerActivityLifecycleListener, this::unregisterActivityLifecycleListener ) - val newSettingsScreenController = NewSettingsScreenControllerImpl(this) + val settingsScreenController = SettingsScreenControllerImpl(this) super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { VBHelperTheme { - MainApplication(settingsScreenController, scanScreenController, newSettingsScreenController) + MainApplication(scanScreenController, settingsScreenController) } } Log.i("MainActivity", "Activity onCreated") @@ -192,16 +188,14 @@ class MainActivity : ComponentActivity() { @Composable private fun MainApplication( - settingsScreenController: SettingsScreenController, scanScreenController: ScanScreenControllerImpl, - newSettingsScreenController: NewSettingsScreenControllerImpl + settingsScreenController: SettingsScreenControllerImpl ) { AppNavigation( applicationNavigationHandlers = AppNavigationHandlers( settingsScreenController, scanScreenController, - newSettingsScreenController ), onClickImportCard = { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt b/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt index b0138b5..5d417b1 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt @@ -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.ScanScreenControllerImpl 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.StorageScreen -import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl +import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl data class AppNavigationHandlers( - val settingsScreenController: SettingsScreenController, + val settingsScreenController: SettingsScreenControllerImpl, val scanScreenController: ScanScreenControllerImpl, - val newSettingsScreenController: NewSettingsScreenControllerImpl ) @Composable @@ -76,7 +74,6 @@ fun AppNavigation( SettingsScreen( navController = navController, settingsScreenController = applicationNavigationHandlers.settingsScreenController, - newSettingsScreenController = applicationNavigationHandlers.newSettingsScreenController, onClickImportCard = onClickImportCard, ) } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt deleted file mode 100644 index 612406b..0000000 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.nacabaro.vbhelper.screens.settingsScreen - -import android.net.Uri - -interface NewSettingsScreenController { - fun onClickOpenDirectory() - fun onClickImportDatabase() -} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt index 42b6e8d..6c0051f 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt @@ -1,9 +1,5 @@ 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.layout.Box import androidx.compose.foundation.layout.Column @@ -22,14 +18,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.github.nacabaro.vbhelper.components.TopBanner -import com.github.nacabaro.vbhelper.navigation.AppNavigation -import com.github.nacabaro.vbhelper.navigation.NavigationItems @Composable fun SettingsScreen( navController: NavController, - settingsScreenController: SettingsScreenController, - newSettingsScreenController: NewSettingsScreenControllerImpl, + settingsScreenController: SettingsScreenControllerImpl, onClickImportCard: () -> Unit ) { Scaffold ( @@ -52,14 +45,14 @@ fun SettingsScreen( ) { SettingsSection("NFC Communication") SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") { - settingsScreenController.apkFilePickLauncher.launch(arrayOf("*/*")) + settingsScreenController.onClickImportApk() } SettingsSection("Data management") SettingsEntry(title = "Export data", description = "Export application database") { - newSettingsScreenController.onClickOpenDirectory() + settingsScreenController.onClickOpenDirectory() } SettingsEntry(title = "Import data", description = "Import application database") { - newSettingsScreenController.onClickImportDatabase() + settingsScreenController.onClickImportDatabase() } SettingsSection("DiM/BEm management") 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> { - return activity.registerForActivityResult(ActivityResultContracts.OpenDocument()) { - onItemPicked.invoke(it) - } -} - @Composable fun SettingsEntry( title: String, diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt deleted file mode 100644 index 6f1f41a..0000000 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt +++ /dev/null @@ -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>) { - - 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> { - 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() - } - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenControllerImpl.kt similarity index 68% rename from app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt rename to app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenControllerImpl.kt index dc03802..8cae654 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenControllerImpl.kt @@ -10,24 +10,33 @@ import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts 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 java.io.File import java.io.InputStream import java.io.OutputStream -class NewSettingsScreenControllerImpl( +class SettingsScreenControllerImpl( private val context: ComponentActivity, -): NewSettingsScreenController { +): SettingsScreenController { + private val roomDbName = "internalDb" private val filePickerLauncher: ActivityResultLauncher private val filePickerOpenerLauncher: ActivityResultLauncher> + private val filePickerApk: ActivityResultLauncher> + private val secretsImporter: SecretsImporter = ApkSecretsImporter() + private val application = context.applicationContext as VBHelper + private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository init { filePickerLauncher = context.registerForActivityResult( ActivityResultContracts.CreateDocument("application/octet-stream") ) { uri -> if (uri != null) { - exportDatabase("internalDb", uri) + exportDatabase(uri) } else { context.runOnUiThread { Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT) @@ -40,13 +49,25 @@ class NewSettingsScreenControllerImpl( ActivityResultContracts.OpenDocument() ) { uri -> if (uri != null) { - importDatabase("internalDb", uri) + importDatabase(uri) } else { context.runOnUiThread { 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() { @@ -57,10 +78,13 @@ class NewSettingsScreenControllerImpl( 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) { try { - val application = context.applicationContext as VBHelper val dbFile = File(context.getDatabasePath(roomDbName).absolutePath) if (!dbFile.exists()) { 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) { try { - var application = context.applicationContext as VBHelper - if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) { context.runOnUiThread { Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show() @@ -153,4 +175,37 @@ class NewSettingsScreenControllerImpl( } 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() + } + } + } + } + } } \ No newline at end of file