From a1855b63c46dd439767e481541e7a19802b4314a Mon Sep 17 00:00:00 2001 From: Jason Goveas <95233082+JustAGhost23@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:09:41 +0530 Subject: [PATCH] Fix HTML Text rendering in course section and discussion (#357) * Store HTML text in models to be used in HTML Text Field Also included is regex to append token to every URL present in the HTML text, as CMS requires for the token to be present as it starts downloading the resource from the URL. * Render HTML text properly by using parseText from HTMLTextView --- .../delegates/CourseSectionDelegate.kt | 2 +- .../bphc/cms/fragments/DiscussionFragment.kt | 5 +- .../crux/bphc/cms/fragments/ForumFragment.kt | 3 +- .../bphc/cms/models/course/CourseSection.kt | 40 ++++- .../crux/bphc/cms/models/course/Module.kt | 68 ++++++--- .../crux/bphc/cms/models/forum/Discussion.kt | 141 ++++++++++-------- 6 files changed, 163 insertions(+), 96 deletions(-) diff --git a/app/src/main/java/crux/bphc/cms/adapters/delegates/CourseSectionDelegate.kt b/app/src/main/java/crux/bphc/cms/adapters/delegates/CourseSectionDelegate.kt index 6379875e..c961517c 100644 --- a/app/src/main/java/crux/bphc/cms/adapters/delegates/CourseSectionDelegate.kt +++ b/app/src/main/java/crux/bphc/cms/adapters/delegates/CourseSectionDelegate.kt @@ -38,7 +38,7 @@ class CourseSectionDelegate(activity: Activity) : AdapterDelegate = RealmList(), var courseId: Int = 0, ) : RealmObject(), CourseContent { @@ -23,16 +26,33 @@ open class CourseSection( @SerializedName("name") var name: String = name get() { return HtmlCompat.fromHtml(field, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() - .trim { it <= ' ' } + .trim { it <= ' ' } + } + + @SerializedName("summary") var summary: String = summary + get() { + val pattern: Pattern = Pattern.compile(URL_REGEX) + val matcher: Matcher = pattern.matcher(field) + + val summaryBuffer = StringBuffer(field.length) + + while (matcher.find()) { + val foundLink = matcher.group(1) + val replaceWith = "" + matcher.appendReplacement(summaryBuffer, replaceWith) + } + matcher.appendTail(summaryBuffer) + + return summaryBuffer.toString().trim { it <= ' ' } } fun deepCopy(): CourseSection = CourseSection( - id, - name, - sectionNum, - summary, - RealmList(*modules.map { it.deepCopy() }.toTypedArray()), - courseId, + id, + name, + sectionNum, + summary, + RealmList(*modules.map { it.deepCopy() }.toTypedArray()), + courseId, ) override fun equals(other: Any?): Boolean { @@ -42,4 +62,8 @@ open class CourseSection( override fun hashCode(): Int { return id } + + companion object { + private const val URL_REGEX = "" + } } \ No newline at end of file diff --git a/app/src/main/java/crux/bphc/cms/models/course/Module.kt b/app/src/main/java/crux/bphc/cms/models/course/Module.kt index 85be0b56..7e6cc2b2 100644 --- a/app/src/main/java/crux/bphc/cms/models/course/Module.kt +++ b/app/src/main/java/crux/bphc/cms/models/course/Module.kt @@ -4,28 +4,31 @@ import androidx.core.text.HtmlCompat import com.google.gson.annotations.SerializedName import crux.bphc.cms.R import crux.bphc.cms.interfaces.CourseContent +import crux.bphc.cms.models.UserAccount import crux.bphc.cms.utils.FileUtils import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import java.util.* +import java.util.regex.Matcher +import java.util.regex.Pattern /** * @author Harshit Agarwal (16-Dec-2016) * @author Abhijeet Viswa */ open class Module( - @PrimaryKey @SerializedName("id") var id: Int = 0, - @SerializedName("instance") var instance: Int = 0, - name: String = "", - @SerializedName("url") var url: String = "", - @SerializedName("modicon") var modIcon: String = "", - @SerializedName("modname") private var modName: String = "", - @SerializedName("description") var description: String = "", - @SerializedName("contents") var contents: RealmList = RealmList(), - var courseSectionId: Int = 0, - var isUnread: Boolean = false, + @PrimaryKey @SerializedName("id") var id: Int = 0, + @SerializedName("instance") var instance: Int = 0, + name: String = "", + @SerializedName("url") var url: String = "", + @SerializedName("modicon") var modIcon: String = "", + @SerializedName("modname") private var modName: String = "", + description: String = "", + @SerializedName("contents") var contents: RealmList = RealmList(), + var courseSectionId: Int = 0, + var isUnread: Boolean = false, ) : RealmObject(), CourseContent { enum class Type { RESOURCE, FORUM, LABEL, ASSIGNMENT, FOLDER, QUIZ, URL, PAGE, DEFAULT, BOOK @@ -34,7 +37,24 @@ open class Module( @SerializedName("name") var name: String = name get() { return HtmlCompat.fromHtml(field, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() - .trim{ it <= ' ' } + .trim{ it <= ' ' } + } + + @SerializedName("description") var description: String = description + get() { + val pattern: Pattern = Pattern.compile(URL_REGEX) + val matcher: Matcher = pattern.matcher(field) + + val descriptionBuffer = StringBuffer(field.length) + + while (matcher.find()) { + val foundLink = matcher.group(1) + val replaceWith = "" + matcher.appendReplacement(descriptionBuffer, replaceWith) + } + matcher.appendTail(descriptionBuffer) + + return descriptionBuffer.toString().trim { it <= ' ' } } @Ignore var modType: Type = Type.DEFAULT @@ -67,20 +87,20 @@ open class Module( } fun deepCopy(): Module = Module( - id, - instance, - name, - url, - modIcon, - modName, - description, - RealmList(*contents.map { it.copy() }.toTypedArray()), - courseSectionId, - isUnread, + id, + instance, + name, + url, + modIcon, + modName, + description, + RealmList(*contents.map { it.copy() }.toTypedArray()), + courseSectionId, + isUnread, ) private fun inferModuleTypeFromModuleName(): Type { - return when (modName.toLowerCase(Locale.ROOT)) { + return when (modName.lowercase(Locale.ROOT)) { "resource" -> Type.RESOURCE "forum" -> Type.FORUM "label" -> Type.LABEL @@ -100,4 +120,8 @@ open class Module( override fun hashCode(): Int { return id } + + companion object { + private const val URL_REGEX = "" + } } \ No newline at end of file diff --git a/app/src/main/java/crux/bphc/cms/models/forum/Discussion.kt b/app/src/main/java/crux/bphc/cms/models/forum/Discussion.kt index 07eaef05..016bb592 100644 --- a/app/src/main/java/crux/bphc/cms/models/forum/Discussion.kt +++ b/app/src/main/java/crux/bphc/cms/models/forum/Discussion.kt @@ -2,9 +2,13 @@ package crux.bphc.cms.models.forum import androidx.core.text.HtmlCompat import com.google.gson.annotations.SerializedName +import crux.bphc.cms.models.UserAccount.token import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import java.util.regex.Matcher +import java.util.regex.Pattern + /** * Model class to represent a Discussion. In Moodle speak, a Discussion is a @@ -18,78 +22,93 @@ import io.realm.annotations.PrimaryKey * @author Abhijeet Viswa */ open class Discussion( - /** - * The ID of the initial post in this discussion - */ - @PrimaryKey @SerializedName("id") var id: Int = 0, - - /** - * Discussion of the ID. Uniquely identifies a thread of discussions. Used - * when querying the replies in a thread. - */ - @SerializedName("discussion") var discussionId:Int = 0, - - /** - * The name i.e subject of the thread. - */ - @SerializedName("name") var name: String = "", - - /** - * Unix epoch when this was last modified - */ - @SerializedName("timemodified") var timeModified: Int = 0, - - /** - * The parent of this discussion. Equals the `id` if a parent - * exists. Else, equals to 0. - */ - @SerializedName("parent") var parent:Int = 0, - - subject: String = "" , - - message: String = "", - - /** - * List of attachments of this post - */ - @SerializedName("attachments") var attachments: RealmList = RealmList(), - - /** - * Name of the user who made this discussion. - */ - @SerializedName("userfullname") var userFullName: String = "", - - /** - * Url to the user's profile picture - */ - @SerializedName("userpictureurl") var userPictureUrl: String = "", - - /** - * If the root discussion i.e the thread has been pinned or not. - */ - @SerializedName("pinned") var isPinned:Boolean = false, - - /** - * The id of the Forum instance that the Discussion is a part of. This is not - * a part of the [crux.bphc.cms.network.MoodleServices.getForumDiscussions]. - */ - var forumId:Int = 0, + /** + * The ID of the initial post in this discussion + */ + @PrimaryKey @SerializedName("id") var id: Int = 0, + + /** + * Discussion of the ID. Uniquely identifies a thread of discussions. Used + * when querying the replies in a thread. + */ + @SerializedName("discussion") var discussionId:Int = 0, + + /** + * The name i.e subject of the thread. + */ + @SerializedName("name") var name: String = "", + + /** + * Unix epoch when this was last modified + */ + @SerializedName("timemodified") var timeModified: Int = 0, + + /** + * The parent of this discussion. Equals the `id` if a parent + * exists. Else, equals to 0. + */ + @SerializedName("parent") var parent:Int = 0, + + subject: String = "" , + + message: String = "", + + /** + * List of attachments of this post + */ + @SerializedName("attachments") var attachments: RealmList = RealmList(), + + /** + * Name of the user who made this discussion. + */ + @SerializedName("userfullname") var userFullName: String = "", + + /** + * Url to the user's profile picture + */ + @SerializedName("userpictureurl") var userPictureUrl: String = "", + + /** + * If the root discussion i.e the thread has been pinned or not. + */ + @SerializedName("pinned") var isPinned:Boolean = false, + + /** + * The id of the Forum instance that the Discussion is a part of. This is not + * a part of the [crux.bphc.cms.network.MoodleServices.getForumDiscussions]. + */ + var forumId:Int = 0, ) : RealmObject() { var subject: String = subject get() { return HtmlCompat.fromHtml(field, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() - .trim { it < ' ' } + .trim { it < ' ' } } private set /** * The content of this discussion. */ - @SerializedName("message") var message: String = "" + @SerializedName("message") var message: String = message get() { - return HtmlCompat.fromHtml(field, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() - .trim { it < ' ' } + val pattern: Pattern = Pattern.compile(URL_REGEX) + val matcher: Matcher = pattern.matcher(field) + + val messageBuffer = StringBuffer(field.length) + + while (matcher.find()) { + val foundLink = matcher.group(1) + val replaceWith = "" + matcher.appendReplacement(messageBuffer, replaceWith) + } + matcher.appendTail(messageBuffer) + + return messageBuffer.toString().trim { it <= ' ' } } private set -} + + companion object { + private const val URL_REGEX = "" + } +} \ No newline at end of file