summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.java47
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java227
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java82
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java162
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java31
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java184
-rw-r--r--app/src/main/res/drawable-hdpi/ic_folder_white_24dp.pngbin0 -> 135 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_folder_white_24dp.pngbin0 -> 122 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_folder_white_24dp.pngbin0 -> 181 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.pngbin0 -> 245 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.pngbin0 -> 325 bytes
-rw-r--r--app/src/main/res/layout/select_folder_layout.xml11
-rw-r--r--app/src/main/res/menu/context_pass.xml11
-rw-r--r--app/src/main/res/menu/pgp_handler_select_folder.xml11
14 files changed, 604 insertions, 162 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
index e33addc3..996942d7 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
@@ -2,8 +2,6 @@ package com.zeapo.pwdstore;
import android.Manifest;
import android.app.Activity;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentTransaction;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -13,11 +11,14 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
+import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -38,6 +39,7 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import java.io.File;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@@ -463,6 +465,17 @@ public class PasswordStore extends AppCompatActivity {
.show();
}
+ public void movePasswords(ArrayList<PasswordItem> values) {
+ Intent intent = new Intent(this, PgpHandler.class);
+ ArrayList<String> fileLocations = new ArrayList<>();
+ for (PasswordItem passwordItem : values){
+ fileLocations.add(passwordItem.getFile().getAbsolutePath());
+ }
+ intent.putExtra("Files",fileLocations);
+ intent.putExtra("Operation", "SELECTFOLDER");
+ startActivityForResult(intent, PgpHandler.REQUEST_CODE_SELECT_FOLDER);
+ }
+
/**
* clears adapter's content and updates it with a fresh list of passwords from the root
*/
@@ -558,6 +571,36 @@ public class PasswordStore extends AppCompatActivity {
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break;
+ case PgpHandler.REQUEST_CODE_SELECT_FOLDER:
+ Log.d("Moving","Moving passwords to "+data.getStringExtra("SELECTED_FOLDER_PATH"));
+ Log.d("Moving", TextUtils.join(", ", data.getStringArrayListExtra("Files")));
+ File target = new File(data.getStringExtra("SELECTED_FOLDER_PATH"));
+ if (!target.isDirectory()){
+ Log.e("Moving","Tried moving passwords to a non-existing folder.");
+ break;
+ }
+
+ Repository repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(activity));
+ Git git = new Git(repo);
+ GitAsyncTask tasks = new GitAsyncTask(activity, false, true, CommitCommand.class);
+
+ for (String string : data.getStringArrayListExtra("Files")){
+ File source = new File(string);
+ if (!source.exists()){
+ Log.e("Moving","Tried moving something that appears non-existent.");
+ continue;
+ }
+ if (!source.renameTo(new File(target.getAbsolutePath()+"/"+source.getName()))){
+ Log.e("Moving","Something went wrong while moving.");
+ }else{
+ tasks.execute(
+ git.add().addFilepattern(source.getAbsolutePath().replace(PasswordRepository.getWorkTree() + "/", "")),
+ git.commit().setMessage("[ANDROID PwdStore] Moved "+string.replace(PasswordRepository.getWorkTree() + "/", "")+" to "+target.getAbsolutePath().replace(PasswordRepository.getWorkTree() + "/","")+target.getAbsolutePath()+"/"+source.getName()+".")
+ );
+ }
+ }
+ updateListAdapter();
+ break;
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java b/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java
new file mode 100644
index 00000000..c9bc3596
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java
@@ -0,0 +1,227 @@
+package com.zeapo.pwdstore;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.zeapo.pwdstore.crypto.PgpHandler;
+import com.zeapo.pwdstore.utils.FolderRecyclerAdapter;
+import com.zeapo.pwdstore.utils.PasswordItem;
+import com.zeapo.pwdstore.utils.PasswordRepository;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * A fragment representing a list of Items.
+ * <p />
+ * Large screen devices (such as tablets) are supported by replacing the ListView
+ * with a GridView.
+ * <p />
+ */
+public class SelectFolderFragment extends Fragment{
+
+ public interface OnFragmentInteractionListener {
+ public void onFragmentInteraction(PasswordItem item);
+ }
+
+ // store the pass files list in a stack
+ private Stack<ArrayList<PasswordItem>> passListStack;
+ private Stack<File> pathStack;
+ private Stack<Integer> scrollPosition;
+ private FolderRecyclerAdapter recyclerAdapter;
+ private RecyclerView recyclerView;
+ private RecyclerView.LayoutManager mLayoutManager;
+ private OnFragmentInteractionListener mListener;
+ private SharedPreferences settings;
+
+ /**
+ * Mandatory empty constructor for the fragment manager to instantiate the
+ * fragment (e.g. upon screen orientation changes).
+ */
+ public SelectFolderFragment() { }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String path = getArguments().getString("Path");
+
+ settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ passListStack = new Stack<ArrayList<PasswordItem>>();
+ scrollPosition = new Stack<Integer>();
+ pathStack = new Stack<File>();
+ recyclerAdapter = new FolderRecyclerAdapter((PgpHandler) getActivity(), mListener,
+ PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity())));
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.password_recycler_view, container, false);
+
+ // use a linear layout manager
+ mLayoutManager = new LinearLayoutManager(getActivity());
+
+ recyclerView = (RecyclerView) view.findViewById(R.id.pass_recycler);
+ recyclerView.setLayoutManager(mLayoutManager);
+
+ // use divider
+ recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), R.drawable.divider));
+
+ // Set the adapter
+ recyclerView.setAdapter(recyclerAdapter);
+
+ final FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ((PasswordStore) getActivity()).createPassword();
+ }
+ });
+
+ registerForContextMenu(recyclerView);
+ return view;
+ }
+
+ @Override
+ public void onAttach(final Context context) {
+ super.onAttach(context);
+ try {
+ mListener = new OnFragmentInteractionListener() {
+ public void onFragmentInteraction(PasswordItem item) {
+ if (item.getType() == PasswordItem.TYPE_CATEGORY) {
+ // push the current password list (non filtered plz!)
+ passListStack.push(pathStack.isEmpty() ?
+ PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(context)) :
+ PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(context)));
+ //push the category were we're going
+ pathStack.push(item.getFile());
+ scrollPosition.push(recyclerView.getVerticalScrollbarPosition());
+
+ recyclerView.scrollToPosition(0);
+ recyclerAdapter.clear();
+ recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context)));
+
+ ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ public void savePosition(Integer position) {
+
+ }
+ };
+ } catch (ClassCastException e) {
+ throw new ClassCastException(context.toString()
+ + " must implement OnFragmentInteractionListener");
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+// mListener.savePosition(mListView.getFirstVisiblePosition());
+// mListView.closeOpenedItems();
+ }
+
+ /**
+ * clears the adapter content and sets it back to the root view
+ */
+ public void updateAdapter() {
+ passListStack.clear();
+ pathStack.clear();
+ scrollPosition.clear();
+ recyclerAdapter.clear();
+ recyclerAdapter.addAll(PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(getActivity())));
+
+ ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ }
+
+ /**
+ * refreshes the adapter with the latest opened category
+ */
+ public void refreshAdapter() {
+ recyclerAdapter.clear();
+ recyclerAdapter.addAll(pathStack.isEmpty() ?
+ PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(getActivity())) :
+ PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(getActivity())));
+ }
+
+ /**
+ * filters the list adapter
+ * @param filter the filter to apply
+ */
+ public void filterAdapter(String filter) {
+ Log.d("FRAG", "filter: " + filter);
+
+ if (filter.isEmpty()) {
+ refreshAdapter();
+ } else {
+ recursiveFilter(filter, pathStack.isEmpty() ? null : pathStack.peek());
+ }
+ }
+
+ /**
+ * recursively filters a directory and extract all the matching items
+ * @param filter the filter to apply
+ * @param dir the directory to filter
+ */
+ private void recursiveFilter(String filter, File dir) {
+ // on the root the pathStack is empty
+ ArrayList<PasswordItem> passwordItems = dir == null ?
+ PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(getActivity())) :
+ PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(getActivity()));
+
+ boolean rec = settings.getBoolean("filter_recursively", true);
+ for (PasswordItem item : passwordItems) {
+ if (item.getType() == PasswordItem.TYPE_CATEGORY && rec) {
+ recursiveFilter(filter, item.getFile());
+ }
+ boolean matches = item.toString().toLowerCase().contains(filter.toLowerCase());
+ boolean inAdapter = recyclerAdapter.getValues().contains(item);
+ if (matches && !inAdapter) {
+ recyclerAdapter.add(item);
+ } else if (!matches && inAdapter) {
+ recyclerAdapter.remove(recyclerAdapter.getValues().indexOf(item));
+ }
+ }
+ }
+
+ /**
+ * Goes back one level back in the path
+ */
+ public void popBack() {
+ if (passListStack.isEmpty())
+ return;
+
+ recyclerView.scrollToPosition(scrollPosition.pop());
+ recyclerAdapter.clear();
+ recyclerAdapter.addAll(passListStack.pop());
+ pathStack.pop();
+ }
+
+ /**
+ * gets the current directory
+ * @return the current directory
+ */
+ public File getCurrentDir() {
+ if (pathStack.isEmpty())
+ return PasswordRepository.getWorkTree();
+ else
+ return pathStack.peek();
+ }
+
+ public boolean isNotEmpty() {
+ return !passListStack.isEmpty();
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java
index 60599d6d..c28e6f82 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpHandler.java
@@ -13,6 +13,8 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
@@ -27,7 +29,9 @@ import android.widget.TextView;
import android.widget.Toast;
import com.google.common.primitives.Longs;
+import com.zeapo.pwdstore.BuildConfig;
import com.zeapo.pwdstore.R;
+import com.zeapo.pwdstore.SelectFolderFragment;
import com.zeapo.pwdstore.UserPreference;
import com.zeapo.pwdstore.pwgenDialogFragment;
import com.zeapo.pwdstore.utils.PasswordRepository;
@@ -57,6 +61,9 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
private Activity activity;
ClipboardManager clipboard;
+ SelectFolderFragment passwordList;
+ private Intent selectFolderData;
+
private boolean registered;
public static final int REQUEST_CODE_SIGN = 9910;
@@ -66,6 +73,7 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
public static final int REQUEST_CODE_GET_KEY = 9914;
public static final int REQUEST_CODE_GET_KEY_IDS = 9915;
public static final int REQUEST_CODE_EDIT = 9916;
+ public static final int REQUEST_CODE_SELECT_FOLDER = 9917;
public final class Constants {
public static final String TAG = "Keychain";
@@ -125,10 +133,15 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
- if (getIntent().getStringExtra("Operation").equals("ENCRYPT")) {
- getMenuInflater().inflate(R.menu.pgp_handler_new_password, menu);
- } else {
- getMenuInflater().inflate(R.menu.pgp_handler, menu);
+ switch (getIntent().getStringExtra("Operation")){
+ case "ENCRYPT":
+ getMenuInflater().inflate(R.menu.pgp_handler_new_password, menu);
+ break;
+ case "SELECTFOLDER":
+ getMenuInflater().inflate(R.menu.pgp_handler_select_folder, menu);
+ break;
+ default:
+ getMenuInflater().inflate(R.menu.pgp_handler, menu);
}
return true;
}
@@ -160,10 +173,22 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
setResult(RESULT_CANCELED);
finish();
return true;
+ case R.id.crypto_select:
+ selectFolder();
+ break;
}
return super.onOptionsItemSelected(item);
}
+ private void selectFolder() {
+ if (selectFolderData == null || passwordList == null){
+ Log.wtf(Constants.TAG,"Folder selected while the app didn't ask for one to be selected?");
+ }
+ selectFolderData.putExtra("SELECTED_FOLDER_PATH",passwordList.getCurrentDir().getAbsolutePath());
+ setResult(RESULT_OK,selectFolderData);
+ finish();
+ }
+
public void editPassword() {
// if in encrypt or in decrypt and password is invisible
// (because !showPassword, so this will instantly close), do nothing
@@ -259,6 +284,7 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
DialogFragment df = new pwgenDialogFragment();
df.show(getFragmentManager(), "generator");
default:
+ Log.wtf(Constants.TAG,"This should not happen.... PgpHandler.java#handleClick(View) default reached.");
// should not happen
}
@@ -407,6 +433,39 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
}
}
+ private void selectFolder(Intent data) {
+
+ if (data.getStringExtra("Operation") == null || !data.getStringExtra("Operation").equals("SELECTFOLDER")){
+ Log.e(Constants.TAG,"PgpHandler#selectFolder(Intent) triggered with incorrect intent.");
+ if (BuildConfig.DEBUG){
+ throw new UnsupportedOperationException("Triggered with incorrect intent.");
+ }
+ return;
+ }
+
+ Log.d(Constants.TAG,"PgpHandler#selectFolder(Intent).");
+
+
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+
+
+ passwordList = new SelectFolderFragment();
+ Bundle args = new Bundle();
+ args.putString("Path", PasswordRepository.getWorkTree().getAbsolutePath());
+
+ passwordList.setArguments(args);
+
+ getSupportActionBar().show();
+
+ fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+
+ fragmentTransaction.replace(R.id.pgp_handler_linearlayout, passwordList, "PasswordsList");
+ fragmentTransaction.commit();
+
+ this.selectFolderData = data;
+ }
+
public class PgpCallback implements OpenPgpApi.IOpenPgpCallback {
boolean returnToCiphertextField;
ByteArrayOutputStream os;
@@ -664,7 +723,11 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
Log.i("PGP", "ISBOUND!!");
Bundle extra = getIntent().getExtras();
- if (extra.getString("Operation").equals("DECRYPT")) {
+ final String operation = extra.getString("Operation");
+ if (operation == null){
+ return;
+ }
+ if (operation.equals("DECRYPT")) {
setContentView(R.layout.decrypt_layout);
((TextView) findViewById(R.id.crypto_password_file)).setText(extra.getString("NAME"));
String cat = new File(extra.getString("FILE_PATH").replace(PasswordRepository.getWorkTree().getAbsolutePath(), ""))
@@ -672,7 +735,7 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
((TextView) findViewById(R.id.crypto_password_category)).setText(cat + "/");
decryptAndVerify(new Intent());
- } else if (extra.getString("Operation").equals("ENCRYPT")) {
+ } else if (operation.equals("ENCRYPT")) {
setContentView(R.layout.encrypt_layout);
Typeface monoTypeface = Typeface.createFromAsset(getAssets(), "fonts/sourcecodepro.ttf");
((EditText) findViewById(R.id.crypto_password_edit)).setTypeface(monoTypeface);
@@ -681,7 +744,7 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
cat = cat.replace(PasswordRepository.getWorkTree().getAbsolutePath(), "");
cat = cat + "/";
((TextView) findViewById(R.id.crypto_password_category)).setText(cat);
- } else if (extra.getString("Operation").equals("GET_KEY_ID")) {
+ } else if (operation.equals("GET_KEY_ID")) {
getKeyIds(new Intent());
// setContentView(R.layout.key_id);
@@ -689,7 +752,7 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
// String keys = keyIDs.split(",").length > 1 ? keyIDs : keyIDs.split(",")[0];
// ((TextView) findViewById(R.id.crypto_key_ids)).setText(keys);
// }
- } else if (extra.getString("Operation").equals("EDIT")) {
+ } else if (operation.equals("EDIT")) {
setContentView(R.layout.decrypt_layout);
((TextView) findViewById(R.id.crypto_password_file)).setText(extra.getString("NAME"));
String cat = new File(extra.getString("FILE_PATH").replace(PasswordRepository.getWorkTree().getAbsolutePath(), ""))
@@ -697,6 +760,9 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
((TextView) findViewById(R.id.crypto_password_category)).setText(cat + "/");
edit(new Intent());
+ } else if (operation.equals("SELECTFOLDER")){
+ setContentView(R.layout.select_folder_layout);
+ selectFolder(getIntent());
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java
new file mode 100644
index 00000000..78b81928
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java
@@ -0,0 +1,162 @@
+package com.zeapo.pwdstore.utils;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.zeapo.pwdstore.R;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.TreeSet;
+
+public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder> {
+ private final Activity activity;
+ protected final ArrayList<PasswordItem> values;
+ protected final Set<Integer> selectedItems = new TreeSet<>();
+
+ public EntryRecyclerAdapter(Activity activity, ArrayList<PasswordItem> values) {
+ this.activity = activity;
+ this.values = values;
+ }
+
+ // Return the size of your dataset (invoked by the layout manager)
+ @Override
+ public int getItemCount() {
+ return values.size();
+ }
+
+ public ArrayList<PasswordItem> getValues() {
+ return this.values;
+ }
+
+ public void clear() {
+ this.values.clear();
+ this.notifyDataSetChanged();
+ }
+
+ public void addAll(ArrayList<PasswordItem> list) {
+ this.values.addAll(list);
+ this.notifyDataSetChanged();
+ }
+
+ public void add(PasswordItem item) {
+ this.values.add(item);
+ this.notifyItemInserted(getItemCount());
+ }
+
+ public void toggleSelection(int position) {
+ if (!selectedItems.remove(position)) {
+ selectedItems.add(position);
+ }
+ }
+
+ // use this after an item is removed to update the positions of items in set
+ // that followed the removed position
+ public void updateSelectedItems(int position, Set<Integer> selectedItems) {
+ Set<Integer> temp = new TreeSet<>();
+ for (int selected : selectedItems) {
+ if (selected > position) {
+ temp.add(selected - 1);
+ } else {
+ temp.add(selected);
+ }
+ }
+ selectedItems.clear();
+ selectedItems.addAll(temp);
+ }
+
+ public void remove(int position) {
+ this.values.remove(position);
+ this.notifyItemRemoved(position);
+
+ // keep selectedItems updated so we know what to notifyItemChanged
+ // (instead of just using notifyDataSetChanged)
+ updateSelectedItems(position, selectedItems);
+ }
+
+ @NonNull
+ protected View.OnLongClickListener getOnLongClickListener(ViewHolder holder, PasswordItem pass) {
+ return new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ return false;
+ }
+ };
+ }
+
+ // Replace the contents of a view (invoked by the layout manager)
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ final PasswordItem pass = getValues().get(position);
+ holder.name.setText(pass.toString());
+ if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
+ holder.typeImage.setImageResource(R.drawable.ic_folder_grey600_24dp);
+ holder.name.setText(pass.toString() + "/");
+ } else {
+ holder.typeImage.setImageResource(R.drawable.ic_action_secure);
+ holder.name.setText(pass.toString());
+ }
+
+ holder.type.setText(pass.getFullPathName());
+ if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
+// holder.card.setCardBackgroundColor(activity.getResources().getColor(R.color.blue_grey_200));
+ } else {
+// holder.card.setCardBackgroundColor(activity.getResources().getColor(R.color.blue_grey_50));
+ }
+
+ holder.view.setOnClickListener(getOnClickListener(holder, pass));
+
+ holder.view.setOnLongClickListener(getOnLongClickListener(holder, pass));
+
+ // after removal, everything is rebound for some reason; views are shuffled?
+ boolean selected = selectedItems.contains(position);
+ holder.view.setSelected(selected);
+ if (selected) {
+ holder.itemView.setBackgroundResource(R.color.deep_orange_200);
+ holder.type.setTextColor(Color.BLACK);
+ } else {
+ holder.itemView.setBackgroundResource(Color.alpha(1));
+ holder.type.setTextColor(ContextCompat.getColor(activity, R.color.grey_500));
+ }
+ }
+
+ @NonNull
+ protected abstract View.OnClickListener getOnClickListener(ViewHolder holder, PasswordItem pass);
+
+ // Provide a reference to the views for each data item
+ // Complex data items may need more than one view per item, and
+ // you provide access to all the views for a data item in a view holder
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ // each data item is just a string in this case
+ public View view;
+ public TextView name;
+ public TextView type;
+ public ImageView typeImage;
+
+ public ViewHolder(View v) {
+ super(v);
+ view = v;
+ name = (TextView) view.findViewById(R.id.label);
+ type = (TextView) view.findViewById(R.id.type);
+ typeImage = (ImageView) view.findViewById(R.id.type_image);
+ }
+ }
+
+ // Create new views (invoked by the layout manager)
+ @Override
+ public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ // create a new view
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.password_row_layout, parent, false);
+ return new ViewHolder(v);
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java
new file mode 100644
index 00000000..04b243e1
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java
@@ -0,0 +1,31 @@
+package com.zeapo.pwdstore.utils;
+
+import android.support.annotation.NonNull;
+import android.view.View;
+
+import com.zeapo.pwdstore.SelectFolderFragment;
+import com.zeapo.pwdstore.crypto.PgpHandler;
+
+import java.util.ArrayList;
+
+public class FolderRecyclerAdapter extends EntryRecyclerAdapter {
+ private final SelectFolderFragment.OnFragmentInteractionListener listener;
+
+ // Provide a suitable constructor (depends on the kind of dataset)
+ public FolderRecyclerAdapter(PgpHandler activity, SelectFolderFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
+ super(activity, values);
+ this.listener = listener;
+ }
+
+ @NonNull
+ protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ listener.onFragmentInteraction(pass);
+ notifyItemChanged(holder.getAdapterPosition());
+ }
+ };
+ }
+
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java
index 0835425a..a4446198 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java
@@ -1,17 +1,10 @@
package com.zeapo.pwdstore.utils;
-import android.graphics.Color;
-import android.os.Build;
-import android.support.v4.content.ContextCompat;
+import android.support.annotation.NonNull;
import android.support.v7.view.ActionMode;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.zeapo.pwdstore.PasswordFragment;
import com.zeapo.pwdstore.PasswordStore;
@@ -19,76 +12,46 @@ import com.zeapo.pwdstore.R;
import java.util.ArrayList;
import java.util.Iterator;
-import java.util.Set;
import java.util.TreeSet;
-public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecyclerAdapter.ViewHolder> {
+public class PasswordRecyclerAdapter extends EntryRecyclerAdapter {
private final PasswordStore activity;
- private final ArrayList<PasswordItem> values;
private final PasswordFragment.OnFragmentInteractionListener listener;
- private final Set<Integer> selectedItems;
private ActionMode mActionMode;
private Boolean canEdit;
- // Provide a reference to the views for each data item
- // Complex data items may need more than one view per item, and
- // you provide access to all the views for a data item in a view holder
- public static class ViewHolder extends RecyclerView.ViewHolder {
- // each data item is just a string in this case
- public View view;
- public TextView name;
- public TextView type;
- public ImageView typeImage;
-
- public ViewHolder(View v) {
- super(v);
- view = v;
- name = (TextView) view.findViewById(R.id.label);
- type = (TextView) view.findViewById(R.id.type);
- typeImage = (ImageView) view.findViewById(R.id.type_image);
- }
- }
-
// Provide a suitable constructor (depends on the kind of dataset)
public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
- this.values = values;
+ super(activity, values);
this.activity = activity;
this.listener = listener;
- selectedItems = new TreeSet<>();
}
- // Create new views (invoked by the layout manager)
@Override
- public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
- int viewType) {
- // create a new view
- View v = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.password_row_layout, parent, false);
- return new ViewHolder(v);
+ @NonNull
+ protected View.OnLongClickListener getOnLongClickListener(final ViewHolder holder, final PasswordItem pass) {
+ return new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (mActionMode != null) {
+ return false;
+ }
+ toggleSelection(holder.getAdapterPosition());
+ canEdit = pass.getType() == PasswordItem.TYPE_PASSWORD;
+ // Start the CAB using the ActionMode.Callback
+ mActionMode = activity.startSupportActionMode(mActionModeCallback);
+ mActionMode.setTitle("" + selectedItems.size());
+ mActionMode.invalidate();
+ notifyItemChanged(holder.getAdapterPosition());
+ return true;
+ }
+ };
}
- // Replace the contents of a view (invoked by the layout manager)
@Override
- public void onBindViewHolder(final ViewHolder holder, int position) {
- final PasswordItem pass = values.get(position);
- holder.name.setText(pass.toString());
- if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
- holder.typeImage.setImageResource(R.drawable.ic_folder_grey600_24dp);
- holder.name.setText(pass.toString() + "/");
- } else {
- holder.typeImage.setImageResource(R.drawable.ic_action_secure);
- holder.name.setText(pass.toString());
- }
- int sdk = Build.VERSION.SDK_INT;
-
- holder.type.setText(pass.getFullPathName());
- if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
-// holder.card.setCardBackgroundColor(activity.getResources().getColor(R.color.blue_grey_200));
- } else {
-// holder.card.setCardBackgroundColor(activity.getResources().getColor(R.color.blue_grey_50));
- }
-
- holder.view.setOnClickListener(new View.OnClickListener() {
+ @NonNull
+ protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
+ return new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mActionMode != null) {
@@ -97,7 +60,7 @@ public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecycl
if (selectedItems.isEmpty()) {
mActionMode.finish();
} else if (selectedItems.size() == 1 && !canEdit) {
- if (values.get(selectedItems.iterator().next()).getType() == PasswordItem.TYPE_PASSWORD) {
+ if (getValues().get(selectedItems.iterator().next()).getType() == PasswordItem.TYPE_PASSWORD) {
canEdit = true;
mActionMode.invalidate();
}
@@ -110,35 +73,7 @@ public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecycl
}
notifyItemChanged(holder.getAdapterPosition());
}
- });
-
- holder.view.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mActionMode != null) {
- return false;
- }
- toggleSelection(holder.getAdapterPosition());
- canEdit = pass.getType() == PasswordItem.TYPE_PASSWORD;
- // Start the CAB using the ActionMode.Callback
- mActionMode = activity.startSupportActionMode(mActionModeCallback);
- mActionMode.setTitle("" + selectedItems.size());
- mActionMode.invalidate();
- notifyItemChanged(holder.getAdapterPosition());
- return true;
- }
- });
-
- // after removal, everything is rebound for some reason; views are shuffled?
- boolean selected = selectedItems.contains(position);
- holder.view.setSelected(selected);
- if (selected) {
- holder.itemView.setBackgroundResource(R.color.deep_orange_200);
- holder.type.setTextColor(Color.BLACK);
- } else {
- holder.itemView.setBackgroundResource(Color.alpha(1));
- holder.type.setTextColor(ContextCompat.getColor(activity, R.color.grey_500));
- }
+ };
}
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -172,9 +107,15 @@ public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecycl
mode.finish(); // Action picked, so close the CAB
return true;
case R.id.menu_edit_password:
- activity.editPassword(values.get(selectedItems.iterator().next()));
+ activity.editPassword(getValues().get(selectedItems.iterator().next()));
mode.finish();
return true;
+ case R.id.menu_move_password:
+ ArrayList<PasswordItem> selectedPasswords = new ArrayList<>();
+ for (Integer id : selectedItems) {
+ selectedPasswords.add(getValues().get(id));
+ }
+ activity.movePasswords(selectedPasswords);
default:
return false;
}
@@ -183,67 +124,12 @@ public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecycl
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
- for (Iterator it = selectedItems.iterator(); it.hasNext();) {
+ for (Iterator<Integer> it = selectedItems.iterator(); it.hasNext(); ) {
// need the setSelected line in onBind
- notifyItemChanged((Integer) it.next());
+ notifyItemChanged(it.next());
it.remove();
}
mActionMode = null;
}
};
-
- // Return the size of your dataset (invoked by the layout manager)
- @Override
- public int getItemCount() {
- return values.size();
- }
-
- public ArrayList<PasswordItem> getValues() {
- return this.values;
- }
-
- public void clear() {
- this.values.clear();
- this.notifyDataSetChanged();
- }
-
- public void addAll(ArrayList<PasswordItem> list) {
- this.values.addAll(list);
- this.notifyDataSetChanged();
- }
-
- public void add(PasswordItem item) {
- this.values.add(item);
- this.notifyItemInserted(values.size());
- }
-
- public void remove(int position) {
- this.values.remove(position);
- this.notifyItemRemoved(position);
-
- // keep selectedItems updated so we know what to notifyItemChanged
- // (instead of just using notifyDataSetChanged)
- updateSelectedItems(position, selectedItems);
- }
-
- public void toggleSelection(int position) {
- if (!selectedItems.remove(position)) {
- selectedItems.add(position);
- }
- }
-
- // use this after an item is removed to update the positions of items in set
- // that followed the removed position
- public void updateSelectedItems(int position, Set<Integer> selectedItems) {
- Set<Integer> temp = new TreeSet<>();
- for (int selected : selectedItems) {
- if (selected > position) {
- temp.add(selected - 1);
- } else {
- temp.add(selected);
- }
- }
- selectedItems.clear();
- selectedItems.addAll(temp);
- }
}
diff --git a/app/src/main/res/drawable-hdpi/ic_folder_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_folder_white_24dp.png
new file mode 100644
index 00000000..02ea533a
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_folder_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_folder_white_24dp.png
new file mode 100644
index 00000000..831d723b
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png
new file mode 100644
index 00000000..71a5a137
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png
new file mode 100644
index 00000000..b93d5a1e
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png
new file mode 100644
index 00000000..a1afbe9d
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/app/src/main/res/layout/select_folder_layout.xml b/app/src/main/res/layout/select_folder_layout.xml
new file mode 100644
index 00000000..142093ec
--- /dev/null
+++ b/app/src/main/res/layout/select_folder_layout.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/pgp_handler_linearlayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"/>
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/menu/context_pass.xml b/app/src/main/res/menu/context_pass.xml
index ac0d88ab..bcdb383c 100644
--- a/app/src/main/res/menu/context_pass.xml
+++ b/app/src/main/res/menu/context_pass.xml
@@ -3,10 +3,15 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".pwdstore">
+ <item android:id="@+id/menu_move_password"
+ android:icon="@drawable/ic_folder_white_24dp"
+ app:showAsAction="ifRoom"
+ android:title="Move"/>
+
<item android:id="@+id/menu_edit_password"
- android:icon="@drawable/ic_edit_white_24dp"
- app:showAsAction="ifRoom"
- android:title="Edit"/>
+ android:icon="@drawable/ic_edit_white_24dp"
+ app:showAsAction="ifRoom"
+ android:title="Edit"/>
<item android:id="@+id/menu_delete_password"
android:icon="@drawable/ic_delete_white_24dp"
diff --git a/app/src/main/res/menu/pgp_handler_select_folder.xml b/app/src/main/res/menu/pgp_handler_select_folder.xml
new file mode 100644
index 00000000..9bfba01b
--- /dev/null
+++ b/app/src/main/res/menu/pgp_handler_select_folder.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:pwstore="http://schemas.android.com/apk/res-auto"
+ tools:context="com.zeapo.pwdstore.crypto.PgpHandler" >
+ <item android:title="Select"
+ android:icon="@drawable/ic_done_white_24dp"
+ pwstore:showAsAction="ifRoom"
+ android:id="@+id/crypto_select"
+ />
+</menu> \ No newline at end of file