From c4e98052dee68acf28e3a5fd52281839d3c030df Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 8 Apr 2021 17:43:20 -0500 Subject: [PATCH] Add markdown support --- app/build.gradle.kts | 6 + app/licenses.yml | 44 ++++++ app/src/main/assets/licenses.json | 108 +++++++++++++++ .../astrid/activity/TaskEditFragment.kt | 24 +++- .../astrid/notes/CommentsController.kt | 2 +- .../main/java/org/tasks/dialogs/Linkify.kt | 127 +++++++++--------- .../java/org/tasks/dialogs/WhatsNewDialog.kt | 5 +- .../main/java/org/tasks/extensions/Context.kt | 19 +++ .../java/org/tasks/preferences/Preferences.kt | 3 + .../java/org/tasks/tasklist/TaskViewHolder.kt | 23 +++- .../org/tasks/ui/DescriptionControlSet.kt | 19 ++- app/src/main/res/layout/task_adapter_row.xml | 4 +- app/src/main/res/values/keys.xml | 1 + app/src/main/res/values/strings.xml | 2 + .../res/xml/preferences_look_and_feel.xml | 6 + deps_fdroid.txt | 15 +++ deps_googleplay.txt | 15 +++ 17 files changed, 342 insertions(+), 81 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b3146d1d..155b2b4cf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -185,6 +185,12 @@ dependencies { implementation("io.reactivex.rxjava2:rxandroid:2.1.1") implementation("androidx.paging:paging-runtime:2.1.2") 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-network-plugin:${Versions.flipper}") diff --git a/app/licenses.yml b/app/licenses.yml index 9a2b10ece..3f41832c4 100644 --- a/app/licenses.yml +++ b/app/licenses.yml @@ -916,3 +916,47 @@ license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt 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 diff --git a/app/src/main/assets/licenses.json b/app/src/main/assets/licenses.json index 899bbb0f0..946bd561e 100644 --- a/app/src/main/assets/licenses.json +++ b/app/src/main/assets/licenses.json @@ -2175,6 +2175,114 @@ "normalizedLicense": "apache2", "url": "https://developer.android.com/jetpack/androidx/releases/activity#1.3.0-alpha04", "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" } ] } \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt index 786badfe9..d5138582e 100755 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt @@ -37,6 +37,8 @@ import com.todoroo.astrid.repeats.RepeatControlSet import com.todoroo.astrid.timers.TimerPlugin import com.todoroo.astrid.ui.StartDateControlSet import dagger.hilt.android.AndroidEntryPoint +import io.noties.markwon.editor.MarkwonEditor +import io.noties.markwon.editor.MarkwonEditorTextWatcher import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -49,6 +51,7 @@ import org.tasks.databinding.FragmentTaskEditBinding import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.Linkify +import org.tasks.extensions.Context.markwon import org.tasks.extensions.Context.openUri import org.tasks.files.FileHelper import org.tasks.fragments.TaskEditControlSetFragmentManager @@ -58,6 +61,7 @@ import org.tasks.themes.ThemeColor import org.tasks.ui.SubtaskControlSet import org.tasks.ui.TaskEditControlFragment import org.tasks.ui.TaskEditViewModel +import java.util.concurrent.Executors import javax.inject.Inject import kotlin.math.abs @@ -138,14 +142,28 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener { toolbar.setOnMenuItemClickListener(this) themeColor.apply(binding.collapsingtoolbarlayout, toolbar) 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.setHorizontallyScrolling(false) title.setTextColor(themeColor.colorOnPrimary) title.setHintTextColor(themeColor.hintOnPrimary) title.maxLines = 5 - title.addTextChangedListener { - editViewModel.title = title.text.toString().trim { it <= ' ' } - } if (model.isNew || preferences.getBoolean(R.string.p_hide_check_button, false)) { binding.fab.visibility = View.INVISIBLE } else if (editViewModel.completed!!) { diff --git a/app/src/main/java/com/todoroo/astrid/notes/CommentsController.kt b/app/src/main/java/com/todoroo/astrid/notes/CommentsController.kt index 9f1c9fd92..bfb28e35f 100644 --- a/app/src/main/java/com/todoroo/astrid/notes/CommentsController.kt +++ b/app/src/main/java/com/todoroo/astrid/notes/CommentsController.kt @@ -87,7 +87,7 @@ class CommentsController @Inject constructor( // name val nameView = view.findViewById(R.id.title) nameView.text = Html.fromHtml(item.message) - Linkify.safeLinkify(nameView, android.text.util.Linkify.ALL) + Linkify.safeLinkify(nameView) // date val date = view.findViewById(R.id.date) diff --git a/app/src/main/java/org/tasks/dialogs/Linkify.kt b/app/src/main/java/org/tasks/dialogs/Linkify.kt index 6ff212f86..f1d9bd310 100644 --- a/app/src/main/java/org/tasks/dialogs/Linkify.kt +++ b/app/src/main/java/org/tasks/dialogs/Linkify.kt @@ -4,16 +4,13 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.text.Spannable -import android.text.SpannableString -import android.text.SpannableStringBuilder -import android.text.style.URLSpan import android.text.util.Linkify -import android.view.View +import android.view.MotionEvent import android.widget.TextView import androidx.core.text.util.LinkifyCompat import dagger.hilt.android.qualifiers.ActivityContext +import me.saket.bettermovementmethod.BetterLinkMovementMethod import org.tasks.R -import org.tasks.Strings.isNullOrEmpty import timber.log.Timber import java.io.UnsupportedEncodingException import java.net.URLDecoder @@ -23,79 +20,81 @@ class Linkify @Inject constructor( @ActivityContext private val context: Context, private val dialogBuilder: DialogBuilder ) { - fun linkify(textView: TextView, onClick: Runnable = Runnable {}) { - if (textView.length() == 0) { + fun linkify(tv: TextView) { + if (tv.length() == 0) { return } - safeLinkify(textView, Linkify.ALL) - textView.setOnClickListener { - if (textView.selectionStart == -1 && textView.selectionEnd == -1) { - onClick.run() - } + safeLinkify(tv) + setMovementMethod(tv) + } + + fun setMovementMethod(tv: TextView, handle: (() -> Unit)? = null) { + val blmm = BetterLinkMovementMethod.newInstance().apply { + setOnLinkClickListener { _, url -> onClick(url, handle ?: {}) } + setOnLinkLongClickListener { _, _ -> tv.performLongClick() } } - val text = textView.text - if (text is SpannableStringBuilder || text is SpannableString) { - val spannable = text as Spannable - val spans = spannable.getSpans(0, text.length, URLSpan::class.java) - for (span in spans) { - val start = spannable.getSpanStart(span) - val end = spannable.getSpanEnd(span) - spannable.removeSpan(span) - spannable.setSpan(ClickHandlingURLSpan(span.url, onClick), start, end, 0) + tv.movementMethod = blmm + tv.setOnTouchListener { _, event -> + val text = tv.text + when { + text is Spannable && blmm.onTouchEvent(tv, text, event) -> true + event.action == MotionEvent.ACTION_UP -> { + handle?.invoke() + false + } + else -> false } } } - private inner class ClickHandlingURLSpan constructor( - url: String?, - private val onEdit: Runnable - ) : URLSpan(url) { - override fun onClick(widget: View) { - var title: String? - val edit = context.getString(R.string.TAd_actionEditTask) - val action: String - val uri = Uri.parse(url) - var scheme = uri.scheme - if (isNullOrEmpty(scheme)) { - scheme = "" + fun onClick(url: String, onEdit: () -> Unit): Boolean { + var title: String? + val edit = context.getString(R.string.TAd_actionEditTask) + val action: String + val uri = Uri.parse(url).let { + if (it.scheme.isNullOrBlank()) { + Uri.parse("https://$url") + } else { + it } - when (scheme) { - "tel" -> { - 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) - } + } + when (uri.scheme) { + "tel" -> { + title = uri.encodedSchemeSpecificPart + action = context.getString(R.string.action_call) } - dialogBuilder - .newDialog(title) - .setItems(listOf(action, edit)) { _, selected -> - if (selected == 0) { - context.startActivity(Intent(Intent.ACTION_VIEW, uri)) - } else { - onEdit.run() - } + "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) { } - .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 { - fun safeLinkify(textView: TextView?, mask: Int) { + fun safeLinkify(textView: TextView?, mask: Int = Linkify.ALL) { try { LinkifyCompat.addLinks(textView!!, mask) } catch (e: UnsatisfiedLinkError) { diff --git a/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt b/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt index a680611cf..7896a9a2d 100644 --- a/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt +++ b/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt @@ -8,13 +8,13 @@ import android.text.method.LinkMovementMethod import android.view.View import androidx.fragment.app.DialogFragment import dagger.hilt.android.AndroidEntryPoint -import io.noties.markwon.Markwon import org.tasks.R import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.analytics.Firebase import org.tasks.billing.Inventory import org.tasks.billing.PurchaseActivity import org.tasks.databinding.DialogWhatsNewBinding +import org.tasks.extensions.Context.markwon import org.tasks.extensions.Context.openUri import org.tasks.preferences.Preferences import java.io.BufferedReader @@ -37,9 +37,8 @@ class WhatsNewDialog : DialogFragment() { val textStream = requireContext().assets.open("CHANGELOG.md") val text = BufferedReader(textStream.reader()).readText() - val markwon = Markwon.builder(requireContext()).build() binding.changelog.movementMethod = LinkMovementMethod.getInstance() - binding.changelog.text = markwon.toMarkdown(text) + requireContext().markwon.setMarkdown(binding.changelog, text) val begForSubscription = !inventory.hasPro val begForRating = !preferences.getBoolean(R.string.p_clicked_rate, false) diff --git a/app/src/main/java/org/tasks/extensions/Context.kt b/app/src/main/java/org/tasks/extensions/Context.kt index 73cb53648..1fdbd8597 100644 --- a/app/src/main/java/org/tasks/extensions/Context.kt +++ b/app/src/main/java/org/tasks/extensions/Context.kt @@ -7,6 +7,11 @@ import android.content.Intent.ACTION_VIEW import android.net.Uri import android.widget.Toast 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 object Context { @@ -46,4 +51,18 @@ object Context { fun Context.toast(text: String?, duration: Int = Toast.LENGTH_LONG) = 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() } diff --git a/app/src/main/java/org/tasks/preferences/Preferences.kt b/app/src/main/java/org/tasks/preferences/Preferences.kt index 9a0d9026e..7649cf8b4 100644 --- a/app/src/main/java/org/tasks/preferences/Preferences.kt +++ b/app/src/main/java/org/tasks/preferences/Preferences.kt @@ -486,6 +486,9 @@ class Preferences @JvmOverloads constructor( fun showGroupHeaders(): Boolean = !usePagedQueries() && !getBoolean(R.string.p_disable_sort_groups, false) + val markdown: Boolean + get() = getBoolean(R.string.p_markdown, false) + companion object { private const val PREF_SORT_SORT = "sort_sort" // $NON-NLS-1$ diff --git a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt index 4abd481dc..e9c9af360 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt +++ b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt @@ -19,6 +19,7 @@ import org.tasks.data.TaskContainer import org.tasks.databinding.TaskAdapterRowBinding import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.dialogs.Linkify +import org.tasks.extensions.Context.markwon import org.tasks.preferences.Preferences import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.ui.CheckBoxProvider @@ -45,7 +46,7 @@ class TaskViewHolder internal constructor( private val linkify: Linkify, private val locale: Locale ) : RecyclerView.ViewHolder(binding.root) { - + private val markwon = if (preferences.markdown) context.markwon else null private val row: ViewGroup = binding.row private val dueDate: TextView = binding.dueDate.apply { setOnClickListener { changeDueDate() } @@ -129,17 +130,29 @@ class TaskViewHolder internal constructor( fun bindView(task: TaskContainer, filter: Filter, sortMode: Int) { this.task = task indent = task.indent - nameView.text = task.title + if (markwon == null) { + nameView.text = task.title + } else { + task.title?.let { markwon.setMarkdown(nameView, it) } + } setupTitleAndCheckbox() setupDueDate(sortMode == SORT_DUE) setupChips(filter, sortMode == SORT_START) 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 } if (preferences.getBoolean(R.string.p_linkify_task_list, false)) { - linkify.linkify(nameView) { onRowBodyClick() } - linkify.linkify(description) { onRowBodyClick() } + linkify.setMovementMethod(nameView) { onRowBodyClick() } + linkify.setMovementMethod(description) { onRowBodyClick() } + if (markwon == null) { + Linkify.safeLinkify(nameView) + Linkify.safeLinkify(description) + } nameView.setOnLongClickListener { onRowBodyLongClick() } description.setOnLongClickListener { onRowBodyLongClick() } } diff --git a/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt index aed2cf2f6..ad5c57ac0 100644 --- a/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt +++ b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt @@ -5,10 +5,14 @@ import android.view.ViewGroup import android.widget.EditText import androidx.core.widget.addTextChangedListener import dagger.hilt.android.AndroidEntryPoint +import io.noties.markwon.editor.MarkwonEditor +import io.noties.markwon.editor.MarkwonEditorTextWatcher import org.tasks.R import org.tasks.databinding.ControlSetDescriptionBinding import org.tasks.dialogs.Linkify +import org.tasks.extensions.Context.markwon import org.tasks.preferences.Preferences +import java.util.concurrent.Executors import javax.inject.Inject @AndroidEntryPoint @@ -27,11 +31,20 @@ class DescriptionControlSet : TaskEditControlFragment() { override fun bind(parent: ViewGroup?) = ControlSetDescriptionBinding.inflate(layoutInflater, parent, true).let { - editText = it.notes.apply { - addTextChangedListener( - onTextChanged = { text, _, _, _ -> textChanged(text) } + editText = it.notes + val markdown = if (preferences.markdown) { + MarkwonEditorTextWatcher.withPreRender( + MarkwonEditor.create(requireContext().markwon), + Executors.newCachedThreadPool(), + editText ) + } else { + null } + editText.addTextChangedListener( + onTextChanged = { text, _, _, _ -> textChanged(text) }, + afterTextChanged = { editable -> markdown?.afterTextChanged(editable) } + ) it.root } diff --git a/app/src/main/res/layout/task_adapter_row.xml b/app/src/main/res/layout/task_adapter_row.xml index 063cb5fe8..29ec92601 100644 --- a/app/src/main/res/layout/task_adapter_row.xml +++ b/app/src/main/res/layout/task_adapter_row.xml @@ -46,7 +46,7 @@ map_theme picker_mode_date picker_mode_time + markdown diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 043a54305..7502b8c92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -724,4 +724,6 @@ File %1$s contained %2$s.\n\n Automation Plugins for Tasker, Automate, and Locale More options + Markdown + Enable Markdown in title and description diff --git a/app/src/main/res/xml/preferences_look_and_feel.xml b/app/src/main/res/xml/preferences_look_and_feel.xml index c83b62076..a69bcf274 100644 --- a/app/src/main/res/xml/preferences_look_and_feel.xml +++ b/app/src/main/res/xml/preferences_look_and_feel.xml @@ -172,6 +172,12 @@ android:targetPackage="@string/app_package" /> + + 1.2.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.kotlinx:kotlinx-collections-immutable-jvm:0.3.4 +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.30 -> 1.4.32 (*) diff --git a/deps_googleplay.txt b/deps_googleplay.txt index d5c6750b0..64c457b67 100644 --- a/deps_googleplay.txt +++ b/deps_googleplay.txt @@ -409,6 +409,21 @@ ++--- io.noties.markwon:core:4.6.2 +| +--- androidx.annotation:annotation:1.1.0 -> 1.2.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.kotlinx:kotlinx-collections-immutable-jvm:0.3.4 +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.30 -> 1.4.32 (*)