mirror of
https://github.com/skyline-emu/skyline.git
synced 2025-01-28 02:37:55 +03:00
Initial implementation of save management
This commit is contained in:
parent
b7548e51cf
commit
a634bca2d2
@ -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))
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user