diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 423c6c333..7d2a52163 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -37,8 +37,6 @@ repositories { // Material View Pager maven { url "http://dl.bintray.com/florent37/maven" } - // Markdown - maven { url "https://s3.amazonaws.com/repo.commonsware.com" } maven { url "https://jitpack.io" } } @@ -70,7 +68,11 @@ dependencies { // Emojis implementation 'com.github.viirus:emoji-lib:0.0.5' // Markdown - implementation 'com.commonsware.cwac:anddown:0.4.0' + implementation "io.noties.markwon:core:4.1.2" + implementation "io.noties.markwon:ext-strikethrough:4.1.2" + implementation "io.noties.markwon:image:4.1.2" + implementation "io.noties.markwon:recycler:4.1.2" + implementation "io.noties.markwon:html:4.1.2" // About View for all dependent Libraries, we are using implementation('com.mikepenz:aboutlibraries:5.9.4@aar') { transitive = true @@ -115,8 +117,8 @@ dependencies { //Push Notifications implementation 'com.google.firebase:firebase-core:17.2.0' implementation 'com.google.firebase:firebase-messaging:20.0.0' - implementation 'com.google.firebase:firebase-config:19.0.2' - implementation 'com.google.firebase:firebase-perf:19.0.0' + implementation 'com.google.firebase:firebase-config:19.0.3' + implementation 'com.google.firebase:firebase-perf:19.0.1' implementation 'com.google.android.gms:play-services-auth:17.0.0' implementation 'io.realm:android-adapters:3.1.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -150,7 +152,7 @@ android { resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW" versionCode 2274 - versionName "2.2.1" + versionName "2.3" } lintOptions { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt index 1914e4be4..a0b46c76b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt @@ -31,6 +31,7 @@ import com.habitrpg.android.habitica.modules.UserRepositoryModule import com.habitrpg.android.habitica.proxy.CrashlyticsProxy import com.habitrpg.android.habitica.ui.activities.IntroActivity import com.habitrpg.android.habitica.ui.activities.LoginActivity +import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.squareup.leakcanary.LeakCanary import com.squareup.leakcanary.RefWatcher @@ -80,6 +81,7 @@ abstract class HabiticaBaseApplication : MultiDexApplication() { refWatcher = LeakCanary.install(this) createBillingAndCheckout() HabiticaIconsHelper.init(this) + MarkdownParser.setup(this) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt index 1ba94e299..9175fdbba 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt @@ -2,6 +2,7 @@ package com.habitrpg.android.habitica.models.tasks import android.os.Parcel import android.os.Parcelable +import android.text.Spanned import androidx.annotation.StringDef import com.google.gson.annotations.SerializedName import com.habitrpg.android.habitica.R @@ -62,9 +63,9 @@ open class Task : RealmObject, Parcelable { // used for buyable items var specialTag: String? = "" @Ignore - var parsedText: CharSequence? = null + var parsedText: Spanned? = null @Ignore - var parsedNotes: CharSequence? = null + var parsedNotes: Spanned? = null var isDue: Boolean? = null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt index 5e9dbbd20..73c34c353 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt @@ -32,10 +32,10 @@ import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.ui.AvatarView import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard import com.habitrpg.android.habitica.ui.helpers.loadImage +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog @@ -184,8 +184,8 @@ class FullProfileActivity : BaseActivity() { } val blurbText = profile.blurb - if (blurbText != null && !blurbText.isEmpty()) { - blurbTextView.text = MarkdownParser.parseMarkdown(blurbText) + if (blurbText != null && blurbText.isNotEmpty()) { + blurbTextView.setMarkdown(blurbText) } user.authentication?.timestamps?.createdAt?.let { joinedView.text = dateFormatter.format(it) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt index 4048e1f28..ecb79af3f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt @@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.ui.activities import android.os.Bundle import android.webkit.WebView -import com.commonsware.cwac.anddown.AndDown import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent +import io.noties.markwon.Markwon +import io.noties.markwon.html.HtmlPlugin import kotlinx.android.synthetic.main.activity_prefs.* import okhttp3.* import java.io.BufferedReader @@ -37,8 +38,12 @@ class GuidelinesActivity: BaseActivity() { val text = reader.readText() response.body()?.close() + val markwon = Markwon.builder(this@GuidelinesActivity) + .usePlugin(HtmlPlugin.create()) + .build() + findViewById(R.id.webview).post { - findViewById(R.id.webview).loadData(AndDown().markdownToHtml(text), "text/html; charset=utf-8", "utf-8") + findViewById(R.id.webview).loadData(markwon.toMarkdown(text).toString(), "text/html; charset=utf-8", "utf-8") } } }) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt index 9db4a5829..419b7fa23 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt @@ -13,8 +13,8 @@ import com.habitrpg.android.habitica.api.MaintenanceApiService import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.helpers.RxErrorHandler -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import com.habitrpg.android.habitica.ui.views.HabiticaEmojiTextView import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.Consumer @@ -48,7 +48,7 @@ class MaintenanceActivity : BaseActivity() { @Suppress("DEPRECATION") imageView.setImageURI(data.getString("imageUrl")?.toUri()) - this.descriptionTextView.text = MarkdownParser.parseMarkdown(data.getString("description")) + this.descriptionTextView.setMarkdown(data.getString("description")) this.descriptionTextView.movementMethod = LinkMovementMethod.getInstance() isDeprecationNotice = data.getBoolean("deprecationNotice") diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt index f849645f0..2757bfefc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt @@ -23,9 +23,9 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.extensions.getThemeColor import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.models.social.ChatMessage -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import io.reactivex.functions.Consumer import javax.inject.Inject @@ -71,7 +71,7 @@ class ReportMessageActivity : BaseActivity() { contentContainer.setOnTouchListener { _, _ -> true } dismissTouchView.setOnClickListener { finish() } - reportExplanationTextView.text = MarkdownParser.parseMarkdown(getString(R.string.report_explanation)) + reportExplanationTextView.setMarkdown(getString(R.string.report_explanation)) BottomSheetBehavior.from(bottomSheetView) .setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/FAQOverviewRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/FAQOverviewRecyclerAdapter.kt index 2f5b21803..c09cc09b6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/FAQOverviewRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/FAQOverviewRecyclerAdapter.kt @@ -12,8 +12,8 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.models.FAQArticle import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.fragments.faq.FAQOverviewFragmentDirections -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import io.reactivex.BackpressureStrategy import io.reactivex.Flowable import io.reactivex.subjects.PublishSubject @@ -105,7 +105,7 @@ class FAQOverviewRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Ada init { val textView = itemView.findViewById(R.id.text_view) - textView.text = MarkdownParser.parseMarkdown(itemView.context.getString(R.string.need_help_header_description, "[Habitica Help Guild](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)")) + textView.setMarkdown(itemView.context.getString(R.string.need_help_header_description, "[Habitica Help Guild](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)")) textView.setOnClickListener { MainNavigationController.navigate(R.id.guildFragment, bundleOf("groupID" to "5481ccf3-5d2d-48a9-a871-70a7380cee5a")) } textView.movementMethod = LinkMovementMethod.getInstance() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PublicGuildsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PublicGuildsRecyclerViewAdapter.kt index 8f5d43b81..106016e3d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PublicGuildsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PublicGuildsRecyclerViewAdapter.kt @@ -14,8 +14,8 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.ui.fragments.social.PublicGuildsFragmentDirections -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import io.reactivex.functions.Consumer import io.realm.Case import io.realm.OrderedRealmCollection @@ -115,7 +115,7 @@ class PublicGuildsRecyclerViewAdapter(data: OrderedRealmCollection?, auto fun bind(guild: Group, isInGroup: Boolean) { this.nameTextView.text = guild.name this.memberCountTextView.text = guild.memberCount.toString() - this.descriptionTextView.text = MarkdownParser.parseMarkdown(guild.summary) + this.descriptionTextView.setMarkdown(guild.summary) if (isInGroup) { this.joinLeaveButton.setText(R.string.leave) } else { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt index 71b8690f1..afc36ec2c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt @@ -30,7 +30,7 @@ import com.habitrpg.android.habitica.ui.activities.GroupFormActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers @@ -137,7 +137,7 @@ class GroupInformationFragment : BaseFragment() { groupSummaryView.movementMethod = LinkMovementMethod.getInstance() if (configManager.noPartyLinkPartyGuild()) { - join_party_description_textview.text = MarkdownParser.parseMarkdown(getString(R.string.join_party_description_guild, "[Party Wanted Guild](https://habitica.com/groups/guild/f2db2a7f-13c5-454d-b3ee-ea1f5089e601)")) + join_party_description_textview.setMarkdown(getString(R.string.join_party_description_guild, "[Party Wanted Guild](https://habitica.com/groups/guild/f2db2a7f-13c5-454d-b3ee-ea1f5089e601)")) join_party_description_textview.setOnClickListener { context?.let { FirebaseAnalytics.getInstance(it).logEvent("clicked_party_wanted", null) } MainNavigationController.navigate(R.id.guildFragment, bundleOf("groupID" to "f2db2a7f-13c5-454d-b3ee-ea1f5089e601")) @@ -225,8 +225,8 @@ class GroupInformationFragment : BaseFragment() { groupDescriptionWrapper.visibility = groupItemVisibility groupNameView.text = group?.name - groupDescriptionView.text = MarkdownParser.parseMarkdown(group?.description) - groupSummaryView.text = MarkdownParser.parseMarkdown(group?.summary) + groupDescriptionView.setMarkdown(group?.description) + groupSummaryView.setMarkdown(group?.summary) gemCountWrapper.visibility = if (group?.balance != null && group.balance > 0) View.VISIBLE else View.GONE gemCountTextView.text = (group?.balance ?: 0 * 4.0).toInt().toString() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt index 8e178ba09..95f23a55d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt @@ -24,9 +24,9 @@ import com.habitrpg.android.habitica.ui.activities.GroupFormActivity import com.habitrpg.android.habitica.ui.activities.GroupInviteActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView import com.habitrpg.android.habitica.ui.helpers.resetViews +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel import com.habitrpg.android.habitica.ui.views.HabiticaIcons import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper @@ -186,8 +186,8 @@ class GuildDetailFragment : BaseFragment() { guildMembersIconView.setImageBitmap(HabiticaIcons.imageOfGuildCrestMedium((guild?.memberCount ?: 0).toFloat())) guildMembersTextView.text = guild?.memberCount.toString() guildBankTextView.text = guild?.gemCount.toString() - guildSummaryView.text = MarkdownParser.parseMarkdown(guild?.summary) - guildDescriptionView.text = MarkdownParser.parseMarkdown(guild?.description) + guildSummaryView.setMarkdown(guild?.summary) + guildDescriptionView.setMarkdown(guild?.description) } companion object { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt index 05617a838..277575df3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt @@ -22,10 +22,7 @@ import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser -import com.habitrpg.android.habitica.ui.helpers.bindOptionalView -import com.habitrpg.android.habitica.ui.helpers.resetViews +import com.habitrpg.android.habitica.ui.helpers.* import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import io.reactivex.functions.Consumer import javax.inject.Inject @@ -152,7 +149,7 @@ class QuestDetailFragment : BaseMainFragment() { return } questTitleView?.text = questContent.text - questDescriptionView?.text = MarkdownParser.parseMarkdown(questContent.notes) + questDescriptionView?.setMarkdown(questContent.notes) DataBindingUtils.loadImage(questScrollImageView, "inventory_quest_scroll_" + questContent.key) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt index 38cdb1106..77a260823 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt @@ -27,6 +27,7 @@ import com.habitrpg.android.habitica.ui.activities.FullProfileActivity import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import com.habitrpg.android.habitica.ui.views.HabiticaEmojiTextView import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog @@ -187,7 +188,7 @@ class ChallengeDetailFragment: BaseMainFragment() { private fun set(challenge: Challenge) { this.challenge = challenge challengeName?.text = EmojiParser.parseEmojis(challenge.name) - challengeDescription?.text = MarkdownParser.parseMarkdown(challenge.description) + challengeDescription?.setMarkdown(challenge.description) challengeLeaderLabel?.username = challenge.leaderName gemAmountView?.text = challenge.prize.toString() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt index cf2340dbb..2249c7ce3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt @@ -120,7 +120,7 @@ class PartyDetailFragment : BaseFragment() { return } titleView?.text = party.name - descriptionView?.text = MarkdownParser.parseMarkdown(party.description) + descriptionView?.setMarkdown(party.description) if (party.quest?.key?.isEmpty() == false) { newQuestButton?.visibility = View.GONE diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/MarkdownParser.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/MarkdownParser.kt index 78aff8452..e98f3ed20 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/MarkdownParser.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/MarkdownParser.kt @@ -1,28 +1,38 @@ package com.habitrpg.android.habitica.ui.helpers -import android.graphics.Color -import android.text.Html -import android.text.Html.FROM_HTML_MODE_LEGACY -import android.text.SpannableStringBuilder +import android.content.Context +import android.text.SpannableString import android.text.Spanned -import android.text.style.ForegroundColorSpan -import com.commonsware.cwac.anddown.AndDown +import android.text.method.LinkMovementMethod +import android.widget.TextView import com.habitrpg.android.habitica.helpers.RxErrorHandler +import io.noties.markwon.Markwon +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin +import io.noties.markwon.image.ImagesPlugin +import io.noties.markwon.image.file.FileSchemeHandler +import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler +import io.noties.markwon.movement.MovementMethodPlugin import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.Consumer import io.reactivex.schedulers.Schedulers import net.pherth.android.emoji_library.EmojiParser -import java.util.regex.Pattern -/** - * @author data5tream - */ + object MarkdownParser { - private val processor = AndDown() + internal var markwon: Markwon? = null - private val regex = Pattern.compile("\\B@[-\\w]+") + fun setup(context: Context) { + markwon = Markwon.builder(context) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(ImagesPlugin.create { + it.addSchemeHandler(OkHttpNetworkSchemeHandler.create()) + .addSchemeHandler(FileSchemeHandler.createWithAssets(context.assets)) + }) + .usePlugin(MovementMethodPlugin.create(LinkMovementMethod.getInstance())) + .build() + } /** * Parses formatted markdown and returns it as styled CharSequence @@ -30,34 +40,15 @@ object MarkdownParser { * @param input Markdown formatted String * @return Stylized CharSequence */ - fun parseMarkdown(input: String?): CharSequence { + fun parseMarkdown(input: String?): Spanned { if (input == null) { - return "" + return SpannableString("") } - val output: SpannableStringBuilder = try { - val html = processor.markdownToHtml(EmojiParser.parseEmojis(input.trim { it <= ' ' })) - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - Html.fromHtml(html, FROM_HTML_MODE_LEGACY) as? SpannableStringBuilder - } else { - @Suppress("DEPRECATION") - (Html.fromHtml(html) as? SpannableStringBuilder) - } ?: SpannableStringBuilder() - } catch (e: UnsatisfiedLinkError) { - SpannableStringBuilder(input) - } catch (e: NoClassDefFoundError) { - SpannableStringBuilder(input) - } - - val matcher = regex.matcher(output) - while (matcher.find()) { - val colorSpan = ForegroundColorSpan(Color.parseColor("#9A62FF")) - output.setSpan(colorSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - return output.trimEnd('\n') + val text = EmojiParser.parseEmojis(input) ?: input + return markwon?.toMarkdown(text) ?: SpannableString(text) } - fun parseMarkdownAsync(input: String?, onSuccess: Consumer) { + fun parseMarkdownAsync(input: String?, onSuccess: Consumer) { Single.just(input ?: "") .map { this.parseMarkdown(it) } .subscribeOn(Schedulers.io()) @@ -74,5 +65,17 @@ object MarkdownParser { fun parseCompiled(input: CharSequence): String? { return EmojiParser.convertToCheatCode(input.toString()) } - } + + +fun TextView.setMarkdown(input: String?) { + MarkdownParser.markwon?.setParsedMarkdown(this, MarkdownParser.parseMarkdown(input)) +} + +fun TextView.setParsedMarkdown(input: Spanned?) { + if (input != null) { + MarkdownParser.markwon?.setParsedMarkdown(this, input) + } else { + text = null + } +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt index e69f3b603..24ba2e679 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt @@ -7,12 +7,10 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.models.responses.TaskDirection import com.habitrpg.android.habitica.models.tasks.Task -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser -import com.habitrpg.android.habitica.ui.helpers.bindColor -import com.habitrpg.android.habitica.ui.helpers.bindOptionalView -import com.habitrpg.android.habitica.ui.helpers.bindView +import com.habitrpg.android.habitica.ui.helpers.* import com.habitrpg.android.habitica.ui.viewHolders.BindableViewHolder import com.habitrpg.android.habitica.ui.views.EllipsisTextView +import io.noties.markwon.utils.NoCopySpannableFactory import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.Action @@ -73,6 +71,8 @@ abstract class BaseTaskViewHolder constructor(itemView: View, var scoreTaskFunc: itemView.setOnClickListener { onClick(it) } itemView.isClickable = true + titleTextView.setOnClickListener { onClick(it) } + notesTextView?.setOnClickListener { onClick(it) } errorIconView?.setOnClickListener { errorButtonClicked?.run()} //Re enable when we find a way to only react when a link is tapped. @@ -115,9 +115,10 @@ abstract class BaseTaskViewHolder constructor(itemView: View, var scoreTaskFunc: if (canContainMarkdown()) { if (data.parsedText != null) { - titleTextView.text = data.parsedText + titleTextView.setParsedMarkdown(data.parsedText) } else { titleTextView.text = data.text + titleTextView.setSpannableFactory(NoCopySpannableFactory.getInstance()); if (data.text.isNotEmpty()) { Single.just(data.text) .map { MarkdownParser.parseMarkdown(it) } @@ -125,13 +126,14 @@ abstract class BaseTaskViewHolder constructor(itemView: View, var scoreTaskFunc: .observeOn(AndroidSchedulers.mainThread()) .subscribe(Consumer{ parsedText -> data.parsedText = parsedText - titleTextView.text = parsedText + titleTextView.setParsedMarkdown(parsedText) }, RxErrorHandler.handleEmptyError()) } if (data.parsedNotes != null) { - notesTextView?.text = data.parsedNotes + notesTextView?.setParsedMarkdown(data.parsedText) } else { notesTextView?.text = data.notes + notesTextView?.setSpannableFactory(NoCopySpannableFactory.getInstance()); data.notes?.let {notes -> if (notes.isEmpty()) { return@let @@ -141,8 +143,8 @@ abstract class BaseTaskViewHolder constructor(itemView: View, var scoreTaskFunc: .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(Consumer { parsedNotes -> - data.parsedNotes = parsedNotes notesTextView?.text = parsedNotes + notesTextView?.setParsedMarkdown(parsedNotes) }, RxErrorHandler.handleEmptyError()) } } @@ -178,10 +180,6 @@ abstract class BaseTaskViewHolder constructor(itemView: View, var scoreTaskFunc: } override fun onClick(v: View) { - if (v != itemView || openTaskDisabled) { - return - } - task?.let { openTaskFunc(it) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt index 28358a747..7d0c1ed8b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt @@ -25,8 +25,8 @@ import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.inventory.QuestProgressCollect import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils -import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView +import com.habitrpg.android.habitica.ui.helpers.setMarkdown import com.habitrpg.android.habitica.ui.views.* import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import io.reactivex.Observable @@ -165,7 +165,7 @@ class QuestProgressView : LinearLayout { setCollectionViews(collection, quest) } } - questDescriptionView.text = MarkdownParser.parseMarkdown(quest.notes) + questDescriptionView.setMarkdown(quest.notes) DataBindingUtils.loadImage(questImageView, "quest_"+quest.key, "gif") DataBindingUtils.loadImage(questFlourishesImageView, "quest_"+quest.key+"_flourishes") val lightColor = quest.colors?.lightColor