Added the ability to move passwords around. (#210)
* Added the ability to move passwords around. * Generified the PasswordRecyclerAdapter and the FolderRecyclerAdapter into EntryRecyclerAdapter
This commit is contained in:
parent
86083f03f5
commit
a6da17417e
14 changed files with 612 additions and 170 deletions
|
@ -2,8 +2,6 @@ package com.zeapo.pwdstore;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.app.FragmentTransaction;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -13,11 +11,14 @@ import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.ActivityCompat;
|
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.content.ContextCompat;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
import android.support.v4.view.MenuItemCompat;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.SearchView;
|
import android.support.v7.widget.SearchView;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
@ -38,6 +39,7 @@ import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -463,6 +465,17 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
.show();
|
.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
|
* 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);
|
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
227
app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java
Normal file
227
app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,8 @@ import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -27,7 +29,9 @@ import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
import com.zeapo.pwdstore.BuildConfig;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
import com.zeapo.pwdstore.SelectFolderFragment;
|
||||||
import com.zeapo.pwdstore.UserPreference;
|
import com.zeapo.pwdstore.UserPreference;
|
||||||
import com.zeapo.pwdstore.pwgenDialogFragment;
|
import com.zeapo.pwdstore.pwgenDialogFragment;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
@ -57,6 +61,9 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
|
||||||
private Activity activity;
|
private Activity activity;
|
||||||
ClipboardManager clipboard;
|
ClipboardManager clipboard;
|
||||||
|
|
||||||
|
SelectFolderFragment passwordList;
|
||||||
|
private Intent selectFolderData;
|
||||||
|
|
||||||
private boolean registered;
|
private boolean registered;
|
||||||
|
|
||||||
public static final int REQUEST_CODE_SIGN = 9910;
|
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 = 9914;
|
||||||
public static final int REQUEST_CODE_GET_KEY_IDS = 9915;
|
public static final int REQUEST_CODE_GET_KEY_IDS = 9915;
|
||||||
public static final int REQUEST_CODE_EDIT = 9916;
|
public static final int REQUEST_CODE_EDIT = 9916;
|
||||||
|
public static final int REQUEST_CODE_SELECT_FOLDER = 9917;
|
||||||
|
|
||||||
public final class Constants {
|
public final class Constants {
|
||||||
public static final String TAG = "Keychain";
|
public static final String TAG = "Keychain";
|
||||||
|
@ -125,10 +133,15 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
if (getIntent().getStringExtra("Operation").equals("ENCRYPT")) {
|
switch (getIntent().getStringExtra("Operation")){
|
||||||
getMenuInflater().inflate(R.menu.pgp_handler_new_password, menu);
|
case "ENCRYPT":
|
||||||
} else {
|
getMenuInflater().inflate(R.menu.pgp_handler_new_password, menu);
|
||||||
getMenuInflater().inflate(R.menu.pgp_handler, menu);
|
break;
|
||||||
|
case "SELECTFOLDER":
|
||||||
|
getMenuInflater().inflate(R.menu.pgp_handler_select_folder, menu);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
getMenuInflater().inflate(R.menu.pgp_handler, menu);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -160,10 +173,22 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.crypto_select:
|
||||||
|
selectFolder();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
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() {
|
public void editPassword() {
|
||||||
// if in encrypt or in decrypt and password is invisible
|
// if in encrypt or in decrypt and password is invisible
|
||||||
// (because !showPassword, so this will instantly close), do nothing
|
// (because !showPassword, so this will instantly close), do nothing
|
||||||
|
@ -259,6 +284,7 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
|
||||||
DialogFragment df = new pwgenDialogFragment();
|
DialogFragment df = new pwgenDialogFragment();
|
||||||
df.show(getFragmentManager(), "generator");
|
df.show(getFragmentManager(), "generator");
|
||||||
default:
|
default:
|
||||||
|
Log.wtf(Constants.TAG,"This should not happen.... PgpHandler.java#handleClick(View) default reached.");
|
||||||
// should not happen
|
// 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 {
|
public class PgpCallback implements OpenPgpApi.IOpenPgpCallback {
|
||||||
boolean returnToCiphertextField;
|
boolean returnToCiphertextField;
|
||||||
ByteArrayOutputStream os;
|
ByteArrayOutputStream os;
|
||||||
|
@ -664,7 +723,11 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
|
||||||
Log.i("PGP", "ISBOUND!!");
|
Log.i("PGP", "ISBOUND!!");
|
||||||
|
|
||||||
Bundle extra = getIntent().getExtras();
|
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);
|
setContentView(R.layout.decrypt_layout);
|
||||||
((TextView) findViewById(R.id.crypto_password_file)).setText(extra.getString("NAME"));
|
((TextView) findViewById(R.id.crypto_password_file)).setText(extra.getString("NAME"));
|
||||||
String cat = new File(extra.getString("FILE_PATH").replace(PasswordRepository.getWorkTree().getAbsolutePath(), ""))
|
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 + "/");
|
((TextView) findViewById(R.id.crypto_password_category)).setText(cat + "/");
|
||||||
decryptAndVerify(new Intent());
|
decryptAndVerify(new Intent());
|
||||||
} else if (extra.getString("Operation").equals("ENCRYPT")) {
|
} else if (operation.equals("ENCRYPT")) {
|
||||||
setContentView(R.layout.encrypt_layout);
|
setContentView(R.layout.encrypt_layout);
|
||||||
Typeface monoTypeface = Typeface.createFromAsset(getAssets(), "fonts/sourcecodepro.ttf");
|
Typeface monoTypeface = Typeface.createFromAsset(getAssets(), "fonts/sourcecodepro.ttf");
|
||||||
((EditText) findViewById(R.id.crypto_password_edit)).setTypeface(monoTypeface);
|
((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.replace(PasswordRepository.getWorkTree().getAbsolutePath(), "");
|
||||||
cat = cat + "/";
|
cat = cat + "/";
|
||||||
((TextView) findViewById(R.id.crypto_password_category)).setText(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());
|
getKeyIds(new Intent());
|
||||||
|
|
||||||
// setContentView(R.layout.key_id);
|
// 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];
|
// String keys = keyIDs.split(",").length > 1 ? keyIDs : keyIDs.split(",")[0];
|
||||||
// ((TextView) findViewById(R.id.crypto_key_ids)).setText(keys);
|
// ((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);
|
setContentView(R.layout.decrypt_layout);
|
||||||
((TextView) findViewById(R.id.crypto_password_file)).setText(extra.getString("NAME"));
|
((TextView) findViewById(R.id.crypto_password_file)).setText(extra.getString("NAME"));
|
||||||
String cat = new File(extra.getString("FILE_PATH").replace(PasswordRepository.getWorkTree().getAbsolutePath(), ""))
|
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 + "/");
|
((TextView) findViewById(R.id.crypto_password_category)).setText(cat + "/");
|
||||||
edit(new Intent());
|
edit(new Intent());
|
||||||
|
} else if (operation.equals("SELECTFOLDER")){
|
||||||
|
setContentView(R.layout.select_folder_layout);
|
||||||
|
selectFolder(getIntent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,17 +1,10 @@
|
||||||
package com.zeapo.pwdstore.utils;
|
package com.zeapo.pwdstore.utils;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.support.annotation.NonNull;
|
||||||
import android.os.Build;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v7.view.ActionMode;
|
import android.support.v7.view.ActionMode;
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
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.PasswordFragment;
|
||||||
import com.zeapo.pwdstore.PasswordStore;
|
import com.zeapo.pwdstore.PasswordStore;
|
||||||
|
@ -19,100 +12,25 @@ import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecyclerAdapter.ViewHolder> {
|
public class PasswordRecyclerAdapter extends EntryRecyclerAdapter {
|
||||||
private final PasswordStore activity;
|
private final PasswordStore activity;
|
||||||
private final ArrayList<PasswordItem> values;
|
|
||||||
private final PasswordFragment.OnFragmentInteractionListener listener;
|
private final PasswordFragment.OnFragmentInteractionListener listener;
|
||||||
private final Set<Integer> selectedItems;
|
|
||||||
private ActionMode mActionMode;
|
private ActionMode mActionMode;
|
||||||
private Boolean canEdit;
|
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)
|
// Provide a suitable constructor (depends on the kind of dataset)
|
||||||
public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
|
public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
|
||||||
this.values = values;
|
super(activity, values);
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
selectedItems = new TreeSet<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new views (invoked by the layout manager)
|
|
||||||
@Override
|
@Override
|
||||||
public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
|
@NonNull
|
||||||
int viewType) {
|
protected View.OnLongClickListener getOnLongClickListener(final ViewHolder holder, final PasswordItem pass) {
|
||||||
// create a new view
|
return new View.OnLongClickListener() {
|
||||||
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());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
holder.view.setOnLongClickListener(new View.OnLongClickListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View v) {
|
public boolean onLongClick(View v) {
|
||||||
if (mActionMode != null) {
|
if (mActionMode != null) {
|
||||||
|
@ -127,18 +45,35 @@ public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecycl
|
||||||
notifyItemChanged(holder.getAdapterPosition());
|
notifyItemChanged(holder.getAdapterPosition());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// after removal, everything is rebound for some reason; views are shuffled?
|
@Override
|
||||||
boolean selected = selectedItems.contains(position);
|
@NonNull
|
||||||
holder.view.setSelected(selected);
|
protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
|
||||||
if (selected) {
|
return new View.OnClickListener() {
|
||||||
holder.itemView.setBackgroundResource(R.color.deep_orange_200);
|
@Override
|
||||||
holder.type.setTextColor(Color.BLACK);
|
public void onClick(View v) {
|
||||||
} else {
|
if (mActionMode != null) {
|
||||||
holder.itemView.setBackgroundResource(Color.alpha(1));
|
toggleSelection(holder.getAdapterPosition());
|
||||||
holder.type.setTextColor(ContextCompat.getColor(activity, R.color.grey_500));
|
mActionMode.setTitle("" + selectedItems.size());
|
||||||
}
|
if (selectedItems.isEmpty()) {
|
||||||
|
mActionMode.finish();
|
||||||
|
} else if (selectedItems.size() == 1 && !canEdit) {
|
||||||
|
if (getValues().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());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
|
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
|
mode.finish(); // Action picked, so close the CAB
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_edit_password:
|
case R.id.menu_edit_password:
|
||||||
activity.editPassword(values.get(selectedItems.iterator().next()));
|
activity.editPassword(getValues().get(selectedItems.iterator().next()));
|
||||||
mode.finish();
|
mode.finish();
|
||||||
return true;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -183,67 +124,12 @@ public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecycl
|
||||||
// Called when the user exits the action mode
|
// Called when the user exits the action mode
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyActionMode(ActionMode mode) {
|
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
|
// need the setSelected line in onBind
|
||||||
notifyItemChanged((Integer) it.next());
|
notifyItemChanged(it.next());
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
mActionMode = null;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
BIN
app/src/main/res/drawable-hdpi/ic_folder_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_folder_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 135 B |
BIN
app/src/main/res/drawable-mdpi/ic_folder_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_folder_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 B |
BIN
app/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 181 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 245 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 325 B |
11
app/src/main/res/layout/select_folder_layout.xml
Normal file
11
app/src/main/res/layout/select_folder_layout.xml
Normal file
|
@ -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>
|
|
@ -3,10 +3,15 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".pwdstore">
|
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"
|
<item android:id="@+id/menu_edit_password"
|
||||||
android:icon="@drawable/ic_edit_white_24dp"
|
android:icon="@drawable/ic_edit_white_24dp"
|
||||||
app:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:title="Edit"/>
|
android:title="Edit"/>
|
||||||
|
|
||||||
<item android:id="@+id/menu_delete_password"
|
<item android:id="@+id/menu_delete_password"
|
||||||
android:icon="@drawable/ic_delete_white_24dp"
|
android:icon="@drawable/ic_delete_white_24dp"
|
||||||
|
|
11
app/src/main/res/menu/pgp_handler_select_folder.xml
Normal file
11
app/src/main/res/menu/pgp_handler_select_folder.xml
Normal file
|
@ -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>
|
Loading…
Reference in a new issue