Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smooth transition from images in Article to Gallery #1645

Merged
merged 11 commits into from Oct 2, 2020
18 changes: 18 additions & 0 deletions app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt
Expand Up @@ -145,4 +145,22 @@ object JavaScriptActionHandler {
"document.head.appendChild(style);" +
"})();"
}

@JvmStatic
fun getElementAtPosition(x: Int, y: Int): String {
dbrant marked this conversation as resolved.
Show resolved Hide resolved
return "(function() {" +
" let element = document.elementFromPoint(${x}, ${y});" +
" let result = {};" +
" result.left = element.getBoundingClientRect().left;" +
" result.top = element.getBoundingClientRect().top;" +
" result.width = element.clientWidth;" +
" result.height = element.clientHeight;" +
" result.src = element.src;" +
" return result;" +
"})();"
}

data class ImageHitInfo(val left: Float = 0f, val top: Float = 0f, val width: Float = 0f, val height: Float = 0f,
val src: String = "", val centerCrop: Boolean = false) {
}
dbrant marked this conversation as resolved.
Show resolved Hide resolved
}
61 changes: 58 additions & 3 deletions app/src/main/java/org/wikipedia/gallery/GalleryActivity.java
Expand Up @@ -15,6 +15,7 @@
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
Expand All @@ -37,6 +38,7 @@
import org.wikipedia.activity.BaseActivity;
import org.wikipedia.analytics.GalleryFunnel;
import org.wikipedia.auth.AccountUtil;
import org.wikipedia.bridge.JavaScriptActionHandler;
import org.wikipedia.commons.FilePageActivity;
import org.wikipedia.commons.ImageTagsProvider;
import org.wikipedia.dataclient.Service;
Expand All @@ -59,6 +61,7 @@
import org.wikipedia.theme.Theme;
import org.wikipedia.util.ClipboardUtil;
import org.wikipedia.util.DeviceUtil;
import org.wikipedia.util.DimenUtil;
import org.wikipedia.util.FeedbackUtil;
import org.wikipedia.util.GradientUtil;
import org.wikipedia.util.ImageUrlUtil;
Expand All @@ -67,6 +70,7 @@
import org.wikipedia.util.log.L;
import org.wikipedia.views.PositionAwareFragmentStateAdapter;
import org.wikipedia.views.ViewAnimations;
import org.wikipedia.views.ViewUtil;
import org.wikipedia.views.WikiErrorView;

import java.io.File;
Expand Down Expand Up @@ -111,10 +115,13 @@ public class GalleryActivity extends BaseActivity implements LinkPreviewDialog.C
public static final String EXTRA_FEATURED_IMAGE = "featuredImage";
public static final String EXTRA_FEATURED_IMAGE_AGE = "featuredImageAge";

private static JavaScriptActionHandler.ImageHitInfo TRANSITION_INFO;

@NonNull private WikipediaApp app = WikipediaApp.getInstance();
@NonNull private ExclusiveBottomSheetPresenter bottomSheetPresenter = new ExclusiveBottomSheetPresenter();
@Nullable private PageTitle pageTitle;

@BindView(R.id.gallery_transition_receiver) ImageView transitionReceiver;
@BindView(R.id.gallery_toolbar_container) ViewGroup toolbarContainer;
@BindView(R.id.gallery_toolbar) Toolbar toolbar;
@BindView(R.id.gallery_toolbar_gradient) View toolbarGradient;
Expand Down Expand Up @@ -255,7 +262,33 @@ public void onCreate(Bundle savedInstanceState) {
}
setControlsShowing(controlsShowing);
});
loadGalleryContent();

if (TRANSITION_INFO != null
&& TRANSITION_INFO.getWidth() > 0 && TRANSITION_INFO.getHeight() > 0) {

float aspect = TRANSITION_INFO.getHeight() / TRANSITION_INFO.getWidth();
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(DimenUtil.getDisplayWidthPx(), (int)(DimenUtil.getDisplayWidthPx() * aspect));
params.gravity = Gravity.CENTER_VERTICAL;
transitionReceiver.setLayoutParams(params);

transitionReceiver.setVisibility(View.VISIBLE);
ViewUtil.loadImage(transitionReceiver, TRANSITION_INFO.getSrc(), TRANSITION_INFO.getCenterCrop(), false);

final int transitionMillis = 500;
transitionReceiver.postDelayed(() -> {
if (isDestroyed()) {
return;
}
loadGalleryContent();
}, transitionMillis);

} else {

TRANSITION_INFO = null;
transitionReceiver.setVisibility(View.GONE);
loadGalleryContent();

}
}

@Override public void onDestroy() {
Expand All @@ -268,6 +301,7 @@ public void onCreate(Bundle savedInstanceState) {
unbinder.unbind();
}

TRANSITION_INFO = null;
super.onDestroy();
}

Expand Down Expand Up @@ -358,6 +392,10 @@ public void onActivityResult(int requestCode, int resultCode, final Intent data)
startCaptionEdit(item);
}

public static void setTransitionInfo(@NonNull JavaScriptActionHandler.ImageHitInfo hitInfo) {
TRANSITION_INFO = hitInfo;
}

private void startCaptionEdit(GalleryItemFragment item) {
PageTitle title = new PageTitle(item.getImageTitle().getPrefixedText(), new WikiSite(Service.COMMONS_URL, sourceWiki.languageCode()));
String currentCaption = item.getMediaInfo().getCaptions().get(sourceWiki.languageCode());
Expand Down Expand Up @@ -470,9 +508,28 @@ public void onBackPressed() {
if (item != null && item.getImageTitle() != null && funnel != null) {
funnel.logGalleryClose(pageTitle, item.getImageTitle().getDisplayText());
}

if (TRANSITION_INFO != null) {
showTransitionReceiver();
}

super.onBackPressed();
}


public void onMediaLoaded() {
hideTransitionReceiver();
}

private void showTransitionReceiver() {
transitionReceiver.setAlpha(1f);
transitionReceiver.setVisibility(View.VISIBLE);
}

private void hideTransitionReceiver() {
transitionReceiver.setVisibility(View.GONE);
}

/**
* Show or hide all the UI controls in this activity (slide them out or in).
* @param showing Whether to show or hide the controls.
Expand Down Expand Up @@ -581,7 +638,6 @@ private void fetchGalleryItems() {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mediaList -> {
updateProgressBar(false);
applyGalleryList(mediaList.getItems("image", "video"));
}, caught -> {
updateProgressBar(false);
Expand All @@ -598,7 +654,6 @@ private void loadGalleryContent() {
FeaturedImage featuredImage = GsonUnmarshaller.unmarshal(FeaturedImage.class,
getIntent().getStringExtra(EXTRA_FEATURED_IMAGE));
featuredImage.setAge(getIntent().getIntExtra(EXTRA_FEATURED_IMAGE_AGE, 0));
updateProgressBar(false);
applyGalleryList(Collections.singletonList(new MediaListItem(featuredImage.title())));
} else {
fetchGalleryItems();
Expand Down
20 changes: 18 additions & 2 deletions app/src/main/java/org/wikipedia/gallery/GalleryItemFragment.java
@@ -1,6 +1,7 @@
package org.wikipedia.gallery;

import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
Expand All @@ -18,6 +19,10 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.github.chrisbanes.photoview.PhotoView;

import org.wikipedia.Constants;
Expand Down Expand Up @@ -54,7 +59,7 @@
import static org.wikipedia.util.PermissionUtil.hasWriteExternalStoragePermission;
import static org.wikipedia.util.PermissionUtil.requestWriteStorageRuntimePermissions;

public class GalleryItemFragment extends Fragment {
public class GalleryItemFragment extends Fragment implements RequestListener<Drawable> {
private static final String ARG_PAGETITLE = "pageTitle";
private static final String ARG_GALLERY_ITEM = "galleryItem";

Expand Down Expand Up @@ -313,10 +318,21 @@ private void loadImage(String url) {
L.v("Loading image from url: " + url);

updateProgressBar(true);
ViewUtil.loadImageWithWhiteBackground(imageView, url);
ViewUtil.loadImageWithWhiteBackground(imageView, url, this);
// TODO: show error if loading failed.
}

@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}

@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
((GalleryActivity) requireActivity()).onMediaLoaded();
return false;
}

private void shareImage() {
if (getMediaInfo() == null) {
return;
Expand Down
45 changes: 42 additions & 3 deletions app/src/main/java/org/wikipedia/page/PageFragment.java
Expand Up @@ -19,6 +19,7 @@
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

Expand All @@ -27,6 +28,7 @@
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
Expand Down Expand Up @@ -100,6 +102,7 @@
import org.wikipedia.util.log.L;
import org.wikipedia.views.ObservableWebView;
import org.wikipedia.views.SwipeRefreshLayoutWithScroll;
import org.wikipedia.views.ViewUtil;
import org.wikipedia.views.WikiErrorView;
import org.wikipedia.wiktionary.WiktionaryDialog;

Expand Down Expand Up @@ -183,6 +186,7 @@ public interface Callback {
private ShareHandler shareHandler;
private CompositeDisposable disposables = new CompositeDisposable();
private ActiveTimer activeTimer = new ActiveTimer();
private ImageView viewForTransition;
private PageReferences references;
private long revision;
@Nullable private AvPlayer avPlayer;
Expand Down Expand Up @@ -621,6 +625,11 @@ public void onResume() {
super.onResume();
initPageScrollFunnel();
activeTimer.resume();

if (viewForTransition != null) {
((ViewGroup) viewForTransition.getParent()).removeView(viewForTransition);
viewForTransition = null;
}
}

@Override
Expand Down Expand Up @@ -1165,9 +1174,39 @@ public void startDescriptionEditActivity(@Nullable String text) {

private void startGalleryActivity(@NonNull String fileName) {
if (app.isOnline()) {
requireActivity().startActivityForResult(GalleryActivity.newIntent(requireActivity(),
model.getTitle(), fileName,
model.getTitle().getWikiSite(), getRevision(), GalleryFunnel.SOURCE_NON_LEAD_IMAGE), ACTIVITY_REQUEST_GALLERY);

bridge.evaluate(JavaScriptActionHandler.getElementAtPosition(DimenUtil.roundedPxToDp(webView.getLastTouchX()), DimenUtil.roundedPxToDp(webView.getLastTouchY())), s -> {
if (!isAdded()) {
return;
}
JavaScriptActionHandler.ImageHitInfo hitInfo = GsonUtil.getDefaultGson().fromJson(s, JavaScriptActionHandler.ImageHitInfo.class);

viewForTransition = new ImageView(requireActivity());
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(DimenUtil.roundedDpToPx(hitInfo.getWidth()), DimenUtil.roundedDpToPx(hitInfo.getHeight()));
params.topMargin = DimenUtil.roundedDpToPx(hitInfo.getTop());
params.leftMargin = DimenUtil.roundedDpToPx(hitInfo.getLeft());
viewForTransition.setLayoutParams(params);
viewForTransition.setTransitionName(getString(R.string.transition_page_gallery));
((ViewGroup) webView.getParent()).addView(viewForTransition);

GalleryActivity.setTransitionInfo(hitInfo);

viewForTransition.post(() -> {
if (!isAdded()) {
return;
}

ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(requireActivity(), viewForTransition, getString(R.string.transition_page_gallery));

requireActivity().startActivityForResult(GalleryActivity.newIntent(requireActivity(),
model.getTitle(), fileName,
model.getTitle().getWikiSite(), getRevision(), GalleryFunnel.SOURCE_NON_LEAD_IMAGE), ACTIVITY_REQUEST_GALLERY, options.toBundle());
});

ViewUtil.loadImage(viewForTransition, hitInfo.getSrc());
});

} else {
Snackbar snackbar = FeedbackUtil.makeSnackbar(requireActivity(), getString(R.string.gallery_not_available_offline_snackbar), FeedbackUtil.LENGTH_DEFAULT);
snackbar.setAction(R.string.gallery_not_available_offline_snackbar_dismiss, view -> snackbar.dismiss());
Expand Down
Expand Up @@ -7,6 +7,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityOptionsCompat;
import androidx.fragment.app.FragmentActivity;

import org.apache.commons.lang3.StringUtils;
Expand All @@ -16,6 +17,7 @@
import org.wikipedia.WikipediaApp;
import org.wikipedia.analytics.GalleryFunnel;
import org.wikipedia.auth.AccountUtil;
import org.wikipedia.bridge.JavaScriptActionHandler;
import org.wikipedia.commons.ImageTagsProvider;
import org.wikipedia.dataclient.Service;
import org.wikipedia.dataclient.ServiceFactory;
Expand Down Expand Up @@ -243,16 +245,26 @@ public void onCallToActionClicked() {
});
}

public void openImageInGallery(@Nullable String language) {
public void openImageInGallery(@Nullable String language) {
if (getPage() != null && isLeadImageEnabled()) {
String imageName = getPage().getPageProperties().getLeadImageName();
if (imageName != null) {
String filename = "File:" + imageName;
WikiSite wiki = language == null ? getTitle().getWikiSite() : WikiSite.forLanguageCode(language);

JavaScriptActionHandler.ImageHitInfo hitInfo = new JavaScriptActionHandler.ImageHitInfo(pageHeaderView.image.getLeft(),
dbrant marked this conversation as resolved.
Show resolved Hide resolved
pageHeaderView.image.getTop(), pageHeaderView.image.getWidth(), pageHeaderView.image.getHeight(),
getLeadImageUrl(), true);

GalleryActivity.setTransitionInfo(hitInfo);

ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(), pageHeaderView.image, getActivity().getString(R.string.transition_page_gallery));

getActivity().startActivityForResult(GalleryActivity.newIntent(getActivity(),
parentFragment.getTitle(), filename, wiki, parentFragment.getRevision(),
GalleryFunnel.SOURCE_LEAD_IMAGE),
Constants.ACTIVITY_REQUEST_GALLERY);
Constants.ACTIVITY_REQUEST_GALLERY, options.toBundle());
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/org/wikipedia/views/ObservableWebView.java
Expand Up @@ -33,6 +33,8 @@ public class ObservableWebView extends WebView {
private long lastScrollTime;
private int totalAmountScrolled;
private int drawEventsWhileSwiping;
private float lastTouchX;
private float lastTouchY;

/**
* Threshold (in pixels) of continuous scrolling, to be considered "fast" scrolling.
Expand Down Expand Up @@ -159,6 +161,9 @@ public boolean onTouchEvent(MotionEvent event) {
return true;
}

lastTouchX = event.getX();
lastTouchY = event.getY();

switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
for (OnDownMotionEventListener listener : onDownMotionEventListeners) {
Expand Down Expand Up @@ -193,6 +198,14 @@ public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}

public float getLastTouchX() {
return lastTouchX;
}

public float getLastTouchY() {
return lastTouchY;
}

private void handleMouseRightClick(float x, float y) {
final int eventTimeTravelMillis = 1000;
post(() -> dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
Expand Down