Merge pull request #12093 from t895/zip-intent
android: Use file picker intent for save exporter
This commit is contained in:
		| @@ -21,6 +21,8 @@ import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding | ||||
| import org.yuzu.yuzu_emu.model.HomeViewModel | ||||
| import org.yuzu.yuzu_emu.model.Installable | ||||
| import org.yuzu.yuzu_emu.ui.main.MainActivity | ||||
| import java.time.LocalDateTime | ||||
| import java.time.format.DateTimeFormatter | ||||
|  | ||||
| class InstallableFragment : Fragment() { | ||||
|     private var _binding: FragmentInstallablesBinding? = null | ||||
| @@ -78,7 +80,15 @@ class InstallableFragment : Fragment() { | ||||
|                     R.string.manage_save_data, | ||||
|                     R.string.import_export_saves_description, | ||||
|                     install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }, | ||||
|                     export = { mainActivity.exportSave() } | ||||
|                     export = { | ||||
|                         mainActivity.exportSaves.launch( | ||||
|                             "yuzu saves - ${ | ||||
|                             LocalDateTime.now().format( | ||||
|                                 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") | ||||
|                             ) | ||||
|                             }.zip" | ||||
|                         ) | ||||
|                     } | ||||
|                 ) | ||||
|             } else { | ||||
|                 Installable( | ||||
|   | ||||
| @@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.ui.main | ||||
| import android.content.Intent | ||||
| import android.net.Uri | ||||
| import android.os.Bundle | ||||
| import android.provider.DocumentsContract | ||||
| import android.view.View | ||||
| import android.view.ViewGroup.MarginLayoutParams | ||||
| import android.view.WindowManager | ||||
| @@ -20,7 +19,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen | ||||
| import androidx.core.view.ViewCompat | ||||
| import androidx.core.view.WindowCompat | ||||
| import androidx.core.view.WindowInsetsCompat | ||||
| import androidx.documentfile.provider.DocumentFile | ||||
| import androidx.lifecycle.Lifecycle | ||||
| import androidx.lifecycle.lifecycleScope | ||||
| import androidx.lifecycle.repeatOnLifecycle | ||||
| @@ -41,7 +39,6 @@ import org.yuzu.yuzu_emu.NativeLibrary | ||||
| import org.yuzu.yuzu_emu.R | ||||
| import org.yuzu.yuzu_emu.activities.EmulationActivity | ||||
| import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | ||||
| import org.yuzu.yuzu_emu.features.DocumentProvider | ||||
| import org.yuzu.yuzu_emu.features.settings.model.Settings | ||||
| import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | ||||
| import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | ||||
| @@ -53,9 +50,6 @@ import org.yuzu.yuzu_emu.model.TaskViewModel | ||||
| import org.yuzu.yuzu_emu.utils.* | ||||
| import java.io.BufferedInputStream | ||||
| import java.io.BufferedOutputStream | ||||
| import java.io.FileOutputStream | ||||
| import java.time.LocalDateTime | ||||
| import java.time.format.DateTimeFormatter | ||||
| import java.util.zip.ZipEntry | ||||
| import java.util.zip.ZipInputStream | ||||
|  | ||||
| @@ -73,7 +67,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | ||||
|  | ||||
|     // Get first subfolder in saves folder (should be the user folder) | ||||
|     val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: "" | ||||
|     private var lastZipCreated: File? = null | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         val splashScreen = installSplashScreen() | ||||
| @@ -656,75 +649,31 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | ||||
|             }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | ||||
|         } | ||||
|  | ||||
|     /** | ||||
|      * Zips the save files located in the given folder path and creates a new zip file with the current date and time. | ||||
|      * @return true if the zip file is successfully created, false otherwise. | ||||
|      */ | ||||
|     private fun zipSave(): Boolean { | ||||
|         try { | ||||
|             val tempFolder = File(getPublicFilesDir().canonicalPath, "temp") | ||||
|             tempFolder.mkdirs() | ||||
|             val saveFolder = File(savesFolderRoot) | ||||
|             val outputZipFile = File( | ||||
|                 tempFolder, | ||||
|                 "yuzu saves - ${ | ||||
|                 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) | ||||
|                 }.zip" | ||||
|             ) | ||||
|             outputZipFile.createNewFile() | ||||
|             val result = FileUtil.zipFromInternalStorage( | ||||
|                 saveFolder, | ||||
|                 savesFolderRoot, | ||||
|                 BufferedOutputStream(FileOutputStream(outputZipFile)) | ||||
|             ) | ||||
|             if (result == TaskState.Failed) { | ||||
|                 return false | ||||
|             } | ||||
|             lastZipCreated = outputZipFile | ||||
|         } catch (e: Exception) { | ||||
|             return false | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Exports the save file located in the given folder path by creating a zip file and sharing it via intent. | ||||
|      */ | ||||
|     fun exportSave() { | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|             val wasZipCreated = zipSave() | ||||
|             val lastZipFile = lastZipCreated | ||||
|             if (!wasZipCreated || lastZipFile == null) { | ||||
|                 withContext(Dispatchers.Main) { | ||||
|                     Toast.makeText( | ||||
|                         this@MainActivity, | ||||
|                         getString(R.string.export_save_failed), | ||||
|                         Toast.LENGTH_LONG | ||||
|                     ).show() | ||||
|                 } | ||||
|                 return@launch | ||||
|             } | ||||
|  | ||||
|             withContext(Dispatchers.Main) { | ||||
|                 val file = DocumentFile.fromSingleUri( | ||||
|                     this@MainActivity, | ||||
|                     DocumentsContract.buildDocumentUri( | ||||
|                         DocumentProvider.AUTHORITY, | ||||
|                         "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.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, | ||||
|                         getString(R.string.share_save_file) | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|     val exportSaves = registerForActivityResult( | ||||
|         ActivityResultContracts.CreateDocument("application/zip") | ||||
|     ) { result -> | ||||
|         if (result == null) { | ||||
|             return@registerForActivityResult | ||||
|         } | ||||
|  | ||||
|         IndeterminateProgressDialogFragment.newInstance( | ||||
|             this, | ||||
|             R.string.save_files_exporting, | ||||
|             false | ||||
|         ) { | ||||
|             val zipResult = FileUtil.zipFromInternalStorage( | ||||
|                 File(savesFolderRoot), | ||||
|                 savesFolderRoot, | ||||
|                 BufferedOutputStream(contentResolver.openOutputStream(result)) | ||||
|             ) | ||||
|             return@newInstance when (zipResult) { | ||||
|                 TaskState.Completed -> getString(R.string.export_success) | ||||
|                 TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) | ||||
|             } | ||||
|         }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | ||||
|     } | ||||
|  | ||||
|     private val startForResultExportSave = | ||||
|   | ||||
| @@ -91,6 +91,7 @@ | ||||
|     <string name="manage_save_data">Manage save data</string> | ||||
|     <string name="manage_save_data_description">Save data found. Please select an option below.</string> | ||||
|     <string name="import_export_saves_description">Import or export save files</string> | ||||
|     <string name="save_files_exporting">Exporting save files…</string> | ||||
|     <string name="save_file_imported_success">Imported successfully</string> | ||||
|     <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> | ||||
|     <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> | ||||
| @@ -256,6 +257,7 @@ | ||||
|     <string name="cancelling">Cancelling</string> | ||||
|     <string name="install">Install</string> | ||||
|     <string name="delete">Delete</string> | ||||
|     <string name="export_success">Exported successfully</string> | ||||
|  | ||||
|     <!-- GPU driver installation --> | ||||
|     <string name="select_gpu_driver">Select GPU driver</string> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 liamwhite
					liamwhite