Add MarkdownProvider

Render markdown for widget and notifications
pull/1466/head
Alex Baker 3 years ago
parent 40245eb4b9
commit 430dc04747

@ -37,8 +37,6 @@ 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
@ -51,17 +49,16 @@ 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
import org.tasks.markdown.MarkdownProvider
import org.tasks.notifications.NotificationManager
import org.tasks.preferences.Preferences
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
@ -78,6 +75,7 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@Inject lateinit var firebase: Firebase
@Inject lateinit var timerPlugin: TimerPlugin
@Inject lateinit var linkify: Linkify
@Inject lateinit var markdownProvider: MarkdownProvider
private val linkifyEnabled: Boolean
get() = preferences.getBoolean(R.string.p_linkify_task_edit, false)
@ -145,21 +143,13 @@ 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(linkifyEnabled)),
Executors.newCachedThreadPool(),
title
)
} else {
null
}
val textWatcher = markdownProvider.markdown(linkifyEnabled).textWatcher(title)
title.addTextChangedListener(
onTextChanged = { _, _, _, _ ->
editViewModel.title = title.text.toString().trim { it <= ' ' }
},
afterTextChanged = {
markdown?.afterTextChanged(it)
textWatcher?.invoke(it)
}
)
title.setText(model.title)

@ -14,8 +14,8 @@ 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.markdown.MarkdownProvider
import org.tasks.preferences.Preferences
import java.io.BufferedReader
import javax.inject.Inject
@ -28,6 +28,7 @@ class WhatsNewDialog : DialogFragment() {
@Inject lateinit var firebase: Firebase
@Inject lateinit var preferences: Preferences
@Inject lateinit var inventory: Inventory
@Inject lateinit var markdownProvider: MarkdownProvider
private var displayedRate = false
private var displayedSubscribe = false
@ -38,7 +39,7 @@ class WhatsNewDialog : DialogFragment() {
val textStream = requireContext().assets.open("CHANGELOG.md")
val text = BufferedReader(textStream.reader()).readText()
binding.changelog.movementMethod = LinkMovementMethod.getInstance()
requireContext().markwon(true).setMarkdown(binding.changelog, text)
markdownProvider.markdown(true).setMarkdown(binding.changelog, text)
val begForSubscription = !inventory.hasPro
val begForRating = !preferences.getBoolean(R.string.p_clicked_rate, false)

@ -5,14 +5,8 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.net.Uri
import android.text.util.Linkify.*
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 {
@ -52,22 +46,4 @@ object Context {
fun Context.toast(text: String?, duration: Int = Toast.LENGTH_LONG) =
text?.let { Toast.makeText(this, it, duration).show() }
fun Context.markwon(linkify: Boolean = false): Markwon {
val builder = Markwon
.builder(this)
.usePlugins(
listOf(
TaskListPlugin.create(this),
TablePlugin.create(this),
StrikethroughPlugin.create()
)
)
if (linkify) {
builder.usePlugin(
LinkifyPlugin.create(WEB_URLS or EMAIL_ADDRESSES or PHONE_NUMBERS, true)
)
}
return builder.build()
}
}

@ -0,0 +1,15 @@
package org.tasks.markdown
import android.text.Editable
import android.widget.EditText
import android.widget.TextView
interface Markdown {
fun textWatcher(editText: EditText): ((Editable?) -> Unit)?
val enabled: Boolean
fun setMarkdown(tv: TextView, markdown: String?)
fun toMarkdown(markdown: String?): CharSequence?
}

@ -0,0 +1,17 @@
package org.tasks.markdown
import android.text.Editable
import android.widget.EditText
import android.widget.TextView
class MarkdownDisabled : Markdown {
override fun textWatcher(editText: EditText): ((Editable?) -> Unit)? = null
override val enabled = false
override fun setMarkdown(tv: TextView, markdown: String?) {
tv.text = markdown
}
override fun toMarkdown(markdown: String?) = markdown
}

@ -0,0 +1,21 @@
package org.tasks.markdown
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.preferences.Preferences
import javax.inject.Inject
class MarkdownProvider @Inject constructor(
@ApplicationContext private val context: Context,
private val preferences: Preferences
){
fun markdown(linkify: Int) = markdown(preferences.getBoolean(linkify, false))
fun markdown(linkify: Boolean = false) =
if (preferences.getBoolean(R.string.p_markdown, false)) {
Markwon(context, linkify)
} else {
MarkdownDisabled()
}
}

@ -0,0 +1,52 @@
package org.tasks.markdown
import android.content.Context
import android.text.util.Linkify.*
import android.widget.EditText
import android.widget.TextView
import io.noties.markwon.editor.MarkwonEditor
import io.noties.markwon.editor.MarkwonEditorTextWatcher
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 java.util.concurrent.Executors
class Markwon(context: Context, linkify: Boolean) : Markdown {
private val markwon: io.noties.markwon.Markwon
override fun textWatcher(editText: EditText) =
MarkwonEditorTextWatcher.withPreRender(
MarkwonEditor.create(markwon), Executors.newCachedThreadPool(), editText
)::afterTextChanged
override val enabled = true
override fun setMarkdown(tv: TextView, markdown: String?) {
if (markdown?.isNotBlank() == true) {
markwon.setMarkdown(tv, markdown)
} else {
tv.text = markdown
}
}
override fun toMarkdown(markdown: String?) = markdown?.let { markwon.toMarkdown(it) }
init {
val builder = io.noties.markwon.Markwon
.builder(context)
.usePlugins(
listOf(
TaskListPlugin.create(context),
TablePlugin.create(context),
StrikethroughPlugin.create()
)
)
if (linkify) {
builder.usePlugin(
LinkifyPlugin.create(WEB_URLS or EMAIL_ADDRESSES or PHONE_NUMBERS, true)
)
}
markwon = builder.build()
}
}

@ -16,6 +16,7 @@ import org.tasks.data.LocationDao
import org.tasks.data.TaskDao
import org.tasks.filters.NotificationsFilter
import org.tasks.intents.TaskIntents
import org.tasks.markdown.MarkdownProvider
import org.tasks.preferences.Preferences
import org.tasks.receivers.CompleteTaskReceiver
import org.tasks.reminders.NotificationActivity
@ -38,7 +39,9 @@ class NotificationManager @Inject constructor(
private val locationDao: LocationDao,
private val localBroadcastManager: LocalBroadcastManager,
private val reminderService: ReminderService,
private val notificationManager: ThrottledNotificationManager) {
private val notificationManager: ThrottledNotificationManager,
private val markdownProvider: MarkdownProvider,
) {
private val colorProvider = ColorProvider(context, preferences)
private val queue = NotificationLimiter(MAX_NOTIFICATIONS)
@ -298,8 +301,9 @@ class NotificationManager @Inject constructor(
}
// read properties
val taskTitle = task.title
val taskDescription = task.notes
val markdown = markdownProvider.markdown()
val taskTitle = markdown.toMarkdown(task.title)
val taskDescription = markdown.toMarkdown(task.notes)
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_REMINDER)
@ -310,7 +314,7 @@ class NotificationManager @Inject constructor(
.setOnlyAlertOnce(false)
.setShowWhen(true)
.setTicker(taskTitle)
val intent = NotificationActivity.newIntent(context, taskTitle, id)
val intent = NotificationActivity.newIntent(context, taskTitle.toString(), id)
builder.setContentIntent(
PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT))
if (type == ReminderService.TYPE_GEOFENCE_ENTER || type == ReminderService.TYPE_GEOFENCE_EXIT) {
@ -321,7 +325,7 @@ class NotificationManager @Inject constructor(
if (type == ReminderService.TYPE_GEOFENCE_ENTER) R.string.location_arrived else R.string.location_departed,
place.displayName))
}
} else if (!isNullOrEmpty(taskDescription)) {
} else if (taskDescription?.isNotBlank() == true) {
builder
.setContentText(taskDescription)
.setStyle(NotificationCompat.BigTextStyle().bigText(taskDescription))

@ -19,7 +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.markdown.Markdown
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils.startOfDay
import org.tasks.ui.CheckBoxProvider
@ -44,9 +44,9 @@ class TaskViewHolder internal constructor(
private val selectedColor: Int,
private val rowPadding: Int,
private val linkify: Linkify,
private val locale: Locale
private val locale: Locale,
private val markdown: Markdown
) : RecyclerView.ViewHolder(binding.root) {
private val markwon = if (preferences.markdown) context.markwon(linkifyEnabled) else null
private val row: ViewGroup = binding.row
private val dueDate: TextView = binding.dueDate.apply {
setOnClickListener { changeDueDate() }
@ -64,9 +64,6 @@ class TaskViewHolder internal constructor(
lateinit var task: TaskContainer
private val linkifyEnabled: Boolean
get() = preferences.getBoolean(R.string.p_linkify_task_list, false)
var indent = 0
set(value) {
field = value
@ -133,26 +130,18 @@ class TaskViewHolder internal constructor(
fun bindView(task: TaskContainer, filter: Filter, sortMode: Int) {
this.task = task
indent = task.indent
if (markwon == null) {
nameView.text = task.title
} else {
task.title?.let { markwon.setMarkdown(nameView, it) }
}
markdown.setMarkdown(nameView, task.title)
setupTitleAndCheckbox()
setupDueDate(sortMode == SORT_DUE)
setupChips(filter, sortMode == SORT_START)
if (preferences.getBoolean(R.string.p_show_description, true)) {
if (markwon == null) {
description.text = task.notes
} else {
task.notes?.let { markwon.setMarkdown(description, it) }
}
markdown.setMarkdown(description, task.notes)
description.visibility = if (task.hasNotes()) View.VISIBLE else View.GONE
}
if (markwon != null || preferences.getBoolean(R.string.p_linkify_task_list, false)) {
if (markdown.enabled || preferences.getBoolean(R.string.p_linkify_task_list, false)) {
linkify.setMovementMethod(nameView) { onRowBodyClick() }
linkify.setMovementMethod(description) { onRowBodyClick() }
if (markwon == null) {
if (!markdown.enabled) {
Linkify.safeLinkify(nameView)
Linkify.safeLinkify(description)
}

@ -10,6 +10,7 @@ import dagger.hilt.android.qualifiers.ActivityContext
import org.tasks.R
import org.tasks.databinding.TaskAdapterRowBinding
import org.tasks.dialogs.Linkify
import org.tasks.markdown.MarkdownProvider
import org.tasks.preferences.Preferences
import org.tasks.preferences.ResourceResolver
import org.tasks.tasklist.TaskViewHolder.ViewHolderCallbacks
@ -32,6 +33,8 @@ class ViewHolderFactory @Inject constructor(
private val background: Int = ResourceResolver.getResourceId(context, R.attr.selectableItemBackground)
private val selectedColor: Int = ResourceResolver.getData(context, R.attr.colorControlHighlight)
private val rowPadding: Int = AndroidUtilities.convertDpToPixels(metrics, preferences.getInt(R.string.p_rowPadding, 16))
private val markdown =
MarkdownProvider(context, preferences).markdown(R.string.p_linkify_task_list)
fun newHeaderViewHolder(parent: ViewGroup?, callback: (Long) -> Unit) =
HeaderViewHolder(
@ -56,5 +59,7 @@ class ViewHolderFactory @Inject constructor(
selectedColor,
rowPadding,
linkify,
locale)
locale,
markdown
)
}

@ -5,20 +5,18 @@ 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.markdown.MarkdownProvider
import org.tasks.preferences.Preferences
import java.util.concurrent.Executors
import javax.inject.Inject
@AndroidEntryPoint
class DescriptionControlSet : TaskEditControlFragment() {
@Inject lateinit var linkify: Linkify
@Inject lateinit var preferences: Preferences
@Inject lateinit var markdownProvider: MarkdownProvider
private lateinit var editText: EditText
@ -35,18 +33,10 @@ class DescriptionControlSet : TaskEditControlFragment() {
override fun bind(parent: ViewGroup?) =
ControlSetDescriptionBinding.inflate(layoutInflater, parent, true).let {
editText = it.notes
val markdown = if (preferences.markdown) {
MarkwonEditorTextWatcher.withPreRender(
MarkwonEditor.create(requireContext().markwon(linkifyEnabled)),
Executors.newCachedThreadPool(),
editText
)
} else {
null
}
val textWatcher = markdownProvider.markdown(linkifyEnabled).textWatcher(editText)
editText.addTextChangedListener(
onTextChanged = { text, _, _, _ -> textChanged(text) },
afterTextChanged = { editable -> markdown?.afterTextChanged(editable) }
afterTextChanged = { editable -> textWatcher?.invoke(editable) }
)
it.root
}

@ -23,6 +23,7 @@ import org.tasks.data.TaskDao
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.date.DateTimeUtils
import org.tasks.locale.Locale
import org.tasks.markdown.Markdown
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences
import org.tasks.tasklist.SectionedDataSource
@ -43,7 +44,8 @@ internal class ScrollableViewsFactory(
private val checkBoxProvider: CheckBoxProvider,
private val locale: Locale,
private val chipProvider: ChipProvider,
private val localBroadcastManager: LocalBroadcastManager
private val localBroadcastManager: LocalBroadcastManager,
private val markdown: Markdown
) : RemoteViewsFactory {
private val indentPadding: Int
private var showDueDates = false
@ -203,11 +205,17 @@ internal class ScrollableViewsFactory(
if (showFullTaskTitle) {
row.setInt(R.id.widget_text, "setMaxLines", Int.MAX_VALUE)
}
row.setTextViewText(R.id.widget_text, task.title)
row.setTextViewText(
R.id.widget_text,
markdown.toMarkdown(task.title)
)
row.setTextColor(R.id.widget_text, textColorTitle)
if (showDescription && task.hasNotes()) {
row.setFloat(R.id.widget_description, "setTextSize", textSize)
row.setTextViewText(R.id.widget_description, task.notes)
row.setTextViewText(
R.id.widget_description,
markdown.toMarkdown(task.notes)
)
row.setViewVisibility(R.id.widget_description, View.VISIBLE)
if (showFullDescription) {
row.setInt(R.id.widget_description, "setMaxLines", Int.MAX_VALUE)

@ -5,17 +5,22 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.RemoteViewsService;
import com.todoroo.astrid.subtasks.SubtasksHelper;
import dagger.hilt.android.AndroidEntryPoint;
import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.TaskDao;
import org.tasks.locale.Locale;
import org.tasks.markdown.MarkdownProvider;
import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.Preferences;
import org.tasks.themes.ColorProvider;
import org.tasks.ui.CheckBoxProvider;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class ScrollableWidgetUpdateService extends RemoteViewsService {
@ -26,6 +31,7 @@ public class ScrollableWidgetUpdateService extends RemoteViewsService {
@Inject Locale locale;
@Inject ChipProvider chipProvider;
@Inject LocalBroadcastManager localBroadcastManager;
@Inject MarkdownProvider markdownProvider;
@Override
public void onStart(Intent intent, int startId) {
@ -57,6 +63,7 @@ public class ScrollableWidgetUpdateService extends RemoteViewsService {
new CheckBoxProvider(context, new ColorProvider(context, preferences)),
locale,
chipProvider,
localBroadcastManager);
localBroadcastManager,
markdownProvider.markdown(false));
}
}

Loading…
Cancel
Save