aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/com/zeapo/pwdstore/utils
diff options
context:
space:
mode:
authorHarsh Shandilya <msfjarvis@gmail.com>2019-10-01 22:14:28 +0530
committerGitHub <noreply@github.com>2019-10-01 22:14:28 +0530
commit073346c157ec34b750d175c8543f346e6eae94de (patch)
treea55d24ef43563f3a2d03d7938a1e96c1f5cd1caf /app/src/main/java/com/zeapo/pwdstore/utils
parentc9dc4034f12b62f4470f92fb13ecd72871f4a5b7 (diff)
downloadHardenedPass-073346c157ec34b750d175c8543f346e6eae94de.tar.gz
HardenedPass-073346c157ec34b750d175c8543f346e6eae94de.zip
Redesign UI and introduce dark theme (#519)
Caveats: - The openpgp preference had to be removed because the open-intents developers are too lazy to update their libraries. Over the coming weeks I will be reimplementing a local solution for this instead. - The autofill dialog is broken but I since it is being worked on in #410 already I'm not going to bother fixing it.
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/utils')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java155
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt118
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java29
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt20
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java110
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt81
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java133
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt116
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java284
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt282
11 files changed, 626 insertions, 711 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java
deleted file mode 100644
index eba6a02e..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.graphics.Color;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.recyclerview.widget.RecyclerView;
-
-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> {
- final Set<Integer> selectedItems = new TreeSet<>();
- private final Activity activity;
- private final ArrayList<PasswordItem> values;
-
- 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());
- }
-
- 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
- View.OnLongClickListener getOnLongClickListener(ViewHolder holder, PasswordItem pass) {
- return v -> false;
- }
-
- // Replace the contents of a view (invoked by the layout manager)
- @SuppressLint("SetTextI18n")
- @Override
- public void onBindViewHolder(@NonNull 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);
- } else {
- holder.typeImage.setImageResource(R.drawable.ic_action_secure);
- holder.name.setText(pass.toString());
- }
-
- holder.type.setText(pass.getFullPathToParent().replaceAll("(^/)|(/$)", ""));
-
- 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.setBackgroundColor(Color.alpha(1));
- holder.type.setTextColor(ContextCompat.getColor(activity, R.color.grey_500));
- }
- }
-
- @NonNull
- protected abstract View.OnClickListener getOnClickListener(ViewHolder holder, PasswordItem pass);
-
- // Create new views (invoked by the layout manager)
- @Override
- @NonNull
- public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull 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);
- }
-
- // 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
- static class ViewHolder extends RecyclerView.ViewHolder {
- // each data item is just a string in this case
- public final View view;
- public final TextView name;
- final TextView type;
- final ImageView typeImage;
-
- ViewHolder(View v) {
- super(v);
- view = v;
- name = view.findViewById(R.id.label);
- type = view.findViewById(R.id.type);
- typeImage = view.findViewById(R.id.type_image);
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt
new file mode 100644
index 00000000..f52756f4
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt
@@ -0,0 +1,118 @@
+package com.zeapo.pwdstore.utils
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.recyclerview.widget.RecyclerView
+
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.widget.MultiselectableLinearLayout
+
+import java.util.ArrayList
+import java.util.TreeSet
+
+abstract class EntryRecyclerAdapter internal constructor(val values: ArrayList<PasswordItem>) : RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder>() {
+ internal val selectedItems: MutableSet<Int> = TreeSet()
+
+ // Return the size of your dataset (invoked by the layout manager)
+ override fun getItemCount(): Int {
+ return values.size
+ }
+
+ fun clear() {
+ this.values.clear()
+ this.notifyDataSetChanged()
+ }
+
+ fun addAll(list: ArrayList<PasswordItem>) {
+ this.values.addAll(list)
+ this.notifyDataSetChanged()
+ }
+
+ fun add(item: PasswordItem) {
+ this.values.add(item)
+ this.notifyItemInserted(itemCount)
+ }
+
+ internal fun toggleSelection(position: Int) {
+ 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
+ fun updateSelectedItems(position: Int, selectedItems: MutableSet<Int>) {
+ val temp = TreeSet<Int>()
+ for (selected in selectedItems) {
+ if (selected > position) {
+ temp.add(selected - 1)
+ } else {
+ temp.add(selected)
+ }
+ }
+ selectedItems.clear()
+ selectedItems.addAll(temp)
+ }
+
+ fun remove(position: Int) {
+ this.values.removeAt(position)
+ this.notifyItemRemoved(position)
+
+ // keep selectedItems updated so we know what to notifyItemChanged
+ // (instead of just using notifyDataSetChanged)
+ updateSelectedItems(position, selectedItems)
+ }
+
+ internal open fun getOnLongClickListener(holder: ViewHolder, pass: PasswordItem): View.OnLongClickListener {
+ return View.OnLongClickListener { false }
+ }
+
+ // Replace the contents of a view (invoked by the layout manager)
+ @SuppressLint("SetTextI18n")
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val pass = values[position]
+ holder.name.text = pass.toString()
+ if (pass.type == PasswordItem.TYPE_CATEGORY) {
+ holder.typeImage.setImageResource(R.drawable.ic_folder_tinted_24dp)
+ } else {
+ holder.typeImage.setImageResource(R.drawable.ic_action_secure)
+ holder.name.text = pass.toString()
+ }
+
+ holder.type.text = pass.fullPathToParent.replace("(^/)|(/$)".toRegex(), "")
+
+ holder.view.setOnClickListener(getOnClickListener(holder, pass))
+
+ holder.view.setOnLongClickListener(getOnLongClickListener(holder, pass))
+
+ // after removal, everything is rebound for some reason; views are shuffled?
+ val selected = selectedItems.contains(position)
+ holder.view.isSelected = selected
+ (holder.itemView as MultiselectableLinearLayout).setMultiSelected(selected)
+ }
+
+ protected abstract fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener
+
+ // Create new views (invoked by the layout manager)
+ override fun onCreateViewHolder(parent: ViewGroup,
+ viewType: Int): ViewHolder {
+ // create a new view
+ val v = LayoutInflater.from(parent.context)
+ .inflate(R.layout.password_row_layout, parent, false)
+ return ViewHolder(v)
+ }
+
+ // 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
+ class ViewHolder(// each data item is just a string in this case
+ val view: View) : RecyclerView.ViewHolder(view) {
+ val name: AppCompatTextView = view.findViewById(R.id.label)
+ val type: AppCompatTextView = view.findViewById(R.id.type)
+ val typeImage: AppCompatImageView = view.findViewById(R.id.type_image)
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
index a2be3ddc..7c2a28c3 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
@@ -1,5 +1,14 @@
package com.zeapo.pwdstore.utils
+import android.content.Context
+import android.util.TypedValue
+
fun String.splitLines(): Array<String> {
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
}
+
+fun Context.resolveAttribute(attr: Int): Int {
+ val typedValue = TypedValue()
+ this.theme.resolveAttribute(attr, typedValue, true)
+ return typedValue.data
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java
deleted file mode 100644
index 1754e0f9..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import com.zeapo.pwdstore.SelectFolderActivity;
-import com.zeapo.pwdstore.SelectFolderFragment;
-
-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(SelectFolderActivity 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 v -> {
- listener.onFragmentInteraction(pass);
- notifyItemChanged(holder.getAdapterPosition());
- };
- }
-
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt
new file mode 100644
index 00000000..4f0ca4c8
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt
@@ -0,0 +1,20 @@
+package com.zeapo.pwdstore.utils
+
+import android.view.View
+
+import com.zeapo.pwdstore.SelectFolderFragment
+
+import java.util.ArrayList
+
+class FolderRecyclerAdapter(private val listener: SelectFolderFragment.OnFragmentInteractionListener,
+ values: ArrayList<PasswordItem>
+) : EntryRecyclerAdapter(values) {
+
+ override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener {
+ return View.OnClickListener {
+ listener.onFragmentInteraction(pass)
+ notifyItemChanged(holder.adapterPosition)
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java
deleted file mode 100644
index 1a498721..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import androidx.annotation.NonNull;
-
-import com.zeapo.pwdstore.crypto.PgpActivity;
-
-import java.io.File;
-
-public class PasswordItem implements Comparable {
-
- public final static char TYPE_CATEGORY = 'c';
- public final static char TYPE_PASSWORD = 'p';
-
- private final char type;
- private final String name;
- private final PasswordItem parent;
- private final File file;
- private final String fullPathToParent;
- private final String longName;
-
- /**
- * Create a password item
- * <p>
- * Make it protected so that we use a builder
- */
- private PasswordItem(String name, PasswordItem parent, char type, File file, File rootDir) {
- this.name = name;
- this.parent = parent;
- this.type = type;
- this.file = file;
- fullPathToParent = file.getAbsolutePath()
- .replace(rootDir.getAbsolutePath(), "")
- .replace(file.getName(), "");
- longName = PgpActivity.getLongName(fullPathToParent, rootDir.getAbsolutePath(), toString());
- }
-
- /**
- * Create a new Category item
- */
- public static PasswordItem newCategory(String name, File file, PasswordItem parent, File rootDir) {
- return new PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir);
- }
-
- /**
- * Create a new parentless category item
- */
- public static PasswordItem newCategory(String name, File file, File rootDir) {
- return new PasswordItem(name, null, TYPE_CATEGORY, file, rootDir);
- }
-
- /**
- * Create a new password item
- */
- public static PasswordItem newPassword(String name, File file, PasswordItem parent, File rootDir) {
- return new PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir);
- }
-
- /**
- * Create a new parentless password item
- */
- public static PasswordItem newPassword(String name, File file, File rootDir) {
- return new PasswordItem(name, null, TYPE_PASSWORD, file, rootDir);
- }
-
- public char getType() {
- return this.type;
- }
-
- String getName() {
- return this.name;
- }
-
- public PasswordItem getParent() {
- return this.parent;
- }
-
- public File getFile() {
- return this.file;
- }
-
- public String getFullPathToParent() {
- return this.fullPathToParent;
- }
-
- public String getLongName() {
- return longName;
- }
-
- @NonNull
- @Override
- public String toString() {
- return this.getName().replace(".gpg", "");
- }
-
- @Override
- public boolean equals(Object o) {
- // Makes it possible to have a category and a password with the same name
- return o != null
- && o.getClass() == PasswordItem.class
- && ((PasswordItem) o).getFile().equals(this.getFile());
- }
-
- @Override
- public int compareTo(@NonNull Object o) {
- PasswordItem other = (PasswordItem) o;
- // Appending the type will make the sort type dependent
- return (this.getType() + this.getName())
- .compareToIgnoreCase(other.getType() + other.getName());
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
new file mode 100644
index 00000000..ddecd2da
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
@@ -0,0 +1,81 @@
+package com.zeapo.pwdstore.utils
+
+import com.zeapo.pwdstore.crypto.PgpActivity
+import java.io.File
+
+data class PasswordItem(
+ val name: String,
+ val parent: PasswordItem? = null,
+ val type: Char,
+ val file: File,
+ val rootDir: File
+) : Comparable<PasswordItem> {
+ val fullPathToParent = file.absolutePath
+ .replace(rootDir.absolutePath, "")
+ .replace(file.name, "")
+
+ val longName = PgpActivity.getLongName(
+ fullPathToParent,
+ rootDir.absolutePath,
+ toString())
+
+ override fun equals(other: Any?): Boolean {
+ return (other is PasswordItem) && (other.file == file)
+ }
+
+ override fun compareTo(other: PasswordItem): Int {
+ return (type + name).compareTo(other.type + other.name, ignoreCase = true)
+ }
+
+ override fun toString(): String {
+ return name.replace(".gpg", "")
+ }
+
+ override fun hashCode(): Int {
+ return 0
+ }
+
+ companion object {
+ const val TYPE_CATEGORY = 'c'
+ const val TYPE_PASSWORD = 'p'
+
+ @JvmStatic
+ fun newCategory(
+ name: String,
+ file: File,
+ parent: PasswordItem,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir)
+ }
+
+ @JvmStatic
+ fun newCategory(
+ name: String,
+ file: File,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir)
+ }
+
+ @JvmStatic
+ fun newPassword(
+ name: String,
+ file: File,
+ parent: PasswordItem,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir)
+ }
+
+ @JvmStatic
+ fun newPassword(
+ name: String,
+ file: File,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir)
+ }
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java
deleted file mode 100644
index 9170e408..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.view.ActionMode;
-
-import com.zeapo.pwdstore.PasswordFragment;
-import com.zeapo.pwdstore.PasswordStore;
-import com.zeapo.pwdstore.R;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.TreeSet;
-
-public class PasswordRecyclerAdapter extends EntryRecyclerAdapter {
- private final PasswordStore activity;
- private final PasswordFragment.OnFragmentInteractionListener listener;
- public ActionMode mActionMode;
- private Boolean canEdit;
- private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
-
- // Called when the action mode is created; startActionMode() was called
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- // Inflate a menu resource providing context menu items
- mode.getMenuInflater().inflate(R.menu.context_pass, menu);
- // hide the fab
- activity.findViewById(R.id.fab).setVisibility(View.GONE);
- return true;
- }
-
- // Called each time the action mode is shown. Always called after onCreateActionMode, but
- // may be called multiple times if the mode is invalidated.
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- if (canEdit) {
- menu.findItem(R.id.menu_edit_password).setVisible(true);
- } else {
- menu.findItem(R.id.menu_edit_password).setVisible(false);
- }
- return true; // Return false if nothing is done
- }
-
- // Called when the user selects a contextual menu item
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_delete_password:
- activity.deletePasswords(PasswordRecyclerAdapter.this, new TreeSet<>(selectedItems));
- mode.finish(); // Action picked, so close the CAB
- return true;
- case R.id.menu_edit_password:
- 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;
- }
- }
-
- // Called when the user exits the action mode
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- for (Iterator<Integer> it = selectedItems.iterator(); it.hasNext(); ) {
- // need the setSelected line in onBind
- notifyItemChanged(it.next());
- it.remove();
- }
- mActionMode = null;
- // show the fab
- activity.findViewById(R.id.fab).setVisibility(View.VISIBLE);
- }
- };
-
- // Provide a suitable constructor (depends on the kind of dataset)
- public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
- super(activity, values);
- this.activity = activity;
- this.listener = listener;
- }
-
- @Override
- @NonNull
- protected View.OnLongClickListener getOnLongClickListener(final ViewHolder holder, final PasswordItem pass) {
- return 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;
- };
- }
-
- @Override
- @NonNull
- protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
- return v -> {
- if (mActionMode != null) {
- toggleSelection(holder.getAdapterPosition());
- 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());
- };
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt
new file mode 100644
index 00000000..d7493a48
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt
@@ -0,0 +1,116 @@
+package com.zeapo.pwdstore.utils
+
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.appcompat.view.ActionMode
+
+import com.zeapo.pwdstore.PasswordFragment
+import com.zeapo.pwdstore.PasswordStore
+import com.zeapo.pwdstore.R
+
+import java.util.ArrayList
+import java.util.TreeSet
+
+class PasswordRecyclerAdapter(private val activity: PasswordStore,
+ private val listener: PasswordFragment.OnFragmentInteractionListener,
+ values: ArrayList<PasswordItem>
+) : EntryRecyclerAdapter(values) {
+ var actionMode: ActionMode? = null
+ private var canEdit: Boolean = false
+ private val actionModeCallback = object : ActionMode.Callback {
+
+ // Called when the action mode is created; startActionMode() was called
+ override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
+ // Inflate a menu resource providing context menu items
+ mode.menuInflater.inflate(R.menu.context_pass, menu)
+ // hide the fab
+ activity.findViewById<View>(R.id.fab).visibility = View.GONE
+ return true
+ }
+
+ // Called each time the action mode is shown. Always called after onCreateActionMode, but
+ // may be called multiple times if the mode is invalidated.
+ override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
+ menu.findItem(R.id.menu_edit_password).isVisible = canEdit
+ return true // Return false if nothing is done
+ }
+
+ // Called when the user selects a contextual menu item
+ override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.menu_delete_password -> {
+ activity.deletePasswords(this@PasswordRecyclerAdapter, TreeSet(selectedItems))
+ mode.finish() // Action picked, so close the CAB
+ return true
+ }
+ R.id.menu_edit_password -> {
+ activity.editPassword(values[selectedItems.iterator().next()])
+ mode.finish()
+ return true
+ }
+ R.id.menu_move_password -> {
+ val selectedPasswords = ArrayList<PasswordItem>()
+ for (id in selectedItems) {
+ selectedPasswords.add(values[id])
+ }
+ activity.movePasswords(selectedPasswords)
+ return false
+ }
+ else -> return false
+ }
+ }
+
+ // Called when the user exits the action mode
+ override fun onDestroyActionMode(mode: ActionMode) {
+ val it = selectedItems.iterator()
+ while (it.hasNext()) {
+ // need the setSelected line in onBind
+ notifyItemChanged(it.next())
+ it.remove()
+ }
+ actionMode = null
+ // show the fab
+ activity.findViewById<View>(R.id.fab).visibility = View.VISIBLE
+ }
+ }
+
+ override fun getOnLongClickListener(holder: ViewHolder, pass: PasswordItem): View.OnLongClickListener {
+ return View.OnLongClickListener {
+ if (actionMode != null) {
+ return@OnLongClickListener false
+ }
+ toggleSelection(holder.adapterPosition)
+ canEdit = pass.type == PasswordItem.TYPE_PASSWORD
+ // Start the CAB using the ActionMode.Callback
+ actionMode = activity.startSupportActionMode(actionModeCallback)
+ actionMode?.title = "" + selectedItems.size
+ actionMode?.invalidate()
+ notifyItemChanged(holder.adapterPosition)
+ true
+ }
+ }
+
+ override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener {
+ return View.OnClickListener {
+ if (actionMode != null) {
+ toggleSelection(holder.adapterPosition)
+ actionMode?.title = "" + selectedItems.size
+ if (selectedItems.isEmpty()) {
+ actionMode?.finish()
+ } else if (selectedItems.size == 1 && (canEdit.not())) {
+ if (values[selectedItems.iterator().next()].type == PasswordItem.TYPE_PASSWORD) {
+ canEdit = true
+ actionMode?.invalidate()
+ }
+ } else if (selectedItems.size >= 1 && canEdit) {
+ canEdit = false
+ actionMode?.invalidate()
+ }
+ } else {
+ listener.onFragmentInteraction(pass)
+ }
+ notifyItemChanged(holder.adapterPosition)
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java
deleted file mode 100644
index 9c27a448..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java
+++ /dev/null
@@ -1,284 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-import org.apache.commons.io.filefilter.FileFilterUtils;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
-import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.transport.RemoteConfig;
-import org.eclipse.jgit.transport.URIish;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-
-import static java.util.Collections.sort;
-
-public class PasswordRepository {
-
- private static Repository repository;
-
- protected PasswordRepository() {
- }
-
- /**
- * Returns the git repository
- *
- * @param localDir needed only on the creation
- * @return the git repository
- */
- public static Repository getRepository(File localDir) {
- if (repository == null && localDir != null) {
- FileRepositoryBuilder builder = new FileRepositoryBuilder();
- try {
- repository = builder.setGitDir(localDir)
- .readEnvironment()
- .build();
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- return repository;
- }
-
- public static boolean isInitialized() {
- return repository != null;
- }
-
- public static void createRepository(File localDir) throws Exception {
- localDir.delete();
-
- Git.init().setDirectory(localDir).call();
- getRepository(localDir);
- }
-
- // TODO add multiple remotes support for pull/push
- public static void addRemote(String name, String url, Boolean replace) {
- StoredConfig storedConfig = repository.getConfig();
- Set<String> remotes = storedConfig.getSubsections("remote");
-
- if (!remotes.contains(name)) {
- try {
- URIish uri = new URIish(url);
- RefSpec refSpec = new RefSpec("+refs/head/*:refs/remotes/" + name + "/*");
-
- RemoteConfig remoteConfig = new RemoteConfig(storedConfig, name);
- remoteConfig.addFetchRefSpec(refSpec);
- remoteConfig.addPushRefSpec(refSpec);
- remoteConfig.addURI(uri);
- remoteConfig.addPushURI(uri);
-
- remoteConfig.update(storedConfig);
-
- storedConfig.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- } else if (replace) {
- try {
- URIish uri = new URIish(url);
-
- RemoteConfig remoteConfig = new RemoteConfig(storedConfig, name);
- // remove the first and eventually the only uri
- if (remoteConfig.getURIs().size() > 0) {
- remoteConfig.removeURI(remoteConfig.getURIs().get(0));
- }
- if (remoteConfig.getPushURIs().size() > 0) {
- remoteConfig.removePushURI(remoteConfig.getPushURIs().get(0));
- }
-
- remoteConfig.addURI(uri);
- remoteConfig.addPushURI(uri);
-
- remoteConfig.update(storedConfig);
-
- storedConfig.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- public static void closeRepository() {
- if (repository != null) repository.close();
- repository = null;
- }
-
- public static File getRepositoryDirectory(Context context) {
- File dir = null;
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
-
- if (settings.getBoolean("git_external", false)) {
- String external_repo = settings.getString("git_external_repo", null);
- if (external_repo != null) {
- dir = new File(external_repo);
- }
- } else {
- dir = new File(context.getFilesDir() + "/store");
- }
-
- return dir;
- }
-
- public static Repository initialize(Context context) {
- File dir = getRepositoryDirectory(context);
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
-
- if (dir == null) {
- return null;
- }
-
- // uninitialize the repo if the dir does not exist or is absolutely empty
- if (!dir.exists() || !dir.isDirectory() || dir.listFiles().length == 0) {
- settings.edit().putBoolean("repository_initialized", false).apply();
- } else {
- settings.edit().putBoolean("repository_initialized", true).apply();
- }
-
- // create the repository static variable in PasswordRepository
- return PasswordRepository.getRepository(new File(dir.getAbsolutePath() + "/.git"));
- }
-
- /**
- * Gets the password items in the root directory
- *
- * @return a list of passwords in the root direcotyr
- */
- public static ArrayList<PasswordItem> getPasswords(File rootDir, PasswordSortOrder sortOrder) {
- return getPasswords(rootDir, rootDir, sortOrder);
- }
-
- /**
- * Gets the .gpg files in a directory
- *
- * @param path the directory path
- * @return the list of gpg files in that directory
- */
- public static ArrayList<File> getFilesList(File path) {
- if (path == null || !path.exists()) return new ArrayList<>();
-
- Log.d("REPO", "current path: " + path.getPath());
- List<File> directories = Arrays.asList(path.listFiles((FileFilter) FileFilterUtils.directoryFileFilter()));
- List<File> files = Arrays.asList(path.listFiles((FileFilter) FileFilterUtils.suffixFileFilter(".gpg")));
-
- ArrayList<File> items = new ArrayList<>();
- items.addAll(directories);
- items.addAll(files);
-
- return items;
- }
-
- /**
- * Gets the passwords (PasswordItem) in a directory
- *
- * @param path the directory path
- * @return a list of password items
- */
- public static ArrayList<PasswordItem> getPasswords(File path, File rootDir, PasswordSortOrder sortOrder) {
- //We need to recover the passwords then parse the files
- ArrayList<File> passList = getFilesList(path);
-
- if (passList.size() == 0) return new ArrayList<>();
-
- ArrayList<PasswordItem> passwordList = new ArrayList<>();
-
- for (File file : passList) {
- if (file.isFile()) {
- if (!file.isHidden()) {
- passwordList.add(PasswordItem.newPassword(file.getName(), file, rootDir));
- }
- } else {
- if (!file.isHidden()) {
- passwordList.add(PasswordItem.newCategory(file.getName(), file, rootDir));
- }
- }
- }
- sort(passwordList, sortOrder.comparator);
- return passwordList;
- }
-
- /**
- * Sets the git user name
- *
- * @param username username
- */
- public static void setUserName(String username) {
- setStringConfig("user", null, "name", username);
- }
-
- /**
- * Sets the git user email
- *
- * @param email email
- */
- public static void setUserEmail(String email) {
- setStringConfig("user", null, "email", email);
- }
-
- /**
- * Sets a git config value
- *
- * @param section config section name
- * @param subsection config subsection name
- * @param name config name
- * @param value the value to be set
- */
- private static void setStringConfig(String section, String subsection, String name, String value) {
- if (isInitialized()) {
- StoredConfig config = repository.getConfig();
- config.setString(section, subsection, name, value);
- try {
- config.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- public enum PasswordSortOrder {
-
- FOLDER_FIRST(new Comparator<PasswordItem>() {
- @Override
- public int compare(PasswordItem p1, PasswordItem p2) {
- return (p1.getType() + p1.getName())
- .compareToIgnoreCase(p2.getType() + p2.getName());
- }
- }),
-
- INDEPENDENT(new Comparator<PasswordItem>() {
- @Override
- public int compare(PasswordItem p1, PasswordItem p2) {
- return p1.getName().compareToIgnoreCase(p2.getName());
- }
- }),
-
- FILE_FIRST(new Comparator<PasswordItem>() {
- @Override
- public int compare(PasswordItem p1, PasswordItem p2) {
- return (p2.getType() + p1.getName())
- .compareToIgnoreCase(p1.getType() + p2.getName());
- }
- });
-
- private Comparator<PasswordItem> comparator;
-
- PasswordSortOrder(Comparator<PasswordItem> comparator) {
- this.comparator = comparator;
- }
-
- public static PasswordSortOrder getSortOrder(SharedPreferences settings) {
- return valueOf(settings.getString("sort_order", FOLDER_FIRST.name()));
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
new file mode 100644
index 00000000..9e94e400
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
@@ -0,0 +1,282 @@
+package com.zeapo.pwdstore.utils
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import org.apache.commons.io.filefilter.FileFilterUtils
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.lib.Repository
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder
+import org.eclipse.jgit.transport.RefSpec
+import org.eclipse.jgit.transport.RemoteConfig
+import org.eclipse.jgit.transport.URIish
+import java.io.File
+import java.io.FileFilter
+import java.util.Comparator
+
+open class PasswordRepository protected constructor() {
+
+ @Suppress("Unused")
+ enum class PasswordSortOrder(val comparator: Comparator<PasswordItem>) {
+
+ FOLDER_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem ->
+ (p1.type + p1.name)
+ .compareTo(p2.type + p2.name, ignoreCase = true)
+ }),
+
+ INDEPENDENT(Comparator { p1: PasswordItem, p2: PasswordItem ->
+ p1.name.compareTo(p2.name, ignoreCase = true)
+ }),
+
+ FILE_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem ->
+ (p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true)
+ });
+
+
+ companion object {
+ @JvmStatic
+ fun getSortOrder(settings: SharedPreferences): PasswordSortOrder {
+ return valueOf(settings.getString("sort_order", null) ?: FOLDER_FIRST.name)
+ }
+ }
+ }
+
+ companion object {
+
+ private var repository: Repository? = null
+
+ /**
+ * Returns the git repository
+ *
+ * @param localDir needed only on the creation
+ * @return the git repository
+ */
+ @JvmStatic
+ fun getRepository(localDir: File?): Repository? {
+ if (repository == null && localDir != null) {
+ val builder = FileRepositoryBuilder()
+ try {
+ repository = builder.setGitDir(localDir)
+ .readEnvironment()
+ .build()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return null
+ }
+
+ }
+ return repository
+ }
+
+ @JvmStatic
+ val isInitialized: Boolean
+ get() = repository != null
+
+ @JvmStatic
+ @Throws(Exception::class)
+ fun createRepository(localDir: File) {
+ localDir.delete()
+
+ Git.init().setDirectory(localDir).call()
+ getRepository(localDir)
+ }
+
+ // TODO add multiple remotes support for pull/push
+ @JvmStatic
+ fun addRemote(name: String, url: String, replace: Boolean?) {
+ val storedConfig = repository!!.config
+ val remotes = storedConfig.getSubsections("remote")
+
+ if (!remotes.contains(name)) {
+ try {
+ val uri = URIish(url)
+ val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*")
+
+ val remoteConfig = RemoteConfig(storedConfig, name)
+ remoteConfig.addFetchRefSpec(refSpec)
+ remoteConfig.addPushRefSpec(refSpec)
+ remoteConfig.addURI(uri)
+ remoteConfig.addPushURI(uri)
+
+ remoteConfig.update(storedConfig)
+
+ storedConfig.save()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ } else if (replace!!) {
+ try {
+ val uri = URIish(url)
+
+ val remoteConfig = RemoteConfig(storedConfig, name)
+ // remove the first and eventually the only uri
+ if (remoteConfig.urIs.size > 0) {
+ remoteConfig.removeURI(remoteConfig.urIs[0])
+ }
+ if (remoteConfig.pushURIs.size > 0) {
+ remoteConfig.removePushURI(remoteConfig.pushURIs[0])
+ }
+
+ remoteConfig.addURI(uri)
+ remoteConfig.addPushURI(uri)
+
+ remoteConfig.update(storedConfig)
+
+ storedConfig.save()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ }
+ }
+
+ @JvmStatic
+ fun closeRepository() {
+ if (repository != null) repository!!.close()
+ repository = null
+ }
+
+ @JvmStatic
+ fun getRepositoryDirectory(context: Context): File? {
+ var dir: File? = null
+ val settings = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
+
+ if (settings.getBoolean("git_external", false)) {
+ val externalRepo = settings.getString("git_external_repo", null)
+ if (externalRepo != null) {
+ dir = File(externalRepo)
+ }
+ } else {
+ dir = File(context.filesDir.toString() + "/store")
+ }
+
+ return dir
+ }
+
+ @JvmStatic
+ fun initialize(context: Context): Repository? {
+ val dir = getRepositoryDirectory(context)
+ val settings = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
+
+ if (dir == null) {
+ return null
+ }
+
+ // uninitialize the repo if the dir does not exist or is absolutely empty
+ if (!dir.exists() || !dir.isDirectory || dir.listFiles()!!.isEmpty()) {
+ settings.edit().putBoolean("repository_initialized", false).apply()
+ } else {
+ settings.edit().putBoolean("repository_initialized", true).apply()
+ }
+
+ // create the repository static variable in PasswordRepository
+ return getRepository(File(dir.absolutePath + "/.git"))
+ }
+
+ /**
+ * Gets the password items in the root directory
+ *
+ * @return a list of passwords in the root direcotyr
+ */
+ @JvmStatic
+ fun getPasswords(rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
+ return getPasswords(rootDir, rootDir, sortOrder)
+ }
+
+ /**
+ * Gets the .gpg files in a directory
+ *
+ * @param path the directory path
+ * @return the list of gpg files in that directory
+ */
+ @JvmStatic
+ fun getFilesList(path: File?): ArrayList<File> {
+ if (path == null || !path.exists()) return ArrayList()
+
+ val directories = (path.listFiles(FileFilterUtils.directoryFileFilter() as FileFilter)
+ ?: emptyArray()).toList()
+ val files = (path.listFiles(FileFilterUtils.suffixFileFilter(".gpg") as FileFilter)
+ ?: emptyArray()).toList()
+
+ val items = ArrayList<File>()
+ items.addAll(directories)
+ items.addAll(files)
+
+ return items
+ }
+
+ /**
+ * Gets the passwords (PasswordItem) in a directory
+ *
+ * @param path the directory path
+ * @return a list of password items
+ */
+ @JvmStatic
+ fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
+ //We need to recover the passwords then parse the files
+ val passList = getFilesList(path)
+
+ if (passList.size == 0) return ArrayList()
+
+ val passwordList = ArrayList<PasswordItem>()
+
+ for (file in passList) {
+ if (file.isFile) {
+ if (!file.isHidden) {
+ passwordList.add(PasswordItem.newPassword(file.name, file, rootDir))
+ }
+ } else {
+ if (!file.isHidden) {
+ passwordList.add(PasswordItem.newCategory(file.name, file, rootDir))
+ }
+ }
+ }
+ passwordList.sortWith(sortOrder.comparator)
+ return passwordList
+ }
+
+ /**
+ * Sets the git user name
+ *
+ * @param username username
+ */
+ @JvmStatic
+ fun setUserName(username: String) {
+ setStringConfig("user", null, "name", username)
+ }
+
+ /**
+ * Sets the git user email
+ *
+ * @param email email
+ */
+ @JvmStatic
+ fun setUserEmail(email: String) {
+ setStringConfig("user", null, "email", email)
+ }
+
+ /**
+ * Sets a git config value
+ *
+ * @param section config section name
+ * @param subsection config subsection name
+ * @param name config name
+ * @param value the value to be set
+ */
+ @JvmStatic
+ @Suppress("SameParameterValue")
+ private fun setStringConfig(section: String, subsection: String?, name: String, value: String) {
+ if (isInitialized) {
+ val config = repository!!.config
+ config.setString(section, subsection, name, value)
+ try {
+ config.save()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ }
+ }
+ }
+}