Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove ReplacementSpan, display diffs using CharacterStyle #3431

Merged
merged 22 commits into from
Jun 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fd93f11
Show the difference between edited statuses
nikclayton Feb 11, 2023
8327cc8
Merge branch 'develop' into 3306-diff-status-edits
nikclayton Mar 1, 2023
1f40e77
Update diffx to 1.1.1
nikclayton Mar 1, 2023
1a85ce5
Style edited strings with Android spans
nikclayton Mar 1, 2023
472e774
Lint
nikclayton Mar 1, 2023
97ae6c2
Move colors in to theme_colors.xml
nikclayton Mar 2, 2023
eb42cbf
Draw a roundrect for the backoround, add start/end padding
nikclayton Mar 2, 2023
3a9c72d
Catch exceptions when parsing XML
nikclayton Mar 2, 2023
530ed78
Move sorting in to Dispatchers.Default coroutine
nikclayton Mar 2, 2023
e9e182d
Merge branch 'develop' into 3306-diff-status-edits
nikclayton Mar 2, 2023
0477f0d
Scope the loader type
nikclayton Mar 2, 2023
b541e73
Remove alpha
nikclayton Mar 10, 2023
fce4a01
Re-order edits, newest status at the bottom
nikclayton Mar 10, 2023
e73c5d3
Remove ReplacementSpan use
nikclayton Mar 10, 2023
1c0a59d
Adjust display of edited statuses
nikclayton Mar 10, 2023
f2acc9c
Merge branch 'develop' into 3306-diff-status-edits
nikclayton Mar 10, 2023
34631e7
Merge branch 'develop' into 3306-diff-status-edits
nikclayton Mar 11, 2023
838966b
Update app/src/main/java/com/keylesspalace/tusky/components/viewthrea…
nikclayton Mar 12, 2023
a135786
Merge remote-tracking branch 'origin/develop' into 3306-diff-status-e…
nikclayton Jun 1, 2023
2a2fd68
Show newest first
nikclayton Jun 1, 2023
1fdeef8
Merge branch '3306-diff-status-edits' of https://github.com/nikclayto…
nikclayton Jun 1, 2023
90923dc
Merge branch 'develop' into 3306-diff-status-edits
nikclayton Jun 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.keylesspalace.tusky.components.viewthread.edits

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface.DEFAULT_BOLD
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
Expand All @@ -11,7 +9,9 @@ import android.text.Html
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.ReplacementSpan
import android.text.TextPaint
import android.text.style.CharacterStyle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -33,11 +33,9 @@ import com.keylesspalace.tusky.util.aspectRatios
import com.keylesspalace.tusky.util.decodeBlurHash
import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.setClickableText
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.unicodeWrap
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.viewdata.toViewData
import org.xml.sax.XMLReader
Expand All @@ -52,13 +50,28 @@ class ViewEditsAdapter(

private val absoluteTimeFormatter = AbsoluteTimeFormatter()

/** Size of large text in this theme, in px */
var largeTextSizePx: Float = 0f

/** Size of medium text in this theme, in px */
var mediumTextSizePx: Float = 0f

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BindingHolder<ItemStatusEditBinding> {
val binding = ItemStatusEditBinding.inflate(LayoutInflater.from(parent.context), parent, false)

binding.statusEditMediaPreview.clipToOutline = true

val typedValue = TypedValue()
val context = binding.root.context
val displayMetrics = context.resources.displayMetrics
context.theme.resolveAttribute(R.attr.status_text_large, typedValue, true)
largeTextSizePx = typedValue.getDimension(displayMetrics)
context.theme.resolveAttribute(R.attr.status_text_medium, typedValue, true)
mediumTextSizePx = typedValue.getDimension(displayMetrics)

return BindingHolder(binding)
}

Expand All @@ -69,24 +82,26 @@ class ViewEditsAdapter(

val context = binding.root.context

val avatarRadius: Int = context.resources
.getDimensionPixelSize(R.dimen.avatar_radius_48dp)

loadAvatar(edit.account.avatar, binding.statusEditAvatar, avatarRadius, animateAvatars)

val infoStringRes = if (position == edits.size - 1) {
val infoStringRes = if (position == edits.lastIndex) {
R.string.status_created_info
} else {
R.string.status_edit_info
}

// Show the most recent version of the status using large text to make it clearer for
// the user, and for similarity with thread view.
val variableTextSize = if (position == edits.lastIndex) {
mediumTextSizePx
} else {
largeTextSizePx
}
binding.statusEditContentWarningDescription.setTextSize(TypedValue.COMPLEX_UNIT_PX, variableTextSize)
binding.statusEditContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, variableTextSize)
binding.statusEditMediaSensitivity.setTextSize(TypedValue.COMPLEX_UNIT_PX, variableTextSize)

val timestamp = absoluteTimeFormatter.format(edit.createdAt, false)

binding.statusEditInfo.text = context.getString(
infoStringRes,
edit.account.name.unicodeWrap(),
timestamp
).emojify(edit.account.emojis, binding.statusEditInfo, animateEmojis)
binding.statusEditInfo.text = context.getString(infoStringRes, timestamp)

if (edit.spoilerText.isEmpty()) {
binding.statusEditContentWarningDescription.hide()
Expand Down Expand Up @@ -198,6 +213,11 @@ class ViewEditsAdapter(
}

override fun getItemCount() = edits.size

companion object {
private const val VIEW_TYPE_EDITS_NEWEST = 0
private const val VIEW_TYPE_EDITS = 1
}
}

/**
Expand Down Expand Up @@ -266,98 +286,31 @@ class TuskyTagHandler(val context: Context) : Html.TagHandler {
}
}

/**
* A span that draws text with additional padding at the start/end of the text. The padding
* is the width of [separator].
*
* Note: The separator string is not included in the final text, so it will not be included
* if the user cuts or copies the text.
*/
open class LRPaddedSpan(val separator: String = " ") : ReplacementSpan() {
/** The width of the separator string, used as padding */
var paddingWidth = 0f

/** Measured width of the span */
var spanWidth = 0f

override fun getSize(
paint: Paint,
text: CharSequence?,
start: Int,
end: Int,
fm: Paint.FontMetricsInt?
): Int {
paddingWidth = paint.measureText(separator, 0, separator.length)
spanWidth = (paddingWidth * 2) + paint.measureText(text, start, end)
return spanWidth.toInt()
}

override fun draw(
canvas: Canvas,
text: CharSequence?,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int,
bottom: Int,
paint: Paint
) {
canvas.drawText(text?.subSequence(start, end).toString(), x + paddingWidth, y.toFloat(), paint)
}
}

/** Span that signifies deleted text */
class DeletedTextSpan(context: Context) : LRPaddedSpan() {
private val bgPaint = Paint()
val radius: Float
class DeletedTextSpan(context: Context) : CharacterStyle() {
private var bgColor: Int

init {
bgPaint.color = context.getColor(R.color.view_edits_background_delete)
radius = context.resources.getDimension(R.dimen.lrPaddedSpanRadius)
bgColor = context.getColor(R.color.view_edits_background_delete)
}

override fun draw(
canvas: Canvas,
text: CharSequence?,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int,
bottom: Int,
paint: Paint
) {
canvas.drawRoundRect(x, top.toFloat(), x + spanWidth, bottom.toFloat(), radius, radius, bgPaint)
paint.isStrikeThruText = true
super.draw(canvas, text, start, end, x, top, y, bottom, paint)
override fun updateDrawState(tp: TextPaint) {
tp.bgColor = bgColor
tp.isStrikeThruText = true
}
}

/** Span that signifies inserted text */
class InsertedTextSpan(context: Context) : LRPaddedSpan() {
val bgPaint = Paint()
val radius: Float
class InsertedTextSpan(context: Context) : CharacterStyle() {
private var bgColor: Int

init {
bgPaint.color = context.getColor(R.color.view_edits_background_insert)
radius = context.resources.getDimension(R.dimen.lrPaddedSpanRadius)
bgColor = context.getColor(R.color.view_edits_background_insert)
}

override fun draw(
canvas: Canvas,
text: CharSequence?,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int,
bottom: Int,
paint: Paint
) {
canvas.drawRoundRect(x, top.toFloat(), x + spanWidth, bottom.toFloat(), radius, radius, bgPaint)
paint.typeface = DEFAULT_BOLD
super.draw(canvas, text, start, end, x, top, y, bottom, paint)
override fun updateDrawState(tp: TextPaint) {
tp.bgColor = bgColor
tp.typeface = DEFAULT_BOLD
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.StatusListActivity
import com.keylesspalace.tusky.components.account.AccountActivity
import com.keylesspalace.tusky.databinding.FragmentViewThreadBinding
import com.keylesspalace.tusky.databinding.FragmentViewEditsBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.unicodeWrap
import com.keylesspalace.tusky.util.viewBinding
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
Expand All @@ -54,7 +57,7 @@ import java.io.IOException
import javax.inject.Inject

class ViewEditsFragment :
Fragment(R.layout.fragment_view_thread),
Fragment(R.layout.fragment_view_edits),
LinkListener,
OnRefreshListener,
MenuProvider,
Expand All @@ -65,7 +68,7 @@ class ViewEditsFragment :

private val viewModel: ViewEditsViewModel by viewModels { viewModelFactory }

private val binding by viewBinding(FragmentViewThreadBinding::bind)
private val binding by viewBinding(FragmentViewEditsBinding::bind)

private lateinit var statusId: String

Expand All @@ -88,6 +91,7 @@ class ViewEditsFragment :
val animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
val useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true)
val avatarRadius: Int = requireContext().resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)

viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collect { uiState ->
Expand Down Expand Up @@ -130,6 +134,15 @@ class ViewEditsFragment :
useBlurhash = useBlurhash,
listener = this@ViewEditsFragment
)

// Focus on the most recent version
(binding.recyclerView.layoutManager as LinearLayoutManager).scrollToPosition(0)

val account = uiState.edits.first().account
loadAvatar(account.avatar, binding.statusAvatar, avatarRadius, animateAvatars)

binding.statusDisplayName.text = account.name.unicodeWrap().emojify(account.emojis, binding.statusDisplayName, animateEmojis)
binding.statusUsername.text = account.username
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,7 @@ class ViewEditsViewModel @Inject constructor(private val api: MastodonApi) : Vie
if (i < sortedEdits.size - 1) {
currentContent = previousContent
previousContent = loader.load(
sortedEdits[i + 1].content.replace(
"<br>",
"<br/>"
)
sortedEdits[i + 1].content.replace("<br>", "<br/>")
)
}
}
Expand Down
Loading