Skip to content

Commit

Permalink
fix: Show sized placeholder for hidden account media (#516)
Browse files Browse the repository at this point in the history
Previous code showed a small icon for account media that the user has
hidden.

Now determine the correct size / aspect ratio for the media and use that
to compute the placeholder (either a blurhash, or the link colour for
consistency with the view on a timeline).

Fixes #513
  • Loading branch information
nikclayton committed Mar 10, 2024
1 parent bdf2d93 commit a4dc3b8
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager.VERTICAL
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.R
import app.pachli.core.accounts.AccountManager
import app.pachli.core.activity.RefreshableFragment
import app.pachli.core.activity.openLink
import app.pachli.core.common.extensions.hide
Expand All @@ -43,16 +42,13 @@ import app.pachli.core.designsystem.R as DR
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.network.model.Attachment
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository
import app.pachli.databinding.FragmentTimelineBinding
import com.google.android.material.color.MaterialColors
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

Expand All @@ -66,12 +62,6 @@ class AccountMediaFragment :
RefreshableFragment,
MenuProvider {

@Inject
lateinit var accountManager: AccountManager

@Inject
lateinit var sharedPreferencesRepository: SharedPreferencesRepository

private val binding by viewBinding(FragmentTimelineBinding::bind)

private val viewModel: AccountMediaViewModel by viewModels()
Expand All @@ -86,11 +76,9 @@ class AccountMediaFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)

val useBlurhash = sharedPreferencesRepository.getBoolean(PrefKeys.USE_BLURHASH, true)

adapter = AccountMediaGridAdapter(
useBlurhash = useBlurhash,
context = view.context,
statusDisplayOptions = viewModel.statusDisplayOptions.value,
onAttachmentClickListener = ::onAttachmentClick,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package app.pachli.components.account.media

import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.PaintDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -14,15 +16,18 @@ import app.pachli.core.activity.decodeBlurHash
import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.visible
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.network.model.Attachment
import app.pachli.databinding.ItemAccountMediaBinding
import app.pachli.util.BindingHolder
import app.pachli.util.StatusDisplayOptions
import app.pachli.util.getFormattedDescription
import app.pachli.util.iconResource
import com.bumptech.glide.Glide
import com.google.android.material.color.MaterialColors

class AccountMediaGridAdapter(
private val useBlurhash: Boolean,
context: Context,
statusDisplayOptions: StatusDisplayOptions,
private val onAttachmentClickListener: (AttachmentViewData, View) -> Unit,
) : PagingDataAdapter<AttachmentViewData, BindingHolder<ItemAccountMediaBinding>>(
object : DiffUtil.ItemCallback<AttachmentViewData>() {
Expand All @@ -35,10 +40,17 @@ class AccountMediaGridAdapter(
}
},
) {
var statusDisplayOptions = statusDisplayOptions
set(value) {
field = value
notifyItemRangeChanged(0, itemCount)
}

private val playableIcon = AppCompatResources.getDrawable(context, R.drawable.ic_play_indicator)
private val mediaHiddenDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_hide_media_24dp)

val defaultSize = context.resources.getDimensionPixelSize(app.pachli.core.designsystem.R.dimen.account_media_grid_default)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAccountMediaBinding> {
val binding = ItemAccountMediaBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
Expand All @@ -49,17 +61,17 @@ class AccountMediaGridAdapter(

val context = root.context

val placeholder = item.attachment.blurhash?.let {
if (useBlurhash) decodeBlurHash(context, it) else null
}

when {
item.sensitive && !item.isRevealed -> {
overlay.show()
overlay.setImageDrawable(mediaHiddenDrawable)
overlay.setBackgroundResource(R.drawable.media_warning_bg)

val (placeholder, width, height) = item.attachment.placeholder(context, preview)

Glide.with(preview)
.load(placeholder)
.override(width, height)
.centerInside()
.into(preview)

Expand All @@ -68,8 +80,11 @@ class AccountMediaGridAdapter(

item.attachment.isPreviewable() -> {
if (item.attachment.type.isPlayable()) overlay.setImageDrawable(playableIcon)
overlay.setBackgroundResource(0)
overlay.visible(item.attachment.type.isPlayable())

val (placeholder, _, _) = item.attachment.placeholder(context, preview)

Glide.with(preview)
.asBitmap()
.load(item.attachment.previewUrl)
Expand All @@ -81,6 +96,7 @@ class AccountMediaGridAdapter(

else -> {
if (item.attachment.type.isPlayable()) overlay.setImageDrawable(playableIcon)
overlay.setBackgroundResource(0)
overlay.visible(item.attachment.type.isPlayable())

Glide.with(preview)
Expand All @@ -101,4 +117,50 @@ class AccountMediaGridAdapter(
true
}
}

/**
* Determine the placeholder for this [Attachment].
*
* @return A triple of the [Drawable] that should be used for the placeholder, and the
* width and height to set on the imageview displaying the placeholder.
*/
fun Attachment.placeholder(context: Context, view: View): Triple<Drawable?, Int, Int> {
// To avoid the list jumping when the user taps the placeholder to reveal the media
// the placeholder must have the same size as the underlying preview.
//
// To do this take the height and width of the `small` image from the attachment
// metadata. If height doesn't exist / is null try and compute it from the width and
// the aspect ratio, falling back to 100 if both are missing.
//
// Do the same to compute the width.
val height = when {
meta?.small?.height != null -> meta?.small?.height!!
meta?.small?.width != null && meta?.small?.aspect != null ->
(meta?.small?.width!! / meta?.small?.aspect!!).toInt()
else -> defaultSize
}

val width = when {
meta?.small?.width != null -> meta?.small?.width!!
meta?.small?.aspect != null -> (height * meta?.small?.aspect!!).toInt()
else -> defaultSize
}

// The drawable's height and width does not need to be as large, as it will be
// automatically scaled by Glide. Set to a max height of 32, and scale the width
// appropriately.
val placeholderHeight = 32
val placeholderWidth = (placeholderHeight * (meta?.small?.aspect ?: 1.0)).toInt()

val placeholder = if (statusDisplayOptions.useBlurhash) {
blurhash?.let { decodeBlurHash(context, it, placeholderWidth, placeholderHeight) }
} else {
PaintDrawable(MaterialColors.getColor(view, android.R.attr.textColorLink)).apply {
intrinsicHeight = placeholderHeight
intrinsicWidth = placeholderWidth
}
}

return Triple(placeholder, width, height)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ import androidx.paging.cachedIn
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.util.StatusDisplayOptionsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class AccountMediaViewModel @Inject constructor(
accountManager: AccountManager,
api: MastodonApi,
statusDisplayOptionsRepository: StatusDisplayOptionsRepository,
) : ViewModel() {

lateinit var accountId: String
Expand All @@ -42,6 +44,8 @@ class AccountMediaViewModel @Inject constructor(

val activeAccount = accountManager.activeAccount!!

val statusDisplayOptions = statusDisplayOptionsRepository.flow

@OptIn(ExperimentalPagingApi::class)
val media = Pager(
config = PagingConfig(
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/drawable/media_warning_bg.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="7dp" />
<solid android:color="@color/color_background_transparent_60" />
</shape>
<solid android:color="@color/color_background_transparent_60" />
</shape>
1 change: 1 addition & 0 deletions app/src/main/res/layout/item_account_media.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="4dp"
android:importantForAccessibility="no" />

</FrameLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ fun loadAvatar(
}
}

fun decodeBlurHash(context: Context, blurhash: String): BitmapDrawable {
return BitmapDrawable(context.resources, BlurHashDecoder.decode(blurhash, 32, 32, 1f))
fun decodeBlurHash(context: Context, blurhash: String, width: Int = 32, height: Int = 32): BitmapDrawable {
return BitmapDrawable(context.resources, BlurHashDecoder.decode(blurhash, width, height, 1f))
}
4 changes: 3 additions & 1 deletion core/designsystem/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

<dimen name="avatar_toolbar_nav_icon_size">36dp</dimen>

<dimen name="profile_media_spacing">3dp</dimen>
<dimen name="profile_media_spacing">4dp</dimen>

<dimen name="preview_image_spacing">4dp</dimen>

Expand All @@ -72,6 +72,8 @@
<dimen name="timeline_status_avatar_height">48dp</dimen>
<dimen name="timeline_status_avatar_width">48dp</dimen>

<dimen name="account_media_grid_default">130dp</dimen>

<!-- Adjust the dimensions of items in the Material drawer to provide a
slightly tighter layout while ensuring that minimum touch sizes are
maintained.
Expand Down

0 comments on commit a4dc3b8

Please sign in to comment.