package de.jeisfeld.augendiagnoselib.util;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import android.webkit.WebView;
import android.widget.Toast;
import java.io.Serializable;
import de.jeisfeld.augendiagnoselib.Application;
import de.jeisfeld.augendiagnoselib.Application.AuthorizationLevel;
import de.jeisfeld.augendiagnoselib.R;
import de.jeisfeld.augendiagnoselib.activities.SettingsActivity;
import de.jeisfeld.augendiagnoselib.fragments.DisplayHtmlFragment;
import de.jeisfeld.augendiagnoselib.util.DialogUtil.ConfirmDialogFragment.ConfirmDialogListener;
import de.jeisfeld.augendiagnoselib.util.DialogUtil.DisplayMessageDialogFragment.MessageDialogListener;
import de.jeisfeld.augendiagnoselib.util.imagefile.EyePhoto;
import de.jeisfeld.augendiagnoselib.util.imagefile.JpegMetadata;
import de.jeisfeld.augendiagnoselib.util.imagefile.JpegSynchronizationUtil;
/**
* Helper class to show standard dialogs.
*/
public final class DialogUtil {
/**
* Parameter to pass the title to the DialogFragment.
*/
private static final String PARAM_TITLE = "title";
/**
* Parameter to pass the message to the DialogFragment (of all types).
*/
private static final String PARAM_MESSAGE = "message";
/**
* Parameter to pass the icon to the DialogFragment (of all types).
*/
private static final String PARAM_ICON = "icon";
/**
* Parameter to pass the text resource for the confirmation button to the ConfirmDialogFragment.
*/
private static final String PARAM_BUTTON_RESOURCE = "buttonResource";
/**
* Parameter to pass the callback listener to the ConfirmDialogFragment.
*/
private static final String PARAM_LISTENER = "listener";
/**
* Parameter to pass the key for the shared preference indicating if the tip should be shown.
*/
private static final String PARAM_PREFERENCE_KEY = "keyPrefTip";
/**
* Instance state flag indicating if a dialog should not be recreated after orientation change.
*/
private static final String PREVENT_RECREATION = "preventRecreation";
/**
* Hide default constructor.
*/
private DialogUtil() {
throw new UnsupportedOperationException();
}
/**
* Display an information message and go back to the current activity.
*
* @param activity the current activity
* @param listener an optional listener waiting for the dialog response. If a listener is given, then the dialog will not
* be automatically recreated on orientation change!
* @param resource the message resource
* @param args arguments for the error message
*/
public static void displayInfo(@NonNull final Activity activity, @Nullable final MessageDialogListener listener, final int resource,
final Object... args) {
String message = String.format(activity.getString(resource), args);
Bundle bundle = new Bundle();
bundle.putCharSequence(PARAM_MESSAGE, message);
bundle.putString(PARAM_TITLE, activity.getString(R.string.title_dialog_info));
bundle.putInt(PARAM_ICON, R.drawable.ic_title_info);
if (listener != null) {
bundle.putSerializable(PARAM_LISTENER, listener);
}
DialogFragment fragment = new DisplayMessageDialogFragment();
fragment.setArguments(bundle);
fragment.show(activity.getFragmentManager(), fragment.getClass().toString());
}
/**
* Display an error and either go back to the current activity or finish the current activity.
*
* @param activity the current activity
* @param resource the error message
* @param listener listener to react on dialog confirmation or dismissal.
* @param args arguments for the error message
*/
private static void displayError(@NonNull final Activity activity, final int resource, @Nullable final MessageDialogListener listener,
final Object... args) {
String message = String.format(activity.getString(resource), args);
Log.w(Application.TAG, "Dialog message: " + message);
Bundle bundle = new Bundle();
bundle.putCharSequence(PARAM_MESSAGE, message);
bundle.putString(PARAM_TITLE, activity.getString(R.string.title_dialog_error));
bundle.putInt(PARAM_ICON, R.drawable.ic_title_error);
if (listener != null) {
bundle.putSerializable(PARAM_LISTENER, listener);
}
DialogFragment fragment = new DisplayMessageDialogFragment();
fragment.setArguments(bundle);
fragment.show(activity.getFragmentManager(), fragment.getClass().toString());
}
/**
* Display an error and either go back to the current activity or finish the current activity.
*
* @param activity the current activity
* @param resource the error message
* @param finishActivity a flag indicating if the activity should be finished.
* @param args arguments for the error message
*/
public static void displayError(@NonNull final Activity activity, final int resource, final boolean finishActivity,
final Object... args) {
MessageDialogListener listener = null;
if (finishActivity) {
listener = new MessageDialogListener() {
/**
* The serial version id.
*/
private static final long serialVersionUID = 1L;
@Override
public void onDialogClick(final DialogFragment dialog) {
activity.finish();
}
@Override
public void onDialogCancel(final DialogFragment dialog) {
activity.finish();
}
};
}
displayError(activity, resource, listener, args);
}
/**
* Display an error indicating insignificant authorization and redirect to settings.
*
* @param activity the current activity
* @param resource the error message
*/
public static void displayAuthorizationError(@NonNull final Activity activity, final int resource) {
MessageDialogListener listener = new MessageDialogListener() {
/**
* The serial version id.
*/
private static final long serialVersionUID = 1L;
@Override
public void onDialogClick(final DialogFragment dialog) {
SettingsActivity.startActivity(activity, R.string.key_dummy_screen_premium_settings);
activity.finish();
}
@Override
public void onDialogCancel(final DialogFragment dialog) {
if (Application.getAuthorizationLevel() == AuthorizationLevel.NO_ACCESS) {
SettingsActivity.startActivity(activity, R.string.key_dummy_screen_premium_settings);
activity.finish();
}
}
};
displayInfo(activity, listener, resource);
}
/**
* Display an error just as toast.
*
* @param context the current activity or context
* @param resource the error message
* @param args arguments for the error message
*/
public static void displayToast(@NonNull final Context context, final int resource, final Object... args) {
String message = String.format(context.getString(resource), args);
Log.d(Application.TAG, "Toast message: " + message);
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
/**
* Display a confirmation message asking for cancel or ok.
*
* @param activity the current activity
* @param listener The listener waiting for the response
* @param buttonResource the display on the positive button
* @param messageResource the confirmation message
* @param args arguments for the confirmation message
*/
public static void displayConfirmationMessage(@NonNull final Activity activity,
final ConfirmDialogListener listener, final int buttonResource,
final int messageResource, final Object... args) {
String message = String.format(activity.getString(messageResource), args);
Bundle bundle = new Bundle();
bundle.putCharSequence(PARAM_MESSAGE, message);
bundle.putInt(PARAM_BUTTON_RESOURCE, buttonResource);
bundle.putSerializable(PARAM_LISTENER, listener);
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
fragment.setArguments(bundle);
fragment.show(activity.getFragmentManager(), fragment.getClass().toString());
}
/**
* Display a tip.
*
* @param activity the triggering activity
* @param messageResource The resource containing the text of the tip.
* @param preferenceResource The resource for the key of the preference storing the information if the tip should be skipped later.
*/
public static void displayTip(@NonNull final Activity activity, final int messageResource,
final int preferenceResource) {
displayTip(activity, R.string.title_dialog_tip, R.drawable.ic_title_tipp, messageResource, preferenceResource);
}
/**
* Display a tip.
*
* @param activity the triggering activity
* @param titleResource The resource containing the title.
* @param iconResource The resource containing the icon.
* @param messageResource The resource containing the text of the tip.
* @param preferenceResource The resource for the key of the preference storing the information if the tip should be skipped later.
*/
private static void displayTip(@NonNull final Activity activity, final int titleResource, final int iconResource,
final int messageResource, final int preferenceResource) {
String message = activity.getString(messageResource);
boolean skip = PreferenceUtil.getSharedPreferenceBoolean(preferenceResource);
if (!skip) {
Bundle bundle = new Bundle();
bundle.putString(PARAM_TITLE, activity.getString(titleResource));
bundle.putInt(PARAM_ICON, iconResource);
bundle.putString(PARAM_MESSAGE, message);
bundle.putInt(PARAM_PREFERENCE_KEY, preferenceResource);
DisplayTipFragment fragment = new DisplayTipFragment();
fragment.setArguments(bundle);
fragment.show(activity.getFragmentManager(), DisplayTipFragment.class.toString());
}
}
/**
* Format one line of the image display.
*
* @param activity the triggering activity.
* @param resource The resource containing the label of the line.
* @param value The value of the parameter.
* @return The formatted line.
*/
private static String formatImageInfoLine(@NonNull final Activity activity, final int resource, @NonNull final String value) {
StringBuilder line = new StringBuilder("<b>");
line.append(activity.getString(resource));
line.append("</b><br>");
if (SystemUtil.isAtLeastVersion(Build.VERSION_CODES.JELLY_BEAN)) {
// Workaround to escape html, but transfer line breaks to HTML
line.append(Html.escapeHtml(value.replace("\n", "|||LINEBREAK|||")).replace("|||LINEBREAK|||", "<br>"));
}
else {
line.append(value.replace("&", "&").replace("\n", "<br>").replace("<", "<").replace(">", ">"));
}
line.append("<br><br>");
return line.toString();
}
/**
* Display the info of this photo.
*
* @param activity the triggering activity
* @param eyePhoto the photo for which the image should be displayed.
*/
public static void displayImageInfo(@NonNull final Activity activity, @NonNull final EyePhoto eyePhoto) {
StringBuilder message = new StringBuilder();
message.append(formatImageInfoLine(activity, R.string.imageinfo_line_filename, eyePhoto.getFilename()));
message.append(formatImageInfoLine(activity, R.string.imageinfo_line_filedate, eyePhoto.getDateString()));
try {
JpegMetadata metadata = JpegSynchronizationUtil.getJpegMetadata(eyePhoto.getAbsolutePath());
if (metadata.getPerson() != null && metadata.getPerson().length() > 0) {
message.append(formatImageInfoLine(activity, R.string.imageinfo_line_name, metadata.getPerson()));
}
if (metadata.getComment() != null && metadata.getComment().length() > 0) {
message.append(formatImageInfoLine(activity, R.string.imageinfo_line_comment, metadata.getComment()));
}
}
catch (Exception e) {
// cannot append metadata.
}
Bundle bundle = new Bundle();
bundle.putCharSequence(PARAM_MESSAGE, fromHtml(message.toString()));
bundle.putString(PARAM_TITLE, activity.getString(R.string.title_dialog_image_info));
bundle.putInt(PARAM_ICON, R.drawable.ic_title_info);
DialogFragment fragment = new DisplayMessageDialogFragment();
fragment.setArguments(bundle);
fragment.show(activity.getFragmentManager(), fragment.getClass().toString());
}
/**
* Check if there was an out of memory error, and if so, display a corresponding message.
*
* @param activity the triggering activity
*/
public static void checkOutOfMemoryError(@NonNull final Activity activity) {
boolean hadOutOfMemoryError = PreferenceUtil.getSharedPreferenceBoolean(R.string.key_internal_outofmemoryerror);
if (hadOutOfMemoryError) {
displayError(activity, R.string.message_dialog_outofmemoryerror, false);
PreferenceUtil.setSharedPreferenceBoolean(R.string.key_internal_outofmemoryerror, false);
}
}
/**
* Convert a html String into a text.
*
* @param html The html
* @return the text
*/
public static Spanned fromHtml(final String html) {
if (VERSION.SDK_INT >= VERSION_CODES.N) {
return fromHtml24(html);
}
else {
return fromHtml23(html);
}
}
/**
* Convert a html String into a text (Android version below N).
*
* @param html The html
* @return the text
*/
@SuppressWarnings("deprecation")
private static Spanned fromHtml23(final String html) {
return Html.fromHtml(html);
}
/**
* Convert a html String into a text (Android version N or higher).
*
* @param html The html
* @return the text
*/
@RequiresApi(api = VERSION_CODES.N)
private static Spanned fromHtml24(final String html) {
return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
}
/**
* Fragment to display an error and go back to the current activity.
*/
public static class DisplayMessageDialogFragment extends DialogFragment {
/**
* The listener called when the dialog is ended.
*/
@Nullable
private MessageDialogListener mListener = null;
@Override
public final Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
CharSequence message = getArguments().getCharSequence(PARAM_MESSAGE);
String title = getArguments().getString(PARAM_TITLE);
int iconResource = getArguments().getInt(PARAM_ICON);
mListener = (MessageDialogListener) getArguments().getSerializable(PARAM_LISTENER);
getArguments().putSerializable(PARAM_LISTENER, null);
// Listeners cannot retain functionality when automatically recreated.
// Therefore, dialogs with listeners must be re-created by the activity on orientation change.
boolean preventRecreation = false;
if (savedInstanceState != null) {
preventRecreation = savedInstanceState.getBoolean(PREVENT_RECREATION);
}
if (preventRecreation) {
dismiss();
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(title)
.setIcon(iconResource)
.setMessage(message)
.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull final DialogInterface dialog, final int id) {
if (mListener != null) {
mListener.onDialogClick(DisplayMessageDialogFragment.this);
}
dialog.dismiss();
}
});
return builder.create();
}
@Override
public final void onCancel(final DialogInterface dialog) {
super.onCancel(dialog);
if (mListener != null) {
mListener.onDialogCancel(DisplayMessageDialogFragment.this);
}
}
@Override
public final void onSaveInstanceState(@NonNull final Bundle outState) {
if (mListener != null) {
// Typically cannot serialize the listener due to its reference to the activity.
mListener = null;
outState.putBoolean(PREVENT_RECREATION, true);
}
super.onSaveInstanceState(outState);
}
/**
* The activity that creates an instance of this dialog listFoldersFragment must implement this interface in
* order to receive event callbacks. Each method passes the DialogFragment in case the host needs to query it.
*/
public interface MessageDialogListener extends Serializable {
/**
* Callback method for ok click from the dialog.
*
* @param dialog the confirmation dialog fragment.
*/
void onDialogClick(DialogFragment dialog);
/**
* Callback method for cancellation of the dialog.
*
* @param dialog the confirmation dialog fragment.
*/
void onDialogCancel(DialogFragment dialog);
}
}
/**
* Fragment to display a confirmation message.
*/
public static class ConfirmDialogFragment extends DialogFragment {
/**
* The listener called when the dialog is ended.
*/
@Nullable
private ConfirmDialogListener mListener = null;
@Override
public final Dialog onCreateDialog(final Bundle savedInstanceState) {
CharSequence message = getArguments().getCharSequence(PARAM_MESSAGE);
int confirmButtonResource = getArguments().getInt(PARAM_BUTTON_RESOURCE);
mListener = (ConfirmDialogListener) getArguments().getSerializable(PARAM_LISTENER);
getArguments().putSerializable(PARAM_LISTENER, null);
// Listeners cannot retain functionality when automatically recreated.
// Therefore, dialogs with listeners must be re-created by the activity on orientation change.
boolean preventRecreation = false;
if (savedInstanceState != null) {
preventRecreation = savedInstanceState.getBoolean(PREVENT_RECREATION);
}
if (preventRecreation) {
mListener = null;
dismiss();
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.title_dialog_confirmation)
.setIcon(R.drawable.ic_title_warning)
.setMessage(message)
.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int id) {
// Send the positive button event back to the host activity
if (mListener != null) {
mListener.onDialogNegativeClick(ConfirmDialogFragment.this);
}
}
}) //
.setPositiveButton(confirmButtonResource, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int id) {
// Send the negative button event back to the host activity
if (mListener != null) {
mListener.onDialogPositiveClick(ConfirmDialogFragment.this);
}
}
});
return builder.create();
}
@Override
public final void onCancel(final DialogInterface dialog) {
super.onCancel(dialog);
if (mListener != null) {
mListener.onDialogNegativeClick(ConfirmDialogFragment.this);
}
}
@Override
public final void onSaveInstanceState(@NonNull final Bundle outState) {
if (mListener != null) {
// Typically cannot serialize the listener due to its reference to the activity.
mListener = null;
outState.putBoolean(PREVENT_RECREATION, true);
}
super.onSaveInstanceState(outState);
}
/**
* The activity that creates an instance of this dialog listFoldersFragment must implement this interface in
* order to receive event callbacks. Each method passes the DialogFragment in case the host needs to query it.
*/
public interface ConfirmDialogListener extends Serializable {
/**
* Callback method for positive click from the confirmation dialog.
*
* @param dialog the confirmation dialog fragment.
*/
void onDialogPositiveClick(DialogFragment dialog);
/**
* Callback method for negative click from the confirmation dialog.
*
* @param dialog the confirmation dialog fragment.
*/
void onDialogNegativeClick(DialogFragment dialog);
}
}
/**
* Fragment to display a tip - the user may decide if to show it again later.
*/
public static class DisplayTipFragment extends DialogFragment {
@Override
public final Dialog onCreateDialog(final Bundle savedInstanceState) {
String message = getArguments().getString(PARAM_MESSAGE);
final int key = getArguments().getInt(PARAM_PREFERENCE_KEY);
String title = getArguments().getString(PARAM_TITLE);
int iconResource = getArguments().getInt(PARAM_ICON);
WebView webView = new WebView(getActivity());
webView.setBackgroundColor(0x00000000);
DisplayHtmlFragment.setOpenLinksInExternalBrowser(webView, null);
message = ReleaseNotesUtil.HTML_PREFIX + message + ReleaseNotesUtil.HTML_POSTFIX;
webView.loadDataWithBaseURL("file:///android_res/drawable/", message, "text/html", "utf-8", "");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(title)
.setIcon(iconResource)
.setView(webView)
.setNegativeButton(R.string.button_show_later, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull final DialogInterface dialog, final int id) {
PreferenceUtil.setSharedPreferenceBoolean(key, false);
dialog.dismiss();
}
}).setPositiveButton(R.string.button_dont_show, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull final DialogInterface dialog, final int id) {
PreferenceUtil.setSharedPreferenceBoolean(key, true);
dialog.dismiss();
}
});
return builder.create();
}
}
}