diff --git a/app/src/main/java/org/stratoemu/strato/AppDialog.kt b/app/src/main/java/org/stratoemu/strato/AppDialog.kt index 49ec42c15..2fc38cefd 100644 --- a/app/src/main/java/org/stratoemu/strato/AppDialog.kt +++ b/app/src/main/java/org/stratoemu/strato/AppDialog.kt @@ -54,7 +54,7 @@ class AppDialog : BottomSheetDialogFragment() { * Used to manage save files */ private lateinit var documentPicker : ActivityResultLauncher> - private lateinit var startForResultExportSave : ActivityResultLauncher + private lateinit var startForResultExportSave : ActivityResultLauncher override fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) @@ -63,7 +63,7 @@ class AppDialog : BottomSheetDialogFragment() { binding.deleteSave.isEnabled = isSaveFileOfThisGame binding.exportSave.isEnabled = isSaveFileOfThisGame } - startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(requireActivity()) + startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(requireActivity(), item.titleId!!) } /** @@ -141,7 +141,7 @@ class AppDialog : BottomSheetDialogFragment() { binding.exportSave.isEnabled = saveExists binding.exportSave.setOnClickListener { - SaveManagementUtils.exportSave(requireContext(), startForResultExportSave, item.titleId, "${item.title} (v${binding.gameVersion.text}) [${item.titleId}]") + SaveManagementUtils.exportSave(startForResultExportSave, "${item.title} (v${binding.gameVersion.text}) [${item.titleId}]") } binding.gameTitleId.setOnLongClickListener { diff --git a/app/src/main/java/org/stratoemu/strato/preference/ImportExportSavesPreference.kt b/app/src/main/java/org/stratoemu/strato/preference/ImportExportSavesPreference.kt index 14dc456e2..79d8a4d8d 100644 --- a/app/src/main/java/org/stratoemu/strato/preference/ImportExportSavesPreference.kt +++ b/app/src/main/java/org/stratoemu/strato/preference/ImportExportSavesPreference.kt @@ -16,7 +16,7 @@ import org.stratoemu.strato.utils.SaveManagementUtils class ImportExportSavesPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) { private val documentPicker = SaveManagementUtils.registerDocumentPicker(context) - private val startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(context) + private val startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(context, "") override fun onClick() { val saveDataExists = SaveManagementUtils.savesFolderRootExists() @@ -29,7 +29,7 @@ class ImportExportSavesPreference @JvmOverloads constructor(context : Context, a if (saveDataExists) { dialog.setMessage(R.string.save_data_found) .setNegativeButton(R.string.export_save) { _, _ -> - SaveManagementUtils.exportSave(context, startForResultExportSave, "", context.getString(R.string.global_save_data_zip_name)) + SaveManagementUtils.exportSave(startForResultExportSave, context.getString(R.string.global_save_data_zip_name)) } } else { dialog.setMessage(R.string.save_data_not_found) diff --git a/app/src/main/java/org/stratoemu/strato/utils/SaveManagementUtils.kt b/app/src/main/java/org/stratoemu/strato/utils/SaveManagementUtils.kt index 8cef9304f..143992b73 100644 --- a/app/src/main/java/org/stratoemu/strato/utils/SaveManagementUtils.kt +++ b/app/src/main/java/org/stratoemu/strato/utils/SaveManagementUtils.kt @@ -33,11 +33,13 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream +import java.io.FileInputStream interface SaveManagementUtils { companion object { private val savesFolderRoot = "${StratoApplication.instance.getPublicFilesDir().canonicalPath}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001" + private var exportZipName: String = "export" fun registerDocumentPicker(context : Context) : ActivityResultLauncher> { return (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.OpenDocument()) { @@ -54,18 +56,25 @@ interface SaveManagementUtils { } } - fun registerStartForResultExportSave(context : Context) : ActivityResultLauncher { - return (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively() + fun registerStartForResultExportSave(context : Context, titleId: String) : ActivityResultLauncher { + File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively() + return (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { uri -> + uri?.let { + exportSave(context, it, titleId) + } } } - - fun registerStartForResultExportSave(fragmentAct : FragmentActivity) : ActivityResultLauncher { + + fun registerStartForResultExportSave(fragmentAct : FragmentActivity, titleId: String) : ActivityResultLauncher { val activity = fragmentAct as AppCompatActivity val activityResultRegistry = fragmentAct.activityResultRegistry - return activityResultRegistry.register("startForResultExportSaveKey", ActivityResultContracts.StartActivityForResult()) { - File(activity.getPublicFilesDir().canonicalPath, "temp").deleteRecursively() + File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively() + + return activityResultRegistry.register("saveExportFolderPickerKey", ActivityResultContracts.CreateDocument("application/zip")) { uri -> + uri?.let { + exportSave(fragmentAct as Context, it, titleId) + } } } @@ -103,7 +112,7 @@ interface SaveManagementUtils { tempFolder.mkdirs() val saveFolder = File(saveFolderPath) - val outputZipFile = File(tempFolder, "$outputZipName - ${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))}.zip") + val outputZipFile = File(tempFolder, "$outputZipName.zip") outputZipFile.createNewFile() ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos -> saveFolder.walkTopDown().forEach { file -> @@ -125,11 +134,11 @@ interface SaveManagementUtils { * @param titleId The title ID of the game to export the save file of. If empty, export all save files. * @param outputZipName The initial part of the name of the zip file to create. */ - fun exportSave(context : Context, startForResultExportSave : ActivityResultLauncher, titleId : String?, outputZipName : String) { + fun exportSave(context : Context, uri: Uri, titleId : String) { if (titleId == null) return CoroutineScope(Dispatchers.IO).launch { val saveFolderPath = "$savesFolderRoot/$titleId" - val zipCreated = zipSave(saveFolderPath, outputZipName) + val zipCreated = zipSave(saveFolderPath, exportZipName) if (zipCreated == null) { withContext(Dispatchers.Main) { Toast.makeText(context, R.string.error, Toast.LENGTH_LONG).show() @@ -137,14 +146,27 @@ interface SaveManagementUtils { return@launch } + try { + context.contentResolver.openOutputStream(uri)?.use { output -> + FileInputStream(zipCreated).use { it.copyTo(output) } + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show() + } + return@launch + } withContext(Dispatchers.Main) { - val file = DocumentFile.fromSingleUri(context, DocumentsContract.buildDocumentUri(DocumentsProvider.AUTHORITY, "${DocumentsProvider.ROOT_ID}/temp/${zipCreated.name}"))!! - val intent = Intent(Intent.ACTION_SEND).setDataAndType(file.uri, "application/zip").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION).putExtra(Intent.EXTRA_STREAM, file.uri) - startForResultExportSave.launch(Intent.createChooser(intent, context.getString(R.string.save_file_share))) + Toast.makeText(context, R.string.save_exported_successfully, Toast.LENGTH_LONG).show() } } } + fun exportSave(startForResultExportSave : ActivityResultLauncher, outputZipName : String) { + exportZipName = "$outputZipName - ${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))}" + startForResultExportSave.launch("$exportZipName.zip") + } + /** * Launches the document picker to import a save file. */ @@ -207,4 +229,4 @@ interface SaveManagementUtils { return true } } -} \ No newline at end of file +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c8b866924..556a18430 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,7 @@ Delete save Import Export + Successfully exported save file Searching for ROMs Invalid file Missing title key