More coroutinification

This commit is contained in:
Matthieu 2020-12-29 22:14:32 +01:00
parent 3a91b02e55
commit 1254a3566d
9 changed files with 276 additions and 216 deletions

View File

@ -12,10 +12,13 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.text.toSpanned import androidx.core.text.toSpanned
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope
import com.h.pixeldroid.R import com.h.pixeldroid.R
import com.h.pixeldroid.utils.api.PixelfedAPI import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account.Companion.getAccountFromId import com.h.pixeldroid.utils.api.objects.Account.Companion.openAccountFromId
import com.h.pixeldroid.utils.api.objects.Mention import com.h.pixeldroid.utils.api.objects.Mention
import kotlinx.coroutines.coroutineScope
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
import java.text.ParseException import java.text.ParseException
@ -51,7 +54,8 @@ fun parseHTMLText(
mentions: List<Mention>?, mentions: List<Mention>?,
api : PixelfedAPI, api : PixelfedAPI,
context: Context, context: Context,
credential: String credential: String,
lifecycleScope: LifecycleCoroutineScope
) : Spanned { ) : Spanned {
//Convert text to spannable //Convert text to spannable
val content = fromHtml(text) val content = fromHtml(text)
@ -103,7 +107,9 @@ fun parseHTMLText(
override fun onClick(widget: View) { override fun onClick(widget: View) {
Log.e("MENTION", "CLICKED") Log.e("MENTION", "CLICKED")
//Retrieve the account for the given profile //Retrieve the account for the given profile
getAccountFromId(accountId, api, context, credential) lifecycleScope.launchWhenCreated {
openAccountFromId(accountId, api, context, credential)
}
} }
} }
} }

View File

@ -3,6 +3,7 @@ package com.h.pixeldroid.posts
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.R import com.h.pixeldroid.R
import com.h.pixeldroid.utils.api.objects.Report import com.h.pixeldroid.utils.api.objects.Report
import com.h.pixeldroid.utils.api.objects.Status import com.h.pixeldroid.utils.api.objects.Status
@ -10,7 +11,9 @@ import com.h.pixeldroid.utils.BaseActivity
import kotlinx.android.synthetic.main.activity_report.* import kotlinx.android.synthetic.main.activity_report.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response import retrofit2.Response
import java.io.IOException
class ReportActivity : BaseActivity() { class ReportActivity : BaseActivity() {
@ -37,33 +40,34 @@ class ReportActivity : BaseActivity() {
val accessToken = user?.accessToken.orEmpty() val accessToken = user?.accessToken.orEmpty()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), textInputLayout.editText?.text.toString())
.enqueue(object : Callback<Report> {
override fun onResponse(
call: Call<Report>,
response: Response<Report>
) {
if (response.body() == null || !response.isSuccessful) {
textInputLayout.error = getString(R.string.report_error)
reportButton.visibility = View.VISIBLE
textInputLayout.editText?.isEnabled = true
reportProgressBar.visibility = View.GONE
} else {
reportProgressBar.visibility = View.GONE
reportButton.isEnabled = false
reportButton.text = getString(R.string.reported)
reportButton.visibility = View.VISIBLE
}
}
override fun onFailure(call: Call<Report>, t: Throwable) { lifecycleScope.launchWhenCreated {
Log.e("REPORT:", t.toString()) try {
} api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), textInputLayout.editText?.text.toString())
})
reportStatus(true)
} catch (exception: IOException) {
reportStatus(false)
} catch (exception: HttpException) {
reportStatus(false)
}
}
} }
} }
private fun reportStatus(success: Boolean){
if(success){
reportProgressBar.visibility = View.GONE
reportButton.isEnabled = false
reportButton.text = getString(R.string.reported)
reportButton.visibility = View.VISIBLE
} else {
textInputLayout.error = getString(R.string.report_error)
reportButton.visibility = View.VISIBLE
textInputLayout.editText?.isEnabled = true
reportProgressBar.visibility = View.GONE
}
}
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {
onBackPressed() onBackPressed()

View File

@ -206,12 +206,24 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
}.attach() }.attach()
} }
private fun setDescription(rootView: View, api: PixelfedAPI, credential: String) { private fun setDescription(
rootView: View,
api: PixelfedAPI,
credential: String,
lifecycleScope: LifecycleCoroutineScope
) {
rootView.findViewById<TextView>(R.id.description).apply { rootView.findViewById<TextView>(R.id.description).apply {
if (status?.content.isNullOrBlank()) { if (status?.content.isNullOrBlank()) {
visibility = View.GONE visibility = View.GONE
} else { } else {
text = parseHTMLText(status?.content.orEmpty(), status?.mentions, api, rootView.context, credential) text = parseHTMLText(
status?.content.orEmpty(),
status?.mentions,
api,
rootView.context,
credential,
lifecycleScope
)
movementMethod = LinkMovementMethod.getInstance() movementMethod = LinkMovementMethod.getInstance()
} }
} }
@ -222,7 +234,7 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val credential = "Bearer ${user.accessToken}" val credential = "Bearer ${user.accessToken}"
//Set the special HTML text //Set the special HTML text
setDescription(holder.view, api, credential) setDescription(holder.view, api, credential, lifecycleScope)
//Activate onclickListeners //Activate onclickListeners
activateLiker( activateLiker(

View File

@ -10,7 +10,9 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi import androidx.paging.ExperimentalPagingApi
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -53,7 +55,10 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
// get the view model // get the view model
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory(db, db.notificationDao(), NotificationsRemoteMediator(apiHolder, db))) viewModel = ViewModelProvider(
this,
ViewModelFactory(db, db.notificationDao(), NotificationsRemoteMediator(apiHolder, db))
)
.get(FeedViewModel::class.java) as FeedViewModel<Notification> .get(FeedViewModel::class.java) as FeedViewModel<Notification>
launch() launch()
@ -62,149 +67,201 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
return view return view
} }
}
/** /**
* View Holder for a [Notification] RecyclerView list item. * View Holder for a [Notification] RecyclerView list item.
*/ */
class NotificationViewHolder(view: View) : RecyclerView.ViewHolder(view) { class NotificationViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val notificationType: TextView = view.notification_type private val notificationType: TextView = view.notification_type
private val notificationTime: TextView = view.notification_time private val notificationTime: TextView = view.notification_time
private val postDescription: TextView = view.notification_post_description private val postDescription: TextView = view.notification_post_description
private val avatar: ImageView = view.notification_avatar private val avatar: ImageView = view.notification_avatar
private val photoThumbnail: ImageView = view.notification_photo_thumbnail private val photoThumbnail: ImageView = view.notification_photo_thumbnail
private var notification: Notification? = null private var notification: Notification? = null
init { init {
itemView.setOnClickListener { itemView.setOnClickListener {
notification?.openActivity() notification?.openActivity()
} }
} }
private fun Notification.openActivity() { private fun Notification.openActivity() {
val intent: Intent = val intent: Intent =
when (type){ when (type) {
Notification.NotificationType.mention, Notification.NotificationType.favourite, Notification.NotificationType.mention, Notification.NotificationType.favourite,
Notification.NotificationType.poll, Notification.NotificationType.reblog -> { Notification.NotificationType.poll, Notification.NotificationType.reblog -> {
openPostFromNotification() openPostFromNotification()
}
Notification.NotificationType.follow -> {
Intent(itemView.context, ProfileActivity::class.java).apply {
putExtra(Account.ACCOUNT_TAG, account)
}
}
null -> return //TODO show an error here?
}
itemView.context.startActivity(intent)
}
private fun Notification.openPostFromNotification(): Intent =
Intent(itemView.context, PostActivity::class.java).apply {
putExtra(Status.POST_TAG, status)
} }
Notification.NotificationType.follow -> {
Intent(itemView.context, ProfileActivity::class.java).apply {
putExtra(Account.ACCOUNT_TAG, account) private fun setNotificationType(
type: Notification.NotificationType,
username: String,
textView: TextView
) {
val context = textView.context
val (format: String, drawable: Drawable?) = when (type) {
Notification.NotificationType.follow -> {
getStringAndDrawable(
context,
R.string.followed_notification,
R.drawable.ic_follow
)
}
Notification.NotificationType.mention -> {
getStringAndDrawable(
context,
R.string.mention_notification,
R.drawable.mention_at_24dp
)
}
Notification.NotificationType.reblog -> {
getStringAndDrawable(
context,
R.string.shared_notification,
R.drawable.ic_reblog_blue
)
}
Notification.NotificationType.favourite -> {
getStringAndDrawable(
context,
R.string.liked_notification,
R.drawable.ic_like_full
)
}
Notification.NotificationType.poll -> {
getStringAndDrawable(context, R.string.poll_notification, R.drawable.poll)
} }
} }
null -> return //TODO show an error here? textView.text = format.format(username)
} textView.setCompoundDrawablesWithIntrinsicBounds(
itemView.context.startActivity(intent) drawable, null, null, null
}
private fun Notification.openPostFromNotification(): Intent =
Intent(itemView.context, PostActivity::class.java).apply {
putExtra(Status.POST_TAG, status)
}
private fun setNotificationType(type: Notification.NotificationType,
username: String,
textView: TextView
){
val context = textView.context
val (format: String, drawable: Drawable?) = when(type) {
Notification.NotificationType.follow -> {
getStringAndDrawable(context, R.string.followed_notification, R.drawable.ic_follow)
}
Notification.NotificationType.mention -> {
getStringAndDrawable(context, R.string.mention_notification, R.drawable.mention_at_24dp)
}
Notification.NotificationType.reblog -> {
getStringAndDrawable(context, R.string.shared_notification, R.drawable.ic_reblog_blue)
}
Notification.NotificationType.favourite -> {
getStringAndDrawable(context, R.string.liked_notification, R.drawable.ic_like_full)
}
Notification.NotificationType.poll -> {
getStringAndDrawable(context, R.string.poll_notification, R.drawable.poll)
}
}
textView.text = format.format(username)
textView.setCompoundDrawablesWithIntrinsicBounds(
drawable,null,null,null
)
}
private fun getStringAndDrawable(context: Context, stringToFormat: Int, drawable: Int): Pair<String, Drawable?>
= Pair(context.getString(stringToFormat), ContextCompat.getDrawable(context, drawable))
fun bind(notification: Notification?, api: PixelfedAPI, accessToken: String) {
this.notification = notification
Glide.with(itemView).load(notification?.account?.avatar_static).circleCrop().into(avatar)
val previewUrl = notification?.status?.media_attachments?.getOrNull(0)?.preview_url
if(!previewUrl.isNullOrBlank()){
Glide.with(itemView).load(previewUrl)
.placeholder(R.drawable.ic_picture_fallback).into(photoThumbnail)
} else{
photoThumbnail.visibility = View.GONE
}
notification?.type?.let { notification.account?.username?.let { username -> setNotificationType(it, username, notificationType) } }
notification?.created_at?.let { setTextViewFromISO8601(it, notificationTime, false, itemView.context) }
//Convert HTML to clickable text
postDescription.text =
parseHTMLText(
notification?.status?.content ?: "",
notification?.status?.mentions,
api,
itemView.context,
"Bearer $accessToken"
) )
}
companion object {
fun create(parent: ViewGroup): NotificationViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_notifications, parent, false)
return NotificationViewHolder(view)
} }
}
} private fun getStringAndDrawable(
context: Context,
stringToFormat: Int,
drawable: Int
): Pair<String, Drawable?> =
Pair(context.getString(stringToFormat), ContextCompat.getDrawable(context, drawable))
class NotificationsAdapter(private val apiHolder: PixelfedAPIHolder, private val db: AppDatabase) : PagingDataAdapter<Notification, RecyclerView.ViewHolder>( fun bind(
UIMODEL_COMPARATOR notification: Notification?,
) { api: PixelfedAPI,
accessToken: String,
lifecycleScope: LifecycleCoroutineScope
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { this.notification = notification
return NotificationViewHolder.create(parent)
}
override fun getItemViewType(position: Int): Int { Glide.with(itemView).load(notification?.account?.avatar_static).circleCrop()
return R.layout.fragment_notifications .into(avatar)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val previewUrl = notification?.status?.media_attachments?.getOrNull(0)?.preview_url
val uiModel = getItem(position) if (!previewUrl.isNullOrBlank()) {
uiModel.let { Glide.with(itemView).load(previewUrl)
(holder as NotificationViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db.userDao().getActiveUser()!!.accessToken) .placeholder(R.drawable.ic_picture_fallback).into(photoThumbnail)
} else {
photoThumbnail.visibility = View.GONE
}
notification?.type?.let {
notification.account?.username?.let { username ->
setNotificationType(
it,
username,
notificationType
)
}
}
notification?.created_at?.let {
setTextViewFromISO8601(
it,
notificationTime,
false,
itemView.context
)
}
//Convert HTML to clickable text
postDescription.text =
parseHTMLText(
notification?.status?.content ?: "",
notification?.status?.mentions,
api,
itemView.context,
"Bearer $accessToken",
lifecycleScope
)
}
companion object {
fun create(parent: ViewGroup): NotificationViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_notifications, parent, false)
return NotificationViewHolder(view)
}
} }
} }
companion object {
private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback<Notification>() { inner class NotificationsAdapter(
override fun areItemsTheSame(oldItem: Notification, newItem: Notification): Boolean { private val apiHolder: PixelfedAPIHolder,
private val db: AppDatabase
) : PagingDataAdapter<Notification, RecyclerView.ViewHolder>(
object : DiffUtil.ItemCallback<Notification>() {
override fun areItemsTheSame(
oldItem: Notification,
newItem: Notification
): Boolean {
return oldItem.id == newItem.id return oldItem.id == newItem.id
} }
override fun areContentsTheSame(oldItem: Notification, newItem: Notification): Boolean = override fun areContentsTheSame(
oldItem: Notification,
newItem: Notification
): Boolean =
oldItem == newItem oldItem == newItem
} }
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return NotificationViewHolder.create(parent)
}
override fun getItemViewType(position: Int): Int {
return R.layout.fragment_notifications
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position)
uiModel.let {
(holder as NotificationViewHolder).bind(
it,
apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
lifecycleScope
)
}
}
} }
} }

View File

@ -107,23 +107,17 @@ class ProfileActivity : BaseActivity() {
} }
private fun getAndSetAccount(id: String){ private fun getAndSetAccount(id: String){
pixelfedAPI.getAccount("Bearer $accessToken", id) lifecycleScope.launchWhenCreated {
.enqueue(object : Callback<Account> { val account = try{
override fun onResponse(call: Call<Account>, response: Response<Account>) { pixelfedAPI.getAccount("Bearer $accessToken", id)
if (response.code() == 200) { } catch (exception: IOException) {
val account = response.body()!! Log.e("ProfileActivity:", exception.toString())
return@launchWhenCreated showError()
setContent(account) } catch (exception: HttpException) {
} else { return@launchWhenCreated showError()
showError() }
} setContent(account)
} }
override fun onFailure(call: Call<Account>, t: Throwable) {
Log.e("ProfileActivity:", t.toString())
showError()
}
})
} }
private fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){ private fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){
@ -151,7 +145,8 @@ class ProfileActivity : BaseActivity() {
val description = findViewById<TextView>(R.id.descriptionTextView) val description = findViewById<TextView>(R.id.descriptionTextView)
description.text = parseHTMLText( description.text = parseHTMLText(
account.note ?: "", emptyList(), pixelfedAPI, account.note ?: "", emptyList(), pixelfedAPI,
applicationContext, "Bearer $accessToken" applicationContext, "Bearer $accessToken",
lifecycleScope
) )
val accountName = findViewById<TextView>(R.id.accountNameTextView) val accountName = findViewById<TextView>(R.id.accountNameTextView)

View File

@ -12,6 +12,7 @@ import android.widget.*
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@ -32,7 +33,9 @@ import com.mikepenz.iconics.utils.paddingDp
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response import retrofit2.Response
import java.io.IOException
/** /**
* This fragment lets you search and use Pixelfed's Discover feature * This fragment lets you search and use Pixelfed's Discover feature
@ -107,25 +110,17 @@ class SearchDiscoverFragment : BaseFragment() {
private fun getDiscover() { private fun getDiscover() {
api.discover("Bearer $accessToken") lifecycleScope.launchWhenCreated {
.enqueue(object : Callback<DiscoverPosts> { try {
val discoverPosts = api.discover("Bearer $accessToken")
override fun onFailure(call: Call<DiscoverPosts>, t: Throwable) { adapter.addPosts(discoverPosts.posts)
showError() showError(show = false)
Log.e("SearchDiscoverFragment:", t.toString()) } catch (exception: IOException) {
} showError()
} catch (exception: HttpException) {
override fun onResponse(call: Call<DiscoverPosts>, response: Response<DiscoverPosts>) { showError()
if(response.code() == 200) { }
val discoverPosts = response.body()!! }
adapter.addPosts(discoverPosts.posts)
showError(show = false)
}
else {
showError()
}
}
})
} }
/** /**

View File

@ -245,10 +245,10 @@ interface PixelfedAPI {
) : Response<List<Account>> ) : Response<List<Account>>
@GET("/api/v1/accounts/{id}") @GET("/api/v1/accounts/{id}")
fun getAccount( suspend fun getAccount(
@Header("Authorization") authorization: String, @Header("Authorization") authorization: String,
@Path("id") accountId : String @Path("id") accountId : String
): Call<Account> ): Account
@GET("/api/v1/statuses/{id}") @GET("/api/v1/statuses/{id}")
suspend fun getStatus( suspend fun getStatus(
@ -266,19 +266,19 @@ interface PixelfedAPI {
// get discover // get discover
@GET("/api/v2/discover/posts") @GET("/api/v2/discover/posts")
fun discover( suspend fun discover(
@Header("Authorization") authorization: String @Header("Authorization") authorization: String
) : Call<DiscoverPosts> ) : DiscoverPosts
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v1/reports") @POST("/api/v1/reports")
@JvmSuppressWildcards @JvmSuppressWildcards
fun report( suspend fun report(
@Header("Authorization") authorization: String, @Header("Authorization") authorization: String,
@Field("account_id") account_id: String, @Field("account_id") account_id: String,
@Field("status_ids") status_ids: List<Status>, @Field("status_ids") status_ids: List<Status>,
@Field("comment") comment: String, @Field("comment") comment: String,
@Field("forward") forward: Boolean = true @Field("forward") forward: Boolean = true
) : Call<Report> ) : Report
} }

View File

@ -6,9 +6,14 @@ import android.util.Log
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import com.h.pixeldroid.profile.ProfileActivity import com.h.pixeldroid.profile.ProfileActivity
import com.h.pixeldroid.utils.api.PixelfedAPI import com.h.pixeldroid.utils.api.PixelfedAPI
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response import retrofit2.Response
import java.io.IOException
import java.io.Serializable import java.io.Serializable
/* /*
@ -52,28 +57,19 @@ data class Account(
/** /**
* @brief Opens an activity of the profile with the given id * @brief Opens an activity of the profile with the given id
*/ */
fun getAccountFromId(id: String, api : PixelfedAPI, context: Context, credential: String) { suspend fun openAccountFromId(id: String, api : PixelfedAPI, context: Context, credential: String) {
Log.e("ACCOUNT_ID", id) val account = try {
api.getAccount(credential, id).enqueue( object : Callback<Account> { api.getAccount(credential, id)
override fun onFailure(call: Call<Account>, t: Throwable) { } catch (exception: IOException) {
Log.e("GET ACCOUNT ERROR", t.toString()) Log.e("GET ACCOUNT ERROR", exception.toString())
return
} catch (exception: HttpException) {
Log.e("ERROR CODE", exception.code().toString())
return
} }
//Open the account page in a separate activity
account.openProfile(context)
override fun onResponse(
call: Call<Account>,
response: Response<Account>
) {
if(response.code() == 200) {
val account = response.body()!!
//Open the account page in a separate activity
account.openProfile(context)
} else {
Log.e("ERROR CODE", response.code().toString())
}
}
})
} }
} }

View File

@ -77,11 +77,6 @@ open class Status(
fun getProfilePicUrl() : String? = account?.avatar fun getProfilePicUrl() : String? = account?.avatar
fun getPostPreviewURL() : String? = media_attachments?.firstOrNull()?.preview_url fun getPostPreviewURL() : String? = media_attachments?.firstOrNull()?.preview_url
/**
* @brief returns the parsed version of the HTML description
*/
private fun getDescription(api: PixelfedAPI, context: Context, credential: String) : Spanned =
parseHTMLText(content ?: "", mentions, api, context, credential)
fun getNLikes(context: Context) : CharSequence { fun getNLikes(context: Context) : CharSequence {
return context.getString(R.string.likes).format(favourites_count.toString()) return context.getString(R.string.likes).format(favourites_count.toString())