package cgeo.geocaching.ui.dialog; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.functions.Action1; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.graphics.drawable.Drawable; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListAdapter; import android.widget.TextView; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import butterknife.ButterKnife; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.functions.Consumer; import org.apache.commons.lang3.StringUtils; /** * Wrapper for {@link AlertDialog}. If you want to show a simple text, use one of the * {@link #message(Activity, String, String)} variants. If you want the user to confirm using Okay/Cancel or * Yes/No, select one of the {@link #confirm(Activity, String, String, String, OnClickListener)} or * {@link #confirmYesNo(Activity, String, String, OnClickListener)} variants. * */ public final class Dialogs { private Dialogs() { // utility class } /** * Confirm using two buttons "yourText" and "Cancel", where "Cancel" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param positiveButton * text of the positive button (which would typically be the OK button) * @param okayListener * listener of the positive button */ public static AlertDialog.Builder confirm(final Activity context, final String title, final String msg, final String positiveButton, final OnClickListener okayListener) { final AlertDialog.Builder builder = new AlertDialog.Builder(context); final AlertDialog dialog = builder.setTitle(title) .setCancelable(true) .setMessage(msg) .setPositiveButton(positiveButton, okayListener) .setNegativeButton(android.R.string.cancel, null) .create(); dialog.setOwnerActivity(context); dialog.show(); return builder; } /** * Confirm using two buttons "yourText" and "Cancel", where "Cancel" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param positiveButton * text of the positive button (which would typically be the OK button) * @param okayListener * listener of the positive button */ public static AlertDialog.Builder confirm(final Activity context, final int title, final int msg, final int positiveButton, final OnClickListener okayListener) { return confirm(context, getString(title), getString(msg), getString(positiveButton), okayListener); } /** * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param yesListener * listener of the positive button */ public static AlertDialog.Builder confirmYesNo(final Activity context, final String title, final String msg, final OnClickListener yesListener) { final AlertDialog.Builder builder = new AlertDialog.Builder(context); final AlertDialog dialog = builder.setTitle(title) .setCancelable(true) .setMessage(msg) .setPositiveButton(android.R.string.yes, yesListener) .setNegativeButton(android.R.string.no, null) .create(); dialog.setOwnerActivity(context); dialog.show(); return builder; } /** * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param yesListener * listener of the positive button */ public static AlertDialog.Builder confirmYesNo(final Activity context, final String title, final int msg, final OnClickListener yesListener) { return confirmYesNo(context, title, getString(msg), yesListener); } /** * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param yesListener * listener of the positive button */ public static AlertDialog.Builder confirmYesNo(final Activity context, final int title, final String msg, final OnClickListener yesListener) { return confirmYesNo(context, getString(title), msg, yesListener); } /** * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param yesListener * listener of the positive button */ public static AlertDialog.Builder confirmYesNo(final Activity context, final int title, final int msg, final OnClickListener yesListener) { return confirmYesNo(context, getString(title), getString(msg), yesListener); } /** * Confirm using two buttons "OK" and "Cancel", where "Cancel" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param okayListener * listener of the positive button */ public static AlertDialog.Builder confirm(final Activity context, final String title, final String msg, final OnClickListener okayListener) { return confirm(context, title, msg, getString(android.R.string.ok), okayListener); } /** * Confirm using two buttons "OK" and "Cancel", where "Cancel" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param okayListener * listener of the positive button */ public static AlertDialog.Builder confirm(final Activity context, final int title, final String msg, final OnClickListener okayListener) { return confirm(context, getString(title), msg, okayListener); } /** * Confirm using two buttons "OK" and "Cancel", where "Cancel" just closes the dialog. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param okayListener * listener of the positive button */ public static AlertDialog.Builder confirm(final Activity context, final int title, final int msg, final OnClickListener okayListener) { return confirm(context, getString(title), getString(msg), okayListener); } /** * Confirm using three configurable buttons "Positive", "Negative" and "Neutral". Buttons text are configurable. * * @param context * activity hosting the dialog * @param title * dialog title * @param msg * dialog message * @param positiveTextButton * Text for the positive button * @param negativeTextButton * Text for the negative button * @param neutralTextButton * Text for the neutral button * @param positiveListener * listener of the positive button * @param negativeListener * listener of the negative button * @param neutralListener * listener of the neutral button */ public static AlertDialog.Builder confirmPositiveNegativeNeutral(final Activity context, final int title, final String msg, final int positiveTextButton, final int negativeTextButton, final int neutralTextButton, final OnClickListener positiveListener, final OnClickListener negativeListener, final OnClickListener neutralListener) { final AlertDialog.Builder builder = new AlertDialog.Builder(context); final AlertDialog dialog = builder.setTitle(title) .setCancelable(true) .setMessage(msg) .setPositiveButton(positiveTextButton, positiveListener) .setNegativeButton(negativeTextButton, negativeListener) .setNeutralButton(neutralTextButton, neutralListener) .create(); dialog.setOwnerActivity(context); dialog.show(); return builder; } private static String getString(@StringRes final int resourceId) { return CgeoApplication.getInstance().getString(resourceId); } /** * Show a message dialog with a single "OK" button. * * @param context * activity owning the dialog * @param message * message dialog content */ public static void message(final Activity context, final String message) { message(context, null, message); } /** * Show a message dialog with a single "OK" button. * * @param context * activity owning the dialog * @param message * message dialog content */ public static void message(final Activity context, final int message) { message(context, null, getString(message)); } /** * Show a message dialog with a single "OK" button. * * @param context * activity owning the dialog * @param title * message dialog title * @param message * message dialog content */ public static void message(final Activity context, @Nullable final String title, final String message) { message(context, title, message, null); } /** * Show a message dialog with a single "OK" button and an eventual icon. * * @param context * activity owning the dialog * @param title * message dialog title * @param message * message dialog content * @param iconObservable * observable (may be <tt>null</tt>) containing the icon(s) to set */ public static void message(final Activity context, @Nullable final String title, final String message, @Nullable final Observable<Drawable> iconObservable) { final Builder builder = new AlertDialog.Builder(context) .setMessage(message) .setCancelable(true) .setPositiveButton(getString(android.R.string.ok), null); if (title != null) { builder.setTitle(title); } builder.setIcon(ImageUtils.getTransparent1x1Drawable(context.getResources())); final AlertDialog dialog = builder.create(); if (iconObservable != null) { iconObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Drawable>() { @Override public void accept(final Drawable drawable) { dialog.setIcon(drawable); } }); } dialog.show(); } /** * Show a message dialog with a single "OK" button and an icon. * * @param context * activity owning the dialog * @param title * message dialog title * @param message * message dialog content */ public static void message(final Activity context, final int title, final String message) { message(context, getString(title), message); } /** * Show a message dialog with a single "OK" button and an icon. * * @param context * activity owning the dialog * @param title * message dialog title * @param message * message dialog content */ public static void message(final Activity context, final int title, final int message) { message(context, getString(title), getString(message)); } /** * Show a message dialog with a single "OK" button and an icon. * * @param context * activity owning the dialog * @param title * message dialog title * @param message * message dialog content * @param iconObservable * message dialog title icon */ public static void message(final Activity context, final int title, final int message, final Observable<Drawable> iconObservable) { message(context, getString(title), getString(message), iconObservable); } /** * Show a message dialog for input from the user. The okay button is only enabled on non empty input. * * @param context * activity owning the dialog * @param title * message dialog title * @param defaultValue * default input value * @param buttonTitle * title of the okay button * @param okayListener * listener to be run on okay */ public static void input(final Activity context, final int title, final String defaultValue, final int buttonTitle, final Action1<String> okayListener) { final EditText input = new EditText(context); input.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_CLASS_TEXT); input.setText(defaultValue); final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(title); builder.setView(input); builder.setPositiveButton(buttonTitle, new OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { okayListener.call(input.getText().toString()); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int whichButton) { dialog.dismiss(); } }); final AlertDialog dialog = builder.create(); input.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { // empty } @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { // empty } @Override public void afterTextChanged(final Editable editable) { enableDialogButtonIfNotEmpty(dialog, editable.toString()); } }); // force keyboard dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); // disable button dialog.show(); enableDialogButtonIfNotEmpty(dialog, defaultValue); moveCursorToEnd(input); } /** * Move the cursor to the end of the input field. * */ public static void moveCursorToEnd(final EditText input) { input.setSelection(input.getText().length(), input.getText().length()); } private static void enableDialogButtonIfNotEmpty(final AlertDialog dialog, final String input) { dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(StringUtils.isNotBlank(input)); } public interface ItemWithIcon { /** * @return the drawable resource, or {@code 0} for no drawable */ @DrawableRes int getIcon(); } public static <T extends ItemWithIcon> void select(final Activity activity, final String title, final List<T> items, final Action1<T> listener) { final ListAdapter adapter = new ArrayAdapter<T>( activity, android.R.layout.select_dialog_item, android.R.id.text1, items) { @Override public View getView(final int position, final View convertView, final ViewGroup parent) { // standard list entry final View v = super.getView(position, convertView, parent); // add image final TextView tv = ButterKnife.findById(v, android.R.id.text1); tv.setCompoundDrawablesWithIntrinsicBounds(items.get(position).getIcon(), 0, 0, 0); // Add margin between image and text final int dp5 = (int) (5 * activity.getResources().getDisplayMetrics().density + 0.5f); tv.setCompoundDrawablePadding(dp5); return v; } }; new AlertDialog.Builder(activity) .setTitle(title) .setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int item) { listener.call(items.get(item)); } }).show(); } public static void dismiss(@Nullable final ProgressDialog dialog) { if (dialog == null) { return; } if (dialog.isShowing()) { dialog.dismiss(); } } public static void selectGlobalTypeFilter(final Activity activity, final Action1<CacheType> okayListener) { final List<CacheType> cacheTypes = new ArrayList<>(); //first add the most used types cacheTypes.add(CacheType.ALL); cacheTypes.add(CacheType.TRADITIONAL); cacheTypes.add(CacheType.MULTI); cacheTypes.add(CacheType.MYSTERY); // then add all other cache types sorted alphabetically final List<CacheType> sorted = new ArrayList<>(Arrays.asList(CacheType.values())); sorted.removeAll(cacheTypes); Collections.sort(sorted, new Comparator<CacheType>() { @Override public int compare(final CacheType left, final CacheType right) { return TextUtils.COLLATOR.compare(left.getL10n(), right.getL10n()); } }); cacheTypes.addAll(sorted); final int checkedItem = Math.max(0, cacheTypes.indexOf(Settings.getCacheType())); final String[] items = new String[cacheTypes.size()]; for (int i = 0; i < cacheTypes.size(); i++) { items[i] = cacheTypes.get(i).getL10n(); } final Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.menu_filter); builder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int position) { final CacheType cacheType = cacheTypes.get(position); Settings.setCacheType(cacheType); okayListener.call(cacheType); dialog.dismiss(); } }); builder.create().show(); } }