Mholloway has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/344687 )
Change subject: Show an accurate message in the link preview when a page doesn't exist ...................................................................... Show an accurate message in the link preview when a page doesn't exist Updates the link preview to show a "page does not exist" message when appropriate. Bug: T161008 Change-Id: I3b95431abb9305b191250ff527272d1869208c54 --- M app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java A app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewErrorView.java M app/src/main/java/org/wikipedia/util/ThrowableUtil.java A app/src/main/res/drawable/ic_portable_wifi_off_black_24px.xml M app/src/main/res/layout/dialog_link_preview.xml A app/src/main/res/layout/view_link_preview_error.xml M app/src/main/res/values-qq/strings.xml M app/src/main/res/values/strings.xml 8 files changed, 251 insertions(+), 54 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia refs/changes/87/344687/1 diff --git a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java index 09154c2..c230ca3 100755 --- a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java +++ b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java @@ -40,17 +40,20 @@ import org.wikipedia.util.log.L; import org.wikipedia.views.ViewUtil; +import java.net.UnknownHostException; + import retrofit2.Call; import retrofit2.Response; import static org.wikipedia.util.L10nUtil.getStringForArticleLanguage; import static org.wikipedia.util.L10nUtil.setConditionalLayoutDirection; +import static org.wikipedia.util.ThrowableUtil.is404; +import static org.wikipedia.util.ThrowableUtil.isOffline; public class LinkPreviewDialog extends ExtendedBottomSheetDialogFragment - implements DialogInterface.OnDismissListener { + implements LinkPreviewErrorView.Callback, DialogInterface.OnDismissListener { public interface Callback { - void onLinkPreviewLoadPage(@NonNull PageTitle title, @NonNull HistoryEntry entry, - boolean inNewTab); + 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); @@ -60,7 +63,7 @@ private LinearLayout dialogContainer; private View contentContainer; - private View offlineContainer; + private LinkPreviewErrorView errorContainer; private ProgressBar progressBar; private TextView extractText; private SimpleDraweeView thumbnailView; @@ -88,8 +91,7 @@ } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { WikipediaApp app = WikipediaApp.getInstance(); pageTitle = getArguments().getParcelable("title"); entrySource = getArguments().getInt("entrySource"); @@ -98,7 +100,7 @@ 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); - offlineContainer = rootView.findViewById(R.id.dialog_link_preview_offline_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); @@ -198,6 +200,28 @@ } } + @Override + public void setOverlayButtonText(@NonNull String text) { + overlayView.setPrimaryButtonText(text); + } + + @Override + public void setOverlayButtonCallback(@NonNull LinkPreviewOverlayView.Callback callback) { + overlayView.setCallback(callback); + } + + @Override + public void onAddToList() { + if (callback() != null) { + callback().onLinkPreviewAddToList(pageTitle); + } + } + + @Override + public void onDismiss() { + dismiss(); + } + private void loadContent() { PageClientFactory .create(pageTitle.getWikiSite(), pageTitle.namespace()) @@ -205,7 +229,7 @@ .enqueue(linkPreviewNetworkOnLoadCallback); } - private void tryLoadingFromCache() { + private void tryLoadingFromCache(@Nullable final UnknownHostException offlineException) { L.v("Loading link preview from Saved Pages"); new LoadSavedPageTask(pageTitle) { @Override public void onFinish(Page page) { @@ -219,7 +243,11 @@ if (!isAdded()) { return; } - showPreviewOfflineNotice(); + if (offlineException != null) { + showError(offlineException); + } else { + showError(caught); + } L.e("Page summary cache request failed", caught); } }.execute(); @@ -230,14 +258,13 @@ setPreviewContents(contents); } - private void showPreviewOfflineNotice() { + private void showError(@Nullable Throwable caught) { dialogContainer.setLayoutTransition(null); progressBar.setVisibility(View.GONE); contentContainer.setVisibility(View.GONE); - offlineContainer.setVisibility(View.VISIBLE); - overlayView.setPrimaryButtonText(getResources().getString(R.string.button_add_to_reading_list)); overlayView.showSecondaryButton(false); - overlayView.setCallback(new OverlayViewOfflineCallback()); + errorContainer.setVisibility(View.VISIBLE); + errorContainer.setError(this, caught); } private void setPreviewContents(@NonNull LinkPreviewContents contents) { @@ -258,15 +285,22 @@ if (summary != null && !summary.hasError()) { showPreview(new LinkPreviewContents(summary, pageTitle.getWikiSite())); } else { - tryLoadingFromCache(); + tryLoadingFromCache(null); logError(summary.hasError() ? summary.getError() : null, "Page summary network request failed"); } } - @Override public void onFailure(Call<PageSummary> call, Throwable t) { - tryLoadingFromCache(); - L.e(t); + @Override public void onFailure(Call<PageSummary> call, Throwable caught) { + L.e(caught); + if (is404(getContext(), caught)) { + // If the page doesn't exist, show an error immediately without checking the cache. + showError(caught); + } else { + // If we're offline, check the cache but pass along the exception so that we show + // the correct error message if the page isn't found in the cache. + tryLoadingFromCache(isOffline(caught) ? (UnknownHostException) caught : null); + } } }; @@ -369,19 +403,6 @@ @Override public void onSecondaryClick() { goToExternalMapsApp(); - } - } - - private class OverlayViewOfflineCallback implements LinkPreviewOverlayView.Callback { - @Override - public void onPrimaryClick() { - if (callback() != null) { - callback().onLinkPreviewAddToList(pageTitle); - } - } - - @Override - public void onSecondaryClick() { } } diff --git a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewErrorView.java b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewErrorView.java new file mode 100644 index 0000000..4d1e02d --- /dev/null +++ b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewErrorView.java @@ -0,0 +1,151 @@ +package org.wikipedia.page.linkpreview; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.wikipedia.R; +import org.wikipedia.util.ThrowableUtil; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class LinkPreviewErrorView extends LinearLayout { + @BindView(R.id.view_link_preview_error_icon) ImageView icon; + @BindView(R.id.view_link_preview_error_text) TextView textView; + + interface Callback { + @NonNull Context getContext(); + void setOverlayButtonText(@NonNull String text); + void setOverlayButtonCallback(@NonNull LinkPreviewOverlayView.Callback buttonCallback); + void onAddToList(); + void onDismiss(); + } + + @Nullable private Callback callback; + + public LinkPreviewErrorView(Context context) { + this(context, null); + } + + public LinkPreviewErrorView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public LinkPreviewErrorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + @TargetApi(21) + public LinkPreviewErrorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + inflate(context, R.layout.view_link_preview_error, this); + ButterKnife.bind(this); + } + + public void setError(@NonNull Callback callback, @Nullable Throwable caught) { + this.callback = callback; + Context context = callback.getContext(); + Resources resources = context.getResources(); + ErrorType errorType = getErrorType(context, caught); + icon.setImageDrawable(ContextCompat.getDrawable(context, errorType.icon())); + textView.setText(resources.getString(errorType.text())); + callback.setOverlayButtonText(resources.getString(errorType.buttonText())); + callback.setOverlayButtonCallback(getCallbackForAction(errorType.buttonAction())); + } + + @NonNull private LinkPreviewOverlayView.Callback getCallbackForAction(ButtonAction action) { + if (action == ButtonAction.ADD_TO_LIST) { + return new ButtonAddToListCallback(); + } + if (action == ButtonAction.DISMISS) { + return new ButtonDismissCallback(); + } + throw new IllegalArgumentException(); + } + + @NonNull private ErrorType getErrorType(@NonNull Context context, @Nullable Throwable caught) { + if (caught != null && ThrowableUtil.is404(context, caught)) { + return ErrorType.PAGE_MISSING; + } + if (caught != null && ThrowableUtil.isOffline(caught)) { + return ErrorType.OFFLINE; + } + return ErrorType.GENERIC; + } + + private enum ErrorType { + OFFLINE(R.drawable.ic_portable_wifi_off_black_24px, R.string.dialog_link_preview_offline_notice, + R.string.button_add_to_reading_list, ButtonAction.ADD_TO_LIST) { + }, + + PAGE_MISSING(R.drawable.ic_error_black_24dp, R.string.error_page_does_not_exist, + R.string.view_link_preview_error_button_dismiss, ButtonAction.DISMISS), + + GENERIC(R.drawable.ic_error_black_24dp, R.string.error_message_generic, + R.string.view_link_preview_error_button_dismiss, ButtonAction.DISMISS); + + @DrawableRes private int icon; + @StringRes private int text; + @StringRes private int buttonText; + private ButtonAction buttonAction; + + ErrorType(@DrawableRes int icon, @StringRes int text, @StringRes int buttonText, ButtonAction buttonAction) { + this.icon = icon; + this.text = text; + this.buttonText = buttonText; + this.buttonAction = buttonAction; + } + + @DrawableRes int icon() { + return icon; + } + + @StringRes int text() { + return text; + } + + @StringRes int buttonText() { + return buttonText; + } + + ButtonAction buttonAction() { + return buttonAction; + } + } + + private class ButtonAddToListCallback implements LinkPreviewOverlayView.Callback { + @Override public void onPrimaryClick() { + if (callback != null) { + callback.onAddToList(); + } + } + + @Override public void onSecondaryClick() { + } + } + + private class ButtonDismissCallback implements LinkPreviewOverlayView.Callback { + @Override public void onPrimaryClick() { + if (callback != null) { + callback.onDismiss(); + } + } + + @Override public void onSecondaryClick() { + } + } + + private enum ButtonAction { + ADD_TO_LIST, DISMISS + } +} diff --git a/app/src/main/java/org/wikipedia/util/ThrowableUtil.java b/app/src/main/java/org/wikipedia/util/ThrowableUtil.java index e8881f7..447bc30 100644 --- a/app/src/main/java/org/wikipedia/util/ThrowableUtil.java +++ b/app/src/main/java/org/wikipedia/util/ThrowableUtil.java @@ -11,6 +11,7 @@ import org.wikipedia.R; import org.wikipedia.createaccount.CreateAccountException; import org.wikipedia.dataclient.mwapi.MwException; +import org.wikipedia.dataclient.okhttp.HttpStatusException; import org.wikipedia.login.LoginClient; import java.net.UnknownHostException; @@ -53,8 +54,10 @@ getApiErrorMessage(context, (ApiException) inner)); } else if (isNetworkError(e)) { result = new AppError(context.getString(R.string.error_network_error), - context.getString(R.string.format_error_server_message, - inner.getLocalizedMessage())); + context.getString(R.string.format_error_server_message, + inner.getLocalizedMessage())); + } else if (e instanceof HttpStatusException) { + result = new AppError(e.getMessage(), Integer.toString(((HttpStatusException) e).code())); } else if (inner instanceof LoginClient.LoginFailedException || inner instanceof CreateAccountException || inner instanceof MwException) { @@ -78,6 +81,10 @@ return e.getDetail() != null && e.getDetail().contains("404"); } + public static boolean isOffline(@NonNull Throwable caught) { + return caught instanceof UnknownHostException; + } + private static boolean isNetworkError(@NonNull Throwable e) { return ThrowableUtil.throwableContainsException(e, HttpRequest.HttpRequestException.class) || ThrowableUtil.throwableContainsException(e, UnknownHostException.class) diff --git a/app/src/main/res/drawable/ic_portable_wifi_off_black_24px.xml b/app/src/main/res/drawable/ic_portable_wifi_off_black_24px.xml new file mode 100644 index 0000000..98b6999 --- /dev/null +++ b/app/src/main/res/drawable/ic_portable_wifi_off_black_24px.xml @@ -0,0 +1,5 @@ +<vector android:autoMirrored="true" android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#000000" android:pathData="M17.56,14.24c0.28,-0.69 0.44,-1.45 0.44,-2.24 0,-3.31 -2.69,-6 -6,-6 -0.79,0 -1.55,0.16 -2.24,0.44l1.62,1.62c0.2,-0.03 0.41,-0.06 0.62,-0.06 2.21,0 4,1.79 4,4 0,0.21 -0.02,0.42 -0.05,0.63l1.61,1.61zM12,4c4.42,0 8,3.58 8,8 0,1.35 -0.35,2.62 -0.95,3.74l1.47,1.47C21.46,15.69 22,13.91 22,12c0,-5.52 -4.48,-10 -10,-10 -1.91,0 -3.69,0.55 -5.21,1.47l1.46,1.46C9.37,4.34 10.65,4 12,4zM3.27,2.5L2,3.77l2.1,2.1C2.79,7.57 2,9.69 2,12c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,17.53 4,14.96 4,12c0,-1.76 0.57,-3.38 1.53,-4.69l1.43,1.44C6.36,9.68 6,10.8 6,12c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-0.65 0.17,-1.25 0.44,-1.79l1.58,1.58L10,12c0,1.1 0.9,2 2,2l0.21,-0.02 0.01,0.01 7.51,7.51L21,20.23 4.27,3.5l-1,-1z"/> +</vector> diff --git a/app/src/main/res/layout/dialog_link_preview.xml b/app/src/main/res/layout/dialog_link_preview.xml index 17863f4..9c6cc46 100755 --- a/app/src/main/res/layout/dialog_link_preview.xml +++ b/app/src/main/res/layout/dialog_link_preview.xml @@ -94,31 +94,13 @@ android:layout_gravity="center_horizontal" /> </LinearLayout> - <LinearLayout - android:id="@+id/dialog_link_preview_offline_container" + <org.wikipedia.page.linkpreview.LinkPreviewErrorView + android:id="@+id/dialog_link_preview_error_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" android:visibility="gone"> - <ImageView - android:layout_width="72dp" - android:layout_height="72dp" - android:layout_margin="24dp" - android:layout_gravity="center_horizontal" - app:srcCompat="@drawable/ic_no_article" - android:tint="?attr/link_preview_offline_text_color" - android:contentDescription="@null"/> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="42dp" - android:layout_marginRight="42dp" - android:textSize="18sp" - android:textColor="?attr/link_preview_offline_text_color" - android:textAlignment="center" - android:lineSpacingMultiplier="1.2" - android:text="@string/dialog_link_preview_offline_notice"/> - </LinearLayout> + + </org.wikipedia.page.linkpreview.LinkPreviewErrorView> <!-- Bottom padding for overlay button --> <View diff --git a/app/src/main/res/layout/view_link_preview_error.xml b/app/src/main/res/layout/view_link_preview_error.xml new file mode 100644 index 0000000..3fe5310 --- /dev/null +++ b/app/src/main/res/layout/view_link_preview_error.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <ImageView + android:id="@+id/view_link_preview_error_icon" + android:layout_width="72dp" + android:layout_height="72dp" + android:layout_margin="24dp" + android:layout_gravity="center_horizontal" + app:srcCompat="@drawable/ic_error_black_24dp" + android:tint="?attr/link_preview_offline_text_color" + android:contentDescription="@null"/> + <TextView + android:id="@+id/view_link_preview_error_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="42dp" + android:layout_marginRight="42dp" + android:textSize="18sp" + android:textColor="?attr/link_preview_offline_text_color" + android:textAlignment="center" + android:lineSpacingMultiplier="1.2" + android:text="@string/error_message_generic"/> +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml index db76529..da9ffe8 100644 --- a/app/src/main/res/values-qq/strings.xml +++ b/app/src/main/res/values-qq/strings.xml @@ -369,6 +369,7 @@ <string name="location_permissions_enable_action">Button label for requesting to turn on location permissions.</string> <string name="error_webview_updating">Error informing the user that the System WebView is being updated, meaning that the app cannot be launched right now, and that the user should try again in a moment.</string> <string name="multi_select_items_selected">Toolbar title shown when the user is selecting multiple items in a list. The \"%d\" symbol is replaced with the number of items currently selected.</string> + <string name="error_message_generic">Generic message informing the user that an error occurred</string> <string name="crash_report_dialog_title">Title of the dialog that pops up when the app crashes, asking user to send a report</string> <string name="crash_report_dialog_text">Text asking the user to send us a crash report</string> <string name="crash_report_dialog_dont_send_button">In the dialog that pops up when the app crashes, text for button that doesn\'t send a crash report. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7dba702..0a1386f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -255,6 +255,9 @@ <string name="location_permissions_enable_action">Turn on</string> <string name="error_webview_updating">The Android System WebView is currently being updated. Please try again in a moment.</string> <string name="multi_select_items_selected">%d selected</string> + <string name="error_message_generic">An error occurred</string> + <string name="view_link_preview_error_button_dismiss">Dismiss</string> + <string name="error_page_does_not_exist">This page does not exist</string> <!-- Crash reporter --> <string name="crash_report_dialog_title">Sorry, app crashed last time</string> -- To view, visit https://gerrit.wikimedia.org/r/344687 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I3b95431abb9305b191250ff527272d1869208c54 Gerrit-PatchSet: 1 Gerrit-Project: apps/android/wikipedia Gerrit-Branch: master Gerrit-Owner: Mholloway <mhollo...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits