Initial implementation of save management

This commit is contained in:
PabloG02 2023-04-07 21:06:37 +02:00 committed by Billy Laws
parent b7548e51cf
commit a634bca2d2
2 changed files with 124 additions and 1 deletions

View File

@ -11,10 +11,13 @@ import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Bundle import android.os.Bundle
import android.provider.DocumentsContract
import android.view.KeyEvent import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -22,8 +25,14 @@ import emu.skyline.data.AppItem
import emu.skyline.data.AppItemTag import emu.skyline.data.AppItemTag
import emu.skyline.databinding.AppDialogBinding import emu.skyline.databinding.AppDialogBinding
import emu.skyline.loader.LoaderResult import emu.skyline.loader.LoaderResult
import emu.skyline.provider.DocumentsProvider
import emu.skyline.settings.SettingsActivity import emu.skyline.settings.SettingsActivity
import emu.skyline.utils.serializable import emu.skyline.utils.serializable
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
/** /**
* This dialog is used to show extra game metadata and provide extra options such as pinning the game to the home screen * This dialog is used to show extra game metadata and provide extra options such as pinning the game to the home screen
@ -47,6 +56,23 @@ class AppDialog : BottomSheetDialogFragment() {
private val item by lazy { requireArguments().serializable<AppItem>(AppItemTag)!! } private val item by lazy { requireArguments().serializable<AppItem>(AppItemTag)!! }
private val savesFolderRoot by lazy { "${requireContext().getPublicFilesDir().canonicalPath}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001/" }
private val documentPicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri ->
if (uri.toString().contains(item.titleId as CharSequence)) {
val saveFolder = File(savesFolderRoot + item.titleId)
val inputZip = requireContext().contentResolver.openInputStream(uri)
if (inputZip != null) {
emu.skyline.utils.ZipUtils.Companion.unzip(inputZip, saveFolder)
binding.deleteSave.isEnabled = true
binding.exportSave.isEnabled = true
}
} else {
Snackbar.make(binding.root, "Zip file must have as name the TitleID of the game", Snackbar.LENGTH_LONG).show()
}
}
}
/** /**
* This inflates the layout of the dialog after initial view creation * This inflates the layout of the dialog after initial view creation
*/ */
@ -101,6 +127,48 @@ class AppDialog : BottomSheetDialogFragment() {
shortcutManager.requestPinShortcut(info.build(), null) shortcutManager.requestPinShortcut(info.build(), null)
} }
val saveFolderPath = savesFolderRoot + item.titleId
val saveExists = File(saveFolderPath).exists()
binding.deleteSave.isEnabled = saveExists
binding.deleteSave.setOnClickListener {
File(saveFolderPath).deleteRecursively()
binding.deleteSave.isEnabled = false
binding.exportSave.isEnabled = false
}
binding.importSave.setOnClickListener {
documentPicker.launch(arrayOf("application/zip"))
}
binding.exportSave.isEnabled = saveExists
binding.exportSave.setOnClickListener {
val saveFolder = File(saveFolderPath)
//val outputZipFile = File.createTempFile("out", ".zip")
val outputZipFile = File("$saveFolderPath.zip")
if (outputZipFile.exists()) outputZipFile.delete()
outputZipFile.createNewFile()
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
saveFolder.walkTopDown().forEach { file ->
val zipFileName = file.absolutePath.removePrefix(saveFolder.absolutePath).removePrefix("/")
if (zipFileName == "") return@forEach
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
zos.putNextEntry(entry)
if (file.isFile) {
file.inputStream().use { fis -> fis.copyTo(zos) }
}
}
}
val file = DocumentFile.fromSingleUri(requireContext(), DocumentsContract.buildDocumentUri(DocumentsProvider.AUTHORITY, "${DocumentsProvider.ROOT_ID}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001/${item.titleId}.zip"))!!
val intent = Intent(Intent.ACTION_SEND)
.setDataAndType(file.uri, "application/zip")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, file.uri)
startActivity(Intent.createChooser(intent, "Share save file"))
//outputZipFile.deleteOnExit()
}
binding.gameTitleId.setOnLongClickListener { binding.gameTitleId.setOnLongClickListener {
val clipboard = requireActivity().getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager val clipboard = requireActivity().getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
clipboard.setPrimaryClip(android.content.ClipData.newPlainText("Title ID", item.titleId)) clipboard.setPrimaryClip(android.content.ClipData.newPlainText("Title ID", item.titleId))

View File

@ -84,12 +84,12 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:id="@+id/FlexboxLayout1"
style="@style/ThemeOverlay.Material3.Button.IconButton.Filled.Tonal" style="@style/ThemeOverlay.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:flexWrap="nowrap" app:flexWrap="nowrap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/game_icon" app:layout_constraintTop_toBottomOf="@id/game_icon"
@ -129,5 +129,60 @@
app:iconGravity="textStart" /> app:iconGravity="textStart" />
</com.google.android.flexbox.FlexboxLayout> </com.google.android.flexbox.FlexboxLayout>
<com.google.android.flexbox.FlexboxLayout
style="@style/ThemeOverlay.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:alignItems="center"
app:flexWrap="nowrap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/FlexboxLayout1"
app:layout_constraintVertical_bias="1">
<TextView
android:id="@+id/textView"
style="?attr/textAppearanceBodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save management"
android:textAlignment="center"
app:layout_maxWidth="140dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/delete_save"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/pin"
app:icon="@drawable/ic_delete"
app:iconGravity="textStart" />
<com.google.android.material.button.MaterialButton
android:id="@+id/import_save"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/pin"
app:icon="@drawable/ic_add"
app:iconGravity="textStart" />
<com.google.android.material.button.MaterialButton
android:id="@+id/export_save"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/pin"
app:icon="@drawable/ic_share"
app:iconGravity="textStart" />
</com.google.android.flexbox.FlexboxLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>