SendNoteActivity.kt 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package io.github.zadam.triliumsender
  2. import android.content.Intent
  3. import android.content.pm.PackageManager
  4. import android.os.Bundle
  5. import android.util.Log
  6. import android.view.View
  7. import android.widget.TextView
  8. import android.widget.Toast
  9. import androidx.appcompat.app.AppCompatActivity
  10. import io.github.zadam.triliumsender.services.HtmlConverter
  11. import io.github.zadam.triliumsender.services.TriliumSettings
  12. import io.github.zadam.triliumsender.services.Utils
  13. import kotlinx.android.synthetic.main.activity_send_note.*
  14. import kotlinx.coroutines.CoroutineScope
  15. import kotlinx.coroutines.Dispatchers
  16. import kotlinx.coroutines.launch
  17. import kotlinx.coroutines.withContext
  18. import okhttp3.OkHttpClient
  19. import okhttp3.Request
  20. import okhttp3.RequestBody.Companion.toRequestBody
  21. import org.json.JSONArray
  22. import org.json.JSONObject
  23. class SendNoteActivity : AppCompatActivity() {
  24. override fun onCreate(savedInstanceState: Bundle?) {
  25. val tag = "SendNoteOnCreateHandler"
  26. super.onCreate(savedInstanceState)
  27. setContentView(R.layout.activity_send_note)
  28. val settings = TriliumSettings(this)
  29. if (!settings.isConfigured()) {
  30. // We can't do anything useful if we're not configured. Abort out.
  31. Toast.makeText(this, getString(R.string.sender_not_configured_note), Toast.LENGTH_LONG).show()
  32. finish()
  33. return
  34. }
  35. if (settings.noteLabel.isNotEmpty()) {
  36. // We have a label to apply to this note! Indicate in the UI.
  37. labelList.text = getString(R.string.label_preview_template, settings.noteLabel)
  38. } else {
  39. // Hide the label text preview.
  40. labelList.visibility = View.GONE
  41. }
  42. // If we're a share-intent, pre-populate the note.
  43. when (intent?.action) {
  44. Intent.ACTION_SEND -> {
  45. if ("text/plain" == intent.type) {
  46. handleSendText(intent)
  47. } else {
  48. // We don't yet have text/html support.
  49. Log.e(tag, "Don't have a handler for share type ${intent.type}.")
  50. }
  51. }
  52. }
  53. sendNoteButton.setOnClickListener {
  54. // Kick off a coroutine to handle the actual send attempt without blocking the UI.
  55. // Since we want to be able to fire Toasts, we should use the Main (UI) scope.
  56. val uiScope = CoroutineScope(Dispatchers.Main)
  57. uiScope.launch {
  58. val success = sendNote(noteTitleEditText.text.toString(), noteContentEditText.text.toString(), settings.triliumAddress, settings.apiToken)
  59. if (success) {
  60. // Announce our success and end the activity.
  61. Toast.makeText(this@SendNoteActivity, getString(R.string.sending_note_complete), Toast.LENGTH_LONG).show()
  62. finish()
  63. } else {
  64. // Announce our failure.
  65. // Do not end the activity, so the user may decide to copy/store their note's contents without it disappearing.
  66. Toast.makeText(this@SendNoteActivity, getString(R.string.sending_note_failed), Toast.LENGTH_LONG).show()
  67. }
  68. }
  69. }
  70. }
  71. /**
  72. * If we're the target of a SEND intent, pre-fill the note's contents.
  73. *
  74. * @param intent, the Intent object, already verified to be of type text/plain.
  75. */
  76. private fun handleSendText(intent: Intent) {
  77. val tag = "SendNoteHandleSendText"
  78. // Many apps will suggest a "Text Subject", as in an email.
  79. val suggestedSubject = intent.getStringExtra((Intent.EXTRA_SUBJECT))
  80. if (suggestedSubject != null && suggestedSubject.isNotEmpty()) {
  81. // Use suggested subject.
  82. noteTitleEditText.setText(suggestedSubject, TextView.BufferType.EDITABLE)
  83. } else {
  84. // Try to craft a sane default title.
  85. var referrerName = "Android"
  86. // SDKs greater than 23 can access the referrer's package URI...
  87. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
  88. val referrer = this.referrer
  89. if (referrer != null && referrer.host != null) {
  90. // Which we can use to get an AppInfo handle...
  91. try {
  92. // Which we can use to get an app label!
  93. val referrerInfo = packageManager.getApplicationInfo(referrer.host.toString(), 0)
  94. val potentialReferrerName = referrerInfo.loadLabel(packageManager).toString()
  95. // Sanity check: is it an empty string?
  96. if (potentialReferrerName.isNotEmpty()) {
  97. referrerName = potentialReferrerName
  98. }
  99. } catch (e: PackageManager.NameNotFoundException) {
  100. // Oh well, we have the default name to fall back on.
  101. Log.w(tag, "Could not find package label for package name ${referrer.host}")
  102. }
  103. }
  104. }
  105. // Ultimately, set the note title.
  106. noteTitleEditText.setText(getString(R.string.share_note_title, referrerName), TextView.BufferType.EDITABLE)
  107. }
  108. // And populate the note body!
  109. noteContentEditText.setText(intent.getStringExtra(Intent.EXTRA_TEXT), TextView.BufferType.EDITABLE)
  110. }
  111. /**
  112. * Attempts to send a note to the Trilium server.
  113. *
  114. * Runs in the IO thread, to avoid blocking the UI thread.
  115. *
  116. * @param noteTitle, the title of the proposed note.
  117. * @param noteText, the body of the proposed note.
  118. * @param triliumAddress, the address of the Trilium server to send this note to.
  119. * @param apiToken, the security token for communicating with the Trilium server.
  120. *
  121. * @return Success or failure, as a boolean.
  122. */
  123. private suspend fun sendNote(noteTitle: String, noteText: String, triliumAddress: String, apiToken: String): Boolean {
  124. val tag = "SendNoteCoroutine"
  125. val settings = TriliumSettings(this)
  126. return withContext(Dispatchers.IO) {
  127. val client = OkHttpClient()
  128. val json = JSONObject()
  129. if (noteTitle.isEmpty()) {
  130. // API does not allow empty note titles, so use a default value if the user doesn't set one.
  131. json.put("title", "Note from Android")
  132. } else {
  133. json.put("title", noteTitle)
  134. }
  135. json.put("content", HtmlConverter().convertPlainTextToHtml(noteText))
  136. if (settings.noteLabel.isNotEmpty()) {
  137. // The api actually supports a list of key-value pairs, but for now we just write one label.
  138. val label = JSONObject()
  139. label.put("name", settings.noteLabel)
  140. label.put("value", "")
  141. val labelList = JSONArray()
  142. labelList.put(label)
  143. json.put("labels", labelList)
  144. }
  145. val body = json.toString().toRequestBody(Utils.JSON)
  146. val request = Request.Builder()
  147. .url("$triliumAddress/api/sender/note")
  148. .addHeader("Authorization", apiToken)
  149. .addHeader("X-Local-Date", Utils.localDateStr())
  150. .post(body)
  151. .build()
  152. return@withContext try {
  153. // In the Dispatchers.IO context, blocking http requests are allowed.
  154. @Suppress("BlockingMethodInNonBlockingContext")
  155. val response = client.newCall(request).execute()
  156. response.code == 200
  157. } catch (e: Exception) {
  158. Log.e(tag, getString(R.string.sending_failed), e)
  159. false
  160. }
  161. }
  162. }
  163. }