revisit Actionbar and File IO

add autosave  timer
hauntED
yova 4 months ago
parent 4c807243eb
commit 182122babf

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_5_API_30_2.avd" />
<type value="SERIAL_NUMBER" />
<value value="10.0.0.10:5555" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2024-01-15T15:22:12.274341182Z" />
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2024-01-23T18:08:32.458084060Z" />
</component>
</project>

@ -48,6 +48,7 @@ import java.io.InputStreamReader
import java.lang.Thread.sleep
import java.net.URLDecoder
import java.time.Instant
import kotlin.concurrent.fixedRateTimer
class MainActivity : AppCompatActivity() {
@ -62,8 +63,8 @@ class MainActivity : AppCompatActivity() {
var metaData = mdMeta()
var mdToAppend: String = ""
var thisFileUri: Uri? = null
var truncate = false
lateinit var tempFile: File
var emptyFile = false
lateinit var pickMultipleVisualMedia: ActivityResultLauncher<PickVisualMediaRequest>
lateinit var ghostSettings: ActivityResultLauncher<Intent>
lateinit var ghostMetaData: ActivityResultLauncher<Intent>
@ -72,6 +73,7 @@ class MainActivity : AppCompatActivity() {
var ghostConnection = false
lateinit var credManager: CredentialManager
var intentScheme = "none"
var lastSaved = Instant.now().toEpochMilli()
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -120,7 +122,7 @@ class MainActivity : AppCompatActivity() {
@JavascriptInterface
fun triggerOpenFile() {
openFile(thisFileUri!!)
openFile()
}
@JavascriptInterface
@ -145,7 +147,7 @@ class MainActivity : AppCompatActivity() {
@JavascriptInterface
fun triggerDisplayName(): String {
return getDisplayName()
return getDisplayName(thisFileUri)
}
@JavascriptInterface
@ -172,6 +174,14 @@ class MainActivity : AppCompatActivity() {
Log.i(javaClass.simpleName,"delivering cursor: $cursor")
return cursor.toString()
}
@JavascriptInterface
fun toggleBar() {
this@MainActivity.runOnUiThread({
if (supportActionBar!!.isShowing) supportActionBar!!.hide()
else supportActionBar!!.show()
})
}
}
webView.addJavascriptInterface(jsi, "Android")
@ -200,6 +210,18 @@ class MainActivity : AppCompatActivity() {
ghostMetaData = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
saveFile()
}
fixedRateTimer("timer",true,0,5000){
this@MainActivity.runOnUiThread {
webView.evaluateJavascript("easyMDE.codemirror.doc.isClean();", {
if (it == "false" && thisFileUri != null) {
saveFile()
this@MainActivity.runOnUiThread {
webView.evaluateJavascript("easyMDE.codemirror.doc.markClean();", {})
}
}
})
}
}
}
override fun onResume() {
@ -305,6 +327,20 @@ class MainActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when (id) {
R.id.new_file -> {
truncate = true
selectFileForSaveAs()
}
R.id.open_file -> {
openFile()
}
R.id.save_file -> {
saveFile()
selectFileForSaveAs()
}
R.id.push_ghost -> {
webView.evaluateJavascript("getHtml();", {
val msg = URLDecoder.decode(it.removeSurrounding("\""))
@ -323,7 +359,7 @@ class MainActivity : AppCompatActivity() {
} else {
Log.i(javaClass.simpleName,"posting $msg")
updatePost(
title = metaData.get("title") ?: getDisplayName(),
title = metaData.get("title") ?: getDisplayName(thisFileUri),
author = credManager.username,
html = msg,
id = metaData.getId()!!
@ -514,7 +550,7 @@ class MainActivity : AppCompatActivity() {
}
fun sendPosting(html: String, author: String): retrofit2.Response<Any> {
val title = metaData.get("title") ?: "test" //getDisplayName()
val title = metaData.get("title") ?: "test"
val post = sendPost(title, updated_at = Instant.now().toString(), authors = listOf(author), html, feature_image = metaData.get("feature_image"))
val postings = sendPostList(listOf(post))
var response: retrofit2.Response<posts> = retrofit2.Response.error(
@ -690,33 +726,33 @@ class MainActivity : AppCompatActivity() {
// Create an image file name
val storageDir = File(cacheDir, "html")
storageDir.mkdir()
tempFile = File(storageDir.path + "/${getDisplayName().split(".")[0]}.html")
tempFile = File(storageDir.path + "/${getDisplayName(thisFileUri).split(".")[0]}.html")
if (tempFile.exists()) tempFile.delete()
tempFile.createNewFile()
}
fun openFile(uri: Uri) {
fun openFile() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
putExtra(DocumentsContract.EXTRA_INITIAL_URI, thisFileUri)
type = "text/*"
getDisplayName().apply { putExtra(Intent.EXTRA_TITLE, getDisplayName()) }
getDisplayName(thisFileUri).apply { putExtra(Intent.EXTRA_TITLE, getDisplayName(
thisFileUri)) }
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
startActivityForResult(intent, OPEN_FILE)
Log.i(javaClass.simpleName, "Buffer gelesen")
}
@SuppressLint("Range")
fun getDisplayName(): String {
fun getDisplayName(uri: Uri?): String {
// via: https://stackoverflow.com/questions/5568874/how-to-extract-the-file-name-from-uri-returned-from-intent-action-get-content
var result: String? = null;
if (thisFileUri == null) return "hauntED.md"
if (thisFileUri!!.getScheme().equals("content")) {
val cursor = getContentResolver().query(thisFileUri!!, null, null, null, null);
if (uri == null) return "hauntED.md"
if (uri!!.getScheme().equals("content")) {
val cursor = getContentResolver().query(uri!!, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
@ -726,7 +762,7 @@ class MainActivity : AppCompatActivity() {
}
}
if (result == null) {
result = thisFileUri!!.getPath();
result = uri!!.getPath();
val cut = result!!.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
@ -739,7 +775,7 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/*"
putExtra(Intent.EXTRA_TITLE, getDisplayName())
putExtra(Intent.EXTRA_TITLE, getDisplayName(thisFileUri))
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
@ -785,11 +821,9 @@ class MainActivity : AppCompatActivity() {
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
thisFileUri = uri
Log.d(javaClass.simpleName, "Saving to: ${getDisplayName(thisFileUri)}\n${getDisplayName(uri)}")
if (getDisplayName(thisFileUri) + " (1)" != getDisplayName(uri)) thisFileUri = uri
saveAs()
webView.evaluateJavascript("easyMDE.codemirror.focus()", ValueCallback<String>() {})
Log.i(javaClass.simpleName, "file newly written")
}
} else if (requestCode == OPEN_FILE && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
@ -797,8 +831,7 @@ class MainActivity : AppCompatActivity() {
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
thisFileUri = uri
saveMetaToSharedPrefs()
readFile(uri)
if (metaData.metaData.get("url") == null) {
deleteVisible = false
@ -811,12 +844,8 @@ class MainActivity : AppCompatActivity() {
}
}
fun getFileContents(): String {
return metaData.toString() + mdeValue
}
fun saveFile(): Boolean {
if (thisFileUri == null) return false
fun saveFile() {
if (thisFileUri == null) return
lateinit var textFile: ParcelFileDescriptor
try {
@ -835,7 +864,7 @@ class MainActivity : AppCompatActivity() {
).show()
}
)
return false
return
// TODO: implement workaround for this bug. See: https://stackoverflow.com/q/69248596
} catch (e: java.io.FileNotFoundException) {
Log.d(javaClass.simpleName, "File not found. Deleted while loaded?\n${e.stackTraceToString()}")
@ -849,7 +878,7 @@ class MainActivity : AppCompatActivity() {
}
)
selectFileForSaveAs()
return false
return
}
catch (e: Exception) {
@ -863,77 +892,80 @@ class MainActivity : AppCompatActivity() {
).show()
}
)
return false
}
/* This block is currently actually not needed as the change logic is made in js */
//TODO: Rework this for change check on network file provider
Log.d(
javaClass.simpleName,
"Size in cache: ${mdeValue.toByteArray().size} Size on disk: ${textFile.statSize}"
)
if (getFileContents().length.toLong() == textFile.statSize) {
Log.d(javaClass.simpleName, "No change on disk, file not saved.")
return false
return
}
try {
contentResolver.openFileDescriptor(thisFileUri!!, "wt")?.use {
FileOutputStream(it.fileDescriptor).use {
it.write(getFileContents().toByteArray())
this. runOnUiThread({
webView.evaluateJavascript("getValue();") {
mdeValue =
metaData.toString() + URLDecoder.decode(it.removeSurrounding("\""))
if (mdeValue.length.toLong() == textFile.statSize) {
Log.d(javaClass.simpleName, "No change on disk, file not saved.")
return@evaluateJavascript
}
contentResolver.openFileDescriptor(thisFileUri!!, "wt")?.use {
FileOutputStream(it.fileDescriptor).use {
it.write(mdeValue.toByteArray())
}
}
Log.d(javaClass.simpleName, "File saved: ${thisFileUri}")
Toast.makeText(
this,
"File saved.",
Toast.LENGTH_SHORT
).show()
textFile.close()
}
}
})
} catch (e: Exception) {
Toast.makeText(
this,
"Error during writing.\n$e",
Toast.LENGTH_LONG
).show()
return false
return
}
Log.d(javaClass.simpleName, "File saved: ${thisFileUri}")
this.runOnUiThread({
Toast.makeText(
this,
"File saved.",
Toast.LENGTH_SHORT
).show()
})
textFile.close()
return true
}
private fun saveAs(): Boolean {
lateinit var textFile: ParcelFileDescriptor
private fun saveAs() {
try {
textFile = contentResolver.openFileDescriptor(thisFileUri!!, "w")!!
textFile.checkError()
} catch (e: Exception) {
lateinit var textFile: ParcelFileDescriptor
try {
textFile = contentResolver.openFileDescriptor(thisFileUri!!, "w")!!
textFile.checkError()
} catch (e: Exception) {
Toast.makeText(
this,
"Problem with accessing file\n$e",
Toast.LENGTH_LONG
).show()
return
}
if (truncate) {
mdeValue = ""
truncate = false
}
FileOutputStream(textFile.fileDescriptor).use {
it.write(mdeValue.toByteArray())
}
Toast.makeText(
this,
"Problem with accessing file\n$e",
"File saved.",
Toast.LENGTH_LONG
).show()
return false
}
try {
FileOutputStream(textFile.fileDescriptor).use {
it.write(getFileContents().toByteArray())
}
Log.i(javaClass.simpleName, "file newly written")
webView.evaluateJavascript("onRead();", ValueCallback<String>() {})
textFile.close()
saveMetaToSharedPrefs()
} catch (e: Exception) {
Toast.makeText(
this,
"Error during writing.\n$e",
Toast.LENGTH_LONG
).show()
return false
}
Toast.makeText(
this,
"File saved.",
Toast.LENGTH_LONG
).show()
textFile.close()
saveMetaToSharedPrefs()
return true
}
@Throws(IOException::class)
@ -962,10 +994,6 @@ class MainActivity : AppCompatActivity() {
Toast.LENGTH_LONG
).show()
})
/*
openFile(uri)
return false
*/
}
try {
contentResolver.openInputStream(uri)?.use { inputStream ->
@ -1004,9 +1032,11 @@ class MainActivity : AppCompatActivity() {
})
return false
}
thisFileUri = uri
saveMetaToSharedPrefs()
this.runOnUiThread({
webView.evaluateJavascript("onRead();", {})
})
return true
}
@ -1035,7 +1065,7 @@ class MainActivity : AppCompatActivity() {
override fun onPause() {
super.onPause()
/*outState.putString("test", "onSaveInstanceState-String")*/
webView.evaluateJavascript("saveFile();", ValueCallback<String>() {})
saveFile()
webView.evaluateJavascript("easyMDE.codemirror.doc.getCursor();") {
metaData.cursor=it
Log.i(javaClass.simpleName,"Cursor: $it")

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -14,7 +14,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constrainedHeight="true"
tools:context=".MainActivity"/>
<androidx.appcompat.widget.Toolbar
@ -22,9 +22,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"/>
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,19 +1,31 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/new_file"
android:title="New File"
android:icon="@android:drawable/ic_menu_add"
app:showAsAction="always" />
<item android:id="@+id/open_file"
android:title="Open File"
android:icon="@drawable/ic_menu_archive"
app:showAsAction="always" />
<item android:id="@+id/save_file"
android:title="Save File"
android:icon="@android:drawable/ic_menu_save"
app:showAsAction="always" />
<item android:id="@+id/push_ghost"
android:title="Push to ghost"
android:icon="@android:drawable/ic_menu_upload"
app:showAsAction="never" />
app:showAsAction="always" />
<item android:id="@+id/settings"
android:title="Settings"
android:icon="@android:drawable/ic_menu_directions"
app:showAsAction="never" />
android:icon="@android:drawable/ic_menu_preferences"
app:showAsAction="always" />
<item android:id="@+id/image"
android:title="Image"
android:icon="@android:drawable/ic_menu_send"
app:showAsAction="never"/>
android:icon="@android:drawable/ic_menu_gallery"
app:showAsAction="always"/>
<item android:id="@+id/metadata"
android:title="Metadata"
android:icon="@android:drawable/ic_menu_set_as"
app:showAsAction="never"/>
android:icon="@android:drawable/ic_menu_more"
app:showAsAction="always"/>
</menu>

@ -22,14 +22,13 @@ function openFile() {
saveFile()
Android.triggerOpenFile()
onRead()
easyMDE.codemirror.doc.markClean()
}
function dispatchCut() {
console.log("dispatch cut")
easyMDE.codemirror.getTextArea().dispatchEvent(new Event("cut"))
}
function getValue() {
return easyMDE.value()
return encodeURIComponent(easyMDE.value())
}
function myPreview() {
saveFile()
@ -48,13 +47,6 @@ function shareText() {
Android.triggerShare(easyMDE.markdown(easyMDE.codemirror.doc.getValue()))
}
function shareGhostText() {
saveFile()
if (confirm('Are you sure you want to publish this?')) {
Android.triggerGhost(easyMDE.markdown(easyMDE.codemirror.doc.getValue()))
}
}
function getHtml() {
return encodeURIComponent(easyMDE.markdown(easyMDE.codemirror.doc.getValue()))
}
@ -67,7 +59,6 @@ function appendText() {
}
}
function pasteText() {
data = new DataTransfer()
data.setData("text/plain", Android.getMdToAppend())
@ -77,6 +68,10 @@ function pasteText() {
document.getElementsByClassName("CodeMirror-scroll")[0].dispatchEvent(event);
saveFile()
}
function toggleBar() {
Android.toggleBar()
easyMDE.codemirror.focus()
}
const easyMDE = new EasyMDE({
spellChecker: false,
@ -108,66 +103,23 @@ const easyMDE = new EasyMDE({
],
toolbar: [
{
name: "more",
className: "fa-solid fa-angles-down",
title: "more",
children: [
{
name: "saveAs",
action: saveAs,
className: "fa fa-star",
title: "saveAs"
},
{
name: "new",
action: blankBuffer,
className: "fa fa-file",
title: "New"
},
/* {
name: "refresh",
action: refresh,
className: "fa fa-refresh",
title: "Refresh"
},*/
{
name: "day",
action: () => easyMDE.codemirror.setOption("theme","solarized"),
className: "fa fa-sun",
title: "Day Theme"
},
{
name: "night",
action: () => easyMDE.codemirror.setOption("theme","3024-night"),
className: "fa fa-moon",
title: "Night Theme"
},
"guide"
]
},
{
name: "save",
action: saveFile,
className: "fa fa-save",
title: "Save"
name: "day",
action: () => easyMDE.codemirror.setOption("theme","solarized"),
className: "fa fa-sun",
title: "Day Theme"
},
{
name: "open",
action: openFile,
className: "fa-regular fa-folder-open",
title: "Open"
name: "night",
action: () => easyMDE.codemirror.setOption("theme","3024-night"),
className: "fa fa-moon",
title: "Night Theme"
},
"guide",
{
name: "share",
action: shareText,
className: "fa fa-share-nodes",
title: "Share"
},
{
name: "shareGhost",
action: shareGhostText,
className: "fa fa-ghost",
title: "Share Ghost"
}, "undo",
{
name: "preview",
@ -176,7 +128,13 @@ const easyMDE = new EasyMDE({
title: "Preview",
noDisable: true
},"redo",
"bold", "italic","link","code"
"bold", "italic","link","code",
{
name: "toggle",
action: toggleBar,
className: "fa fa-expand",
title: "Toggle Bar",
}
]
});
onRead();
onRead();
Loading…
Cancel
Save