Unciv/android/src/com/unciv/app/AndroidSaverLoader.kt
SomeTroglodyte eb33b7d513
Custom save/load UI tweaks and blocking saving online MP games locally (#10358)
* Allow typing Y and N in file names in Linux custom save file dialog

* Fix default custom save location suggestion on X11

* Minor linting

* Allow clean reactivation of custom buttons after cancelling the file chooser

* User-Cancel support on Android and non-X11 desktop

* Block custom save for online multiplayer games

* Fix X11 custom save remembering location

* Fix X11 custom save "Save" button enabling, forbid potentially bad names

* Add overwrite confirmation to X11 custom save

* Redefine how local saving of online multiplayer games is blocked
2023-10-29 19:00:45 +02:00

123 lines
4.1 KiB
Kotlin

package com.unciv.app
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import com.unciv.logic.files.PlatformSaverLoader
import com.unciv.utils.Log
import java.io.InputStream
import java.io.OutputStream
class AndroidSaverLoader(private val activity: Activity) : PlatformSaverLoader {
private val contentResolver = activity.contentResolver
private val requests = HashMap<Int, Request>()
private var requestCode = 100
private class Request(
val onFileChosen: (Uri) -> Unit,
onError: (ex: Exception) -> Unit
) {
val onCancel: () -> Unit = { onError(PlatformSaverLoader.Cancelled()) }
}
override fun saveGame(
data: String,
suggestedLocation: String,
onSaved: (location: String) -> Unit,
onError: (ex: Exception) -> Unit
) {
// When we loaded, we returned a "content://" URI as file location.
val suggestedUri = Uri.parse(suggestedLocation)
val fileName = getFilename(suggestedUri, suggestedLocation)
val onFileChosen = { uri: Uri ->
var stream: OutputStream? = null
try {
stream = contentResolver.openOutputStream(uri, "rwt")
stream!!.writer().use { it.write(data) }
onSaved(uri.toString())
} catch (ex: Exception) {
onError(ex)
} finally {
stream?.close()
}
}
requests[requestCode] = Request(onFileChosen, onError)
openSaveFileChooser(fileName, suggestedUri, requestCode)
requestCode += 1
}
override fun loadGame(
onLoaded: (data: String, location: String) -> Unit,
onError: (ex: Exception) -> Unit
) {
val onFileChosen = {uri: Uri ->
var stream: InputStream? = null
try {
stream = contentResolver.openInputStream(uri)
val text = stream!!.reader().use { it.readText() }
onLoaded(text, uri.toString())
} catch (ex: Exception) {
onError(ex)
} finally {
stream?.close()
}
}
requests[requestCode] = Request(onFileChosen, onError)
openLoadFileChooser(requestCode)
requestCode += 1
}
fun onActivityResult(requestCode: Int, data: Intent?) {
val request = requests.remove(requestCode) ?: return
// data is null if the user back out of the activity without choosing a file
if (data == null) return request.onCancel()
val uri: Uri = data.data ?: return
request.onFileChosen(uri)
}
private fun openSaveFileChooser(fileName: String, uri: Uri, requestCode: Int) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.type = "application/json"
intent.putExtra(Intent.EXTRA_TITLE, fileName)
if (uri.scheme == "content")
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
activity.startActivityForResult(intent, requestCode)
}
private fun openLoadFileChooser(requestCode: Int) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
/* It is theoretically possible to use an initial URI here,
however, the only Android URIs we have are obtained from here, so, no dice */
activity.startActivityForResult(intent, requestCode)
}
private fun getFilename(uri: Uri, suggestedLocation: String): String {
if (uri.scheme != "content")
return suggestedLocation
try {
contentResolver.query(uri, null, null, null, null).use {
if (it?.moveToFirst() == true)
return it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
else
return ""
}
} catch(ex: Exception) {
Log.error("Failed to get filename from Uri", ex)
return suggestedLocation.split("2F").last() // I have no idea why but the content path ends with this before the filename
}
}
}