package de.jeisfeld.augendiagnoselib.components;
import android.app.Activity;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.preference.ListPreference;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import de.jeisfeld.augendiagnoselib.Application;
import de.jeisfeld.augendiagnoselib.fragments.DirectoryChooserDialogFragment;
import de.jeisfeld.augendiagnoselib.fragments.DirectoryChooserDialogFragment.ChosenDirectoryListener;
import de.jeisfeld.augendiagnoselib.util.imagefile.FileUtil;
/**
* A variant of ListPreference that allows to choose from a list of given folders (configured in the menu configuration)
* or to select a custom folder via the directory browser.
*/
public class DirectorySelectionPreference extends ListPreference {
/**
* List value to represent a custom folder to be chosen.
*/
private static final String CUSTOM_FOLDER = "__custom__";
/**
* Tag to be replaced by the external storage root.
*/
private static final String EXTERNAL_STORAGE_PREFIX = "__ext_storage__";
/**
* Tag to be replaced by the application's cache directory.
*/
private static final String CACHE_DIR_PREFIX = "__cache_dir__";
/**
* List tag to be replaced by the default camera folder.
*/
private static final String CAMERA_FOLDER_PREFIX = "__folder_camera__";
/**
* List tag to be replaced by the default eye-fi folder.
*/
private static final String EYEFI_FOLDER_PREFIX = "__folder_eyefi__";
/**
* The selected index in the list.
*/
private int mSelectedIndex = -1;
/**
* The custom directory selected via directory browser. Value is null if no custom directory is selected.
*/
@Nullable
private String mSelectedCustomDir = null;
/**
* The list index of the custom directory.
*/
private int mCustomIndex = -1;
/**
* A listener called when the dialog is closed.
*/
@Nullable
private OnDialogClosedListener mOnDialogClosedListener = null;
/**
* The constructor replaces placeholders for external storage and camera folder.
*
* @param context The Context this is associated with.
* @param attrs (from Preference) The attributes of the XML tag that is inflating the preference.
*/
public DirectorySelectionPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
CharSequence[] entryValues = getEntryValues();
// Update special values for external storage and camera folder
for (int i = 0; i < entryValues.length; i++) {
String value = entryValues[i].toString();
String mappedValue = replaceSpecialFolderTags(value);
if (value.equals(CUSTOM_FOLDER)) {
mCustomIndex = i;
}
else if (!mappedValue.equals(value)) {
entryValues[i] = mappedValue;
}
}
}
/**
* Replace special folder tags in a path.
*
* @param path The path.
* @return The path with special folder tags replaced.
*/
@NonNull
public static String replaceSpecialFolderTags(@NonNull final String path) {
if (path.startsWith(EXTERNAL_STORAGE_PREFIX)) {
return FileUtil.getSdCardPath() + path.substring(EXTERNAL_STORAGE_PREFIX.length());
}
else if (path.startsWith(CACHE_DIR_PREFIX)) {
return FileUtil.getTempCameraFolder().getAbsolutePath() + path.substring(CACHE_DIR_PREFIX.length());
}
else if (path.startsWith(CAMERA_FOLDER_PREFIX)) {
return FileUtil.getDefaultCameraFolder() + path.substring(CAMERA_FOLDER_PREFIX.length());
}
else if (path.startsWith(EYEFI_FOLDER_PREFIX)) {
return FileUtil.getDefaultEyeFiFolder() + path.substring(EYEFI_FOLDER_PREFIX.length());
}
else {
return path;
}
}
/**
* Standard constructor.
*
* @param context The Context this is associated with.
*/
public DirectorySelectionPreference(final Context context) {
this(context, null);
}
/**
* Create the dialog and prepare the creation of the directory selection dialog.
*
* @param builder The DialogBuilder to be customized.
*/
@Override
protected final void onPrepareDialogBuilder(@NonNull final Builder builder) {
super.onPrepareDialogBuilder(builder);
final CharSequence[] entries = getEntries();
int clickedDialogEntryIndex = findIndexOfValue(getValue());
if (clickedDialogEntryIndex < 0) {
clickedDialogEntryIndex = mCustomIndex;
}
builder.setSingleChoiceItems(entries, clickedDialogEntryIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull final DialogInterface dialog, final int which) {
mSelectedCustomDir = null;
if (getEntryValues()[which].toString().equals(CUSTOM_FOLDER)) {
// determine custom folder via dialog
ChosenDirectoryListener listener = new ChosenDirectoryListener() {
private static final long serialVersionUID = -220546291074442095L;
@Override
public void onChosenDir(final String chosenDir) {
mSelectedIndex = which;
mSelectedCustomDir = chosenDir;
DirectorySelectionPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
dialog.dismiss();
}
@Override
public void onCancelled() {
DirectorySelectionPreference.this.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
dialog.dismiss();
}
};
try {
Activity activity = (Activity) getContext();
DirectoryChooserDialogFragment.displayDirectoryChooserDialog(activity, listener, getValue());
}
catch (ClassCastException e) {
Log.e(Application.TAG, "Could not open directory chooser", e);
}
}
else {
mSelectedIndex = which;
setValueIndex(which);
DirectorySelectionPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
dialog.dismiss();
}
}
});
}
/**
* Fill the value after closing the dialog. This is mostly the same as in ListPreference, but takes special care in
* the case of custom folder.
*
* @param positiveResult (from DialogPreference) positiveResult Whether the positive button was clicked (true), or the negative
* button was clicked or the dialog was canceled (false).
*/
@Override
protected final void onDialogClosed(final boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult && mSelectedIndex >= 0 && getEntryValues() != null) {
String value;
if (mSelectedCustomDir != null) {
value = mSelectedCustomDir;
}
else {
value = getEntryValues()[mSelectedIndex].toString();
}
if (callChangeListener(value)) {
setValue(value);
setSummary(value);
}
}
if (mOnDialogClosedListener != null) {
mOnDialogClosedListener.onDialogClosed();
}
}
/**
* Trigger the dialog programmatically.
*/
public final void showDialog() {
showDialog(null);
}
/**
* Set a listener called when the dialog is closed.
*
* @param listener The listener.
*/
public final void setOnDialogClosedListener(final OnDialogClosedListener listener) {
mOnDialogClosedListener = listener;
}
/**
* Listener for additional actions to be done when the dialog is closed.
*/
public interface OnDialogClosedListener {
/**
* Actions done when the dialog is closed.
*/
void onDialogClosed();
}
}