package org.wikipedia.page.linkpreview; import android.content.DialogInterface; import android.content.Intent; import android.location.Location; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.PopupMenu; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import com.facebook.drawee.view.SimpleDraweeView; import org.wikipedia.Constants; import org.wikipedia.R; import org.wikipedia.WikipediaApp; import org.wikipedia.activity.FragmentUtil; import org.wikipedia.analytics.GalleryFunnel; import org.wikipedia.analytics.LinkPreviewFunnel; import org.wikipedia.concurrency.CallbackTask; import org.wikipedia.dataclient.ServiceError; import org.wikipedia.dataclient.page.PageClientFactory; import org.wikipedia.dataclient.page.PageSummary; import org.wikipedia.gallery.GalleryActivity; import org.wikipedia.gallery.GalleryCollection; import org.wikipedia.gallery.GalleryCollectionClient; import org.wikipedia.gallery.GalleryItem; import org.wikipedia.gallery.GalleryThumbnailScrollView; import org.wikipedia.gallery.ImageInfo; import org.wikipedia.history.HistoryEntry; import org.wikipedia.page.ExtendedBottomSheetDialogFragment; import org.wikipedia.page.PageTitle; import org.wikipedia.util.GeoUtil; import org.wikipedia.util.log.L; import org.wikipedia.views.ViewUtil; import java.util.ArrayList; import java.util.List; import java.util.Map; import retrofit2.Call; import retrofit2.Response; import static org.wikipedia.util.L10nUtil.getStringForArticleLanguage; import static org.wikipedia.util.L10nUtil.setConditionalLayoutDirection; public class LinkPreviewDialog extends ExtendedBottomSheetDialogFragment implements LinkPreviewErrorView.Callback, DialogInterface.OnDismissListener { public interface Callback { void onLinkPreviewLoadPage(@NonNull PageTitle title, @NonNull HistoryEntry entry, boolean inNewTab); void onLinkPreviewCopyLink(@NonNull PageTitle title); void onLinkPreviewAddToList(@NonNull PageTitle title); void onLinkPreviewShareLink(@NonNull PageTitle title); } private boolean navigateSuccess = false; private LinearLayout dialogContainer; private View contentContainer; private LinkPreviewErrorView errorContainer; private ProgressBar progressBar; private TextView extractText; private SimpleDraweeView thumbnailView; private GalleryThumbnailScrollView thumbnailGallery; private LinkPreviewOverlayView overlayView; private View toolbarView; private View overflowButton; private PageTitle pageTitle; private int entrySource; @Nullable private Location location; @NonNull private GalleryCollectionClient client = new GalleryCollectionClient(); private LinkPreviewFunnel funnel; public static LinkPreviewDialog newInstance(PageTitle title, int entrySource, @Nullable Location location) { LinkPreviewDialog dialog = new LinkPreviewDialog(); Bundle args = new Bundle(); args.putParcelable("title", title); args.putInt("entrySource", entrySource); if (location != null) { args.putParcelable("location", location); } dialog.setArguments(args); return dialog; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { WikipediaApp app = WikipediaApp.getInstance(); pageTitle = getArguments().getParcelable("title"); entrySource = getArguments().getInt("entrySource"); location = getArguments().getParcelable("location"); View rootView = inflater.inflate(R.layout.dialog_link_preview, container); dialogContainer = (LinearLayout) rootView.findViewById(R.id.dialog_link_preview_container); contentContainer = rootView.findViewById(R.id.dialog_link_preview_content_container); errorContainer = (LinkPreviewErrorView) rootView.findViewById(R.id.dialog_link_preview_error_container); progressBar = (ProgressBar) rootView.findViewById(R.id.link_preview_progress); toolbarView = rootView.findViewById(R.id.link_preview_toolbar); toolbarView.setOnClickListener(goToPageListener); TextView titleText = (TextView) rootView.findViewById(R.id.link_preview_title); titleText.setText(pageTitle.getDisplayText()); setConditionalLayoutDirection(rootView, pageTitle.getWikiSite().languageCode()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { // for oldish devices, reset line spacing to 1, since it truncates the descenders. titleText.setLineSpacing(0, 1.0f); } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // for <5.0, give the title a bit more bottom padding, since these versions // incorrectly cut off the bottom of the text when line spacing is <1. final int bottomPadding = 8; ViewUtil.setBottomPaddingDp(titleText, bottomPadding); } extractText = (TextView) rootView.findViewById(R.id.link_preview_extract); thumbnailView = (SimpleDraweeView) rootView.findViewById(R.id.link_preview_thumbnail); thumbnailGallery = (GalleryThumbnailScrollView) rootView.findViewById(R.id.link_preview_thumbnail_gallery); if (app.isImageDownloadEnabled()) { CallbackTask.execute(new CallbackTask.Task<Map<String, ImageInfo>>() { @Override public Map<String, ImageInfo> execute() throws Throwable { return client.request(pageTitle.getWikiSite(), pageTitle, true); } }, new CallbackTask.Callback<Map<String, ImageInfo>>() { @Override public void success(@Nullable Map<String, ImageInfo> result) { setThumbGallery(result); thumbnailGallery.setGalleryViewListener(galleryViewListener); } @Override public void failure(Throwable caught) { L.w("Failed to fetch gallery collection.", caught); } }); } overflowButton = rootView.findViewById(R.id.link_preview_overflow_button); overflowButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { PopupMenu popupMenu = new PopupMenu(getActivity(), overflowButton); popupMenu.inflate(R.menu.menu_link_preview); popupMenu.setOnMenuItemClickListener(menuListener); popupMenu.show(); } }); // show the progress bar while we load content... progressBar.setVisibility(View.VISIBLE); // and kick off the task to load all the things... loadContent(); funnel = new LinkPreviewFunnel(app, entrySource); funnel.logLinkClick(); return rootView; } public void goToLinkedPage() { navigateSuccess = true; funnel.logNavigate(); if (getDialog() != null) { getDialog().dismiss(); } HistoryEntry newEntry = new HistoryEntry(pageTitle, entrySource); loadPage(pageTitle, newEntry, false); } @Override public void onResume() { super.onResume(); if (overlayView == null) { ViewGroup containerView = (ViewGroup) getDialog().findViewById(android.R.id.content); overlayView = new LinkPreviewOverlayView(getContext()); overlayView.setCallback(new OverlayViewCallback()); overlayView.setPrimaryButtonText(getStringForArticleLanguage(pageTitle, R.string.button_continue_to_article)); overlayView.showSecondaryButton(location != null); containerView.addView(overlayView); } } @Override public void onDestroyView() { thumbnailGallery.setGalleryViewListener(null); toolbarView.setOnClickListener(null); overflowButton.setOnClickListener(null); overlayView.setCallback(null); overlayView = null; super.onDestroyView(); } @Override public void onDismiss(DialogInterface dialogInterface) { super.onDismiss(dialogInterface); if (!navigateSuccess) { funnel.logCancel(); } } @Override public void onActivityResult(int requestCode, int resultCode, final Intent data) { if (requestCode == Constants.ACTIVITY_REQUEST_GALLERY && resultCode == GalleryActivity.ACTIVITY_RESULT_PAGE_SELECTED) { startActivity(data); } else { super.onActivityResult(requestCode, resultCode, data); } } @Override public void onAddToList() { if (callback() != null) { callback().onLinkPreviewAddToList(pageTitle); } } @Override public void onDismiss() { dismiss(); } private void loadContent() { PageClientFactory .create(pageTitle.getWikiSite(), pageTitle.namespace()) .summary(pageTitle.getPrefixedText()) .enqueue(linkPreviewNetworkOnLoadCallback); } private void showPreview(@NonNull LinkPreviewContents contents) { progressBar.setVisibility(View.GONE); setPreviewContents(contents); } private void setThumbGallery(Map<String, ImageInfo> result) { List<GalleryItem> list = new ArrayList<>(); for (Map.Entry<String, ImageInfo> entry : result.entrySet()) { if (GalleryCollection.shouldIncludeImage(entry.getValue())) { list.add(new GalleryItem(entry.getKey(), entry.getValue())); } } if (!list.isEmpty()) { thumbnailGallery.setGalleryCollection(new GalleryCollection(list)); } } private void showError(@Nullable Throwable caught) { dialogContainer.setLayoutTransition(null); dialogContainer.setMinimumHeight(0); progressBar.setVisibility(View.GONE); contentContainer.setVisibility(View.GONE); overlayView.showSecondaryButton(false); errorContainer.setVisibility(View.VISIBLE); errorContainer.setError(caught); errorContainer.setCallback(this); LinkPreviewErrorType errorType = LinkPreviewErrorType.get(caught); overlayView.setPrimaryButtonText(getResources().getString(errorType.buttonText())); overlayView.setCallback(errorType.buttonAction(errorContainer)); if (errorType != LinkPreviewErrorType.OFFLINE) { toolbarView.setOnClickListener(null); overflowButton.setVisibility(View.GONE); } } private void setPreviewContents(@NonNull LinkPreviewContents contents) { if (contents.getExtract().length() > 0) { extractText.setText(contents.getExtract()); } ViewUtil.loadImageUrlInto(thumbnailView, contents.getTitle().getThumbUrl()); } private retrofit2.Callback<PageSummary> linkPreviewNetworkOnLoadCallback = new retrofit2.Callback<PageSummary>() { @Override public void onResponse(Call<PageSummary> call, Response<PageSummary> rsp) { if (!isAdded()) { return; } PageSummary summary = rsp.body(); if (summary != null && !summary.hasError()) { showPreview(new LinkPreviewContents(summary, pageTitle.getWikiSite())); } else { showError(null); logError(summary.hasError() ? summary.getError() : null, "Page summary network request failed"); } } @Override public void onFailure(Call<PageSummary> call, Throwable caught) { L.e(caught); if (!isAdded()) { return; } showError(caught); } }; private PopupMenu.OnMenuItemClickListener menuListener = new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { Callback callback = callback(); switch (item.getItemId()) { case R.id.menu_link_preview_open_in_new_tab: loadPage(pageTitle, new HistoryEntry(pageTitle, entrySource), true); dismiss(); return true; case R.id.menu_link_preview_add_to_list: if (callback != null) { callback.onLinkPreviewAddToList(pageTitle); } return true; case R.id.menu_link_preview_share_page: if (callback != null) { callback.onLinkPreviewShareLink(pageTitle); } return true; case R.id.menu_link_preview_copy_link: if (callback != null) { callback.onLinkPreviewCopyLink(pageTitle); } dismiss(); return true; default: break; } return false; } }; private GalleryThumbnailScrollView.GalleryViewListener galleryViewListener = new GalleryThumbnailScrollView.GalleryViewListener() { @Override public void onGalleryItemClicked(String imageName) { startActivityForResult(GalleryActivity.newIntent(getContext(), pageTitle, imageName, pageTitle.getWikiSite(), GalleryFunnel.SOURCE_LINK_PREVIEW), Constants.ACTIVITY_REQUEST_GALLERY); } }; private View.OnClickListener goToPageListener = new View.OnClickListener() { @Override public void onClick(View v) { goToLinkedPage(); } }; private void goToExternalMapsApp() { if (location != null) { dismiss(); GeoUtil.sendGeoIntent(getActivity(), location, pageTitle.getDisplayText()); } } private void loadPage(PageTitle title, HistoryEntry entry, boolean inNewTab) { Callback callback = callback(); if (callback != null) { callback.onLinkPreviewLoadPage(title, entry, inNewTab); } } private void logError(@Nullable ServiceError error, @NonNull String message) { if (error != null) { message += ": " + error.toString(); } L.e(message); } private class OverlayViewCallback implements LinkPreviewOverlayView.Callback { @Override public void onPrimaryClick() { goToLinkedPage(); } @Override public void onSecondaryClick() { goToExternalMapsApp(); } } @Nullable private Callback callback() { return FragmentUtil.getCallback(this, Callback.class); } }