Add markdown support

pull/1459/head
Alex Baker 4 years ago
parent 9960e9a0a9
commit c4e98052de

@ -185,6 +185,12 @@ dependencies {
implementation("io.reactivex.rxjava2:rxandroid:2.1.1") implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
implementation("androidx.paging:paging-runtime:2.1.2") implementation("androidx.paging:paging-runtime:2.1.2")
implementation("io.noties.markwon:core:${Versions.markwon}") implementation("io.noties.markwon:core:${Versions.markwon}")
implementation("io.noties.markwon:editor:${Versions.markwon}")
implementation("io.noties.markwon:ext-tasklist:${Versions.markwon}")
implementation("io.noties.markwon:ext-strikethrough:${Versions.markwon}")
implementation("io.noties.markwon:ext-tables:${Versions.markwon}")
implementation("io.noties.markwon:linkify:${Versions.markwon}")
implementation("me.saket:better-link-movement-method:2.2.0")
debugImplementation("com.facebook.flipper:flipper:${Versions.flipper}") debugImplementation("com.facebook.flipper:flipper:${Versions.flipper}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${Versions.flipper}") debugImplementation("com.facebook.flipper:flipper-network-plugin:${Versions.flipper}")

@ -916,3 +916,47 @@
license: The Apache Software License, Version 2.0 license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/activity#1.3.0-alpha04 url: https://developer.android.com/jetpack/androidx/releases/activity#1.3.0-alpha04
- artifact: io.noties.markwon:editor:+
name: editor
copyrightHolder: Dimitry Ivanov
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/noties/Markwon
- artifact: io.noties.markwon:ext-tasklist:+
name: ext-tasklist
copyrightHolder: Dimitry Ivanov
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/noties/Markwon
- artifact: io.noties.markwon:ext-strikethrough:+
name: ext-strikethrough
copyrightHolder: Dimitry Ivanov
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/noties/Markwon
- artifact: com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:+
name: commonmark-ext-gfm-strikethrough
copyrightHolder: Atlassian and others
license: BSD 2-Clause
- artifact: me.saket:better-link-movement-method:+
name: better-link-movement-method
copyrightHolder: Saket Narayan
license: Apache License v2
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0
url: https://github.com/Saketme/BetterLinkMovementMethod
- artifact: io.noties.markwon:linkify:+
name: linkify
copyrightHolder: Dimitry Ivanov
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/noties/Markwon
- artifact: io.noties.markwon:ext-tables:+
name: ext-tables
copyrightHolder: Dimitry Ivanov
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/noties/Markwon
- artifact: com.atlassian.commonmark:commonmark-ext-gfm-tables:+
name: commonmark-ext-gfm-tables
copyrightHolder: Atlassian and others
license: BSD 2-Clause

@ -2175,6 +2175,114 @@
"normalizedLicense": "apache2", "normalizedLicense": "apache2",
"url": "https://developer.android.com/jetpack/androidx/releases/activity#1.3.0-alpha04", "url": "https://developer.android.com/jetpack/androidx/releases/activity#1.3.0-alpha04",
"libraryName": "activity-compose" "libraryName": "activity-compose"
},
{
"artifactId": {
"name": "editor",
"group": "io.noties.markwon",
"version": "+"
},
"copyrightHolder": "Dimitry Ivanov",
"copyrightStatement": "Copyright © Dimitry Ivanov. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"url": "https://github.com/noties/Markwon",
"libraryName": "editor"
},
{
"artifactId": {
"name": "ext-tasklist",
"group": "io.noties.markwon",
"version": "+"
},
"copyrightHolder": "Dimitry Ivanov",
"copyrightStatement": "Copyright © Dimitry Ivanov. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"url": "https://github.com/noties/Markwon",
"libraryName": "ext-tasklist"
},
{
"artifactId": {
"name": "ext-strikethrough",
"group": "io.noties.markwon",
"version": "+"
},
"copyrightHolder": "Dimitry Ivanov",
"copyrightStatement": "Copyright © Dimitry Ivanov. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"url": "https://github.com/noties/Markwon",
"libraryName": "ext-strikethrough"
},
{
"artifactId": {
"name": "commonmark-ext-gfm-strikethrough",
"group": "com.atlassian.commonmark",
"version": "+"
},
"copyrightHolder": "Atlassian and others",
"copyrightStatement": "Copyright © Atlassian and others. All rights reserved.",
"license": "BSD 2-Clause",
"normalizedLicense": "bsd_2_clauses",
"libraryName": "commonmark-ext-gfm-strikethrough"
},
{
"artifactId": {
"name": "better-link-movement-method",
"group": "me.saket",
"version": "+"
},
"copyrightHolder": "Saket Narayan",
"copyrightStatement": "Copyright © Saket Narayan. All rights reserved.",
"license": "Apache License v2",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"normalizedLicense": "apache2",
"url": "https://github.com/Saketme/BetterLinkMovementMethod",
"libraryName": "better-link-movement-method"
},
{
"artifactId": {
"name": "linkify",
"group": "io.noties.markwon",
"version": "+"
},
"copyrightHolder": "Dimitry Ivanov",
"copyrightStatement": "Copyright © Dimitry Ivanov. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"url": "https://github.com/noties/Markwon",
"libraryName": "linkify"
},
{
"artifactId": {
"name": "ext-tables",
"group": "io.noties.markwon",
"version": "+"
},
"copyrightHolder": "Dimitry Ivanov",
"copyrightStatement": "Copyright © Dimitry Ivanov. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"url": "https://github.com/noties/Markwon",
"libraryName": "ext-tables"
},
{
"artifactId": {
"name": "commonmark-ext-gfm-tables",
"group": "com.atlassian.commonmark",
"version": "+"
},
"copyrightHolder": "Atlassian and others",
"copyrightStatement": "Copyright © Atlassian and others. All rights reserved.",
"license": "BSD 2-Clause",
"normalizedLicense": "bsd_2_clauses",
"libraryName": "commonmark-ext-gfm-tables"
} }
] ]
} }

@ -37,6 +37,8 @@ import com.todoroo.astrid.repeats.RepeatControlSet
import com.todoroo.astrid.timers.TimerPlugin import com.todoroo.astrid.timers.TimerPlugin
import com.todoroo.astrid.ui.StartDateControlSet import com.todoroo.astrid.ui.StartDateControlSet
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.noties.markwon.editor.MarkwonEditor
import io.noties.markwon.editor.MarkwonEditorTextWatcher
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -49,6 +51,7 @@ import org.tasks.databinding.FragmentTaskEditBinding
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.Linkify import org.tasks.dialogs.Linkify
import org.tasks.extensions.Context.markwon
import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.openUri
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.fragments.TaskEditControlSetFragmentManager import org.tasks.fragments.TaskEditControlSetFragmentManager
@ -58,6 +61,7 @@ import org.tasks.themes.ThemeColor
import org.tasks.ui.SubtaskControlSet import org.tasks.ui.SubtaskControlSet
import org.tasks.ui.TaskEditControlFragment import org.tasks.ui.TaskEditControlFragment
import org.tasks.ui.TaskEditViewModel import org.tasks.ui.TaskEditViewModel
import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
@ -138,14 +142,28 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
toolbar.setOnMenuItemClickListener(this) toolbar.setOnMenuItemClickListener(this)
themeColor.apply(binding.collapsingtoolbarlayout, toolbar) themeColor.apply(binding.collapsingtoolbarlayout, toolbar)
val title = binding.title val title = binding.title
val markdown = if (preferences.markdown) {
MarkwonEditorTextWatcher.withPreRender(
MarkwonEditor.create(requireContext().markwon),
Executors.newCachedThreadPool(),
title
)
} else {
null
}
title.addTextChangedListener(
onTextChanged = { _, _, _, _ ->
editViewModel.title = title.text.toString().trim { it <= ' ' }
},
afterTextChanged = {
markdown?.afterTextChanged(it)
}
)
title.setText(model.title) title.setText(model.title)
title.setHorizontallyScrolling(false) title.setHorizontallyScrolling(false)
title.setTextColor(themeColor.colorOnPrimary) title.setTextColor(themeColor.colorOnPrimary)
title.setHintTextColor(themeColor.hintOnPrimary) title.setHintTextColor(themeColor.hintOnPrimary)
title.maxLines = 5 title.maxLines = 5
title.addTextChangedListener {
editViewModel.title = title.text.toString().trim { it <= ' ' }
}
if (model.isNew || preferences.getBoolean(R.string.p_hide_check_button, false)) { if (model.isNew || preferences.getBoolean(R.string.p_hide_check_button, false)) {
binding.fab.visibility = View.INVISIBLE binding.fab.visibility = View.INVISIBLE
} else if (editViewModel.completed!!) { } else if (editViewModel.completed!!) {

@ -87,7 +87,7 @@ class CommentsController @Inject constructor(
// name // name
val nameView = view.findViewById<TextView>(R.id.title) val nameView = view.findViewById<TextView>(R.id.title)
nameView.text = Html.fromHtml(item.message) nameView.text = Html.fromHtml(item.message)
Linkify.safeLinkify(nameView, android.text.util.Linkify.ALL) Linkify.safeLinkify(nameView)
// date // date
val date = view.findViewById<TextView>(R.id.date) val date = view.findViewById<TextView>(R.id.date)

@ -4,16 +4,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.style.URLSpan
import android.text.util.Linkify import android.text.util.Linkify
import android.view.View import android.view.MotionEvent
import android.widget.TextView import android.widget.TextView
import androidx.core.text.util.LinkifyCompat import androidx.core.text.util.LinkifyCompat
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import me.saket.bettermovementmethod.BetterLinkMovementMethod
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import timber.log.Timber import timber.log.Timber
import java.io.UnsupportedEncodingException import java.io.UnsupportedEncodingException
import java.net.URLDecoder import java.net.URLDecoder
@ -23,79 +20,81 @@ class Linkify @Inject constructor(
@ActivityContext private val context: Context, @ActivityContext private val context: Context,
private val dialogBuilder: DialogBuilder private val dialogBuilder: DialogBuilder
) { ) {
fun linkify(textView: TextView, onClick: Runnable = Runnable {}) { fun linkify(tv: TextView) {
if (textView.length() == 0) { if (tv.length() == 0) {
return return
} }
safeLinkify(textView, Linkify.ALL) safeLinkify(tv)
textView.setOnClickListener { setMovementMethod(tv)
if (textView.selectionStart == -1 && textView.selectionEnd == -1) { }
onClick.run()
} fun setMovementMethod(tv: TextView, handle: (() -> Unit)? = null) {
val blmm = BetterLinkMovementMethod.newInstance().apply {
setOnLinkClickListener { _, url -> onClick(url, handle ?: {}) }
setOnLinkLongClickListener { _, _ -> tv.performLongClick() }
} }
val text = textView.text tv.movementMethod = blmm
if (text is SpannableStringBuilder || text is SpannableString) { tv.setOnTouchListener { _, event ->
val spannable = text as Spannable val text = tv.text
val spans = spannable.getSpans(0, text.length, URLSpan::class.java) when {
for (span in spans) { text is Spannable && blmm.onTouchEvent(tv, text, event) -> true
val start = spannable.getSpanStart(span) event.action == MotionEvent.ACTION_UP -> {
val end = spannable.getSpanEnd(span) handle?.invoke()
spannable.removeSpan(span) false
spannable.setSpan(ClickHandlingURLSpan(span.url, onClick), start, end, 0) }
else -> false
} }
} }
} }
private inner class ClickHandlingURLSpan constructor( fun onClick(url: String, onEdit: () -> Unit): Boolean {
url: String?, var title: String?
private val onEdit: Runnable val edit = context.getString(R.string.TAd_actionEditTask)
) : URLSpan(url) { val action: String
override fun onClick(widget: View) { val uri = Uri.parse(url).let {
var title: String? if (it.scheme.isNullOrBlank()) {
val edit = context.getString(R.string.TAd_actionEditTask) Uri.parse("https://$url")
val action: String } else {
val uri = Uri.parse(url) it
var scheme = uri.scheme
if (isNullOrEmpty(scheme)) {
scheme = ""
} }
when (scheme) { }
"tel" -> { when (uri.scheme) {
title = uri.encodedSchemeSpecificPart "tel" -> {
action = context.getString(R.string.action_call) title = uri.encodedSchemeSpecificPart
} action = context.getString(R.string.action_call)
"mailto" -> {
title = uri.encodedSchemeSpecificPart
action = context.getString(R.string.action_open)
}
"geo" -> {
title = uri.encodedQuery!!.replaceFirst("q=".toRegex(), "")
try {
title = URLDecoder.decode(title, "utf-8")
} catch (ignored: UnsupportedEncodingException) {
}
action = context.getString(R.string.action_open)
}
else -> {
title = url
action = context.getString(R.string.action_open)
}
} }
dialogBuilder "mailto" -> {
.newDialog(title) title = uri.encodedSchemeSpecificPart
.setItems(listOf(action, edit)) { _, selected -> action = context.getString(R.string.action_open)
if (selected == 0) { }
context.startActivity(Intent(Intent.ACTION_VIEW, uri)) "geo" -> {
} else { title = uri.encodedQuery!!.replaceFirst("q=".toRegex(), "")
onEdit.run() try {
} title = URLDecoder.decode(title, "utf-8")
} catch (ignored: UnsupportedEncodingException) {
} }
.show() action = context.getString(R.string.action_open)
}
else -> {
title = url
action = context.getString(R.string.action_open)
}
} }
dialogBuilder
.newDialog(title)
.setItems(listOf(action, edit)) { _, selected ->
if (selected == 0) {
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
} else {
onEdit()
}
}
.show()
return true
} }
companion object { companion object {
fun safeLinkify(textView: TextView?, mask: Int) { fun safeLinkify(textView: TextView?, mask: Int = Linkify.ALL) {
try { try {
LinkifyCompat.addLinks(textView!!, mask) LinkifyCompat.addLinks(textView!!, mask)
} catch (e: UnsatisfiedLinkError) { } catch (e: UnsatisfiedLinkError) {

@ -8,13 +8,13 @@ import android.text.method.LinkMovementMethod
import android.view.View import android.view.View
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.noties.markwon.Markwon
import org.tasks.R import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.billing.PurchaseActivity import org.tasks.billing.PurchaseActivity
import org.tasks.databinding.DialogWhatsNewBinding import org.tasks.databinding.DialogWhatsNewBinding
import org.tasks.extensions.Context.markwon
import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.openUri
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import java.io.BufferedReader import java.io.BufferedReader
@ -37,9 +37,8 @@ class WhatsNewDialog : DialogFragment() {
val textStream = requireContext().assets.open("CHANGELOG.md") val textStream = requireContext().assets.open("CHANGELOG.md")
val text = BufferedReader(textStream.reader()).readText() val text = BufferedReader(textStream.reader()).readText()
val markwon = Markwon.builder(requireContext()).build()
binding.changelog.movementMethod = LinkMovementMethod.getInstance() binding.changelog.movementMethod = LinkMovementMethod.getInstance()
binding.changelog.text = markwon.toMarkdown(text) requireContext().markwon.setMarkdown(binding.changelog, text)
val begForSubscription = !inventory.hasPro val begForSubscription = !inventory.hasPro
val begForRating = !preferences.getBoolean(R.string.p_clicked_rate, false) val begForRating = !preferences.getBoolean(R.string.p_clicked_rate, false)

@ -7,6 +7,11 @@ import android.content.Intent.ACTION_VIEW
import android.net.Uri import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import io.noties.markwon.Markwon
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.ext.tasklist.TaskListPlugin
import io.noties.markwon.linkify.LinkifyPlugin
import org.tasks.R import org.tasks.R
object Context { object Context {
@ -46,4 +51,18 @@ object Context {
fun Context.toast(text: String?, duration: Int = Toast.LENGTH_LONG) = fun Context.toast(text: String?, duration: Int = Toast.LENGTH_LONG) =
text?.let { Toast.makeText(this, it, duration).show() } text?.let { Toast.makeText(this, it, duration).show() }
val Context.markwon: Markwon
get() =
Markwon
.builder(this)
.usePlugins(
listOf(
TaskListPlugin.create(this),
TablePlugin.create(this),
LinkifyPlugin.create(android.text.util.Linkify.ALL, true),
StrikethroughPlugin.create()
)
)
.build()
} }

@ -486,6 +486,9 @@ class Preferences @JvmOverloads constructor(
fun showGroupHeaders(): Boolean = fun showGroupHeaders(): Boolean =
!usePagedQueries() && !getBoolean(R.string.p_disable_sort_groups, false) !usePagedQueries() && !getBoolean(R.string.p_disable_sort_groups, false)
val markdown: Boolean
get() = getBoolean(R.string.p_markdown, false)
companion object { companion object {
private const val PREF_SORT_SORT = "sort_sort" // $NON-NLS-1$ private const val PREF_SORT_SORT = "sort_sort" // $NON-NLS-1$

@ -19,6 +19,7 @@ import org.tasks.data.TaskContainer
import org.tasks.databinding.TaskAdapterRowBinding import org.tasks.databinding.TaskAdapterRowBinding
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.Linkify import org.tasks.dialogs.Linkify
import org.tasks.extensions.Context.markwon
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
import org.tasks.ui.CheckBoxProvider import org.tasks.ui.CheckBoxProvider
@ -45,7 +46,7 @@ class TaskViewHolder internal constructor(
private val linkify: Linkify, private val linkify: Linkify,
private val locale: Locale private val locale: Locale
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
private val markwon = if (preferences.markdown) context.markwon else null
private val row: ViewGroup = binding.row private val row: ViewGroup = binding.row
private val dueDate: TextView = binding.dueDate.apply { private val dueDate: TextView = binding.dueDate.apply {
setOnClickListener { changeDueDate() } setOnClickListener { changeDueDate() }
@ -129,17 +130,29 @@ class TaskViewHolder internal constructor(
fun bindView(task: TaskContainer, filter: Filter, sortMode: Int) { fun bindView(task: TaskContainer, filter: Filter, sortMode: Int) {
this.task = task this.task = task
indent = task.indent indent = task.indent
nameView.text = task.title if (markwon == null) {
nameView.text = task.title
} else {
task.title?.let { markwon.setMarkdown(nameView, it) }
}
setupTitleAndCheckbox() setupTitleAndCheckbox()
setupDueDate(sortMode == SORT_DUE) setupDueDate(sortMode == SORT_DUE)
setupChips(filter, sortMode == SORT_START) setupChips(filter, sortMode == SORT_START)
if (preferences.getBoolean(R.string.p_show_description, true)) { if (preferences.getBoolean(R.string.p_show_description, true)) {
description.text = task.notes if (markwon == null) {
description.text = task.notes
} else {
task.notes?.let { markwon.setMarkdown(description, it) }
}
description.visibility = if (task.hasNotes()) View.VISIBLE else View.GONE description.visibility = if (task.hasNotes()) View.VISIBLE else View.GONE
} }
if (preferences.getBoolean(R.string.p_linkify_task_list, false)) { if (preferences.getBoolean(R.string.p_linkify_task_list, false)) {
linkify.linkify(nameView) { onRowBodyClick() } linkify.setMovementMethod(nameView) { onRowBodyClick() }
linkify.linkify(description) { onRowBodyClick() } linkify.setMovementMethod(description) { onRowBodyClick() }
if (markwon == null) {
Linkify.safeLinkify(nameView)
Linkify.safeLinkify(description)
}
nameView.setOnLongClickListener { onRowBodyLongClick() } nameView.setOnLongClickListener { onRowBodyLongClick() }
description.setOnLongClickListener { onRowBodyLongClick() } description.setOnLongClickListener { onRowBodyLongClick() }
} }

@ -5,10 +5,14 @@ import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.noties.markwon.editor.MarkwonEditor
import io.noties.markwon.editor.MarkwonEditorTextWatcher
import org.tasks.R import org.tasks.R
import org.tasks.databinding.ControlSetDescriptionBinding import org.tasks.databinding.ControlSetDescriptionBinding
import org.tasks.dialogs.Linkify import org.tasks.dialogs.Linkify
import org.tasks.extensions.Context.markwon
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -27,11 +31,20 @@ class DescriptionControlSet : TaskEditControlFragment() {
override fun bind(parent: ViewGroup?) = override fun bind(parent: ViewGroup?) =
ControlSetDescriptionBinding.inflate(layoutInflater, parent, true).let { ControlSetDescriptionBinding.inflate(layoutInflater, parent, true).let {
editText = it.notes.apply { editText = it.notes
addTextChangedListener( val markdown = if (preferences.markdown) {
onTextChanged = { text, _, _, _ -> textChanged(text) } MarkwonEditorTextWatcher.withPreRender(
MarkwonEditor.create(requireContext().markwon),
Executors.newCachedThreadPool(),
editText
) )
} else {
null
} }
editText.addTextChangedListener(
onTextChanged = { text, _, _, _ -> textChanged(text) },
afterTextChanged = { editable -> markdown?.afterTextChanged(editable) }
)
it.root it.root
} }

@ -46,7 +46,7 @@
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toEndOf="@id/completeBox" android:layout_toEndOf="@id/completeBox"
android:layout_toStartOf="@id/due_date" android:layout_toStartOf="@id/due_date"
@ -66,7 +66,7 @@
<TextView <TextView
android:id="@+id/description" android:id="@+id/description"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/title" android:layout_below="@id/title"
android:layout_marginTop="@dimen/task_list_item_spacing" android:layout_marginTop="@dimen/task_list_item_spacing"

@ -444,4 +444,5 @@
<string name="p_map_theme">map_theme</string> <string name="p_map_theme">map_theme</string>
<string name="p_picker_mode_date">picker_mode_date</string> <string name="p_picker_mode_date">picker_mode_date</string>
<string name="p_picker_mode_time">picker_mode_time</string> <string name="p_picker_mode_time">picker_mode_time</string>
<string name="p_markdown">markdown</string>
</resources> </resources>

@ -724,4 +724,6 @@ File %1$s contained %2$s.\n\n
<string name="upgrade_automation">Automation</string> <string name="upgrade_automation">Automation</string>
<string name="upgrade_automation_description">Plugins for Tasker, Automate, and Locale</string> <string name="upgrade_automation_description">Plugins for Tasker, Automate, and Locale</string>
<string name="more_options">More options</string> <string name="more_options">More options</string>
<string name="markdown">Markdown</string>
<string name="markdown_description">Enable Markdown in title and description</string>
</resources> </resources>

@ -172,6 +172,12 @@
android:targetPackage="@string/app_package" /> android:targetPackage="@string/app_package" />
</Preference> </Preference>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/p_markdown"
android:summary="@string/markdown_description"
android:title="@string/markdown" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:key="@string/p_linkify_task_edit" android:key="@string/p_linkify_task_edit"

@ -292,6 +292,21 @@
++--- io.noties.markwon:core:4.6.2 ++--- io.noties.markwon:core:4.6.2
+| +--- androidx.annotation:annotation:1.1.0 -> 1.2.0 +| +--- androidx.annotation:annotation:1.1.0 -> 1.2.0
+| \--- com.atlassian.commonmark:commonmark:0.13.0 +| \--- com.atlassian.commonmark:commonmark:0.13.0
++--- io.noties.markwon:editor:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*)
++--- io.noties.markwon:ext-tasklist:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*)
++--- io.noties.markwon:ext-strikethrough:4.6.2
+| +--- io.noties.markwon:core:4.6.2 (*)
+| \--- com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:0.13.0
+| \--- com.atlassian.commonmark:commonmark:0.13.0
++--- io.noties.markwon:ext-tables:4.6.2
+| +--- io.noties.markwon:core:4.6.2 (*)
+| \--- com.atlassian.commonmark:commonmark-ext-gfm-tables:0.13.0
+| \--- com.atlassian.commonmark:commonmark:0.13.0
++--- io.noties.markwon:linkify:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*)
++--- me.saket:better-link-movement-method:2.2.0
++--- org.jetbrains.kotlin:kotlin-stdlib:1.4.32 (*) ++--- org.jetbrains.kotlin:kotlin-stdlib:1.4.32 (*)
++--- org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4 ++--- org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.30 -> 1.4.32 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.30 -> 1.4.32 (*)

@ -409,6 +409,21 @@
++--- io.noties.markwon:core:4.6.2 ++--- io.noties.markwon:core:4.6.2
+| +--- androidx.annotation:annotation:1.1.0 -> 1.2.0 +| +--- androidx.annotation:annotation:1.1.0 -> 1.2.0
+| \--- com.atlassian.commonmark:commonmark:0.13.0 +| \--- com.atlassian.commonmark:commonmark:0.13.0
++--- io.noties.markwon:editor:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*)
++--- io.noties.markwon:ext-tasklist:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*)
++--- io.noties.markwon:ext-strikethrough:4.6.2
+| +--- io.noties.markwon:core:4.6.2 (*)
+| \--- com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:0.13.0
+| \--- com.atlassian.commonmark:commonmark:0.13.0
++--- io.noties.markwon:ext-tables:4.6.2
+| +--- io.noties.markwon:core:4.6.2 (*)
+| \--- com.atlassian.commonmark:commonmark-ext-gfm-tables:0.13.0
+| \--- com.atlassian.commonmark:commonmark:0.13.0
++--- io.noties.markwon:linkify:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*)
++--- me.saket:better-link-movement-method:2.2.0
++--- org.jetbrains.kotlin:kotlin-stdlib:1.4.32 (*) ++--- org.jetbrains.kotlin:kotlin-stdlib:1.4.32 (*)
++--- org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4 ++--- org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.30 -> 1.4.32 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.30 -> 1.4.32 (*)

Loading…
Cancel
Save