Skip to content

Commit

Permalink
Merge pull request #20431 from wordpress-mobile/fix/12629-achievement…
Browse files Browse the repository at this point in the history
…-image-loading-2

Replaces result listener animation with a Glide animation
  • Loading branch information
mkevins authored Mar 15, 2024
2 parents 22edd92 + 8a8e1c1 commit 43338f0
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ class NotificationsDetailListFragment : ListFragment(), NotificationFragment {
noteObject, imageManager, notificationsUtilsWrapper,
mOnNoteBlockTextClickListener
)
preloadImage(noteBlock)
}

// Badge notifications apply different colors and formatting
Expand All @@ -447,6 +448,14 @@ class NotificationsDetailListFragment : ListFragment(), NotificationFragment {
return pingbackUrl
}

private fun preloadImage(noteBlock: NoteBlock) {
if (noteBlock.hasImageMediaItem()) {
noteBlock.noteMediaItem?.url?.let {
imageManager.preload(requireContext(), it)
}
}
}

@Suppress("OVERRIDE_DEPRECATION")
override fun doInBackground(vararg params: Void): List<NoteBlock>? {
if (notification == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.MediaController;
import android.widget.VideoView;
Expand All @@ -26,6 +23,7 @@
import org.wordpress.android.fluxc.tools.FormattableContent;
import org.wordpress.android.fluxc.tools.FormattableMedia;
import org.wordpress.android.fluxc.tools.FormattableRange;
import org.wordpress.android.util.image.GlidePopTransitionOptions;
import org.wordpress.android.ui.notifications.utils.NotificationsUtilsWrapper;
import org.wordpress.android.util.AccessibilityUtils;
import org.wordpress.android.util.AppLog;
Expand All @@ -48,7 +46,6 @@ public class NoteBlock {
protected final NotificationsUtilsWrapper mNotificationsUtilsWrapper;
private boolean mIsBadge;
private boolean mIsPingback;
private boolean mHasAnimatedBadge;
private boolean mIsViewMilestone;

public interface OnNoteBlockTextClickListener {
Expand Down Expand Up @@ -107,7 +104,7 @@ public void setIsPingback() {
mIsPingback = true;
}

FormattableMedia getNoteMediaItem() {
@Nullable public FormattableMedia getNoteMediaItem() {
return FormattableContentUtilsKt.getMediaOrNull(mNoteData, 0);
}

Expand All @@ -127,7 +124,7 @@ private boolean hasMediaArray() {
return mNoteData.getMedia() != null && !mNoteData.getMedia().isEmpty();
}

boolean hasImageMediaItem() {
public boolean hasImageMediaItem() {
return hasMediaArray()
&& getNoteMediaItem() != null
&& !TextUtils.isEmpty(getNoteMediaItem().getType())
Expand Down Expand Up @@ -161,27 +158,22 @@ public View configureView(final View view) {
if (hasImageMediaItem()) {
noteBlockHolder.getImageView().setVisibility(View.VISIBLE);
// Request image, and animate it when loaded
mImageManager
.loadWithResultListener(noteBlockHolder.getImageView(), ImageType.IMAGE,
StringUtils.notNullStr(getNoteMediaItem().getUrl()), ScaleType.CENTER, null,
new ImageManager.RequestListener<Drawable>() {
@Override
public void onLoadFailed(@Nullable Exception e, @Nullable Object model) {
if (e != null) {
AppLog.e(T.NOTIFS, e);
}
noteBlockHolder.hideImageView();
}
mImageManager.animateWithResultListener(noteBlockHolder.getImageView(), ImageType.IMAGE,
StringUtils.notNullStr(getNoteMediaItem().getUrl()),
GlidePopTransitionOptions.INSTANCE.pop(),
new ImageManager.RequestListener<Drawable>() {
@Override
public void onLoadFailed(@Nullable Exception e, @Nullable Object model) {
if (e != null) {
AppLog.e(T.NOTIFS, e);
}
noteBlockHolder.hideImageView();
}

@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Object model) {
if (!mHasAnimatedBadge && view.getContext() != null) {
mHasAnimatedBadge = true;
Animation pop = AnimationUtils.loadAnimation(view.getContext(), R.anim.pop);
noteBlockHolder.getImageView().startAnimation(pop);
}
}
});
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Object model) {
}
});

if (mIsBadge) {
noteBlockHolder.getImageView().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
Expand Down Expand Up @@ -318,14 +310,7 @@ public View getDivider() {

public ImageView getImageView() {
if (mImageView == null) {
mImageView = new ImageView(mRootLayout.getContext());
int imageSize = DisplayUtils.dpToPx(mRootLayout.getContext(), 180);
int imagePadding = mRootLayout.getContext().getResources().getDimensionPixelSize(R.dimen.margin_large);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(imageSize, imageSize);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
mImageView.setLayoutParams(layoutParams);
mImageView.setPadding(0, imagePadding, 0, 0);
mRootLayout.addView(mImageView, 0);
mImageView = mRootLayout.findViewById(R.id.image);
}

return mImageView;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.wordpress.android.util.image

import android.graphics.drawable.Drawable
import android.view.animation.AnimationUtils
import com.bumptech.glide.TransitionOptions
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.request.transition.NoTransition
import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.request.transition.TransitionFactory
import org.wordpress.android.R
import org.wordpress.android.util.AppLog
import java.lang.RuntimeException

object GlidePopTransitionOptions : TransitionOptions<GlidePopTransitionOptions, Drawable>() {
fun pop(): GlidePopTransitionOptions {
return transition(GlidePopTransitionFactory())
}
}

class GlidePopTransition : Transition<Drawable> {
@Suppress("TooGenericExceptionCaught")
override fun transition(current: Drawable?, adapter: Transition.ViewAdapter?): Boolean {
adapter?.view?.context?.let {
adapter.setDrawable(current)
try {
val pop = AnimationUtils.loadAnimation(it, R.anim.pop)
adapter.view.startAnimation(pop)
} catch (e: RuntimeException) {
AppLog.e(AppLog.T.UTILS, "Error animating drawable: $e")
}
}
return true
}
}

class GlidePopTransitionFactory : TransitionFactory<Drawable> {
private val transition: GlidePopTransition by lazy { GlidePopTransition() }
override fun build(dataSource: DataSource?, isFirstResource: Boolean): Transition<Drawable> {
return if (dataSource == DataSource.MEMORY_CACHE) NoTransition.get<Drawable>() else transition
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.TransitionOptions
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop
Expand Down Expand Up @@ -301,6 +302,47 @@ class ImageManager @Inject constructor(
.clearOnDetach()
}

/**
* Loads an image from the "imgUrl" into the ImageView animating it with the provided Glide animation.
* Adds a placeholder and an error placeholder depending on the ImageType and attaches a ResultListener.
*/
fun animateWithResultListener(
imageView: ImageView,
imageType: ImageType,
imgUrl: String,
transitionOptions: TransitionOptions<*, in Drawable>,
requestListener: RequestListener<Drawable>
) {
val context = imageView.context
if (!context.isAvailable()) return
Glide.with(context)
.load(Uri.parse(imgUrl))
.addFallback(imageType)
.addPlaceholder(imageType)
.applyScaleType(CENTER)
.attachRequestListener(requestListener)
.transition(transitionOptions)
.into(imageView)
.clearOnDetach()
}

/**
* Preloads an image from the provided `imgUrl`.
*/
fun preload(context: Context, imgUrl: String) {
if (!context.isAvailable()) return
try {
Glide.with(context)
.downloadOnly()
.load(Uri.parse(imgUrl))
.submit()
.get() // This makes each call blocking, so subsequent calls can be cancelled if needed.
} catch (e: ExecutionException) {
// This is a best effort preload, so we don't want to crash the app if an `ExecutionException` is thrown.
AppLog.e(AppLog.T.UTILS, "Error preloading image $imgUrl: $e")
}
}

/**
* Preloads an [MShot].
*
Expand Down
9 changes: 9 additions & 0 deletions WordPress/src/main/res/layout/note_block_basic.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
android:layout_gravity="center"
android:orientation="vertical">

<ImageView
android:id="@+id/image"
android:layout_width="@dimen/notifications_image_size"
android:layout_height="@dimen/notifications_image_size"
android:layout_gravity="center_horizontal"
android:contentDescription="@null"
android:paddingTop="@dimen/margin_large"
android:visibility="gone" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
Expand Down
1 change: 1 addition & 0 deletions WordPress/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@
<dimen name="notifications_icon_size">40dp</dimen>
<dimen name="notifications_multiple_icon_height">44dp</dimen>
<dimen name="notifications_multiple_icon_width">48dp</dimen>
<dimen name="notifications_image_size">180dp</dimen>

<dimen name="progress_bar_height">3dp</dimen>
<dimen name="activity_progress_bar_height">5dp</dimen>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.wordpress.android.util.image

import android.graphics.drawable.Drawable
import android.view.View
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.request.transition.NoTransition
import com.bumptech.glide.request.transition.Transition
import junit.framework.TestCase
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.whenever
import org.wordpress.android.BaseUnitTest

@ExperimentalCoroutinesApi
class GlidePopTransitionOptionsTest : BaseUnitTest() {
@Test
fun testBuildWithCache() {
val glidePopTransitionFactory = GlidePopTransitionFactory()

val result = glidePopTransitionFactory.build(DataSource.MEMORY_CACHE, true)

TestCase.assertTrue(result is NoTransition)
}

@Test
fun testBuildWithNoCache() {
val glidePopTransitionFactory = GlidePopTransitionFactory()

val result = glidePopTransitionFactory.build(DataSource.REMOTE, true)

TestCase.assertTrue(result is GlidePopTransition)
}

@Test
fun testTransition() {
val glidePopTransition = GlidePopTransition()
val drawable: Drawable = Mockito.mock()
val viewAdapter: Transition.ViewAdapter = Mockito.mock()
val view: View = Mockito.mock()
whenever(viewAdapter.view).thenReturn(view)
whenever(view.context).thenReturn(Mockito.mock())

val result = glidePopTransition.transition(drawable, viewAdapter)

TestCase.assertTrue(result)
Mockito.verify(viewAdapter).setDrawable(drawable)
}
}

0 comments on commit 43338f0

Please sign in to comment.