fix: Show sized placeholder for hidden account media (#516)
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
This commit is contained in:
parent
bdf2d9329e
commit
a4dc3b85bd
|
@ -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
|
||||
|
@ -43,8 +42,6 @@ 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
|
||||
|
@ -52,7 +49,6 @@ 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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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>() {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ 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
|
||||
|
||||
|
@ -32,6 +33,7 @@ import javax.inject.Inject
|
|||
class AccountMediaViewModel @Inject constructor(
|
||||
accountManager: AccountManager,
|
||||
api: MastodonApi,
|
||||
statusDisplayOptionsRepository: StatusDisplayOptionsRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
lateinit var accountId: String
|
||||
|
@ -42,6 +44,8 @@ class AccountMediaViewModel @Inject constructor(
|
|||
|
||||
val activeAccount = accountManager.activeAccount!!
|
||||
|
||||
val statusDisplayOptions = statusDisplayOptionsRepository.flow
|
||||
|
||||
@OptIn(ExperimentalPagingApi::class)
|
||||
val media = Pager(
|
||||
config = PagingConfig(
|
||||
|
|
|
@ -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" />
|
||||
<solid android:color="@color/color_background_transparent_60" />
|
||||
</shape>
|
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue