Skip to content

Commit

Permalink
Support "attachment:" links.
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoruoruo committed Dec 23, 2020
1 parent 1b6b848 commit e667da9
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.orgzly.android.util

import android.os.Environment
import android.text.style.URLSpan
import com.orgzly.android.ui.views.style.AttachmentLinkSpan
import com.orgzly.android.ui.views.style.FileLinkSpan
import com.orgzly.android.ui.views.style.IdLinkSpan
import org.hamcrest.CoreMatchers.equalTo
Expand Down Expand Up @@ -54,6 +55,10 @@ class OrgFormatterLinkTest(private val param: Parameter) : OrgFormatterTest() {
Parameter("[[file:orgzly-tests/document.txt]]", "file:orgzly-tests/document.txt", listOf(Span(0, 30, FileLinkSpan::class.java))),
Parameter("[[file:orgzly-tests/document.txt][Document]]", "Document", listOf(Span(0, 8, FileLinkSpan::class.java))),

Parameter("attachment:orgzly-tests/document.txt", "attachment:orgzly-tests/document.txt", listOf(Span(0, 36, AttachmentLinkSpan::class.java))),
Parameter("[[attachment:orgzly-tests/document.txt]]", "attachment:orgzly-tests/document.txt", listOf(Span(0, 36, AttachmentLinkSpan::class.java))),
Parameter("[[attachment:orgzly-tests/document.txt][Document]]", "Document", listOf(Span(0, 8, AttachmentLinkSpan::class.java))),

Parameter("id:45DFE015-255E-4B86-B957-F7FD77364DCA", "id:45DFE015-255E-4B86-B957-F7FD77364DCA", listOf(Span(0, 39, IdLinkSpan::class.java))),
Parameter("[[id:45DFE015-255E-4B86-B957-F7FD77364DCA]]", "id:45DFE015-255E-4B86-B957-F7FD77364DCA", listOf(Span(0, 39, IdLinkSpan::class.java))),
Parameter("id:foo", "id:foo", listOf(Span(0, 6, IdLinkSpan::class.java))),
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/orgzly/android/ui/AttachmentSpanLoader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.orgzly.android.ui

import android.text.Spannable
import com.orgzly.android.ui.views.TextViewWithMarkup
import com.orgzly.android.ui.views.style.AttachmentLinkSpan
import com.orgzly.android.usecase.FindAttachmentPath
import com.orgzly.android.usecase.UseCaseRunner

object AttachmentSpanLoader {
/** Find all `attachment:` links and set up the prefix directory based on `ID` property. */
fun loadAttachmentPaths(noteId: Long, textWithMarkup: TextViewWithMarkup) {
SpanUtils.forEachSpan(textWithMarkup.text as Spannable, AttachmentLinkSpan::class.java) { span ->
val prefix = UseCaseRunner.run(FindAttachmentPath(noteId)).userData
if (prefix != null) {
span.prefix = prefix as String
}
}
}
}
12 changes: 8 additions & 4 deletions app/src/main/java/com/orgzly/android/ui/ImageLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.os.Environment
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
import android.text.Spannable
import android.text.style.ClickableSpan
import android.text.style.ImageSpan
import android.view.View
import com.orgzly.BuildConfig
Expand All @@ -24,6 +25,7 @@ import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.request.RequestOptions
import com.orgzly.R
import com.orgzly.android.ui.views.style.AttachmentLinkSpan
import com.orgzly.android.usecase.LinkFindTarget
import com.orgzly.android.usecase.UseCaseRunner
import com.orgzly.android.util.LogUtils
Expand All @@ -41,14 +43,16 @@ object ImageLoader {
&& AppPermissions.isGranted(context, AppPermissions.Usage.EXTERNAL_FILES_ACCESS)) {
// Load the associated image for each FileLinkSpan
SpanUtils.forEachSpan(textWithMarkup.text as Spannable, FileLinkSpan::class.java) { span ->
loadImage(textWithMarkup, span)
loadImage(textWithMarkup, span, span.path)
}
// Load the associated image for each AttachmentLinkSpan
SpanUtils.forEachSpan(textWithMarkup.text as Spannable, AttachmentLinkSpan::class.java) { span ->
loadImage(textWithMarkup, span, span.getPrefixedPath())
}
}
}

private fun loadImage(textWithMarkup: TextViewWithMarkup, span: FileLinkSpan) {
val path = span.path

private fun loadImage(textWithMarkup: TextViewWithMarkup, span: ClickableSpan, path: String) {
if (hasSupportedExtension(path)) {
val text = textWithMarkup.text as Spannable
// Get the current context
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/orgzly/android/ui/note/NoteFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ class NoteFragment : Fragment(), View.OnClickListener, TimestampDialogFragment.O

binding.bodyView.setRawText(binding.bodyEdit.text.toString())

AttachmentSpanLoader.loadAttachmentPaths(noteId, binding.bodyView)
ImageLoader.loadImages(binding.bodyView)

binding.bodyView.visibility = View.VISIBLE
Expand Down Expand Up @@ -463,6 +464,7 @@ class NoteFragment : Fragment(), View.OnClickListener, TimestampDialogFragment.O

binding.bodyView.setRawText(payload.content ?: "")

AttachmentSpanLoader.loadAttachmentPaths(noteId, binding.bodyView)
ImageLoader.loadImages(binding.bodyView)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.orgzly.android.App
import com.orgzly.android.db.entity.Note
import com.orgzly.android.db.entity.NoteView
import com.orgzly.android.prefs.AppPreferences
import com.orgzly.android.ui.AttachmentSpanLoader
import com.orgzly.android.ui.ImageLoader
import com.orgzly.android.ui.TimeType
import com.orgzly.android.ui.util.TitleGenerator
Expand Down Expand Up @@ -132,6 +133,7 @@ class NoteItemViewBinder(private val context: Context, private val inBook: Boole
}
}

AttachmentSpanLoader.loadAttachmentPaths(note.id, holder.binding.itemHeadContent)
ImageLoader.loadImages(holder.binding.itemHeadContent)

holder.binding.itemHeadContent.visibility = View.VISIBLE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.orgzly.android.ui.views.style

import android.os.Handler
import android.text.style.ClickableSpan
import android.view.View
import com.orgzly.android.ui.views.TextViewWithMarkup

/**
* This [ClickableSpan] corresponds to "attachment:" link. What comes after `:` is `path`. The full
* path also needs a prefix which is derived from `ID` property for example.
*/
class AttachmentLinkSpan(val path: String) : ClickableSpan() {
var prefix: String? = null

override fun onClick(widget: View) {
if (widget is TextViewWithMarkup && prefix != null) {
Handler().post { // Run after onClick to prevent Snackbar from closing immediately
widget.followLinkToFile(getPrefixedPath())
}
}
}

fun getPrefixedPath(): String {
return "$prefix/$path"
}
}
40 changes: 40 additions & 0 deletions app/src/main/java/com/orgzly/android/usecase/FindAttachmentPath.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.orgzly.android.usecase

import android.content.Context
import com.orgzly.android.App
import com.orgzly.android.data.DataRepository
import com.orgzly.android.db.entity.NoteProperty
import com.orgzly.android.util.AttachmentUtils

/**
* An [UseCase] that finds the attachment directory path with the given [noteId].
* Corresponds to `org-attach-dir`. Currently checks the ID property of the given node.
*
* Note that this finds the expected directory path, even if the directory doesn't exist.
*
* TODO: Also check DIR property.
* TODO: Also check inherited property, based on a preference as in `org-attach-use-inheritance`
*/
class FindAttachmentPath(val noteId: Long) : UseCase() {
val context: Context = App.getAppContext();

override fun run(dataRepository: DataRepository): UseCaseResult {
val noteProperties = dataRepository.getNoteProperties(noteId)
val idStr = getProperty(noteProperties, "ID")

val path = if (idStr == null) null else AttachmentUtils.getAttachDir(context, idStr)

return UseCaseResult(
userData = path
)
}

private fun getProperty(noteProperties: List<NoteProperty>, propertyName: String): String? {
for (property: NoteProperty in noteProperties) {
if (property.name == propertyName) {
return property.value
}
}
return null
}
}
5 changes: 4 additions & 1 deletion app/src/main/java/com/orgzly/android/util/OrgFormatter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object OrgFormatter {

private const val SYSTEM_LINK_SCHEMES = "https?|mailto|tel|voicemail|geo|sms|smsto|mms|mmsto"

private const val CUSTOM_LINK_SCHEMES = "id|file"
private const val CUSTOM_LINK_SCHEMES = "id|file|attachment"

// Supported link schemas for plain links
private const val LINK_SCHEMES = "(?:$SYSTEM_LINK_SCHEMES|$CUSTOM_LINK_SCHEMES)"
Expand Down Expand Up @@ -158,6 +158,9 @@ object OrgFormatter {
link.startsWith("id:") ->
IdLinkSpan(link.substring(3))

link.startsWith("attachment:") ->
AttachmentLinkSpan(link.substring(11))

link.startsWith("#") ->
CustomIdLinkSpan(link.substring(1))

Expand Down

0 comments on commit e667da9

Please sign in to comment.