diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index b1759b1..4264c2f 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -1,17 +1,17 @@ - + - + - - + + - - + + \ No newline at end of file diff --git a/app/src/main/java/org/wntr/mdeditor/MainActivity.kt b/app/src/main/java/org/wntr/mdeditor/MainActivity.kt index 7e3db00..8e878e3 100644 --- a/app/src/main/java/org/wntr/mdeditor/MainActivity.kt +++ b/app/src/main/java/org/wntr/mdeditor/MainActivity.kt @@ -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 lateinit var ghostSettings: ActivityResultLauncher lateinit var ghostMetaData: ActivityResultLauncher @@ -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 { - 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 = 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() {}) - 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() {}) + + 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() {}) + saveFile() webView.evaluateJavascript("easyMDE.codemirror.doc.getCursor();") { metaData.cursor=it Log.i(javaClass.simpleName,"Cursor: $it") diff --git a/app/src/main/res/drawable-hdpi/ic_menu_archive.png b/app/src/main/res/drawable-hdpi/ic_menu_archive.png new file mode 100644 index 0000000..e2d9bc1 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_archive.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_menu_archive.png b/app/src/main/res/drawable-ldpi/ic_menu_archive.png new file mode 100644 index 0000000..719ecd8 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_menu_archive.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_archive.png b/app/src/main/res/drawable-mdpi/ic_menu_archive.png new file mode 100644 index 0000000..49ac569 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu_archive.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_archive.png b/app/src/main/res/drawable-xhdpi/ic_menu_archive.png new file mode 100644 index 0000000..b1be9d5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_archive.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_menu_archive.png b/app/src/main/res/drawable-xxhdpi/ic_menu_archive.png new file mode 100644 index 0000000..a2d93b9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_menu_archive.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d0c3d78..5ecf787 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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"/> + app:layout_constraintTop_toTopOf="parent"/> \ No newline at end of file diff --git a/app/src/main/res/menu/ghost_menu.xml b/app/src/main/res/menu/ghost_menu.xml index 0977109..995a22d 100644 --- a/app/src/main/res/menu/ghost_menu.xml +++ b/app/src/main/res/menu/ghost_menu.xml @@ -1,19 +1,31 @@ + + + + app:showAsAction="always" /> + android:icon="@android:drawable/ic_menu_preferences" + app:showAsAction="always" /> + android:icon="@android:drawable/ic_menu_gallery" + app:showAsAction="always"/> + android:icon="@android:drawable/ic_menu_more" + app:showAsAction="always"/> diff --git a/app/src/main/res/raw/controller.js b/app/src/main/res/raw/controller.js index 0d4cd30..abe6319 100644 --- a/app/src/main/res/raw/controller.js +++ b/app/src/main/res/raw/controller.js @@ -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(); \ No newline at end of file