From 458f5ea1d9c85be78edf8d7d2d8077b20b12f168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20den=20Berg?= Date: Thu, 18 Aug 2016 13:58:39 +0200 Subject: [PATCH] Added the ability to move passwords around. --- .../com/zeapo/pwdstore/PasswordStore.java | 47 +++- .../zeapo/pwdstore/SelectFolderFragment.java | 227 ++++++++++++++++++ .../com/zeapo/pwdstore/crypto/PgpHandler.java | 82 ++++++- .../pwdstore/utils/FolderRecyclerAdapter.java | 178 ++++++++++++++ .../utils/PasswordRecyclerAdapter.java | 6 + .../drawable-hdpi/ic_folder_white_24dp.png | Bin 0 -> 135 bytes .../drawable-mdpi/ic_folder_white_24dp.png | Bin 0 -> 122 bytes .../drawable-xhdpi/ic_folder_white_24dp.png | Bin 0 -> 181 bytes .../drawable-xxhdpi/ic_folder_white_24dp.png | Bin 0 -> 245 bytes .../drawable-xxxhdpi/ic_folder_white_24dp.png | Bin 0 -> 325 bytes .../main/res/layout/select_folder_layout.xml | 11 + app/src/main/res/menu/context_pass.xml | 11 +- .../res/menu/pgp_handler_select_folder.xml | 11 + 13 files changed, 560 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java create mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java create mode 100644 app/src/main/res/drawable-hdpi/ic_folder_white_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_folder_white_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png create mode 100644 app/src/main/res/layout/select_folder_layout.xml create mode 100644 app/src/main/res/menu/pgp_handler_select_folder.xml diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java index e33addc36..996942d76 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 values) { + Intent intent = new Intent(this, PgpHandler.class); + ArrayList 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 000000000..c9bc35967 --- /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. + *

+ * Large screen devices (such as tablets) are supported by replacing the ListView + * with a GridView. + *

+ */ +public class SelectFolderFragment extends Fragment{ + + public interface OnFragmentInteractionListener { + public void onFragmentInteraction(PasswordItem item); + } + + // store the pass files list in a stack + private Stack> passListStack; + private Stack pathStack; + private Stack 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>(); + scrollPosition = new Stack(); + pathStack = new Stack(); + 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 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 399564a17..007f30f06 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; } @@ -157,10 +170,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 @@ -233,6 +258,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 } @@ -381,6 +407,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; @@ -638,7 +697,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(), "")) @@ -646,7 +709,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); @@ -655,7 +718,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); @@ -663,7 +726,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(), "")) @@ -671,6 +734,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/FolderRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java new file mode 100644 index 000000000..d4e7b84a8 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java @@ -0,0 +1,178 @@ +package com.zeapo.pwdstore.utils; + +import android.graphics.Color; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import android.support.v7.view.ActionMode; +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 com.zeapo.pwdstore.SelectFolderFragment; +import com.zeapo.pwdstore.crypto.PgpHandler; + +import java.util.ArrayList; +import java.util.Set; +import java.util.TreeSet; + +public class FolderRecyclerAdapter extends RecyclerView.Adapter { + private final PgpHandler activity; + private final ArrayList values; + private final SelectFolderFragment.OnFragmentInteractionListener listener; + private final Set 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 FolderRecyclerAdapter(PgpHandler activity, SelectFolderFragment.OnFragmentInteractionListener listener, ArrayList values) { + this.values = values; + this.activity = activity; + this.listener = listener; + selectedItems = new TreeSet<>(); + } + + // Create new views (invoked by the layout manager) + @Override + public FolderRecyclerAdapter.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); + } + + // 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() { + @Override + public void onClick(View v) { + if (mActionMode != null) { + toggleSelection(holder.getAdapterPosition()); + mActionMode.setTitle("" + selectedItems.size()); + if (selectedItems.isEmpty()) { + mActionMode.finish(); + } else if (selectedItems.size() == 1 && !canEdit) { + if (values.get(selectedItems.iterator().next()).getType() == PasswordItem.TYPE_PASSWORD) { + canEdit = true; + mActionMode.invalidate(); + } + } else if (selectedItems.size() >= 1 && canEdit) { + canEdit = false; + mActionMode.invalidate(); + } + } else { + listener.onFragmentInteraction(pass); + } + notifyItemChanged(holder.getAdapterPosition()); + } + }); + + // 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)); + } + } + + // Return the size of your dataset (invoked by the layout manager) + @Override + public int getItemCount() { + return values.size(); + } + + public ArrayList getValues() { + return this.values; + } + + public void clear() { + this.values.clear(); + this.notifyDataSetChanged(); + } + + public void addAll(ArrayList 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 selectedItems) { + Set 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/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java index 0835425ae..0f186b215 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java +++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java @@ -175,6 +175,12 @@ public class PasswordRecyclerAdapter extends RecyclerView.Adapter|k0wldT1B8K;pQnpsh{y4_S5I>?DDbdcG|@L2`{{(qt) zvm)<`Q=Hj9{dp?B-k)#6`~UxbmC(cg19_d}4r?#Evfm-&!|_RRhEx83m({x|$*?rj VKUyK^_-~+L44$rjF6*2Ung9auEG+;4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..71a5a137c463dfd97dfad592c86b7eac773664c5 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0D8c!F;kch)?uX*!1I0(1~u3k7j z(@|)CtD?lCSzab=p|%h2vhJCDWr_9aDM8CMGG^>pz2)3<9-DOK6&2^5E;t7@iOv_Z za^ku1A-hLl9mAHDjh3bd#2aH97>pYj7@1i1Fnhmp-(Sr7N?SnU?z8GoH}30QFuL9E fEIT*+)p6OLF7v?Cmm9c&?qKkA^>bP0l+XkK)+RzI literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b93d5a1e4a48fb9cddfa530aacfc84e5ea4ad9c9 GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw_ISEDhGg7(d&7~dDL|z4q4_1- z7ebF0dKZ*BYIUwt;W*;>)jV%O+J|}OmjAiz^vX--1bb;NmdKI;Vst0GcUV@&Et; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a1afbe9daf5a5e3945f33915f8817be26b539148 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z{un2;uunK>+P+xzK0zI93I~B z(C26sy3FL&q}$ba<$&VE(+|=Gi?KQ4VXb{{b%I@HcvNQkRwH8&_$~OogvaO4 zw%oRd*KGoKY|H)oF+L(JmZ@K7yH~M)rTJtXSd}W-{epFln3^$y#Qw2YO4`4lFD^P$~Ceo|M5hK8FSd#^MHkMkW@H6#Nv%Xop z`uOoFkL` + + + + \ 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 ac0d88abd..bcdb383cd 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"> + + + android:icon="@drawable/ic_edit_white_24dp" + app:showAsAction="ifRoom" + android:title="Edit"/> +

+ + \ No newline at end of file