Browse Source

Fix for 4.0 file action context menu (re-implement it via DialogFragment)

Andy Scherzinger 9 years ago
parent
commit
ba80646e4e

+ 14 - 0
res/layout/file_actions.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:layout_margin="@dimen/standard_margin">
+
+    <ListView
+        android:id="@+id/file_actions_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+    </ListView>
+
+</LinearLayout> 

+ 12 - 0
res/layout/simple_dialog_list_item.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall"
+    android:textColor="@color/dialog_list_item"
+    android:textSize="16sp"
+    android:gravity="center_vertical"
+    android:paddingLeft="@dimen/standard_padding"
+    android:paddingRight="@dimen/standard_padding"
+    android:minHeight="48dp"/>

+ 1 - 0
res/values/colors.xml

@@ -38,6 +38,7 @@
 
     <!-- Colors -->
     <color name="color_accent">@color/owncloud_blue_accent</color>
+    <color name="dialog_list_item">#1F1F1F</color>
 
     <!-- standard material color definitions -->
     <color name="primary">@color/owncloud_blue</color>

+ 1 - 3
res/values/styles.xml

@@ -59,9 +59,7 @@
 		<item name="android:textColorPrimary">@color/primary</item>
 	</style>
 
-	<style name="ownCloud.Dialog" parent="Theme.AppCompat.Light.Dialog">
-		
-	</style>
+	<style name="ownCloud.Dialog" parent="Theme.AppCompat.Light.Dialog" />
 
 	<style name="ownCloud.Button" parent="Widget.AppCompat.Button">
 		<item name="colorButtonNormal">@color/primary</item>

+ 146 - 0
src/com/owncloud/android/ui/dialog/FileActionsDialogFragment.java

@@ -0,0 +1,146 @@
+package com.owncloud.android.ui.dialog;
+
+import android.support.v4.app.DialogFragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.ui.dialog.parcel.MenuItemParcelable;
+import com.owncloud.android.ui.dialog.parcel.MenuParcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Dialog for choosing a file action.
+ */
+public class FileActionsDialogFragment extends DialogFragment implements
+        OnItemClickListener {
+    private static final String ARG_ITEM_LIST = "ITEM_LIST";
+    private static final String ARG_FILE_POSITION = "FILE_POSITION";
+    public static final String FTAG_FILE_ACTIONS = "FILE_ACTIONS_FRAGMENT";
+
+    private List<MenuItemParcelable> menuItems;
+
+    private int filePosition;
+
+    private ListView mListView;
+
+    /**
+     * Listener interface for the file action fragment.
+     */
+    public interface FileActionsDialogFragmentListener {
+        public boolean onFileActionChosen(int menuId, int filePosition);
+    }
+
+    /**
+     * Public factory method to create new FileActionsDialogFragment instances.
+     *
+     * @param menu menu to be display.
+     * @return Dialog ready to show.
+     */
+    public static FileActionsDialogFragment newInstance(Menu menu, int filePosition) {
+        FileActionsDialogFragment fragment = new FileActionsDialogFragment();
+        Bundle args = new Bundle();
+
+        MenuParcelable menuParcelable = new MenuParcelable();
+        menuParcelable.setMenuItems(calculateMenuParcel(menu));
+
+        args.putParcelable(ARG_ITEM_LIST, menuParcelable);
+        args.putInt(ARG_FILE_POSITION, filePosition);
+
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setStyle(DialogFragment.STYLE_NORMAL, R.style.ownCloud_Dialog);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.file_actions, null, false);
+        mListView = (ListView) view.findViewById(R.id.file_actions_list);
+
+        getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+        return view;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        filePosition = getArguments().getInt(ARG_FILE_POSITION);
+
+        MenuParcelable menu = getArguments().getParcelable(ARG_ITEM_LIST);
+        menuItems = menu.getMenuItems();
+        List<String> stringList = new ArrayList<String>();
+        for (int i = 0; i < menuItems.size(); i++) {
+            stringList.add(menuItems.get(i).getMenuText());
+        }
+
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+                R.layout.simple_dialog_list_item, stringList);
+
+        mListView.setAdapter(adapter);
+
+        mListView.setOnItemClickListener(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        dismiss();
+        ((FileActionsDialogFragmentListener) getTargetFragment()).onFileActionChosen(menuItems.get(position).getMenuItemId(), filePosition);
+    }
+
+    /**
+     * calculates a parcelable list of MenuItemParcelable based on the given menu and the visibility of the menu items.
+     *
+     * @param menu the menu to be displayed
+     * @return a filtered List of parcelables based on the menu
+     */
+    private static List<MenuItemParcelable> calculateMenuParcel(Menu menu) {
+        int index = 0;
+        boolean hasNext = true;
+        List<MenuItemParcelable> itemList = new ArrayList<MenuItemParcelable>();
+        MenuParcelable menuParcelable = new MenuParcelable();
+        try {
+            while (hasNext) {
+                MenuItem item = menu.getItem(index);
+                if (item.isVisible()) {
+                    itemList.add(new MenuItemParcelable(item));
+                }
+                index++;
+            }
+        } catch (IndexOutOfBoundsException iobe) {
+            // reach the end of the item list
+        }
+
+        return itemList;
+    }
+}

+ 64 - 0
src/com/owncloud/android/ui/dialog/parcel/MenuItemParcelable.java

@@ -0,0 +1,64 @@
+package com.owncloud.android.ui.dialog.parcel;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.MenuItem;
+
+public class MenuItemParcelable implements Parcelable {
+    int menuItemId;
+
+    String menuText;
+
+    public MenuItemParcelable() {
+    }
+
+    public MenuItemParcelable(MenuItem menuItem) {
+        menuItemId = menuItem.getItemId();
+        menuText = menuItem.getTitle().toString();
+        menuItem.getMenuInfo();
+    }
+
+    public MenuItemParcelable(Parcel read) {
+        menuItemId = read.readInt();
+    }
+
+    public void setMenuItemId(int id) {
+        menuItemId = id;
+    }
+
+    public int getMenuItemId() {
+        return menuItemId;
+    }
+
+    public String getMenuText() {
+        return menuText;
+    }
+
+    public void setMenuText(String menuText) {
+        this.menuText = menuText;
+    }
+
+    public static final Parcelable.Creator<MenuItemParcelable> CREATOR =
+            new Parcelable.Creator<MenuItemParcelable>() {
+
+                @Override
+                public MenuItemParcelable createFromParcel(Parcel source) {
+                    return new MenuItemParcelable(source);
+                }
+
+                @Override
+                public MenuItemParcelable[] newArray(int size) {
+                    return new MenuItemParcelable[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(menuItemId);
+    }
+}

+ 51 - 0
src/com/owncloud/android/ui/dialog/parcel/MenuParcelable.java

@@ -0,0 +1,51 @@
+package com.owncloud.android.ui.dialog.parcel;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MenuParcelable implements Parcelable {
+
+    private List<MenuItemParcelable> menuItems = new ArrayList<MenuItemParcelable>();
+
+    public List<MenuItemParcelable> getMenuItems() {
+        return menuItems;
+    }
+
+    public void setMenuItems(List<MenuItemParcelable> menuItems) {
+        this.menuItems = menuItems;
+    }
+
+    public MenuParcelable() {
+        menuItems = new ArrayList<MenuItemParcelable>();
+    }
+
+    public MenuParcelable(Parcel in) {
+        in.readTypedList(menuItems, MenuItemParcelable.CREATOR);
+    }
+
+    public static final Parcelable.Creator<MenuParcelable> CREATOR = new Parcelable.Creator<MenuParcelable>() {
+
+        @Override
+        public MenuParcelable createFromParcel(Parcel in) {
+            return new MenuParcelable(in);
+        }
+
+        @Override
+        public MenuParcelable[] newArray(int size) {
+            return new MenuParcelable[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel outParcel, int flags) {
+        outParcel.writeTypedList(menuItems);
+    }
+}

+ 87 - 23
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -29,11 +29,13 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.view.ContextMenu;
+import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.PopupMenu;
 
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
@@ -48,10 +50,12 @@ import com.owncloud.android.ui.activity.FolderPickerActivity;
 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
 import com.owncloud.android.ui.adapter.FileListListAdapter;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
+import com.owncloud.android.ui.dialog.FileActionsDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
 import com.owncloud.android.ui.preview.PreviewImageFragment;
 import com.owncloud.android.ui.preview.PreviewMediaFragment;
+import com.owncloud.android.utils.DialogMenuItem;
 import com.owncloud.android.utils.FileStorageUtils;
 
 /**
@@ -59,7 +63,7 @@ import com.owncloud.android.utils.FileStorageUtils;
  * 
  * TODO refactor to get rid of direct dependency on FileDisplayActivity
  */
-public class OCFileListFragment extends ExtendedListFragment {
+public class OCFileListFragment extends ExtendedListFragment implements FileActionsDialogFragment.FileActionsDialogFragmentListener {
     
     private static final String TAG = OCFileListFragment.class.getSimpleName();
 
@@ -139,9 +143,63 @@ public class OCFileListFragment extends ExtendedListFragment {
                 );
         setListAdapter(mAdapter);
 
-        registerForContextMenu();
+        registerLongClickListener();
   }
 
+    private void registerLongClickListener() {
+        getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+            public boolean onItemLongClick(AdapterView<?> arg0, View v,
+                                           int index, long arg3) {
+                showFileAction(index);
+                return true;
+            }
+        });
+    }
+
+
+    private void showFileAction(int fileIndex) {
+        Bundle args = getArguments();
+        PopupMenu pm = new PopupMenu(getActivity(),null);
+        Menu menu = pm.getMenu();
+
+        boolean allowContextualActions =
+                (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
+
+        if (allowContextualActions) {
+            MenuInflater inflater = getActivity().getMenuInflater();
+
+            inflater.inflate(R.menu.file_actions_menu, menu);
+            OCFile targetFile = (OCFile) mAdapter.getItem(fileIndex);
+
+            if (mContainerActivity.getStorageManager() != null) {
+                FileMenuFilter mf = new FileMenuFilter(
+                        targetFile,
+                        mContainerActivity.getStorageManager().getAccount(),
+                        mContainerActivity,
+                        getActivity()
+                );
+                mf.filter(menu);
+            }
+
+            /// TODO break this direct dependency on FileDisplayActivity... if possible
+            MenuItem item = menu.findItem(R.id.action_open_file_with);
+            FileFragment frag = ((FileDisplayActivity)getActivity()).getSecondFragment();
+            if (frag != null && frag instanceof FileDetailFragment &&
+                    frag.getFile().getFileId() == targetFile.getFileId()) {
+                item = menu.findItem(R.id.action_see_details);
+                if (item != null) {
+                    item.setVisible(false);
+                    item.setEnabled(false);
+                }
+            }
+
+            MenuItem menuItem = new DialogMenuItem(item.getItemId());
+            FileActionsDialogFragment dialog = FileActionsDialogFragment.newInstance(menu, fileIndex);
+            dialog.setTargetFragment(this, 0);
+            dialog.show(getFragmentManager(), FileActionsDialogFragment.FTAG_FILE_ACTIONS);
+        }
+    }
+
     /**
      * Saves the current listed folder.
      */
@@ -154,7 +212,7 @@ public class OCFileListFragment extends ExtendedListFragment {
     /**
      * Call this, when the user presses the up button.
      * 
-     * Tries to move up the current folder one level. If the parent folder was removed from the 
+     * Tries to move up the current folder one level. If the parent folder was removed from the
      * database, it continues browsing up until finding an existing folders.
      * 
      * return       Count of folder levels browsed up.
@@ -243,7 +301,6 @@ public class OCFileListFragment extends ExtendedListFragment {
     @Override
     public void onCreateContextMenu (
             ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
-        super.onCreateContextMenu(menu, v, menuInfo);
         Bundle args = getArguments();
         boolean allowContextualActions = 
                 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true); 
@@ -276,16 +333,11 @@ public class OCFileListFragment extends ExtendedListFragment {
             }
         }
     }
-    
-    
-    /**
-     * {@inhericDoc}
-     */
+
     @Override
-    public boolean onContextItemSelected (MenuItem item) {
-        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();        
-        mTargetFile = (OCFile) mAdapter.getItem(info.position);
-        switch (item.getItemId()) {                
+    public boolean onFileActionChosen(int menuId, int filePosition) {
+        mTargetFile = (OCFile) mAdapter.getItem(filePosition);
+        switch (menuId) {
             case R.id.action_share_file: {
                 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
                 return true;
@@ -308,14 +360,14 @@ public class OCFileListFragment extends ExtendedListFragment {
                 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
                 return true;
             }
-            case R.id.action_download_file: 
+            case R.id.action_download_file:
             case R.id.action_sync_file: {
                 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
                 return true;
             }
             case R.id.action_cancel_download:
             case R.id.action_cancel_upload: {
-                ((FileDisplayActivity)mContainerActivity).cancelTransference(mTargetFile);
+                ((FileDisplayActivity) mContainerActivity).cancelTransference(mTargetFile);
                 return true;
             }
             case R.id.action_see_details: {
@@ -326,8 +378,8 @@ public class OCFileListFragment extends ExtendedListFragment {
                 // Obtain the file
                 if (!mTargetFile.isDown()) {  // Download the file
                     Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
-                    ((FileDisplayActivity)mContainerActivity).startDownloadForSending(mTargetFile);
-                    
+                    ((FileDisplayActivity) mContainerActivity).startDownloadForSending(mTargetFile);
+
                 } else {
                     mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
                 }
@@ -341,16 +393,30 @@ public class OCFileListFragment extends ExtendedListFragment {
                 getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
                 return true;
             }
-            case R.id.action_favorite_file:{
+            case R.id.action_favorite_file: {
                 mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, true);
                 return true;
             }
-            case R.id.action_unfavorite_file:{
+            case R.id.action_unfavorite_file: {
                 mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, false);
                 return true;
             }
             default:
-                return super.onContextItemSelected(item); 
+                return false;
+        }
+    }
+    
+    /**
+     * {@inhericDoc}
+     */
+    @Override
+    public boolean onContextItemSelected (MenuItem item) {
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+        boolean matched = onFileActionChosen(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).position);
+        if(!matched) {
+            return super.onContextItemSelected(item);
+        } else {
+            return matched;
         }
     }
 
@@ -443,6 +509,7 @@ public class OCFileListFragment extends ExtendedListFragment {
             if (version != null && version.supportsRemoteThumbnails() &&
                 imagesCount > 0 && imagesCount == filesCount) {
                 switchToGridView();
+                registerLongClickListener();
             } else {
                 switchToListView();
             }
@@ -489,7 +556,6 @@ public class OCFileListFragment extends ExtendedListFragment {
         return output;
     }
 
-
     public void sortByName(boolean descending) {
         mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
     }
@@ -501,6 +567,4 @@ public class OCFileListFragment extends ExtendedListFragment {
     public void sortBySize(boolean descending) {
         mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
     }
-
-    
 }

+ 227 - 0
src/com/owncloud/android/utils/DialogMenuItem.java

@@ -0,0 +1,227 @@
+package com.owncloud.android.utils;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.ActionProvider;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+
+/**
+ * Created by scherzia on 17.08.2015.
+ */
+public class DialogMenuItem implements MenuItem {
+    int itemId;
+    CharSequence title;
+
+    public DialogMenuItem(int itemId) {
+        this.itemId = itemId;
+    }
+
+    @Override
+    public int getItemId() {
+        return itemId;
+    }
+
+    @Override
+    public int getGroupId() {
+        return 0;
+    }
+
+    @Override
+    public int getOrder() {
+        return 0;
+    }
+
+    @Override
+    public MenuItem setTitle(CharSequence title) {
+        this.title = title;
+        return this;
+    }
+
+    @Override
+    public MenuItem setTitle(int title) {
+        return this;
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return this.title;
+    }
+
+    @Override
+    public MenuItem setTitleCondensed(CharSequence title) {
+        return null;
+    }
+
+    @Override
+    public CharSequence getTitleCondensed() {
+        return null;
+    }
+
+    @Override
+    public MenuItem setIcon(Drawable icon) {
+        return null;
+    }
+
+    @Override
+    public MenuItem setIcon(int iconRes) {
+        return null;
+    }
+
+    @Override
+    public Drawable getIcon() {
+        return null;
+    }
+
+    @Override
+    public MenuItem setIntent(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public Intent getIntent() {
+        return null;
+    }
+
+    @Override
+    public MenuItem setShortcut(char numericChar, char alphaChar) {
+        return null;
+    }
+
+    @Override
+    public MenuItem setNumericShortcut(char numericChar) {
+        return null;
+    }
+
+    @Override
+    public char getNumericShortcut() {
+        return 0;
+    }
+
+    @Override
+    public MenuItem setAlphabeticShortcut(char alphaChar) {
+        return null;
+    }
+
+    @Override
+    public char getAlphabeticShortcut() {
+        return 0;
+    }
+
+    @Override
+    public MenuItem setCheckable(boolean checkable) {
+        return null;
+    }
+
+    @Override
+    public boolean isCheckable() {
+        return false;
+    }
+
+    @Override
+    public MenuItem setChecked(boolean checked) {
+        return null;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return false;
+    }
+
+    @Override
+    public MenuItem setVisible(boolean visible) {
+        return null;
+    }
+
+    @Override
+    public boolean isVisible() {
+        return false;
+    }
+
+    @Override
+    public MenuItem setEnabled(boolean enabled) {
+        return null;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean hasSubMenu() {
+        return false;
+    }
+
+    @Override
+    public SubMenu getSubMenu() {
+        return null;
+    }
+
+    @Override
+    public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
+        return null;
+    }
+
+    @Override
+    public ContextMenu.ContextMenuInfo getMenuInfo() {
+        return null;
+    }
+
+    @Override
+    public void setShowAsAction(int actionEnum) {
+
+    }
+
+    @Override
+    public MenuItem setShowAsActionFlags(int actionEnum) {
+        return null;
+    }
+
+    @Override
+    public MenuItem setActionView(View view) {
+        return null;
+    }
+
+    @Override
+    public MenuItem setActionView(int resId) {
+        return null;
+    }
+
+    @Override
+    public View getActionView() {
+        return null;
+    }
+
+    @Override
+    public MenuItem setActionProvider(ActionProvider actionProvider) {
+        return null;
+    }
+
+    @Override
+    public ActionProvider getActionProvider() {
+        return null;
+    }
+
+    @Override
+    public boolean expandActionView() {
+        return false;
+    }
+
+    @Override
+    public boolean collapseActionView() {
+        return false;
+    }
+
+    @Override
+    public boolean isActionViewExpanded() {
+        return false;
+    }
+
+    @Override
+    public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+        return null;
+    }
+}