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 1c5f7c09b..1954fb151 100755 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt @@ -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) diff --git a/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt b/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt index b94799a27..6ad5c787f 100644 --- a/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt +++ b/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt @@ -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) diff --git a/app/src/main/java/org/tasks/extensions/Context.kt b/app/src/main/java/org/tasks/extensions/Context.kt index 6b30429af..73cb53648 100644 --- a/app/src/main/java/org/tasks/extensions/Context.kt +++ b/app/src/main/java/org/tasks/extensions/Context.kt @@ -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() - } } diff --git a/app/src/main/java/org/tasks/markdown/Markdown.kt b/app/src/main/java/org/tasks/markdown/Markdown.kt new file mode 100644 index 000000000..6d633390a --- /dev/null +++ b/app/src/main/java/org/tasks/markdown/Markdown.kt @@ -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? +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/markdown/MarkdownDisabled.kt b/app/src/main/java/org/tasks/markdown/MarkdownDisabled.kt new file mode 100644 index 000000000..5b2cb5b41 --- /dev/null +++ b/app/src/main/java/org/tasks/markdown/MarkdownDisabled.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/markdown/MarkdownProvider.kt b/app/src/main/java/org/tasks/markdown/MarkdownProvider.kt new file mode 100644 index 000000000..ad418a331 --- /dev/null +++ b/app/src/main/java/org/tasks/markdown/MarkdownProvider.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/markdown/Markwon.kt b/app/src/main/java/org/tasks/markdown/Markwon.kt new file mode 100644 index 000000000..a58ac7dda --- /dev/null +++ b/app/src/main/java/org/tasks/markdown/Markwon.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/notifications/NotificationManager.kt b/app/src/main/java/org/tasks/notifications/NotificationManager.kt index 0d0a68406..906432a0e 100644 --- a/app/src/main/java/org/tasks/notifications/NotificationManager.kt +++ b/app/src/main/java/org/tasks/notifications/NotificationManager.kt @@ -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)) diff --git a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt index f9203eb12..55a386f5c 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt +++ b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt @@ -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) } diff --git a/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt b/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt index 4b794b901..6b230c0c4 100644 --- a/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt +++ b/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt @@ -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 + ) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt index 9015b3937..d5170758c 100644 --- a/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt +++ b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt @@ -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 } diff --git a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt index 89f16ce3a..6a8e84a51 100644 --- a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt +++ b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt @@ -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) diff --git a/app/src/main/java/org/tasks/widget/ScrollableWidgetUpdateService.java b/app/src/main/java/org/tasks/widget/ScrollableWidgetUpdateService.java index 9b9297cd5..900113c1a 100644 --- a/app/src/main/java/org/tasks/widget/ScrollableWidgetUpdateService.java +++ b/app/src/main/java/org/tasks/widget/ScrollableWidgetUpdateService.java @@ -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)); } }