Add new activity for selecting the destination folder when moving files, including...
authorjabarros <jabarros@solidgear.es>
Wed, 13 Aug 2014 14:43:50 +0000 (16:43 +0200)
committerjabarros <jabarros@solidgear.es>
Wed, 13 Aug 2014 14:43:50 +0000 (16:43 +0200)
AndroidManifest.xml
res/layout/files_move.xml [new file with mode: 0644]
res/values/strings.xml
src/com/owncloud/android/ui/activity/MoveActivity.java [new file with mode: 0644]
src/com/owncloud/android/ui/adapter/FolderListListAdapter.java [new file with mode: 0644]
src/com/owncloud/android/ui/fragment/MoveFileListFragment.java [new file with mode: 0644]
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

index 242586a..1223b54 100644 (file)
         <service android:name=".services.observer.FileObserverService"/>
         
         <activity 
-            android:name=".ui.activity.CopyToClipboardActivity" 
-               android:label="@string/copy_link"
-               android:icon="@drawable/copy_link" />
+                       android:name=".ui.activity.MoveActivity"
+                       android:label="@string/app_name"/>
         
     </application>
 
diff --git a/res/layout/files_move.xml b/res/layout/files_move.xml
new file mode 100644 (file)
index 0000000..491bcd8
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_color"
+    android:orientation="vertical" >
+
+       <FrameLayout 
+               android:layout_width="match_parent"
+               android:layout_height="0dip"
+        android:layout_weight="1"
+               android:id="@+id/fragment_container" />
+       
+       <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal" >
+
+        <Button
+            android:id="@+id/move_files_btn_cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/common_cancel" />
+
+               <Button
+                   android:id="@+id/move_files_btn_choose"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:layout_weight="1"
+                   android:text="@string/move_choose_button_text" />
+
+       </LinearLayout>
+
+ </LinearLayout>
\ No newline at end of file
index 0c3b1e7..87e766b 100644 (file)
        <string name="auth_redirect_non_secure_connection_title">Secure connection is redirected through a non secure route.</string>
 
        <string name="actionbar_move">Move</string>
+       <string name="file_list_empty_moving">Nothing in here. You can add a folder!</string>
+       <string name="move_choose_button_text">Choose</string>
 
 </resources>
diff --git a/src/com/owncloud/android/ui/activity/MoveActivity.java b/src/com/owncloud/android/ui/activity/MoveActivity.java
new file mode 100644 (file)
index 0000000..5d431c0
--- /dev/null
@@ -0,0 +1,606 @@
+package com.owncloud.android.ui.activity;
+
+import java.io.IOException;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.Window;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.OwnCloudCredentials;
+import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.services.observer.FileObserverService;
+import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
+import com.owncloud.android.ui.fragment.FileFragment;
+import com.owncloud.android.ui.fragment.MoveFileListFragment;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.Log_OC;
+
+public class MoveActivity extends HookActivity implements FileFragment.ContainerActivity, 
+    OnNavigationListener, OnClickListener{
+    
+    private ArrayAdapter<String> mDirectories;
+    
+    private SyncBroadcastReceiver mSyncBroadcastReceiver;
+
+    public static final int DIALOG_SHORT_WAIT = 0;
+    
+    public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS";
+
+    private static final String TAG = MoveActivity.class.getSimpleName();
+    
+    private static final String TAG_LIST_OF_FILES = "LIST_OF_FILES";
+       
+    private boolean mSyncInProgress = false;
+
+    private Button mCancelBtn;
+    private Button mChooseBtn;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log_OC.d(TAG, "onCreate() start");
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+
+        super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account is valid
+
+        /// grant that FileObserverService is watching favourite files
+        if (savedInstanceState == null) {
+            Intent initObserversIntent = FileObserverService.makeInitIntent(this);
+            startService(initObserversIntent);
+        }
+
+        /// USER INTERFACE
+
+        // Inflate and set the layout view
+        setContentView(R.layout.files_move);    
+        if (savedInstanceState == null) {
+            createMinFragments();
+        }
+
+        initControls();
+
+        // Action bar setup
+        mDirectories = new CustomArrayAdapter<String>(this, R.layout.sherlock_spinner_dropdown_item);
+        getSupportActionBar().setHomeButtonEnabled(true);       // mandatory since Android ICS, according to the official documentation
+        setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);    // always AFTER setContentView(...) ; to work around bug in its implementation
+        
+        setBackgroundText();
+
+        Log_OC.d(TAG, "onCreate() end");
+        
+    }
+
+    private void createMinFragments() {
+        MoveFileListFragment listOfFiles = new MoveFileListFragment();
+        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+        transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FILES);
+        transaction.commit();
+    }
+
+    /**
+     * Show a text message on screen view for notifying user if content is
+     * loading or folder is empty
+     */
+    private void setBackgroundText() {
+        MoveFileListFragment MoveFileListFragment = getListOfFilesFragment();
+        if (MoveFileListFragment != null) {
+            int message = R.string.file_list_loading;
+            if (!mSyncInProgress) {
+                // In case folder list is empty
+                message = R.string.file_list_empty_moving;
+            }
+            MoveFileListFragment.setMessageForEmptyList(getString(message));
+        } else {
+            Log.e(TAG, "MoveFileListFragment is null");
+        }
+    }
+
+    private MoveFileListFragment getListOfFilesFragment() {
+        Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(MoveActivity.TAG_LIST_OF_FILES);
+        if (listOfFiles != null) {
+            return (MoveFileListFragment)listOfFiles;
+        }
+        Log_OC.wtf(TAG, "Access to unexisting list of files fragment!!");
+        return null;
+    }
+
+    // Custom array adapter to override text colors
+    private class CustomArrayAdapter<T> extends ArrayAdapter<T> {
+
+        public CustomArrayAdapter(MoveActivity ctx, int view) {
+            super(ctx, view);
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View v = super.getView(position, convertView, parent);
+
+            ((TextView) v).setTextColor(getResources().getColorStateList(
+                    android.R.color.white));
+            
+            fixRoot((TextView) v );
+            return v;
+        }
+
+        public View getDropDownView(int position, View convertView,
+                ViewGroup parent) {
+            View v = super.getDropDownView(position, convertView, parent);
+
+            ((TextView) v).setTextColor(getResources().getColorStateList(
+                    android.R.color.white));
+
+            fixRoot((TextView) v );
+            return v;
+        }
+
+        private void fixRoot(TextView v) {
+            if (v.getText().equals(OCFile.PATH_SEPARATOR)) {
+                v.setText(R.string.default_display_name_for_root_folder);
+            }
+        }
+
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * Updates action bar and second fragment, if in dual pane mode.
+     */
+    @Override
+    public void onBrowsedDownTo(OCFile directory) {
+        pushDirname(directory);
+        
+        // Sync Folder
+        startSyncFolderOperation(directory);
+        
+    }
+
+    /**
+     * Shows the information of the {@link OCFile} received as a 
+     * parameter in the second fragment.
+     * 
+     * @param file          {@link OCFile} whose details will be shown
+     */
+    @Override
+    public void showDetails(OCFile file) {
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
+            
+    }
+
+    /**
+     * Pushes a directory to the drop down list
+     * @param directory to push
+     * @throws IllegalArgumentException If the {@link OCFile#isFolder()} returns false.
+     */
+    public void pushDirname(OCFile directory) {
+        if(!directory.isFolder()){
+            throw new IllegalArgumentException("Only directories may be pushed!");
+        }
+        mDirectories.insert(directory.getFileName(), 0);
+        setFile(directory);
+    }
+
+    public void startSyncFolderOperation(OCFile folder) {
+        long currentSyncTime = System.currentTimeMillis(); 
+        
+        mSyncInProgress = true;
+                
+        // perform folder synchronization
+        RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,  
+                                                                        currentSyncTime, 
+                                                                        false,
+                                                                        getFileOperationsHelper().isSharedSupported(),
+                                                                        getStorageManager(), 
+                                                                        getAccount(), 
+                                                                        getApplicationContext()
+                                                                      );
+        synchFolderOp.execute(getAccount(), this, null, null);
+        
+        setSupportProgressBarIndeterminateVisibility(true);
+
+        setBackgroundText();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log_OC.e(TAG, "onResume() start");
+        
+        // refresh list of files
+        refreshListOfFilesFragment();
+
+        // Listen for sync messages
+        IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
+        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
+        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
+        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+        mSyncBroadcastReceiver = new SyncBroadcastReceiver();
+        registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
+        
+        Log_OC.d(TAG, "onResume() end");
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getSherlock().getMenuInflater();
+        inflater.inflate(R.menu.main_menu, menu);
+        menu.findItem(R.id.action_upload).setVisible(false);
+        menu.findItem(R.id.action_settings).setVisible(false);
+        menu.findItem(R.id.action_sync_account).setVisible(false);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        boolean retval = true;
+        switch (item.getItemId()) {
+        case R.id.action_create_dir: {
+            CreateFolderDialogFragment dialog = 
+                    CreateFolderDialogFragment.newInstance(getCurrentDir());
+            dialog.show(getSupportFragmentManager(), "createdirdialog");
+            break;
+        }
+        case android.R.id.home: {
+            OCFile currentDir = getCurrentDir();
+            if(currentDir != null && currentDir.getParentId() != 0) {
+                onBackPressed();
+            }
+            break;
+        }
+        default:
+            retval = super.onOptionsItemSelected(item);
+        }
+        return retval;
+    }
+
+    private OCFile getCurrentDir() {
+        OCFile file = getFile();
+        if (file != null) {
+            if (file.isFolder()) {
+                return file;
+            } else if (getStorageManager() != null) {
+                String parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName()));
+                return getStorageManager().getFileByPath(parentPath);
+            }
+        }
+        return null;
+    }
+    
+    protected void refreshListOfFilesFragment() {
+        MoveFileListFragment fileListFragment = getListOfFilesFragment();
+        if (fileListFragment != null) { 
+            fileListFragment.listDirectory();
+        }
+    }
+
+    private class SyncBroadcastReceiver extends BroadcastReceiver {
+
+        /**
+         * {@link BroadcastReceiver} to enable syncing feedback in UI
+         */
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                String event = intent.getAction();
+                Log_OC.d(TAG, "Received broadcast " + event);
+                String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
+                String synchFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH); 
+                RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncAdapter.EXTRA_RESULT);
+                boolean sameAccount = (getAccount() != null && accountName.equals(getAccount().name) && getStorageManager() != null); 
+    
+                if (sameAccount) {
+                    
+                    if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
+                        mSyncInProgress = true;
+                        
+                    } else {
+                        OCFile currentFile = (getFile() == null) ? null : getStorageManager().getFileByPath(getFile().getRemotePath());
+                        OCFile currentDir = (getCurrentDir() == null) ? null : getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
+    
+                        if (currentDir == null) {
+                            // current folder was removed from the server 
+                            Toast.makeText( MoveActivity.this, 
+                                            String.format(getString(R.string.sync_current_folder_was_removed), mDirectories.getItem(0)), 
+                                            Toast.LENGTH_LONG)
+                                .show();
+                            browseToRoot();
+                            
+                        } else {
+                            if (currentFile == null && !getFile().isFolder()) {
+                                // currently selected file was removed in the server, and now we know it
+                                currentFile = currentDir;
+                            }
+
+                            if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) {
+                                MoveFileListFragment fileListFragment = getListOfFilesFragment();
+                                if (fileListFragment != null) {
+                                    fileListFragment.listDirectory(currentDir);
+                                }
+                            }
+                            setFile(currentFile);
+                        }
+                        
+                        mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+                                
+                        if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+                                    equals(event) &&
+                                /// TODO refactor and make common
+                                synchResult != null && !synchResult.isSuccess() &&  
+                                (synchResult.getCode() == ResultCode.UNAUTHORIZED   || 
+                                    synchResult.isIdPRedirection()                  ||
+                                    (synchResult.isException() && synchResult.getException() 
+                                            instanceof AuthenticatorException))) {
+
+                            OwnCloudClient client = null;
+                            try {
+                                OwnCloudAccount ocAccount = 
+                                        new OwnCloudAccount(getAccount(), context);
+                                client = (OwnCloudClientManagerFactory.getDefaultSingleton().
+                                        removeClientFor(ocAccount));
+                                // TODO get rid of these exceptions
+                            } catch (AccountNotFoundException e) {
+                                e.printStackTrace();
+                            } catch (AuthenticatorException e) {
+                                e.printStackTrace();
+                            } catch (OperationCanceledException e) {
+                                e.printStackTrace();
+                            } catch (IOException e) {
+                                e.printStackTrace();
+                            }
+                            
+                            if (client != null) {
+                                OwnCloudCredentials cred = client.getCredentials();
+                                if (cred != null) {
+                                    AccountManager am = AccountManager.get(context);
+                                    if (cred.authTokenExpires()) {
+                                        am.invalidateAuthToken(
+                                                getAccount().type, 
+                                                cred.getAuthToken()
+                                        );
+                                    } else {
+                                        am.clearPassword(getAccount());
+                                    }
+                                }
+                            }
+                            
+                            requestCredentialsUpdate();
+                            
+                        }
+                    }
+                    removeStickyBroadcast(intent);
+                    Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
+                    setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);
+
+                    setBackgroundText();
+                        
+                }
+                
+            } catch (RuntimeException e) {
+                // avoid app crashes after changing the serial id of RemoteOperationResult 
+                // in owncloud library with broadcast notifications pending to process
+                removeStickyBroadcast(intent);
+            }
+        }
+    }
+
+    public void browseToRoot() {
+        MoveFileListFragment listOfFiles = getListOfFilesFragment(); 
+        if (listOfFiles != null) {  // should never be null, indeed
+            while (mDirectories.getCount() > 1) {
+                popDirname();
+            }
+            OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
+            listOfFiles.listDirectory(root);
+            setFile(listOfFiles.getCurrentFile());
+            startSyncFolderOperation(root);
+        }
+    }
+
+    /**
+     * Pops a directory name from the drop down list
+     * @return True, unless the stack is empty
+     */
+    public boolean popDirname() {
+        mDirectories.remove(mDirectories.getItem(0));
+        return !mDirectories.isEmpty();
+    }
+
+    private void setNavigationListWithFolder(OCFile file) {
+        mDirectories.clear();
+        OCFile fileIt = file;
+        String parentPath;
+        while(fileIt != null && fileIt.getFileName() != OCFile.ROOT_PATH) {
+            if (fileIt.isFolder()) {
+                mDirectories.add(fileIt.getFileName());
+            }
+            // get parent from path
+            parentPath = fileIt.getRemotePath().substring(0, fileIt.getRemotePath().lastIndexOf(fileIt.getFileName()));
+            fileIt = getStorageManager().getFileByPath(parentPath);
+        }
+        mDirectories.add(OCFile.PATH_SEPARATOR);
+    }
+
+    @Override
+    public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+        if (itemPosition != 0) {
+            String targetPath = "";
+            for (int i=itemPosition; i < mDirectories.getCount() - 1; i++) {
+                targetPath = mDirectories.getItem(i) + OCFile.PATH_SEPARATOR + targetPath; 
+            }
+            targetPath = OCFile.PATH_SEPARATOR + targetPath;
+            OCFile targetFolder = getStorageManager().getFileByPath(targetPath);
+            if (targetFolder != null) {
+                browseTo(targetFolder);
+            }
+
+            // the next operation triggers a new call to this method, but it's necessary to 
+            // ensure that the name exposed in the action bar is the current directory when the 
+            // user selected it in the navigation list
+            if (getSupportActionBar().getNavigationMode() == ActionBar.NAVIGATION_MODE_LIST  && itemPosition != 0)
+                getSupportActionBar().setSelectedNavigationItem(0);
+        }
+        return true;
+    }
+
+    public void browseTo(OCFile folder) {
+        if (folder == null || !folder.isFolder()) {
+            throw new IllegalArgumentException("Trying to browse to invalid folder " + folder);
+        }
+        MoveFileListFragment listOfFiles = getListOfFilesFragment();
+        if (listOfFiles != null) {
+            setNavigationListWithFolder(folder);
+            listOfFiles.listDirectory(folder);
+            setFile(listOfFiles.getCurrentFile());
+            startSyncFolderOperation(folder);
+        } else {
+            Log_OC.e(TAG, "Unexpected null when accessing list fragment");
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        MoveFileListFragment listOfFiles = getListOfFilesFragment();
+        if (listOfFiles != null) {  // should never be null, indeed
+            if (mDirectories.getCount() <= 1) {
+                finish();
+                return;
+            }
+            int levelsUp = listOfFiles.onBrowseUp();
+            for (int i=0; i < levelsUp && mDirectories.getCount() > 1 ; i++) {
+                popDirname();
+            }
+        }
+        if (listOfFiles != null) {  // should never be null, indeed
+            setFile(listOfFiles.getCurrentFile());
+        }
+    }
+
+    private void updateNavigationElementsInActionBar(OCFile chosenFile) {
+        ActionBar actionBar = getSupportActionBar();
+        if (chosenFile == null) {
+            // only list of files - set for browsing through folders
+            OCFile currentDir = getCurrentDir();
+            boolean noRoot = (currentDir != null && currentDir.getParentId() != 0);
+            actionBar.setDisplayHomeAsUpEnabled(noRoot);
+            actionBar.setDisplayShowTitleEnabled(!noRoot);
+            if (!noRoot) {
+                actionBar.setTitle(getString(R.string.default_display_name_for_root_folder));
+            }
+            actionBar.setNavigationMode(!noRoot ? ActionBar.NAVIGATION_MODE_STANDARD : ActionBar.NAVIGATION_MODE_LIST);
+            actionBar.setListNavigationCallbacks(mDirectories, this);   // assuming mDirectories is updated
+
+        } else {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setDisplayShowTitleEnabled(true);
+            actionBar.setTitle(chosenFile.getFileName());
+            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+        }
+    }
+
+    /**
+     *  Called when the ownCloud {@link Account} associated to the Activity was just updated.
+     */
+    @Override
+    protected void onAccountSet(boolean stateWasRecovered) {
+        super.onAccountSet(stateWasRecovered);
+        if (getAccount() != null) {
+            /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account
+            OCFile file = getFile();
+            // get parent from path
+            String parentPath = "";
+            if (file != null) {
+                if (file.isDown() && file.getLastSyncDateForProperties() == 0) {
+                    // upload in progress - right now, files are not inserted in the local cache until the upload is successful
+                    // get parent from path
+                    parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName()));
+                    if (getStorageManager().getFileByPath(parentPath) ==  null)
+                        file = null; // not able to know the directory where the file is uploading
+                } else {
+                    file = getStorageManager().getFileByPath(file.getRemotePath());   // currentDir = null if not in the current Account
+                }
+            }
+            if (file == null) {
+                // fall back to root folder
+                file = getStorageManager().getFileByPath(OCFile.ROOT_PATH);  // never returns null
+            }
+            setFile(file);
+            setNavigationListWithFolder(file);
+
+            if (!stateWasRecovered) {
+                Log_OC.e(TAG, "Initializing Fragments in onAccountChanged..");
+                if (file.isFolder()) {
+                    startSyncFolderOperation(file);
+                }
+            } else {
+                updateNavigationElementsInActionBar(file.isFolder() ? null : file);
+            }
+        }
+    }
+
+    /**
+     * Set controllers
+     */
+    private void initControls(){
+        mCancelBtn = (Button) findViewById(R.id.move_files_btn_cancel);
+        mCancelBtn.setOnClickListener(this);
+        mChooseBtn = (Button) findViewById(R.id.move_files_btn_choose);
+        mChooseBtn.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mCancelBtn) {
+            finish();
+        }
+    }
+}
diff --git a/src/com/owncloud/android/ui/adapter/FolderListListAdapter.java b/src/com/owncloud/android/ui/adapter/FolderListListAdapter.java
new file mode 100644 (file)
index 0000000..b164e03
--- /dev/null
@@ -0,0 +1,233 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *   Copyright (C) 2012-2014 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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.owncloud.android.ui.adapter;
+
+import java.util.Vector;
+
+import android.accounts.Account;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.ui.activity.ComponentsGetter;
+import com.owncloud.android.utils.DisplayUtils;
+
+
+/**
+ * This Adapter populates a ListView with all the folders in an ownCloud instance.
+ */
+public class FolderListListAdapter extends BaseAdapter implements ListAdapter {
+    private final static String PERMISSION_SHARED_WITH_ME = "S";
+
+    private Context mContext;
+    private OCFile mFile = null;
+    private Vector<OCFile> mFolders = null;
+
+    private FileDataStorageManager mStorageManager;
+    private Account mAccount;
+    private ComponentsGetter mTransferServiceGetter;
+    
+    public FolderListListAdapter(Context context, ComponentsGetter transferServiceGetter) {
+        mContext = context;
+        mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
+        mTransferServiceGetter = transferServiceGetter;
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    @Override
+    public int getCount() {
+        return mFolders != null ? mFolders.size() : 0;
+    }
+
+    @Override
+    public Object getItem(int position) {
+        if (mFolders == null || mFolders.size() <= position)
+            return null;
+        return mFolders.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        if (mFolders == null || mFolders.size() <= position)
+            return 0;
+        return mFolders.get(position).getFileId();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return 0;
+    }
+
+    @SuppressLint("InflateParams")
+       @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View view = convertView;
+        if (view == null) {
+            LayoutInflater inflator = (LayoutInflater) mContext
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            view = inflator.inflate(R.layout.list_item, null);
+        }
+
+        if (mFolders != null && mFolders.size() > position) {
+            OCFile file = mFolders.get(position);
+            TextView fileName = (TextView) view.findViewById(R.id.Filename);
+            String name = file.getFileName();
+
+            fileName.setText(name);
+            ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1);
+            ImageView sharedIconV = (ImageView) view.findViewById(R.id.sharedIcon);
+            ImageView sharedWithMeIconV = (ImageView) view.findViewById(R.id.sharedWithMeIcon);
+            sharedWithMeIconV.setVisibility(View.GONE);
+
+            ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);
+            localStateView.bringToFront();
+            FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();
+            FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
+
+            if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {
+                localStateView.setImageResource(R.drawable.downloading_file_indicator);
+                localStateView.setVisibility(View.VISIBLE);
+            } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {
+                localStateView.setImageResource(R.drawable.uploading_file_indicator);
+                localStateView.setVisibility(View.VISIBLE);
+            } else if (file.isDown()) {
+                localStateView.setImageResource(R.drawable.local_file_indicator);
+                localStateView.setVisibility(View.VISIBLE);
+            } else {
+                localStateView.setVisibility(View.INVISIBLE);
+            }
+            
+            TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);
+            TextView lastModV = (TextView) view.findViewById(R.id.last_mod);
+            ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);
+            
+                
+            fileSizeV.setVisibility(View.INVISIBLE);
+            lastModV.setVisibility(View.VISIBLE);
+            lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp()));
+            checkBoxV.setVisibility(View.GONE);
+            view.findViewById(R.id.imageView3).setVisibility(View.GONE);
+
+            if (checkIfFileIsSharedWithMe(file)) {
+                fileIcon.setImageResource(R.drawable.shared_with_me_folder);
+                sharedWithMeIconV.setVisibility(View.VISIBLE);
+            } else {
+                fileIcon.setImageResource(DisplayUtils.getResourceId(file.getMimetype(), file.getFileName()));
+            }
+
+            // If folder is sharedByLink, icon folder must be changed to
+            // folder-public one
+            if (file.isShareByLink()) {
+                fileIcon.setImageResource(R.drawable.folder_public);
+            }
+
+            if (file.isShareByLink()) {
+                sharedIconV.setVisibility(View.VISIBLE);
+            } else {
+                sharedIconV.setVisibility(View.GONE);
+            }
+        } 
+
+        return view;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return 1;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return (mFolders == null || mFolders.isEmpty());
+    }
+
+    /**
+     * Change the adapted directory for a new one
+     * @param directory                 New file to adapt. Can be NULL, meaning "no content to adapt".
+     * @param updatedStorageManager     Optional updated storage manager; used to replace mStorageManager if is different (and not NULL)
+     */
+    public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) {
+        mFile = directory;
+        if (updatedStorageManager != null && updatedStorageManager != mStorageManager) {
+            mStorageManager = updatedStorageManager;
+            mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
+        }
+        if (mStorageManager != null) {
+            // Only take into account the folders for after being listed
+            mFolders = getFolders(mStorageManager.getFolderContent(mFile));
+        } else {
+            mFolders = null;
+        }
+        notifyDataSetChanged();
+    }
+    
+    /**
+     * Filter for getting only the folders
+     * @param files
+     * @return Vector<OCFile>
+     */
+    public Vector<OCFile> getFolders(Vector<OCFile> files) {
+        Vector<OCFile> ret = new Vector<OCFile>(); 
+        OCFile current = null; 
+        for (int i=0; i<files.size(); i++) {
+            current = files.get(i);
+            if (current.isFolder()) {
+                ret.add(current);
+            }
+        }
+        return ret;
+    }
+    
+    /**
+     * Check if parent folder does not include 'S' permission and if file/folder
+     * is shared with me
+     * 
+     * @param file: OCFile
+     * @return boolean: True if it is shared with me and false if it is not
+     */
+    private boolean checkIfFileIsSharedWithMe(OCFile file) {
+        return (mFile.getPermissions() != null && !mFile.getPermissions().contains(PERMISSION_SHARED_WITH_ME)
+                && file.getPermissions() != null && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));
+    }
+}
diff --git a/src/com/owncloud/android/ui/fragment/MoveFileListFragment.java b/src/com/owncloud/android/ui/fragment/MoveFileListFragment.java
new file mode 100644 (file)
index 0000000..03ca62a
--- /dev/null
@@ -0,0 +1,319 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *   Copyright (C) 2012-2014 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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.owncloud.android.ui.fragment;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.MoveActivity;
+import com.owncloud.android.ui.adapter.FolderListListAdapter;
+import com.owncloud.android.utils.Log_OC;
+
+/**
+ * A Fragment that lists all folders in a given path.
+ * 
+ * TODO refactorize to get rid of direct dependency on MoveActivity
+ * 
+ */
+public class MoveFileListFragment extends ExtendedListFragment {
+    
+    private static final String TAG = MoveFileListFragment.class.getSimpleName();
+
+    private static final String MY_PACKAGE = MoveFileListFragment.class.getPackage() != null ? MoveFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
+    private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
+
+    private static final String KEY_INDEXES = "INDEXES";
+    private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
+    private static final String KEY_TOPS = "TOPS";
+    private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
+    private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE";
+    
+    private FileFragment.ContainerActivity mContainerActivity;
+   
+    private OCFile mFile = null;
+    private FolderListListAdapter mAdapter;
+
+    // Save the state of the scroll in browsing
+    private ArrayList<Integer> mIndexes;
+    private ArrayList<Integer> mFirstPositions;
+    private ArrayList<Integer> mTops;
+
+    private int mHeightCell = 0;
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        Log_OC.e(TAG, "onAttach");
+        try {
+            mContainerActivity = (FileFragment.ContainerActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement " + 
+                    FileFragment.ContainerActivity.class.getSimpleName());
+        }
+    }
+
+    
+    @Override
+    public void onDetach() {
+        mContainerActivity = null;
+        super.onDetach();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        Log_OC.e(TAG, "onActivityCreated() start");
+        
+        mAdapter = new FolderListListAdapter(getSherlockActivity(), mContainerActivity); 
+                
+        if (savedInstanceState != null) {
+            mFile = savedInstanceState.getParcelable(EXTRA_FILE);
+            mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
+            mFirstPositions = savedInstanceState.getIntegerArrayList(KEY_FIRST_POSITIONS);
+            mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
+            mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
+            setMessageForEmptyList(savedInstanceState.getString(KEY_EMPTY_LIST_MESSAGE));
+            
+        } else {
+            mIndexes = new ArrayList<Integer>();
+            mFirstPositions = new ArrayList<Integer>();
+            mTops = new ArrayList<Integer>();
+            mHeightCell = 0;
+            
+        }
+        
+        mAdapter = new FolderListListAdapter(getSherlockActivity(), mContainerActivity);
+        
+        setListAdapter(mAdapter);
+        
+        registerForContextMenu(getListView());
+        getListView().setOnCreateContextMenuListener(this);
+  }
+    
+    /**
+     * Saves the current listed folder.
+     */
+    @Override
+    public void onSaveInstanceState (Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(EXTRA_FILE, mFile);
+        outState.putIntegerArrayList(KEY_INDEXES, mIndexes);
+        outState.putIntegerArrayList(KEY_FIRST_POSITIONS, mFirstPositions);
+        outState.putIntegerArrayList(KEY_TOPS, mTops);
+        outState.putInt(KEY_HEIGHT_CELL, mHeightCell);
+        outState.putString(KEY_EMPTY_LIST_MESSAGE, getEmptyViewText());
+    }
+    
+    /**
+     * 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 database, 
+     * it continues browsing up until finding an existing folders.
+     * 
+     * return       Count of folder levels browsed up.
+     */
+    public int onBrowseUp() {
+        OCFile parentDir = null;
+        int moveCount = 0;
+        
+        if(mFile != null){
+            FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
+            
+            String parentPath = null;
+            if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
+                parentPath = new File(mFile.getRemotePath()).getParent();
+                parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
+                parentDir = storageManager.getFileByPath(parentPath);
+                moveCount++;
+            } else {
+                parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);    // never returns null; keep the path in root folder
+            }
+            while (parentDir == null) {
+                parentPath = new File(parentPath).getParent();
+                parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
+                parentDir = storageManager.getFileByPath(parentPath);
+                moveCount++;
+            }   // exit is granted because storageManager.getFileByPath("/") never returns null
+            mFile = parentDir;           
+        }
+        
+        if (mFile != null) {
+            listDirectory(mFile);
+
+            ((MoveActivity)mContainerActivity).startSyncFolderOperation(mFile);
+            
+            // restore index and top position
+            restoreIndexAndTopPosition();
+            
+        }   // else - should never happen now
+   
+        return moveCount;
+    }
+    
+    /*
+     * Restore index and position
+     */
+    private void restoreIndexAndTopPosition() {
+        if (mIndexes.size() > 0) {  
+            // needs to be checked; not every browse-up had a browse-down before 
+            
+            int index = mIndexes.remove(mIndexes.size() - 1);
+            
+            int firstPosition = mFirstPositions.remove(mFirstPositions.size() -1);
+            
+            int top = mTops.remove(mTops.size() - 1);
+            
+            mList.setSelectionFromTop(firstPosition, top);
+            
+            // Move the scroll if the selection is not visible
+            int indexPosition = mHeightCell*index;
+            int height = mList.getHeight();
+            
+            if (indexPosition > height) {
+                if (android.os.Build.VERSION.SDK_INT >= 11)
+                {
+                    mList.smoothScrollToPosition(index); 
+                }
+                else if (android.os.Build.VERSION.SDK_INT >= 8)
+                {
+                    mList.setSelectionFromTop(index, 0);
+                }
+                
+            }
+        }
+    }
+    
+    /*
+     * Save index and top position
+     */
+    private void saveIndexAndTopPosition(int index) {
+        
+        mIndexes.add(index);
+        
+        int firstPosition = mList.getFirstVisiblePosition();
+        mFirstPositions.add(firstPosition);
+        
+        View view = mList.getChildAt(0);
+        int top = (view == null) ? 0 : view.getTop() ;
+
+        mTops.add(top);
+        
+        // Save the height of a cell
+        mHeightCell = (view == null || mHeightCell != 0) ? mHeightCell : view.getHeight();
+    }
+    
+    @Override
+    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
+        OCFile file = (OCFile) mAdapter.getItem(position);
+        if (file != null) {
+            if (file.isFolder()) { 
+                // update state and view of this fragment
+                listDirectory(file);
+                // then, notify parent activity to let it update its state and view, and other fragments
+                mContainerActivity.onBrowsedDownTo(file);
+                // save index and top position
+                saveIndexAndTopPosition(position);
+                
+            } 
+            
+        } else {
+            Log_OC.d(TAG, "Null object in ListAdapter!!");
+        }
+        
+    }
+
+    /**
+     * Use this to query the {@link OCFile} that is currently
+     * being displayed by this fragment
+     * @return The currently viewed OCFile
+     */
+    public OCFile getCurrentFile(){
+        return mFile;
+    }
+    
+    /**
+     * Calls {@link MoveFileListFragment#listDirectory(OCFile)} with a null parameter
+     */
+    public void listDirectory(){
+        listDirectory(null);
+    }
+    
+    /**
+     * Lists the given directory on the view. When the input parameter is null,
+     * it will either refresh the last known directory. list the root
+     * if there never was a directory.
+     * 
+     * @param directory File to be listed
+     */
+    public void listDirectory(OCFile directory) {
+        FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
+        if (storageManager != null) {
+
+            // Check input parameters for null
+            if(directory == null){
+                if(mFile != null){
+                    directory = mFile;
+                } else {
+                    directory = storageManager.getFileByPath("/");
+                    if (directory == null) return; // no files, wait for sync
+                }
+            }
+        
+        
+            // If that's not a directory -> List its parent
+            if(!directory.isFolder()){
+                Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
+                directory = storageManager.getFileById(directory.getParentId());
+            }
+
+            mAdapter.swapDirectory(directory, storageManager);
+            if (mFile == null || !mFile.equals(directory)) {
+                mList.setSelectionFromTop(0, 0);
+            }
+            mFile = directory;
+        }
+    }
+
+
+    @Override
+    public void onRefresh() {
+        super.onRefresh();
+        
+        if (mFile != null) {
+            // Refresh mFile
+            mFile = mContainerActivity.getStorageManager().getFileById(mFile.getFileId());
+
+            listDirectory(mFile);
+            
+            ((MoveActivity)mContainerActivity).startSyncFolderOperation(mFile);
+        }
+    }
+}
index 5259807..b604874 100644 (file)
@@ -21,6 +21,7 @@ import java.io.File;
 import java.util.ArrayList;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.view.ContextMenu;
 import android.view.MenuInflater;
@@ -34,6 +35,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.FileMenuFilter;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.activity.MoveActivity;
 import com.owncloud.android.ui.adapter.FileListListAdapter;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
@@ -378,6 +380,12 @@ public class OCFileListFragment extends ExtendedListFragment {
                 }
                 return true;
             }
+            case R.id.action_move: {
+                Intent i = new Intent(getActivity(), MoveActivity.class);
+                startActivity(i);
+
+                return true;
+            }
             default:
                 return super.onContextItemSelected(item); 
         }