/* * Copyright (C) 2014-2018 Mikhail Kulesh * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. You should have received a copy of the GNU General * Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mkulesh.micromath.fman; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.Message; import androidx.appcompat.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuItem; import android.view.View; import android.view.View.OnLongClickListener; import android.view.Window; import android.widget.AdapterView; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.TextView; import android.widget.Toast; import com.mkulesh.micromath.dialogs.DialogBase; import com.mkulesh.micromath.dialogs.DialogRadioGroup; import com.mkulesh.micromath.dialogs.SimpleDialog; import com.mkulesh.micromath.plus.R; import com.mkulesh.micromath.utils.CompatUtils; import com.mkulesh.micromath.utils.ViewUtils; public class Commander extends DialogBase implements CommanderIf { /** * Interface definition for a callback to be invoked when a file is selected. */ public interface OnFileSelectedListener { /** * Called when a file has been selected. */ void onSelectFile(Uri uri, FileType fileType, final AdapterIf adapter); } public static final String PREF_LAST_SELECTED_PATH = "fman_last_selected_path"; public static final String PREF_LAST_SELECTED_FILE_TYPE = "fman_last_selected_file_type"; public static final String PREF_ADAPTER_MODE = "fman_adapter_mode"; protected static final int HOME_SCHEMA_H = AdapterHome.ORG_SCHEME.hashCode(); protected static final int FILE_SCHEMA_H = AdapterFileSystem.ORG_SCHEME.hashCode(); protected static final int SAF_SCHEMA_H = AdapterDocuments.ORG_SCHEME.hashCode(); protected static final int ASSETS_SCHEMA_H = AdapterAssets.ORG_SCHEME.hashCode(); private final OnFileSelectedListener listener; private final AppCompatActivity context; private final FileListView fileListView; private final ImageButton homeButton; private final SelectionMode selectionMode; private final CharSequence[] assetFilter; private final Button okButton; private final EditText fileName; private Button fileTypeButton = null; private FileType fileType = null; private AdapterIf destAdapter = null; public Commander(AppCompatActivity context, int nameId, SelectionMode selectionMode, CharSequence[] assetFilter, OnFileSelectedListener listener) { // Call superclass constructor. super(context, R.layout.fman_commander_layout, nameId); maximize(); // set attributes this.context = context; this.listener = listener; fileListView = new FileListView(this); fileListView.adapterMode = pref.getInt(PREF_ADAPTER_MODE, 0); fileListView.applySettings(); homeButton = findViewById(R.id.fman_action_home); homeButton.setOnClickListener(this); ViewUtils.setImageButtonColorAttr(context, homeButton, R.attr.colorDialogContent); homeButton.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { return ViewUtils.showButtonDescription(getContext(), v); } }); this.selectionMode = selectionMode; this.assetFilter = assetFilter; okButton = findViewById(R.id.dialog_button_ok); // prepare file name field fileName = findViewById(R.id.dialog_file_new_name_value); fileName.addTextChangedListener(new EditTextWatcher()); fileName.setText(""); // set components visibility switch (selectionMode) { case OPEN: (findViewById(R.id.dialog_button_panel)).setVisibility(View.GONE); (findViewById(R.id.fman_file_type_layout)).setVisibility(View.GONE); (findViewById(R.id.dialog_file_new_name_layout)).setVisibility(View.GONE); (findViewById(R.id.dialog_content_panel)).setPadding(0, 0, 0, getContext().getResources() .getDimensionPixelSize(R.dimen.dialog_panel_vertical_padding)); break; case SAVE_AS: case EXPORT: (findViewById(R.id.dialog_button_panel)).setVisibility(View.VISIBLE); (findViewById(R.id.fman_file_type_layout)) .setVisibility(selectionMode == SelectionMode.EXPORT ? View.VISIBLE : View.GONE); (findViewById(R.id.dialog_file_new_name_layout)).setVisibility(View.VISIBLE); fileTypeButton = findViewById(R.id.fman_file_type_button); fileTypeButton.setOnClickListener(this); fileTypeButton.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { return ViewUtils.showButtonDescription(getContext(), v); } }); prepareButtonImage(fileTypeButton); fileName.requestFocus(); break; } // restore the last selected path and file type String lastPath = pref.getString(PREF_LAST_SELECTED_PATH, null); if (lastPath != null) { Navigate(Uri.parse(lastPath), null); } else { Navigate(Uri.parse(AdapterHome.DEFAULT_LOC), null); } try { fileType = FileType.valueOf(pref.getString(PREF_LAST_SELECTED_FILE_TYPE, null)); } catch (Exception e) { fileType = FileType.PNG_IMAGE; } if (fileTypeButton != null) { fileTypeButton.setText(fileType.getDescriptionId()); } } private boolean isFileSelected() { return getListAdapter() != null && fileName.getText().length() > 0 && getCurrentPath() != null; } @Override public void onClick(View v) { if (v.getId() == R.id.dialog_button_ok && isFileSelected()) { final String fName = fileName.getText().toString(); Uri fUri = getListAdapter().getItemUri(fName); if (fUri == null) { fUri = getListAdapter().newFile(fName); } notifyListeners(fName, fUri); return; } else if (v.getId() == R.id.fman_action_home) { dispatchCommand(v.getId()); return; } else if (v.getId() == R.id.fman_file_type_button) { final DialogRadioGroup d = new DialogRadioGroup(context, R.string.fman_file_type_selection, FileType.values().length, new DialogRadioGroup.EventHandler() { public void onCreate(RadioButton[] rb) { for (int i = 0; i < rb.length; i++) { rb[i].setText(FileType.values()[i].getDescriptionId()); rb[i].setChecked(i == fileType.ordinal()); } } public void onClick(int whichButton) { fileType = FileType.values()[whichButton]; if (fileTypeButton != null) { fileTypeButton.setText(fileType.getDescriptionId()); } } }); d.show(); return; } closeDialog(); } public FileListView getFileListView() { return fileListView; } private AdapterIf getListAdapter() { return fileListView.getListAdapter(); } public AdapterIf CreateAdapter(Uri uri) { AdapterIf ca = CreateAdapterInstance(uri); if (ca != null) { ca.Init(this); } return ca; } public AdapterIf CreateAdapterInstance(Uri uri) { String scheme = uri.getScheme(); if (!FileUtils.str(scheme)) return new AdapterFileSystem(context); final int scheme_h = scheme.hashCode(); if (selectionMode == SelectionMode.OPEN && ASSETS_SCHEMA_H == scheme_h) return new AdapterAssets(context, assetFilter); if (FILE_SCHEMA_H == scheme_h) return new AdapterFileSystem(context); if (HOME_SCHEMA_H == scheme_h) return new AdapterHome(context); if (SAF_SCHEMA_H == scheme_h && AdapterDocuments.isExternalStorageDocument(uri)) return new AdapterDocuments(context); return new AdapterFileSystem(context); } private void changeSorting(int sort_mode) { AdapterIf ca = getListAdapter(); int cur_mode = ca.setMode(0, 0); boolean asc = (cur_mode & AdapterIf.MODE_SORT_DIR) == AdapterIf.SORT_ASC; int sorted = cur_mode & AdapterIf.MODE_SORTING; if (sorted == sort_mode) ca.setMode(AdapterIf.MODE_SORT_DIR, asc ? AdapterIf.SORT_DSC : AdapterIf.SORT_ASC); else ca.setMode(AdapterIf.MODE_SORTING | AdapterIf.MODE_SORT_DIR, sort_mode | AdapterIf.SORT_ASC); fileListView.adapterMode = ca.getMode() & (AdapterIf.MODE_SORTING | AdapterIf.MODE_SORT_DIR); } private final void NavigateInternal(Uri uri, String posTo) { fileListView.Navigate(uri, posTo); okButton.setEnabled(isFileSelected()); } public final void operationFinished() { if (null != destAdapter) { destAdapter = null; } } public void dispatchCommand(int id) { try { final int selectedPos = fileListView.getSelected(); final String selectedItem = selectedPos < 0 ? null : getListAdapter().getItemName(selectedPos, false); switch (id) { case R.id.fman_action_rename: { if (selectedPos < 0 || selectedItem == null) { showMessage(context.getString(R.string.fman_error_no_items)); return; } final SimpleDialog d = new SimpleDialog(context, R.layout.fman_input_dialog, R.string.fman_rename_title, new SimpleDialog.EventHandler() { public void onCreate(SimpleDialog d, LinearLayout dLayout) { final TextView prompt = dLayout.findViewById(R.id.fman_input_dialog_prompt); prompt.setText(context.getString(R.string.fman_rename_dialog_prompt, selectedItem)); final EditText edit = dLayout.findViewById(R.id.fman_input_dialog_edit_field); edit.setText(selectedItem); edit.requestFocus(); } public void onClick(LinearLayout dLayout, int whichButton) { if (whichButton == R.id.dialog_button_ok) { ViewUtils.Debug(Commander.this, "Renaming item " + selectedItem); final EditText edit = dLayout.findViewById(R.id.fman_input_dialog_edit_field); final String new_name = edit.getText().toString(); getListAdapter().renameItem(selectedPos, new_name); fileListView.setSelection(new_name); } } }); d.show(); break; } case R.id.fman_action_delete: { if (selectedPos < 0 || selectedItem == null) { showMessage(context.getString(R.string.fman_error_no_items)); return; } final SimpleDialog d = new SimpleDialog(context, R.layout.fman_message_dialog, R.string.fman_delete_title, new SimpleDialog.EventHandler() { public void onCreate(SimpleDialog d, LinearLayout dLayout) { final ImageView image = dLayout.findViewById(R.id.fman_message_dialog_icon); d.setImage(image, R.drawable.ic_action_content_discard, CompatUtils.getThemeColorAttr(context, R.attr.colorDialogContent)); final TextView prompt = dLayout.findViewById(R.id.fman_message_dialog_prompt); prompt.setText(context.getString(R.string.fman_delete_dialog_prompt, selectedItem)); } public void onClick(LinearLayout dLayout, int whichButton) { if (whichButton == R.id.dialog_button_ok) { ViewUtils.Debug(Commander.this, "Deleting item " + selectedItem); if (getListAdapter().deleteItem(selectedPos)) { fileListView.listView.clearChoices(); } } } }); d.show(); break; } case R.id.fman_action_create_folder: final SimpleDialog d = new SimpleDialog(context, R.layout.fman_input_dialog, R.string.fman_create_folder_title, new SimpleDialog.EventHandler() { public void onCreate(SimpleDialog d, LinearLayout dLayout) { final TextView prompt = dLayout.findViewById(R.id.fman_input_dialog_prompt); prompt.setText(context.getString(R.string.fman_create_folder_dialog_prompt)); final EditText edit = dLayout.findViewById(R.id.fman_input_dialog_edit_field); edit.setText(""); edit.requestFocus(); } public void onClick(LinearLayout dLayout, int whichButton) { if (whichButton == R.id.dialog_button_ok) { final EditText edit = dLayout.findViewById(R.id.fman_input_dialog_edit_field); final String new_name = edit.getText().toString(); getListAdapter().createFolder(new_name); fileListView.setSelection(new_name); } } }); d.show(); break; case R.id.fman_action_home: Navigate(Uri.parse(AdapterHome.DEFAULT_LOC), null); break; case R.id.fman_action_sort_by_name: changeSorting(AdapterIf.SORT_NAME); break; case R.id.fman_action_sort_by_ext: changeSorting(AdapterIf.SORT_EXT); break; case R.id.fman_action_sort_by_size: changeSorting(AdapterIf.SORT_SIZE); break; case R.id.fman_action_sort_by_date: changeSorting(AdapterIf.SORT_DATE); break; case R.id.fman_action_refresh: fileListView.refreshList(null); break; default: getListAdapter().doIt(id, selectedPos); break; } } catch (Throwable e) { e.printStackTrace(); } } /* * Context menu handling */ @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { try { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; if (info != null) { fileListView.setSelection(info.position, 0); } menu.setHeaderTitle(R.string.fman_operation); AdapterIf ca = getListAdapter(); ca.populateContextMenu(menu, info, fileListView.getSelected()); } catch (Exception e) { e.printStackTrace(); } } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { if (featureId != Window.FEATURE_CONTEXT_MENU) { return false; } try { AdapterView.AdapterContextMenuInfo info; info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); if (info == null) return false; fileListView.setSelection(info.position, 0); dispatchCommand(item.getItemId()); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /* * Commander interface implementation */ @Override public void issue(Intent in, int ret) { if (in == null) return; try { if (ret == 0) context.startActivity(in); else context.startActivityForResult(in, ret); } catch (Exception e) { e.printStackTrace(); } } @Override public void showError(final String errMsg) { try { final SimpleDialog d = new SimpleDialog(context, R.layout.fman_message_dialog, R.string.fman_warning_dialog_title, new SimpleDialog.EventHandler() { public void onCreate(SimpleDialog d, LinearLayout dLayout) { d.disableButton(R.id.dialog_button_ok); final TextView prompt = dLayout.findViewById(R.id.fman_message_dialog_prompt); prompt.setText(errMsg); } public void onClick(LinearLayout dLayout, int whichButton) { // nothing to do } }); d.show(); return; } catch (Exception e) { e.printStackTrace(); } showMessage(errMsg); } @Override public void Navigate(Uri uri, String posTo) { if (uri != null) { ViewUtils.Debug(this, "External navigation request: uri = " + uri.toString()); NavigateInternal(uri, posTo); } } @Override public void Open(Uri uri) { if (uri != null && listener != null) { final int selectedPos = fileListView.getSelected(); final String selectedItem = selectedPos < 0 ? null : getListAdapter().getItemName(selectedPos, false); notifyListeners(selectedItem, uri); } } @Override public boolean notifyMe(Message progress) { final boolean TERMINATE = true, CONTINUE = false; String string = null; try { if (progress.obj != null) { if (progress.obj instanceof Bundle) string = ((Bundle) progress.obj).getString(MESSAGE_STRING); else if (progress.obj instanceof String) { string = (String) progress.obj; } } Bundle b = progress.getData(); String cookie = b != null ? b.getString(NOTIFY_COOKIE) : null; if (progress.what == OPERATION_IN_PROGRESS) { return CONTINUE; } operationFinished(); switch (progress.what) { case OPERATION_FAILED: case OPERATION_FAILED_REFRESH_REQUIRED: if (FileUtils.str(string)) showError(string); if (progress.what == OPERATION_FAILED_REFRESH_REQUIRED) { String posto = b != null ? b.getString(NOTIFY_POSTO) : null; fileListView.refreshList(posto); } else { fileListView.askRedrawList(); } return TERMINATE; case OPERATION_COMPLETED_REFRESH_REQUIRED: String posto = b != null ? b.getString(NOTIFY_POSTO) : null; Uri uri = b != null ? (Uri) b.getParcelable(NOTIFY_URI) : null; if (uri != null) { Navigate(uri, posto); break; } fileListView.refreshList(posto); break; case OPERATION_COMPLETED: if (FileUtils.str(cookie)) { String item_name = cookie.substring(1); fileListView.recoverAfterRefresh(item_name); } else { fileListView.recoverAfterRefresh(null); } break; } } catch (Exception e) { e.printStackTrace(); } return TERMINATE; } public final void showMessage(String s) { Toast.makeText(context, s, Toast.LENGTH_LONG).show(); } /** * Text change processing class */ private class EditTextWatcher implements TextWatcher { EditTextWatcher() { // empty } @Override public void afterTextChanged(Editable s) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { okButton.setEnabled(s.length() > 0 && getCurrentPath() != null); } } private String getCurrentPath() { AdapterIf ca = getListAdapter(); if (ca != null && !(ca instanceof AdapterHome)) { return ca.getUri().getPath(); } return null; } private void notifyListeners(final String fileName, final Uri uri) { final AdapterIf ca = getListAdapter(); if (ca == null || listener == null || fileName == null || uri == null) { return; } // Verify if a confirmation dialog must be show. if (selectionMode != SelectionMode.OPEN) { if (ca.getItemUri(fileName) != null) { final SimpleDialog d = new SimpleDialog(context, R.layout.fman_message_dialog, R.string.fman_warning_dialog_title, new SimpleDialog.EventHandler() { public void onCreate(SimpleDialog d, LinearLayout dLayout) { final ImageView image = dLayout.findViewById(R.id.fman_message_dialog_icon); d.setImage(image, R.drawable.ic_action_content_save, CompatUtils.getThemeColorAttr(context, R.attr.colorDialogContent)); final TextView prompt = dLayout.findViewById(R.id.fman_message_dialog_prompt); prompt.setText(context.getString(R.string.fman_overwrite_file, fileName)); } public void onClick(LinearLayout dLayout, int whichButton) { if (whichButton == R.id.dialog_button_ok) { storePreferences(); closeDialog(); listener.onSelectFile(uri, fileType, ca); } } }); d.show(); } else { storePreferences(); closeDialog(); listener.onSelectFile(uri, fileType, ca); } } else { storePreferences(); closeDialog(); listener.onSelectFile(uri, fileType, ca); } } private void storePreferences() { SharedPreferences.Editor editor = pref.edit(); editor.putString(PREF_LAST_SELECTED_PATH, getListAdapter().getUri().toString()); editor.putString(PREF_LAST_SELECTED_FILE_TYPE, fileType.toString()); editor.putInt(PREF_ADAPTER_MODE, fileListView.adapterMode); editor.commit(); } public SelectionMode getSelectionMode() { return selectionMode; } public void setFileName(CharSequence name) { fileName.setText(name); } }