package de.jeisfeld.augendiagnoselib.fragments; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListView; import java.io.File; import java.io.FileFilter; import java.text.CollationKey; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import de.jeisfeld.augendiagnoselib.Application; import de.jeisfeld.augendiagnoselib.Application.AuthorizationLevel; import de.jeisfeld.augendiagnoselib.R; import de.jeisfeld.augendiagnoselib.activities.ListFoldersBaseActivity; import de.jeisfeld.augendiagnoselib.activities.ListFoldersForDisplayActivity; import de.jeisfeld.augendiagnoselib.util.DialogUtil; import de.jeisfeld.augendiagnoselib.util.PreferenceUtil; import de.jeisfeld.augendiagnoselib.util.SystemUtil; import de.jeisfeld.augendiagnoselib.util.imagefile.EyePhoto; import de.jeisfeld.augendiagnoselib.util.imagefile.FileUtil; /** * Base listFoldersFragment to display the list of subfolders of a folder Abstract class - child classes determine the * detailed actions. The folders should contain eye photos (following the name policy). */ public abstract class ListFoldersBaseFragment extends Fragment { /** * The resource key of the parent folder. */ protected static final String STRING_FOLDER = "de.jeisfeld.augendiagnoselib.FOLDER"; /** * The maximum allowed number of names in the trial version. */ public static final int TRIAL_MAX_NAMES = 3; // PUBLIC_FIELDS:START /** * The parent folder. */ @Nullable protected File mParentFolder; /** * The list view containing the folders. */ protected ListView mListView; // PUBLIC_FIELDS:END /** * The array adapter displaying the list of names. */ @Nullable private ArrayAdapter<String> mDirectoryListAdapter = null; /** * Initialize the listFoldersFragment with parentFolder. * * @param initialParentFolder The parent folder for which the fragment should be created. */ public final void setParameters(final String initialParentFolder) { Bundle args = new Bundle(); args.putString(STRING_FOLDER, initialParentFolder); setArguments(args); } @Override public final void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); //noinspection ConstantConditions mParentFolder = new File(args.getString(STRING_FOLDER)); } /* * Inflate View */ @Override public final View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_list_names, container, false); } // OVERRIDABLE @Override public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (getView() == null) { return; } mListView = (ListView) getView().findViewById(R.id.listViewNames); createList(); EditText editTextSearch = (EditText) getView().findViewById(R.id.searchName); editTextSearch.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(@NonNull final CharSequence s, final int start, final int before, final int count) { mDirectoryListAdapter.getFilter().filter(s.toString()); } @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { // do nothing } @Override public void afterTextChanged(final Editable s) { // do nothing } }); setOnItemClickListener(); } /** * Listener for a short click on a list item. */ protected abstract void setOnItemClickListener(); /** * Fill the list of subfolders and create the list adapter. */ private void createList() { List<String> folderNames = getFolderNames(mParentFolder); if (mDirectoryListAdapter == null) { // fill initial adapter mDirectoryListAdapter = new ArrayAdapter<>(getActivity(), R.layout.adapter_list_names, folderNames); mListView.setAdapter(mDirectoryListAdapter); mListView.setTextFilterEnabled(true); } else { // update existing adapter mDirectoryListAdapter.clear(); mDirectoryListAdapter.addAll(folderNames); if (getView() != null) { mDirectoryListAdapter.getFilter().filter(((EditText) getView().findViewById(R.id.searchName)).getText()); } mDirectoryListAdapter.notifyDataSetChanged(); } getActivity().findViewById(R.id.textViewNoImages).setVisibility(folderNames.size() == 0 ? View.VISIBLE : View.GONE); } /** * Get the list of subfolders, using getFileNameForSorting() for ordering. * * @param parentFolder The parent folder. * @return The list of subfolders. */ @NonNull public static List<String> getFolderNames(@NonNull final File parentFolder) { File[] folders = parentFolder.listFiles(new FileFilter() { @Override public boolean accept(@NonNull final File pathname) { return pathname.isDirectory(); } }); List<String> folderNames = new ArrayList<>(); if (folders == null) { return folderNames; } Collator collator = Collator.getInstance(); final Map<File, CollationKey> collationMap = new HashMap<>(); for (File folder : folders) { collationMap.put(folder, collator.getCollationKey(getFilenameForSorting(folder))); } Arrays.sort(folders, new Comparator<File>() { @Override public int compare(final File f1, final File f2) { return collationMap.get(f1).compareTo(collationMap.get(f2)); } }); for (File f : folders) { folderNames.add(f.getName()); } if (Application.getAuthorizationLevel() == AuthorizationLevel.TRIAL_ACCESS && folderNames.size() > TRIAL_MAX_NAMES) { folderNames = folderNames.subList(0, TRIAL_MAX_NAMES); } return folderNames; } /** * Helper method to return the name of the file for sorting. Allows sorting by last name, and giving precedence to * iris topography folders. * * @param f The file * @return The name for Sorting */ @NonNull private static String getFilenameForSorting(@NonNull final File f) { String name = f.getName(); boolean sortByLastName = PreferenceUtil.getSharedPreferenceBoolean(R.string.key_sort_by_last_name); if (sortByLastName) { int index = name.lastIndexOf(' '); if (index >= 0) { String firstName = name.substring(0, index); String lastName = name.substring(index + 1); name = lastName + " " + firstName; } } return name; } /** * Rename a folder in the list, and rename all files in it (according to EyePhoto name policy). * * @param oldName the old name. * @param newName the new name. */ private void renameFolderAndFiles(@NonNull final String oldName, @NonNull final String newName) { final File oldFolder = new File(mParentFolder, oldName.trim()); final File newFolder = new File(mParentFolder, newName.trim()); // rename folder and ensure that list is refreshed boolean success = FileUtil.renameFolder(oldFolder, newFolder); // STORE_PROPERTY createList(); if (!success) { // In Kitkat workaround, try to delete old folder only in the end - if done immediately, it fails. DialogUtil.displayError(getActivity(), R.string.message_dialog_failed_to_move_folder_partially, false, oldFolder.getAbsolutePath(), newFolder.getAbsolutePath()); return; } if (oldFolder.exists()) { // try to delete old folder in separate thread. This is not successful directly after moving files. FileUtil.rmdirAsynchronously(getActivity(), oldFolder, new Runnable() { @Override public void run() { createList(); } }); } // rename files in the new folder File[] files = newFolder.listFiles(); if (files == null) { files = new File[0]; } for (File f : files) { EyePhoto source = new EyePhoto(f.getAbsolutePath()); if (!source.isFormatted()) { DialogUtil.displayError(getActivity(), R.string.message_dialog_unformatted_file, false, oldFolder.getAbsolutePath()); continue; } if (!source.changePersonName(newName)) { DialogUtil.displayError(getActivity(), R.string.message_dialog_failed_to_rename_file, false, oldFolder.getAbsolutePath(), newFolder.getAbsolutePath()); } } // In two-pane mode, refresh right pane if (getActivity() instanceof ListFoldersForDisplayActivity && SystemUtil.isTablet()) { ListFoldersForDisplayActivity activity = (ListFoldersForDisplayActivity) getActivity(); activity.popBackStack(); activity.listPicturesForName(newName); } } /** * Delete a folder in the list, including all photos. * * @param name the name for which the folder should be deleted. */ protected final void deleteFolder(@NonNull final String name) { File folder = new File(mParentFolder, name.trim()); // delete files in folder. FileUtil.deleteFilesInFolder(folder); // delete folder and ensure that list is refreshed if (folder.delete()) { createList(); } else { FileUtil.rmdirAsynchronously(getActivity(), folder, new Runnable() { @Override public void run() { createList(); } }); } } /** * Show the dialog to change the selected name. * * @param oldName The old name to be renamed * @param inputText The name to be initially displayed */ protected final void showChangeNameDialog(final CharSequence oldName, final CharSequence inputText) { DisplayChangeNameFragment fragment = new DisplayChangeNameFragment(); Bundle bundle = new Bundle(); bundle.putCharSequence("inputText", inputText); bundle.putCharSequence("oldName", oldName); fragment.setArguments(bundle); fragment.show(getActivity().getFragmentManager(), DisplayChangeNameFragment.class.toString()); } /** * Fragment to change the name. */ public static class DisplayChangeNameFragment extends DialogFragment { @Override public final Dialog onCreateDialog(final Bundle savedInstanceState) { final CharSequence inputText = getArguments().getCharSequence("inputText"); final CharSequence oldName = getArguments().getCharSequence("oldName"); if (oldName == null) { return null; } // This is a workaround - better solution might be a layout. // If the EditText is always recreated, then the content will be lost on orientation change. EditText input0; input0 = (EditText) getActivity().findViewById(R.id.editName); if (input0 == null) { input0 = new EditText(getActivity()); input0.setText(inputText); input0.setId(R.id.editName); } final EditText input = input0; AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) // .setTitle(R.string.title_dialog_change_name) // .setView(input) // .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(@NonNull final DialogInterface dialog, final int id) { dialog.dismiss(); } }).setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int id) { ListFoldersBaseActivity activity = (ListFoldersBaseActivity) getActivity(); activity.getListFoldersFragment().renameFolderAndFiles(oldName.toString(), input.getText().toString()); } }); return builder.create(); } } }