diff --git a/app/src/main/java/app/pachli/components/account/media/AccountMediaFragment.kt b/app/src/main/java/app/pachli/components/account/media/AccountMediaFragment.kt index abf0aae52..42018f116 100644 --- a/app/src/main/java/app/pachli/components/account/media/AccountMediaFragment.kt +++ b/app/src/main/java/app/pachli/components/account/media/AccountMediaFragment.kt @@ -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, ) diff --git a/app/src/main/java/app/pachli/components/account/media/AccountMediaGridAdapter.kt b/app/src/main/java/app/pachli/components/account/media/AccountMediaGridAdapter.kt index 313334cba..7f95ad8b2 100644 --- a/app/src/main/java/app/pachli/components/account/media/AccountMediaGridAdapter.kt +++ b/app/src/main/java/app/pachli/components/account/media/AccountMediaGridAdapter.kt @@ -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>( object : DiffUtil.ItemCallback() { @@ -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 { 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 { + // 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) + } } diff --git a/app/src/main/java/app/pachli/components/account/media/AccountMediaViewModel.kt b/app/src/main/java/app/pachli/components/account/media/AccountMediaViewModel.kt index b591cc33f..2eb4dc9ce 100644 --- a/app/src/main/java/app/pachli/components/account/media/AccountMediaViewModel.kt +++ b/app/src/main/java/app/pachli/components/account/media/AccountMediaViewModel.kt @@ -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( diff --git a/app/src/main/res/drawable/media_warning_bg.xml b/app/src/main/res/drawable/media_warning_bg.xml index c1628a627..9dc0ae570 100644 --- a/app/src/main/res/drawable/media_warning_bg.xml +++ b/app/src/main/res/drawable/media_warning_bg.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + + diff --git a/app/src/main/res/layout/item_account_media.xml b/app/src/main/res/layout/item_account_media.xml index ef5b16ee1..122119dff 100644 --- a/app/src/main/res/layout/item_account_media.xml +++ b/app/src/main/res/layout/item_account_media.xml @@ -18,6 +18,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:padding="4dp" android:importantForAccessibility="no" /> diff --git a/core/activity/src/main/kotlin/app/pachli/core/activity/ImageLoadingHelper.kt b/core/activity/src/main/kotlin/app/pachli/core/activity/ImageLoadingHelper.kt index 5a0fd65bb..cab74e49a 100644 --- a/core/activity/src/main/kotlin/app/pachli/core/activity/ImageLoadingHelper.kt +++ b/core/activity/src/main/kotlin/app/pachli/core/activity/ImageLoadingHelper.kt @@ -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)) } diff --git a/core/designsystem/src/main/res/values/dimens.xml b/core/designsystem/src/main/res/values/dimens.xml index 0eb525e35..43bff4744 100644 --- a/core/designsystem/src/main/res/values/dimens.xml +++ b/core/designsystem/src/main/res/values/dimens.xml @@ -61,7 +61,7 @@ 36dp - 3dp + 4dp 4dp @@ -72,6 +72,8 @@ 48dp 48dp + 130dp +