Skip to content

Commit

Permalink
feat: Display uncropped media on account media pages (#464)
Browse files Browse the repository at this point in the history
Previously media on the "Media" tab was displayed scaled and cropped to
a square aspect ratio, effectively forcing the user to tap every image
to see it.

Now, display the images scaled but not cropped, layed out with
`StaggeredGridLayoutManager`. This shows each image in full (still
scaled) providing a better experience when scrolling down.

Scrolling up can occasionally introduce gaps in the grid as images are
re-placed as viewholders are reused. When this happens images animate to
a better position when scrolling stops.
  • Loading branch information
nikclayton authored Feb 22, 2024
1 parent 203784d commit 9071a89
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.recyclerview.widget.GridLayoutManager
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
Expand Down Expand Up @@ -94,11 +95,10 @@ class AccountMediaFragment :
)

val columnCount = view.context.resources.getInteger(DR.integer.profile_media_column_count)
val imageSpacing = view.context.resources.getDimensionPixelSize(DR.dimen.profile_media_spacing)

binding.recyclerView.addItemDecoration(GridSpacingItemDecoration(columnCount, imageSpacing, 0))

binding.recyclerView.layoutManager = GridLayoutManager(view.context, columnCount)
val layoutManager = StaggeredGridLayoutManager(columnCount, VERTICAL)
layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter

binding.swipeRefreshLayout.isEnabled = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package app.pachli.components.account.media

import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -20,8 +19,6 @@ import app.pachli.util.BindingHolder
import app.pachli.util.getFormattedDescription
import app.pachli.util.iconResource
import com.bumptech.glide.Glide
import com.google.android.material.color.MaterialColors
import java.util.Random

class AccountMediaGridAdapter(
private val useBlurhash: Boolean,
Expand All @@ -39,81 +36,69 @@ class AccountMediaGridAdapter(
},
) {

private val baseItemBackgroundColor = MaterialColors.getColor(context, com.google.android.material.R.attr.colorSurface, Color.BLACK)
private val playableIcon = AppCompatResources.getDrawable(context, R.drawable.ic_play_indicator)
private val mediaHiddenDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_hide_media_24dp)

private val itemBgBaseHSV = FloatArray(3)
private val random = Random()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAccountMediaBinding> {
val binding = ItemAccountMediaBinding.inflate(LayoutInflater.from(parent.context), parent, false)
Color.colorToHSV(baseItemBackgroundColor, itemBgBaseHSV)
itemBgBaseHSV[2] = itemBgBaseHSV[2] + random.nextFloat() / 3f - 1f / 6f
binding.root.setBackgroundColor(Color.HSVToColor(itemBgBaseHSV))
return BindingHolder(binding)
}

override fun onBindViewHolder(holder: BindingHolder<ItemAccountMediaBinding>, position: Int) {
val context = holder.binding.root.context
override fun onBindViewHolder(holder: BindingHolder<ItemAccountMediaBinding>, position: Int) = with(holder.binding) {
val item = getItem(position) ?: return

getItem(position)?.let { item ->
val imageView = holder.binding.accountMediaImageView
val overlay = holder.binding.accountMediaImageViewOverlay
val context = root.context

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

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

Glide.with(imageView)
.load(placeholder)
.centerInside()
.into(imageView)
Glide.with(preview)
.load(placeholder)
.centerInside()
.into(preview)

imageView.contentDescription = context.getString(R.string.post_media_hidden_title)
}
preview.contentDescription = context.getString(R.string.post_media_hidden_title)
}

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

Glide.with(imageView)
.asBitmap()
.load(item.attachment.previewUrl)
.placeholder(placeholder)
.centerInside()
.into(imageView)
Glide.with(preview)
.asBitmap()
.load(item.attachment.previewUrl)
.placeholder(placeholder)
.into(preview)

imageView.contentDescription = item.attachment.getFormattedDescription(context)
}
preview.contentDescription = item.attachment.getFormattedDescription(context)
}

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

Glide.with(imageView)
.load(item.attachment.iconResource())
.centerInside()
.into(imageView)
Glide.with(preview)
.load(item.attachment.iconResource())
.into(preview)

imageView.contentDescription = item.attachment.getFormattedDescription(context)
}
preview.contentDescription = item.attachment.getFormattedDescription(context)
}
}

holder.binding.root.setOnClickListener {
onAttachmentClickListener(item, imageView)
}
root.setOnClickListener {
onAttachmentClickListener(item, preview)
}

holder.binding.root.setOnLongClickListener { view ->
val description = item.attachment.getFormattedDescription(view.context)
Toast.makeText(context, description, Toast.LENGTH_LONG).show()
true
}
root.setOnLongClickListener { view ->
val description = item.attachment.getFormattedDescription(view.context)
Toast.makeText(context, description, Toast.LENGTH_LONG).show()
true
}
}
}

This file was deleted.

This file was deleted.

16 changes: 10 additions & 6 deletions app/src/main/res/layout/item_account_media.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:padding="@dimen/profile_media_spacing">

<app.pachli.components.account.media.SquareImageView
android:id="@+id/accountMediaImageView"
<!-- content description is set in code -->
<ImageView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scaleType="centerCrop" />
android:layout_height="match_parent"
android:adjustViewBounds="true"
tools:ignore="ContentDescription" />

<ImageView
android:id="@+id/accountMediaImageViewOverlay"
android:id="@+id/overlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
Expand Down

0 comments on commit 9071a89

Please sign in to comment.