Merge branch 'feature/ListNodesFragment' into develop

This commit is contained in:
J-Jamet
2018-05-30 11:16:13 +02:00
63 changed files with 1909 additions and 959 deletions

View File

@@ -19,8 +19,6 @@
*/
package com.kunzisoft.keepass.tests.database;
import java.util.List;
import android.content.Context;
import android.test.AndroidTestCase;
@@ -30,9 +28,11 @@ import com.kunzisoft.keepass.database.PwDatabaseV3;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.action.DeleteGroupRunnable;
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
import com.kunzisoft.keepass.search.SearchDbHelper;
import java.util.List;
public class DeleteEntry extends AndroidTestCase {
private static final String GROUP1_NAME = "Group1";
private static final String ENTRY1_NAME = "Test1";

View File

@@ -24,7 +24,6 @@ import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
@@ -49,10 +48,11 @@ import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.action.AddEntryRunnable;
import com.kunzisoft.keepass.database.action.OnFinishRunnable;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.action.RunnableOnFinish;
import com.kunzisoft.keepass.database.action.UpdateEntryRunnable;
import com.kunzisoft.keepass.database.action.node.AddEntryRunnable;
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
@@ -67,6 +67,8 @@ import com.kunzisoft.keepass.view.EntryEditCustomField;
import java.util.UUID;
import javax.annotation.Nullable;
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.UNDEFINED_ICON_ID;
public class EntryEditActivity extends LockingHideActivity
@@ -251,7 +253,7 @@ public class EntryEditActivity extends LockingHideActivity
mCallbackNewEntry = populateNewEntry();
// Open a progress dialog and save entry
OnFinishRunnable onFinish = new AfterSave();
AfterActionNodeOnFinish onFinish = new AfterSave();
EntryEditActivity act = EntryEditActivity.this;
RunnableOnFinish task;
if ( mIsNew ) {
@@ -560,14 +562,10 @@ public class EntryEditActivity extends LockingHideActivity
}
}
private final class AfterSave extends OnFinishRunnable {
AfterSave() {
super(new Handler());
}
private final class AfterSave extends AfterActionNodeOnFinish {
@Override
public void run() {
public void run(@Nullable PwNode oldNode, @Nullable PwNode newNode) {
runOnUiThread(() -> {
if ( mSuccess ) {
finish();
@@ -578,6 +576,6 @@ public class EntryEditActivity extends LockingHideActivity
SaveDatabaseProgressTaskDialogFragment.stop(EntryEditActivity.this);
});
}
}
}
}

View File

@@ -33,8 +33,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
@@ -50,17 +50,21 @@ import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.database.action.AddGroupRunnable;
import com.kunzisoft.keepass.database.action.DeleteEntryRunnable;
import com.kunzisoft.keepass.database.action.DeleteGroupRunnable;
import com.kunzisoft.keepass.database.action.UpdateGroupRunnable;
import com.kunzisoft.keepass.database.action.node.AddGroupRunnable;
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable;
import com.kunzisoft.keepass.database.action.node.DeleteEntryRunnable;
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
import com.kunzisoft.keepass.database.action.node.MoveEntryRunnable;
import com.kunzisoft.keepass.database.action.node.MoveGroupRunnable;
import com.kunzisoft.keepass.database.action.node.UpdateGroupRunnable;
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
@@ -69,16 +73,25 @@ import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.search.SearchResultsActivity;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
import com.kunzisoft.keepass.tasks.UIToastTask;
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
import com.kunzisoft.keepass.view.AddNodeButtonView;
public class GroupActivity extends ListNodesActivity
implements GroupEditDialogFragment.EditGroupListener, IconPickerDialogFragment.IconPickerListener {
import net.cachapa.expandablelayout.ExpandableLayout;
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
public class GroupActivity extends ListNodesActivity
implements GroupEditDialogFragment.EditGroupListener,
IconPickerDialogFragment.IconPickerListener,
NodeAdapter.NodeMenuListener,
ListNodesFragment.OnScrollListener {
private static final String TAG = GroupActivity.class.getName();
private Toolbar toolbar;
private ExpandableLayout toolbarPasteExpandableLayout;
private Toolbar toolbarPaste;
private ImageView iconView;
private AddNodeButtonView addNodeButtonView;
@@ -87,13 +100,15 @@ public class GroupActivity extends ListNodesActivity
protected boolean isRoot = false;
protected boolean readOnly = false;
private static final String TAG = "Group Activity:";
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY";
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
private PwGroup oldGroupToUpdate;
private PwNode nodeToCopy;
private PwNode nodeToMove;
public static void launch(Activity act) {
recordFirstTimeBeforeLaunch(act);
startRecordTime(act);
launch(act, (PwGroup) null);
}
@@ -110,7 +125,7 @@ public class GroupActivity extends ListNodesActivity
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity act, AssistStructure assistStructure) {
if ( assistStructure != null ) {
recordFirstTimeBeforeLaunch(act);
startRecordTime(act);
launch(act, null, assistStructure);
} else {
launch(act);
@@ -137,34 +152,50 @@ public class GroupActivity extends ListNodesActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w(TAG, "Retrieved tree");
Log.i(TAG, "Started creating tree");
if ( mCurrentGroup == null ) {
Log.w(TAG, "Group was null");
return;
}
if (savedInstanceState != null
&& savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY)) {
oldGroupToUpdate = (PwGroup) savedInstanceState.getSerializable(OLD_GROUP_TO_UPDATE_KEY);
}
// Construct main view
// Construct main view
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
attachFragmentToContentView();
iconView = findViewById(R.id.icon);
addNodeButtonView = findViewById(R.id.add_node_button);
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
// Hide when scroll
RecyclerView recyclerView = findViewById(R.id.nodes_list);
recyclerView.addOnScrollListener(addNodeButtonView.hideButtonOnScrollListener());
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("");
setSupportActionBar(toolbar);
groupNameView = findViewById(R.id.group_name);
if ( mCurrentGroup.getParent() != null )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
toolbarPaste = findViewById(R.id.toolbar_paste);
toolbarPaste.inflateMenu(R.menu.node_paste_menu);
toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
toolbarPaste.setNavigationOnClickListener(view -> {
toolbarPasteExpandableLayout.collapse();
nodeToCopy = null;
nodeToMove = null;
});
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
oldGroupToUpdate = (PwGroup) savedInstanceState.getSerializable(OLD_GROUP_TO_UPDATE_KEY);
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) {
nodeToCopy = (PwNode) savedInstanceState.getSerializable(NODE_TO_COPY_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener());
}
else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
nodeToMove = (PwNode) savedInstanceState.getSerializable(NODE_TO_MOVE_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
}
}
addNodeButtonView.setAddGroupClickListener(v -> {
GroupEditDialogFragment.build()
@@ -174,23 +205,41 @@ public class GroupActivity extends ListNodesActivity
addNodeButtonView.setAddEntryClickListener(v ->
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
setGroupTitle();
Log.w(TAG, "Finished creating tree");
Log.i(TAG, "Finished creating tree");
if (isRoot) {
showWarnings();
}
}
protected PwGroup initCurrentGroup() {
PwGroup currentGroup;
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putSerializable(GROUP_ID_KEY, mCurrentGroup.getId());
outState.putSerializable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
if (nodeToCopy != null)
outState.putSerializable(NODE_TO_COPY_KEY, nodeToCopy);
if (nodeToMove != null)
outState.putSerializable(NODE_TO_MOVE_KEY, nodeToMove);
super.onSaveInstanceState(outState);
}
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
PwGroupId pwGroupId = null; // TODO Parcelable
if (savedInstanceState != null
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = (PwGroupId) savedInstanceState.getSerializable(GROUP_ID_KEY);
} else {
if (getIntent() != null)
pwGroupId = (PwGroupId) getIntent().getSerializableExtra(GROUP_ID_KEY);
}
Database db = App.getDB();
readOnly = db.isReadOnly();
PwGroup root = db.getPwDatabase().getRootGroup();
Log.w(TAG, "Creating tree view");
PwGroupId pwGroupId = (PwGroupId) getIntent().getSerializableExtra(GROUP_ID_KEY);
PwGroup currentGroup;
if ( pwGroupId == null ) {
currentGroup = root;
} else {
@@ -209,67 +258,212 @@ public class GroupActivity extends ListNodesActivity
}
@Override
protected RecyclerView defineNodeList() {
return (RecyclerView) findViewById(R.id.nodes_list);
public void assignToolbarElements() {
super.assignToolbarElements();
// Assign the group icon depending of IconPack or custom icon
if ( mCurrentGroup != null ) {
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon());
}
if (toolbar != null) {
if ( mCurrentGroup.containsParent() )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
else {
toolbar.setNavigationIcon(null);
}
}
}
}
@Override
protected void addOptionsToAdapter(NodeAdapter nodeAdapter) {
super.addOptionsToAdapter(nodeAdapter);
public void onScrolled(int dy) {
if (addNodeButtonView != null)
addNodeButtonView.hideButtonOnScrollListener(dy);
}
nodeAdapter.setActivateContextMenu(true);
nodeAdapter.setNodeMenuListener(new NodeAdapter.NodeMenuListener() {
@Override
public boolean onOpenMenuClick(PwNode node) {
switch (node.getType()) {
case GROUP:
GroupActivity.launch(GroupActivity.this, (PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(GroupActivity.this, (PwEntry) node);
break;
}
return true;
}
@Override
public boolean onOpenMenuClick(PwNode node) {
onNodeClick(node);
return true;
}
@Override
public boolean onEditMenuClick(PwNode node) {
switch (node.getType()) {
case GROUP:
oldGroupToUpdate = (PwGroup) node;
GroupEditDialogFragment.build(node)
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
break;
case ENTRY:
EntryEditActivity.launch(GroupActivity.this, (PwEntry) node);
break;
}
return true;
}
@Override
public boolean onEditMenuClick(PwNode node) {
switch (node.getType()) {
case GROUP:
oldGroupToUpdate = (PwGroup) node;
GroupEditDialogFragment.build(node)
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
break;
case ENTRY:
EntryEditActivity.launch(GroupActivity.this, (PwEntry) node);
break;
}
return true;
}
@Override
public boolean onDeleteMenuClick(PwNode node) {
switch (node.getType()) {
case GROUP:
deleteGroup((PwGroup) node);
break;
case ENTRY:
deleteEntry((PwEntry) node);
break;
}
return true;
@Override
public boolean onCopyMenuClick(PwNode node) {
toolbarPasteExpandableLayout.expand();
nodeToCopy = node;
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener());
return false;
}
private class OnCopyMenuItemClickListener implements Toolbar.OnMenuItemClickListener{
@Override
public boolean onMenuItemClick(MenuItem item) {
toolbarPasteExpandableLayout.collapse();
switch (item.getItemId()) {
case R.id.menu_paste:
switch (nodeToCopy.getType()) {
case GROUP:
Log.e(TAG, "Copy not allowed for group");
break;
case ENTRY:
copyNode((PwEntry) nodeToCopy, mCurrentGroup);
break;
}
nodeToCopy = null;
return true;
}
});
return true;
}
}
private void copyNode(PwEntry entryToCopy, PwGroup newParent) {
CopyEntryRunnable task = new CopyEntryRunnable(this, App.getDB(), entryToCopy, newParent,
new AfterAddNode());
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
}
@Override
public boolean onMoveMenuClick(PwNode node) {
toolbarPasteExpandableLayout.expand();
nodeToMove = node;
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
return false;
}
private class OnMoveMenuItemClickListener implements Toolbar.OnMenuItemClickListener{
@Override
public boolean onMenuItemClick(MenuItem item) {
toolbarPasteExpandableLayout.collapse();
switch (item.getItemId()) {
case R.id.menu_paste:
switch (nodeToMove.getType()) {
case GROUP:
moveGroup((PwGroup) nodeToMove, mCurrentGroup);
break;
case ENTRY:
moveEntry((PwEntry) nodeToMove, mCurrentGroup);
break;
}
nodeToMove = null;
return true;
}
return true;
}
}
private void moveGroup(PwGroup groupToMove, PwGroup newParent) {
MoveGroupRunnable task = new MoveGroupRunnable(
this,
App.getDB(),
groupToMove,
newParent,
new AfterAddNode());
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
}
private void moveEntry(PwEntry entryToMove, PwGroup newParent) {
MoveEntryRunnable task = new MoveEntryRunnable(
this,
App.getDB(),
entryToMove,
newParent,
new AfterAddNode());
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
}
@Override
public boolean onDeleteMenuClick(PwNode node) {
switch (node.getType()) {
case GROUP:
deleteGroup((PwGroup) node);
break;
case ENTRY:
deleteEntry((PwEntry) node);
break;
}
return true;
}
private void deleteGroup(PwGroup group) {
//TODO Verify trash recycle bin
DeleteGroupRunnable task = new DeleteGroupRunnable(
this,
App.getDB(),
group,
new AfterDeleteNode());
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
}
private void deleteEntry(PwEntry entry) {
DeleteEntryRunnable task = new DeleteEntryRunnable(
this,
App.getDB(),
entry,
new AfterDeleteNode());
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
}
@Override
protected void onResume() {
super.onResume();
// Refresh the group icon
assignGroupIcon();
// Show button on resume
addNodeButtonView.showButton();
if (addNodeButtonView != null)
addNodeButtonView.showButton();
}
/**
@@ -279,7 +473,8 @@ public class GroupActivity extends ListNodesActivity
private void checkAndPerformedEducation(Menu menu) {
// If no node, show education to add new one
if (mAdapter.getItemCount() <= 0) {
if (listNodesFragment != null
&& listNodesFragment.isEmpty()) {
if (!PreferencesUtil.isEducationNewNodePerformed(this)) {
TapTargetView.showFor(this,
@@ -407,56 +602,8 @@ public class GroupActivity extends ListNodesActivity
protected void onStop() {
super.onStop();
// Hide button
addNodeButtonView.hideButton();
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
super.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
// Show button if hide after sort
addNodeButtonView.showButton();
}
/**
* Assign the group icon depending of IconPack or custom icon
*/
protected void assignGroupIcon() {
if (mCurrentGroup != null) {
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon());
}
}
}
private void deleteEntry(PwEntry entry) {
Handler handler = new Handler();
DeleteEntryRunnable task = new DeleteEntryRunnable(this, App.getDB(), entry,
new AfterDeleteNode(handler, entry));
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
}
private void deleteGroup(PwGroup group) {
//TODO Verify trash recycle bin
Handler handler = new Handler();
DeleteGroupRunnable task = new DeleteGroupRunnable(this, App.getDB(), group,
new AfterDeleteNode(handler, group));
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
if (addNodeButtonView != null)
addNodeButtonView.hideButton();
}
@Override
@@ -564,7 +711,7 @@ public class GroupActivity extends ListNodesActivity
AddGroupRunnable addGroupRunnable = new AddGroupRunnable(this,
App.getDB(),
newGroup,
new AfterAddNode(new Handler()));
new AfterAddNode());
addGroupRunnable.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
@@ -583,14 +730,15 @@ public class GroupActivity extends ListNodesActivity
} catch (Exception ignored) {} // TODO custom icon
updateGroup.setIcon(iconStandard);
mAdapter.removeNode(oldGroupToUpdate);
if (listNodesFragment != null)
listNodesFragment.removeNode(oldGroupToUpdate);
// If group updated save it in the database
UpdateGroupRunnable updateGroupRunnable = new UpdateGroupRunnable(this,
App.getDB(),
oldGroupToUpdate,
updateGroup,
new AfterUpdateNode(new Handler()));
new AfterUpdateNode());
updateGroupRunnable.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
@@ -603,10 +751,79 @@ public class GroupActivity extends ListNodesActivity
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putSerializable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
super.onSaveInstanceState(outState);
class AfterAddNode extends AfterActionNodeOnFinish {
public void run(PwNode oldNode, PwNode newNode) {
super.run();
runOnUiThread(() -> {
if (mSuccess) {
if (listNodesFragment != null)
listNodesFragment.addNode(newNode);
} else {
displayMessage(GroupActivity.this);
}
SaveDatabaseProgressTaskDialogFragment.stop(GroupActivity.this);
});
}
}
class AfterUpdateNode extends AfterActionNodeOnFinish {
public void run(PwNode oldNode, PwNode newNode) {
super.run();
runOnUiThread(() -> {
if (mSuccess) {
if (listNodesFragment != null)
listNodesFragment.updateNode(oldNode, newNode);
} else {
displayMessage(GroupActivity.this);
}
SaveDatabaseProgressTaskDialogFragment.stop(GroupActivity.this);
});
}
}
class AfterDeleteNode extends AfterActionNodeOnFinish {
@Override
public void run(PwNode oldNode, PwNode newNode) {
super.run();
runOnUiThread(() -> {
if ( mSuccess) {
if (listNodesFragment != null)
listNodesFragment.removeNode(oldNode);
PwGroup parent = oldNode.getParent();
Database db = App.getDB();
PwDatabase database = db.getPwDatabase();
if (db.isRecycleBinAvailable() &&
db.isRecycleBinEnabled()) {
PwGroup recycleBin = database.getRecycleBin();
// Add trash if it doesn't exists
if (parent.equals(recycleBin)
&& mCurrentGroup != null
&& mCurrentGroup.getParent() == null
&& !mCurrentGroup.equals(recycleBin)) {
if (listNodesFragment != null)
listNodesFragment.addNode(parent);
}
}
} else {
mHandler.post(new UIToastTask(GroupActivity.this, "Unrecoverable error: " + mMessage));
App.setShutdown();
finish();
}
SaveDatabaseProgressTaskDialogFragment.stop(GroupActivity.this);
});
}
}
@Override
@@ -637,4 +854,16 @@ public class GroupActivity extends ListNodesActivity
}
}
}
@Override
protected void openGroup(PwGroup group) {
super.openGroup(group);
addNodeButtonView.showButton();
}
@Override
public void onBackPressed() {
super.onBackPressed();
addNodeButtonView.showButton();
}
}

View File

@@ -22,51 +22,40 @@ package com.kunzisoft.keepass.activities;
import android.annotation.SuppressLint;
import android.app.assist.AssistStructure;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.support.annotation.Nullable;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.database.action.AfterActionNodeOnFinish;
import com.kunzisoft.keepass.database.action.OnFinishRunnable;
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.password.AssignPasswordHelper;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
import com.kunzisoft.keepass.tasks.UIToastTask;
import com.kunzisoft.keepass.utils.MenuUtil;
public abstract class ListNodesActivity extends LockingActivity
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.OnNodeClickCallback,
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
protected PwGroup mCurrentGroup;
protected NodeAdapter mAdapter;
protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private SharedPreferences prefs;
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
protected ListNodesFragment listNodesFragment;
protected PwGroup mCurrentGroup;
protected TextView groupNameView;
protected AutofillHelper autofillHelper;
@@ -84,17 +73,11 @@ public abstract class ListNodesActivity extends LockingActivity
return;
}
prefs = PreferenceManager.getDefaultSharedPreferences(this);
invalidateOptionsMenu();
invalidateOptionsMenu();
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
// TODO Move in search
setContentView(R.layout.list_nodes);
mCurrentGroup = initCurrentGroup();
mAdapter = new NodeAdapter(this);
addOptionsToAdapter(mAdapter);
initializeListNodesFragment(mCurrentGroup);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
@@ -102,42 +85,71 @@ public abstract class ListNodesActivity extends LockingActivity
}
}
protected abstract PwGroup initCurrentGroup();
protected abstract RecyclerView defineNodeList();
protected void addOptionsToAdapter(NodeAdapter nodeAdapter) {
mAdapter.setOnNodeClickListener(this);
}
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
@Override
protected void onResume() {
super.onResume();
// Add elements to the list
mAdapter.rebuildList(mCurrentGroup);
assignListToNodeAdapter(defineNodeList());
// Refresh the title
assignToolbarElements();
}
protected void setGroupTitle() {
if ( mCurrentGroup != null ) {
String name = mCurrentGroup.getName();
TextView tv = findViewById(R.id.group_name);
if ( name != null && name.length() > 0 ) {
if ( tv != null ) {
tv.setText(name);
}
} else {
if ( tv != null ) {
tv.setText(getText(R.string.root));
}
}
}
protected void initializeListNodesFragment(PwGroup currentGroup) {
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
if (listNodesFragment == null)
listNodesFragment = ListNodesFragment.newInstance(currentGroup);
}
/**
* Attach the fragment's list of node.
* <br />
* <strong>R.id.nodes_list_fragment_container</strong> must be the id of the container
*/
protected void attachFragmentToContentView() {
getSupportFragmentManager().beginTransaction().replace(
R.id.nodes_list_fragment_container,
listNodesFragment,
LIST_NODES_FRAGMENT_TAG)
.commit();
}
public void assignToolbarElements() {
if (mCurrentGroup != null) {
String title = mCurrentGroup.getName();
if (title != null && title.length() > 0) {
if (groupNameView != null) {
groupNameView.setText(title);
groupNameView.invalidate();
}
} else {
if (groupNameView != null) {
groupNameView.setText(getText(R.string.root));
groupNameView.invalidate();
}
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
protected void assignListToNodeAdapter(RecyclerView recyclerView) {
recyclerView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mAdapter);
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
return super.onOptionsItemSelected(item);
}
}
@Override
@@ -150,7 +162,7 @@ public abstract class ListNodesActivity extends LockingActivity
if (assistStructure != null) {
switch (node.getType()) {
case GROUP:
GroupActivity.launch(this, (PwGroup) node, assistStructure);
openGroup((PwGroup) node);
break;
case ENTRY:
// Build response with the entry selected
@@ -163,7 +175,7 @@ public abstract class ListNodesActivity extends LockingActivity
if ( assistStructure == null ){
switch (node.getType()) {
case GROUP:
GroupActivity.launch(this, (PwGroup) node);
openGroup((PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node);
@@ -172,69 +184,26 @@ public abstract class ListNodesActivity extends LockingActivity
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
protected void openGroup(PwGroup group) {
// Check Timeout
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.tree, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
// Toggle setting
Editor editor = prefs.edit();
editor.putString(getString(R.string.sort_node_key), sortNodeEnum.name());
editor.putBoolean(getString(R.string.sort_ascending_key), ascending);
editor.putBoolean(getString(R.string.sort_group_before_key), groupsBefore);
editor.putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom);
editor.apply();
// Tell the adapter to refresh it's list
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
mAdapter.rebuildList(mCurrentGroup);
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group.getId());
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.nodes_list_fragment_container,
newListNodeFragment,
LIST_NODES_FRAGMENT_TAG)
.addToBackStack(LIST_NODES_FRAGMENT_TAG)
.commit();
listNodesFragment = newListNodeFragment;
mCurrentGroup = group;
assignToolbarElements();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_sort:
SortDialogFragment sortDialogFragment;
PwDatabase database = App.getDB().getPwDatabase();
/*
// TODO Recycle bin bottom
if (database.isRecycleBinAvailable() && database.isRecycleBinEnabled()) {
sortDialogFragment =
SortDialogFragment.getInstance(
PrefsUtil.getListSort(this),
PrefsUtil.getAscendingSort(this),
PrefsUtil.getGroupsBeforeSort(this),
PrefsUtil.getRecycleBinBottomSort(this));
} else {
*/
sortDialogFragment =
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(this),
PreferencesUtil.getAscendingSort(this),
PreferencesUtil.getGroupsBeforeSort(this));
//}
sortDialogFragment.show(getSupportFragmentManager(), "sortDialog");
return true;
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
return super.onOptionsItemSelected(item);
}
}
@Override
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
@@ -253,29 +222,16 @@ public abstract class ListNodesActivity extends LockingActivity
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
if (listNodesFragment != null)
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
PwNode newNode = (PwNode) data.getSerializableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
if (newNode != null) {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter.addNode(newNode);
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode);
mAdapter.rebuildList(mCurrentGroup);
}
} else {
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
}
}
break;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
@@ -300,83 +256,18 @@ public abstract class ListNodesActivity extends LockingActivity
}
}
class AfterAddNode extends AfterActionNodeOnFinish {
AfterAddNode(Handler handler) {
super(handler);
}
@Override
public void onBackPressed() {
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
public void run(PwNode oldNode, PwNode newNode) {
super.run();
super.onBackPressed();
runOnUiThread(() -> {
if (mSuccess) {
mAdapter.addNode(newNode);
} else {
displayMessage(ListNodesActivity.this);
}
SaveDatabaseProgressTaskDialogFragment.stop(ListNodesActivity.this);
});
}
}
class AfterUpdateNode extends AfterActionNodeOnFinish {
AfterUpdateNode(Handler handler) {
super(handler);
}
public void run(PwNode oldNode, PwNode newNode) {
super.run();
runOnUiThread(() -> {
if (mSuccess) {
mAdapter.updateNode(oldNode, newNode);
} else {
displayMessage(ListNodesActivity.this);
}
SaveDatabaseProgressTaskDialogFragment.stop(ListNodesActivity.this);
});
}
}
class AfterDeleteNode extends OnFinishRunnable {
private PwNode pwNode;
AfterDeleteNode(Handler handler, PwNode pwNode) {
super(handler);
this.pwNode = pwNode;
}
@Override
public void run() {
super.run();
runOnUiThread(() -> {
if ( mSuccess) {
mAdapter.removeNode(pwNode);
PwGroup parent = pwNode.getParent();
Database db = App.getDB();
PwDatabase database = db.getPwDatabase();
if (db.isRecycleBinAvailable() &&
db.isRecycleBinEnabled()) {
PwGroup recycleBin = database.getRecycleBin();
// Add trash if it doesn't exists
if (parent.equals(recycleBin)
&& mCurrentGroup != null
&& mCurrentGroup.getParent() == null
&& !mCurrentGroup.equals(recycleBin)) {
mAdapter.addNode(parent);
}
}
} else {
mHandler.post(new UIToastTask(ListNodesActivity.this, "Unrecoverable error: " + mMessage));
App.setShutdown();
finish();
}
SaveDatabaseProgressTaskDialogFragment.stop(ListNodesActivity.this);
});
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// to refresh fragment
listNodesFragment.rebuildList();
mCurrentGroup = listNodesFragment.getMainGroup();
assignToolbarElements();
}
}
}

View File

@@ -0,0 +1,298 @@
package com.kunzisoft.keepass.activities;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.stylish.StylishFragment;
public class ListNodesFragment extends StylishFragment implements
SortDialogFragment.SortSelectionListener {
private static final String TAG = ListNodesFragment.class.getName();
private static final String GROUP_KEY = "GROUP_KEY";
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private NodeAdapter.NodeClickCallback nodeClickCallback;
private NodeAdapter.NodeMenuListener nodeMenuListener;
private OnScrollListener onScrollListener;
private RecyclerView listView;
protected PwGroup mCurrentGroup;
protected NodeAdapter mAdapter;
// Preferences for sorting
private SharedPreferences prefs;
public static ListNodesFragment newInstance(PwGroup group) {
Bundle bundle = new Bundle();
if (group != null) {
bundle.putSerializable(GROUP_KEY, group);
}
ListNodesFragment listNodesFragment = new ListNodesFragment();
listNodesFragment.setArguments(bundle);
return listNodesFragment;
}
public static ListNodesFragment newInstance(PwGroupId groupId) {
Bundle bundle=new Bundle();
if (groupId != null) {
bundle.putSerializable(GROUP_ID_KEY, groupId);
}
ListNodesFragment listNodesFragment = new ListNodesFragment();
listNodesFragment.setArguments(bundle);
return listNodesFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
nodeClickCallback = (NodeAdapter.NodeClickCallback) context;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(context.toString()
+ " must implement " + NodeAdapter.NodeClickCallback.class.getName());
}
try {
nodeMenuListener = (NodeAdapter.NodeMenuListener) context;
} catch (ClassCastException e) {
nodeMenuListener = null;
// Context menu can be omit
Log.w(TAG, context.toString()
+ " must implement " + NodeAdapter.NodeMenuListener.class.getName());
}
try {
onScrollListener = (OnScrollListener) context;
} catch (ClassCastException e) {
onScrollListener = null;
// Context menu can be omit
Log.w(TAG, context.toString()
+ " must implement " + RecyclerView.OnScrollListener.class.getName());
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mCurrentGroup = initCurrentGroup();
if (getActivity() != null) {
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
mAdapter.setOnNodeClickListener(nodeClickCallback);
if (nodeMenuListener != null) {
mAdapter.setActivateContextMenu(true);
mAdapter.setNodeMenuListener(nodeMenuListener);
}
}
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
}
protected PwGroup initCurrentGroup() { // TODO Change by parcelable
Database db = App.getDB();
PwGroup root = db.getPwDatabase().getRootGroup();
PwGroup currentGroup = null;
if (getArguments() != null) {
// Contains all the group in element
if (getArguments().containsKey(GROUP_KEY)) {
currentGroup = (PwGroup) getArguments().getSerializable(GROUP_KEY);
}
// Contains only the group id, so the group must be retrieve
if (getArguments().containsKey(GROUP_ID_KEY)) {
PwGroupId pwGroupId = (PwGroupId) getArguments().getSerializable(GROUP_ID_KEY);
if ( pwGroupId != null )
currentGroup = db.getPwDatabase().getGroupByGroupId(pwGroupId);
}
}
if ( currentGroup == null ) {
currentGroup = root;
}
return currentGroup;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// To apply theme
View rootView = inflater.cloneInContext(getContextThemed())
.inflate(R.layout.list_nodes_fragment, container, false);
listView = rootView.findViewById(R.id.nodes_list);
if (onScrollListener != null) {
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
onScrollListener.onScrolled(dy);
}
});
}
return rootView;
}
@Override
public void onResume() {
super.onResume();
rebuildList();
}
public void rebuildList() {
// Add elements to the list
mAdapter.rebuildList(mCurrentGroup);
assignListToNodeAdapter(listView);
}
protected void assignListToNodeAdapter(RecyclerView recyclerView) {
recyclerView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(mAdapter);
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
// Toggle setting
SharedPreferences.Editor editor = prefs.edit();
editor.putString(getString(R.string.sort_node_key), sortNodeEnum.name());
editor.putBoolean(getString(R.string.sort_ascending_key), ascending);
editor.putBoolean(getString(R.string.sort_group_before_key), groupsBefore);
editor.putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom);
editor.apply();
// Tell the adapter to refresh it's list
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
mAdapter.rebuildList(mCurrentGroup);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.tree, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_sort:
SortDialogFragment sortDialogFragment;
PwDatabase database = App.getDB().getPwDatabase();
/*
// TODO Recycle bin bottom
if (database.isRecycleBinAvailable() && database.isRecycleBinEnabled()) {
sortDialogFragment =
SortDialogFragment.getInstance(
PrefsUtil.getListSort(this),
PrefsUtil.getAscendingSort(this),
PrefsUtil.getGroupsBeforeSort(this),
PrefsUtil.getRecycleBinBottomSort(this));
} else {
*/
sortDialogFragment =
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(getContext()),
PreferencesUtil.getAscendingSort(getContext()),
PreferencesUtil.getGroupsBeforeSort(getContext()));
//}
sortDialogFragment.show(getChildFragmentManager(), "sortDialog");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
PwNode newNode = (PwNode) data.getSerializableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
if (newNode != null) {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter.addNode(newNode);
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode);
mAdapter.rebuildList(mCurrentGroup);
}
} else {
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
}
}
break;
}
}
public boolean isEmpty() {
return mAdapter == null || mAdapter.getItemCount() <= 0;
}
public void addNode(PwNode newNode) {
mAdapter.addNode(newNode);
}
public void updateNode(PwNode oldNode, PwNode newNode) {
mAdapter.updateNode(oldNode, newNode);
}
public void removeNode(PwNode pwNode) {
mAdapter.removeNode(pwNode);
}
public PwGroup getMainGroup() {
return mCurrentGroup;
}
public interface OnScrollListener {
/**
* Callback method to be invoked when the RecyclerView has been scrolled. This will be
* called after the scroll has completed.
*
* @param dy The amount of vertical scroll.
*/
void onScrolled(int dy);
}
}

View File

@@ -44,7 +44,12 @@ public abstract class LockingActivity extends StylishActivity {
private ScreenReceiver screenReceiver;
private boolean exitLock;
protected static void recordFirstTimeBeforeLaunch(Activity activity) {
/**
* Called to start a record time,
* Generally used for a first launch or for a fragment change
*/
protected static void startRecordTime(Activity activity) {
TimeoutHelper.recordTime(activity);
}

View File

@@ -20,8 +20,6 @@
package com.kunzisoft.keepass.adapters;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
@@ -30,7 +28,7 @@ class EntryViewHolder extends BasicViewHolder {
EntryViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.entry_container);
icon = (ImageView) itemView.findViewById(R.id.entry_icon);
text = (TextView) itemView.findViewById(R.id.entry_text);
icon = itemView.findViewById(R.id.entry_icon);
text = itemView.findViewById(R.id.entry_text);
}
}

View File

@@ -20,8 +20,6 @@
package com.kunzisoft.keepass.adapters;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
@@ -30,7 +28,7 @@ class GroupViewHolder extends BasicViewHolder {
GroupViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.group_container);
icon = (ImageView) itemView.findViewById(R.id.group_icon);
text = (TextView) itemView.findViewById(R.id.group_text);
icon = itemView.findViewById(R.id.group_icon);
text = itemView.findViewById(R.id.group_text);
}
}

View File

@@ -28,7 +28,7 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.util.SortedListAdapterCallback;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@@ -47,12 +47,13 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private Context context;
private LayoutInflater inflater;
private MenuInflater menuInflater;
private float textSize;
private SortNodeEnum listSort;
private boolean groupsBeforeSort;
private boolean ascendingSort;
private OnNodeClickCallback onNodeClickCallback;
private NodeClickCallback nodeClickCallback;
private NodeMenuListener nodeMenuListener;
private boolean activateContextMenu;
@@ -63,13 +64,11 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
* Create node list adapter with contextMenu or not
* @param context Context to use
*/
public NodeAdapter(final Context context) {
public NodeAdapter(final Context context, MenuInflater menuInflater) {
this.inflater = LayoutInflater.from(context);
this.menuInflater = menuInflater;
this.context = context;
this.textSize = PreferencesUtil.getListTextSize(context);
this.listSort = PreferencesUtil.getListSort(context);
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
assignPreferences();
this.activateContextMenu = false;
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
@@ -101,11 +100,19 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
this.activateContextMenu = activate;
}
private void assignPreferences() {
this.textSize = PreferencesUtil.getListTextSize(context);
this.listSort = PreferencesUtil.getListSort(context);
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
}
/**
* Rebuild the list by clear and build children from the group
*/
public void rebuildList(PwGroup group) {
this.nodeSortedList.clear();
assignPreferences();
if (group != null) {
this.nodeSortedList.addAll(group.getDirectChildren());
}
@@ -208,8 +215,8 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
/**
* Assign a listener when a node is clicked
*/
public void setOnNodeClickListener(OnNodeClickCallback onNodeClickCallback) {
this.onNodeClickCallback = onNodeClickCallback;
public void setOnNodeClickListener(NodeClickCallback nodeClickCallback) {
this.nodeClickCallback = nodeClickCallback;
}
/**
@@ -222,7 +229,7 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
/**
* Callback listener to redefine to do an action when a node is click
*/
public interface OnNodeClickCallback {
public interface NodeClickCallback {
void onNodeClick(PwNode node);
}
@@ -232,6 +239,8 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
public interface NodeMenuListener {
boolean onOpenMenuClick(PwNode node);
boolean onEditMenuClick(PwNode node);
boolean onCopyMenuClick(PwNode node);
boolean onMoveMenuClick(PwNode node);
boolean onDeleteMenuClick(PwNode node);
}
@@ -247,8 +256,8 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
@Override
public void onClick(View v) {
if (onNodeClickCallback != null)
onNodeClickCallback.onNodeClick(node);
if (nodeClickCallback != null)
nodeClickCallback.onNodeClick(node);
}
}
@@ -257,10 +266,6 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
*/
private class ContextMenuBuilder implements View.OnCreateContextMenuListener {
private static final int MENU_OPEN = Menu.FIRST;
private static final int MENU_EDIT = MENU_OPEN + 1;
private static final int MENU_DELETE = MENU_EDIT + 1;
private PwNode node;
private NodeMenuListener menuListener;
@@ -271,15 +276,25 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
MenuItem clearMenu = contextMenu.add(Menu.NONE, MENU_OPEN, Menu.NONE, R.string.menu_open);
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
menuInflater.inflate(R.menu.node_menu, contextMenu);
MenuItem menuItem = contextMenu.findItem(R.id.menu_open);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
if (!App.getDB().isReadOnly() && !node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
// Edition
clearMenu = contextMenu.add(Menu.NONE, MENU_EDIT, Menu.NONE, R.string.menu_edit);
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
menuItem = contextMenu.findItem(R.id.menu_edit);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Copy (not for group)
if (node.getType().equals(PwNode.Type.ENTRY)) {
menuItem = contextMenu.findItem(R.id.menu_copy);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}
// Move
menuItem = contextMenu.findItem(R.id.menu_move);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Deletion
clearMenu = contextMenu.add(Menu.NONE, MENU_DELETE, Menu.NONE, R.string.menu_delete);
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
menuItem = contextMenu.findItem(R.id.menu_delete);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}
}
@@ -289,11 +304,15 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
if (menuListener == null)
return false;
switch ( item.getItemId() ) {
case MENU_OPEN:
case R.id.menu_open:
return menuListener.onOpenMenuClick(node);
case MENU_EDIT:
case R.id.menu_edit:
return menuListener.onEditMenuClick(node);
case MENU_DELETE:
case R.id.menu_copy:
return menuListener.onCopyMenuClick(node);
case R.id.menu_move:
return menuListener.onMoveMenuClick(node);
case R.id.menu_delete:
return menuListener.onDeleteMenuClick(node);
default:
return false;

View File

@@ -51,6 +51,9 @@ import java.io.OutputStream;
import java.io.SyncFailedException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
public class Database {
@@ -659,6 +662,44 @@ public class Database {
}
}
/**
* @return A duplicate entry with the same values, a new UUID,
* @param entryToCopy
* @param newParent
*/
public @Nullable PwEntry copyEntry(PwEntry entryToCopy, PwGroup newParent) {
try {
// TODO encapsulate
switch (getPwDatabase().getVersion()) {
case V3:
PwEntryV3 entryV3Copied = ((PwEntryV3) entryToCopy).clone();
entryV3Copied.setUUID(UUID.randomUUID());
entryV3Copied.setParent((PwGroupV3) newParent);
addEntryTo(entryV3Copied, newParent);
return entryV3Copied;
case V4:
PwEntryV4 entryV4Copied = ((PwEntryV4) entryToCopy).clone();
entryV4Copied.setUUID(UUID.randomUUID());
entryV4Copied.setParent((PwGroupV4) newParent);
addEntryTo(entryV4Copied, newParent);
return entryV4Copied;
}
} catch (Exception e) {
Log.e(TAG, "This version of PwEntry can't be updated", e);
}
return null;
}
public void moveEntry(PwEntry entryToMove, PwGroup newParent) {
removeEntryFrom(entryToMove, entryToMove.parent);
addEntryTo(entryToMove, newParent);
}
public void moveGroup(PwGroup groupToMove, PwGroup newParent) {
removeGroupFrom(groupToMove, groupToMove.parent);
addGroupTo(groupToMove, newParent);
}
public void deleteEntry(PwEntry entry) {
try {
switch (getPwDatabase().getVersion()) {

View File

@@ -119,6 +119,13 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
parent = prt;
}
/**
* @return true if parent is present (can be a root or a detach element)
*/
public boolean containsParent() {
return getParent() != null;
}
public PwDate getCreationTime() {
return creation;
}

View File

@@ -0,0 +1,42 @@
package com.kunzisoft.keepass.database.action;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
public abstract class ActionDatabaseRunnable extends RunnableOnFinish {
protected Database mDb;
protected Context mContext;
protected boolean mDontSave;
public ActionDatabaseRunnable(Context context, Database db, OnFinishRunnable finish, boolean dontSave) {
super(finish);
this.mDb = db;
this.mContext = context;
this.mDontSave = dontSave;
this.mFinish = new AfterActionRunnable(finish);
}
@Override
public void run() {
// Commit to disk
SaveDatabaseRunnable save = new SaveDatabaseRunnable(mContext, mDb, mFinish, mDontSave);
save.run();
}
abstract protected void onFinish(boolean success, String message);
private class AfterActionRunnable extends OnFinishRunnable {
AfterActionRunnable(OnFinishRunnable finish) {
super(finish);
}
@Override
public void run() {
onFinish(mSuccess, mMessage);
}
}
}

View File

@@ -30,77 +30,56 @@ import com.kunzisoft.keepass.utils.UriUtil;
import java.io.IOException;
import java.io.InputStream;
public class AssignPasswordInDBRunnable extends RunnableOnFinish {
public class AssignPasswordInDBRunnable extends ActionDatabaseRunnable {
private String mPassword;
private Uri mKeyfile;
private Database mDb;
private boolean mDontSave;
private Context ctx;
private byte[] mBackupKey;
public AssignPasswordInDBRunnable(Context ctx, Database db, String password, Uri keyfile, OnFinishRunnable finish) {
this(ctx, db, password, keyfile, finish, false);
}
public AssignPasswordInDBRunnable(Context ctx, Database db, String password, Uri keyfile, OnFinishRunnable finish, boolean dontSave) {
super(finish);
super(ctx, db, finish, dontSave);
mDb = db;
mPassword = password;
mKeyfile = keyfile;
mDontSave = dontSave;
this.ctx = ctx;
this.mPassword = password;
this.mKeyfile = keyfile;
}
@Override
public void run() {
PwDatabase pm = mDb.getPwDatabase();
byte[] backupKey = new byte[pm.getMasterKey().length];
System.arraycopy(pm.getMasterKey(), 0, backupKey, 0, backupKey.length);
mBackupKey = new byte[pm.getMasterKey().length];
System.arraycopy(pm.getMasterKey(), 0, mBackupKey, 0, mBackupKey.length);
// Set key
try {
InputStream is = UriUtil.getUriInputStream(ctx, mKeyfile);
InputStream is = UriUtil.getUriInputStream(mContext, mKeyfile);
pm.retrieveMasterKey(mPassword, is);
} catch (InvalidKeyFileException e) {
erase(backupKey);
erase(mBackupKey);
finish(false, e.getMessage());
return;
} catch (IOException e) {
erase(backupKey);
erase(mBackupKey);
finish(false, e.getMessage());
return;
}
// Save Database
mFinish = new AfterSave(backupKey, mFinish);
SaveDBRunnable save = new SaveDBRunnable(ctx, mDb, mFinish, mDontSave);
save.run();
super.run();
}
private class AfterSave extends OnFinishRunnable {
private byte[] mBackup;
public AfterSave(byte[] backup, OnFinishRunnable finish) {
super(finish);
mBackup = backup;
}
@Override
public void run() {
if ( ! mSuccess ) {
// Erase the current master key
erase(mDb.getPwDatabase().getMasterKey());
mDb.getPwDatabase().setMasterKey(mBackup);
}
super.run();
}
}
@Override
protected void onFinish(boolean success, String message) {
if (!success) {
// Erase the current master key
erase(mDb.getPwDatabase().getMasterKey());
mDb.getPwDatabase().setMasterKey(mBackupKey);
}
}
/** Overwrite the array as soon as we don't need it to avoid keeping the extra data in memory
* @param array

View File

@@ -56,7 +56,7 @@ public class CreateDBRunnable extends RunnableOnFinish {
App.clearShutdown();
// Commit changes
SaveDBRunnable save = new SaveDBRunnable(ctx, db, mFinish, mDontSave);
SaveDatabaseRunnable save = new SaveDatabaseRunnable(ctx, db, mFinish, mDontSave);
mFinish = null;
save.run();
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
/** Task to delete entries
* @author bpellin
*
*/
public class DeleteEntryRunnable extends RunnableOnFinish {
private Database mDb;
private PwEntry mEntry;
private boolean mDontSave;
private Context ctx;
public DeleteEntryRunnable(Context ctx, Database db, PwEntry entry, OnFinishRunnable finish) {
this(ctx, db, entry, finish, false);
}
public DeleteEntryRunnable(Context ctx, Database db, PwEntry entry, OnFinishRunnable finish, boolean dontSave) {
super(finish);
this.mDb = db;
this.mEntry = entry;
this.mDontSave = dontSave;
this.ctx = ctx;
}
@Override
public void run() {
PwGroup parent = mEntry.getParent();
// Remove Entry from parent
boolean recycle = mDb.canRecycle(mEntry);
if (recycle) {
mDb.recycle(mEntry);
}
else {
mDb.deleteEntry(mEntry);
}
// Save
mFinish = new AfterDelete(mFinish, parent, mEntry, recycle);
// Commit database
SaveDBRunnable save = new SaveDBRunnable(ctx, mDb, mFinish, mDontSave);
save.run();
}
private class AfterDelete extends OnFinishRunnable {
private PwGroup mParent;
private PwEntry mEntry;
private boolean recycled;
AfterDelete(OnFinishRunnable finish, PwGroup parent, PwEntry entry, boolean r) {
super(finish);
mParent = parent;
mEntry = entry;
recycled = r;
}
@Override
public void run() {
if ( !mSuccess ) {
if (recycled) {
mDb.undoRecycle(mEntry, mParent);
}
else {
mDb.undoDeleteEntry(mEntry, mParent);
}
}
// TODO Callback after delete entry
super.run();
}
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action;
import android.content.Context;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import java.util.ArrayList;
import java.util.List;
public class DeleteGroupRunnable extends RunnableOnFinish {
private Context mContext;
private Database mDb;
private PwGroup<PwGroup, PwGroup, PwEntry> mGroup;
private boolean mDontSave;
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, OnFinishRunnable finish) {
super(finish);
setMembers(ctx, db, group, false);
}
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, OnFinishRunnable finish, boolean dontSave) {
super(finish);
setMembers(ctx, db, group, dontSave);
}
private void setMembers(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, boolean dontSave) {
mDb = db;
mGroup = group;
mContext = ctx;
mDontSave = dontSave;
}
@Override
public void run() {
PwGroup parent = mGroup.getParent();
// Remove Group from parent
boolean recycle = mDb.canRecycle(mGroup);
if (recycle) {
mDb.recycle(mGroup);
}
else {
// TODO tests
// Remove child entries
List<PwEntry> childEnt = new ArrayList<>(mGroup.getChildEntries()); // TODO new Methods
for ( int i = 0; i < childEnt.size(); i++ ) {
DeleteEntryRunnable task = new DeleteEntryRunnable(mContext, mDb, childEnt.get(i), null, true);
task.run();
}
// Remove child groups
List<PwGroup> childGrp = new ArrayList<>(mGroup.getChildGroups());
for ( int i = 0; i < childGrp.size(); i++ ) {
DeleteGroupRunnable task = new DeleteGroupRunnable(mContext, mDb, childGrp.get(i), null, true);
task.run();
}
mDb.deleteGroup(mGroup);
// Remove from PwDatabaseV3
// TODO ENcapsulate
mDb.getPwDatabase().getGroups().remove(mGroup);
}
// Save
mFinish = new AfterDelete(mFinish, parent, mGroup, recycle);
// Commit Database
SaveDBRunnable save = new SaveDBRunnable(mContext, mDb, mFinish, mDontSave);
save.run();
}
private class AfterDelete extends OnFinishRunnable {
private PwGroup mParent;
private PwGroup mGroup;
private boolean recycled;
AfterDelete(OnFinishRunnable finish, PwGroup parent, PwGroup mGroup, boolean recycle) {
super(finish);
this.mParent = parent;
this.mGroup = mGroup;
this.recycled = recycle;
}
public void run() {
if ( !mSuccess ) {
if (recycled) {
mDb.undoRecycle(mGroup, mParent);
}
else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
App.setShutdown();
// TODO TEST pm.undoDeleteGroup(mGroup, mParent);
}
}
// TODO Callback after delete group
super.run();
}
}
}

View File

@@ -24,6 +24,7 @@ import android.net.Uri;
import java.io.Serializable;
public class FileOnFinishRunnable extends OnFinishRunnable implements Serializable {
private Uri mFilename = null;
protected FileOnFinishRunnable mOnFinish;

View File

@@ -76,11 +76,29 @@ public class OnFinishRunnable implements Runnable {
}
}
// TODO Move
/**
* ONLY to use in UIThread, typically in an Activity, Fragment or a Service
* @param ctx Context to show the message
*/
protected void displayMessage(Context ctx) {
if ( mMessage != null && mMessage.length() > 0 ) {
Toast.makeText(ctx, mMessage, Toast.LENGTH_LONG).show();
}
}
public boolean isSuccess() {
return mSuccess;
}
public void setSuccess(boolean mSuccess) {
this.mSuccess = mSuccess;
}
public String getMessage() {
return mMessage;
}
public void setMessage(String mMessage) {
this.mMessage = mMessage;
}
}

View File

@@ -26,13 +26,13 @@ import com.kunzisoft.keepass.database.exception.PwDbOutputException;
import java.io.IOException;
public class SaveDBRunnable extends RunnableOnFinish {
public class SaveDatabaseRunnable extends RunnableOnFinish {
private Context mCtx;
private Database mDb;
private boolean mDontSave;
public SaveDBRunnable(Context ctx, Database db, OnFinishRunnable finish, boolean dontSave) {
public SaveDatabaseRunnable(Context ctx, Database db, OnFinishRunnable finish, boolean dontSave) {
super(finish);
this.mDb = db;
@@ -40,7 +40,7 @@ public class SaveDBRunnable extends RunnableOnFinish {
this.mCtx = ctx;
}
public SaveDBRunnable(Context ctx, Database db, OnFinishRunnable finish) {
public SaveDatabaseRunnable(Context ctx, Database db, OnFinishRunnable finish) {
this(ctx, db, finish, false);
}

View File

@@ -0,0 +1,33 @@
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.action.ActionDatabaseRunnable;
abstract class ActionNodeDatabaseRunnable extends ActionDatabaseRunnable {
private AfterActionNodeOnFinish callbackRunnable;
public ActionNodeDatabaseRunnable(Context context, Database db, AfterActionNodeOnFinish finish, boolean dontSave) {
super(context, db, finish, dontSave);
this.callbackRunnable = finish;
}
/**
* Callback method who return the node(s) modified after an action
* - Add : @param oldNode NULL, @param newNode CreatedNode
* - Copy : @param oldNode NodeToCopy, @param newNode NodeCopied
* - Delete : @param oldNode NodeToDelete, @param NULL
* - Move : @param oldNode NULL, @param NodeToMove
* - Update : @param oldNode NodeToUpdate, @param NodeUpdated
*/
protected void callbackNodeAction(boolean success, String message, PwNode oldNode, PwNode newNode) {
if (callbackRunnable != null) {
callbackRunnable.setSuccess(success);
callbackRunnable.setMessage(message);
callbackRunnable.run(oldNode, newNode);
}
}
}

View File

@@ -17,57 +17,40 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action;
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
public class AddEntryRunnable extends RunnableOnFinish {
public class AddEntryRunnable extends ActionNodeDatabaseRunnable {
protected Database mDb;
private PwEntry mEntry;
private Context ctx;
private boolean mDontSave;
private PwEntry mNewEntry;
public AddEntryRunnable(Context ctx, Database db, PwEntry entry, OnFinishRunnable finish) {
this(ctx, db, entry, finish, false);
public AddEntryRunnable(Context ctx, Database db, PwEntry entryToAdd, AfterActionNodeOnFinish finish) {
this(ctx, db, entryToAdd, finish, false);
}
public AddEntryRunnable(Context ctx, Database db, PwEntry entry, OnFinishRunnable finish, boolean dontSave) {
super(finish);
public AddEntryRunnable(Context ctx, Database db, PwEntry entryToAdd, AfterActionNodeOnFinish finish, boolean dontSave) {
super(ctx, db, finish, dontSave);
this.mDb = db;
this.mEntry = entry;
this.ctx = ctx;
this.mDontSave = dontSave;
this.mFinish = new AfterAdd(mFinish);
this.mNewEntry = entryToAdd;
}
@Override
public void run() {
mDb.addEntryTo(mEntry, mEntry.getParent());
mDb.addEntryTo(mNewEntry, mNewEntry.getParent());
// Commit to disk
SaveDBRunnable save = new SaveDBRunnable(ctx, mDb, mFinish, mDontSave);
save.run();
super.run();
}
private class AfterAdd extends OnFinishRunnable {
AfterAdd(OnFinishRunnable finish) {
super(finish);
}
@Override
public void run() {
if ( !mSuccess ) {
mDb.removeEntryFrom(mEntry, mEntry.getParent());
}
// TODO if add entry callback
super.run();
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
mDb.removeEntryFrom(mNewEntry, mNewEntry.getParent());
}
callbackNodeAction(success, message, null, mNewEntry);
}
}

View File

@@ -17,34 +17,25 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action;
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
public class AddGroupRunnable extends RunnableOnFinish {
public class AddGroupRunnable extends ActionNodeDatabaseRunnable {
protected Database mDb;
private PwGroup mNewGroup;
private Context ctx;
private boolean mDontSave;
public AddGroupRunnable(Context ctx, Database db, PwGroup newGroup, AfterActionNodeOnFinish afterAddNode) {
this(ctx, db, newGroup, afterAddNode, false);
}
public AddGroupRunnable(Context ctx, Database db, PwGroup newGroup, AfterActionNodeOnFinish afterAddNode,
boolean dontSave) {
super(afterAddNode);
public AddGroupRunnable(Context ctx, Database db, PwGroup newGroup, AfterActionNodeOnFinish afterAddNode, boolean dontSave) {
super(ctx, db, afterAddNode, dontSave);
this.mDb = db;
this.mNewGroup = newGroup;
this.mDontSave = dontSave;
this.ctx = ctx;
this.mFinish = new AfterAdd(mFinish);
}
@Override
@@ -52,28 +43,14 @@ public class AddGroupRunnable extends RunnableOnFinish {
mDb.addGroupTo(mNewGroup, mNewGroup.getParent());
// Commit to disk
SaveDBRunnable save = new SaveDBRunnable(ctx, mDb, mFinish, mDontSave);
save.run();
super.run();
}
private class AfterAdd extends OnFinishRunnable {
AfterAdd(OnFinishRunnable finish) {
super(finish);
}
@Override
public void run() {
if ( !mSuccess ) {
mDb.removeGroupFrom(mNewGroup, mNewGroup.getParent());
}
// TODO Better callback
AfterActionNodeOnFinish afterAddNode =
(AfterActionNodeOnFinish) super.mOnFinish;
afterAddNode.mSuccess = mSuccess;
afterAddNode.mMessage = mMessage;
afterAddNode.run(null, mNewGroup);
}
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
mDb.removeGroupFrom(mNewGroup, mNewGroup.getParent());
}
callbackNodeAction(success, message, null, mNewGroup);
}
}

View File

@@ -17,16 +17,20 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action;
package com.kunzisoft.keepass.database.action.node;
import android.os.Handler;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.action.OnFinishRunnable;
import javax.annotation.Nullable;
public abstract class AfterActionNodeOnFinish extends OnFinishRunnable {
public AfterActionNodeOnFinish(Handler handler) {
super(handler);
public AfterActionNodeOnFinish() {
super(new Handler());
}
public abstract void run(PwNode oldNode, PwNode newNode);
public abstract void run(@Nullable PwNode oldNode, @Nullable PwNode newNode);
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import android.util.Log;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
public class CopyEntryRunnable extends ActionNodeDatabaseRunnable {
private static final String TAG = CopyEntryRunnable.class.getName();
private PwEntry mEntryToCopy;
private PwEntry mEntryCopied;
private PwGroup mNewParent;
public CopyEntryRunnable(Context context, Database db, PwEntry oldE, PwGroup newParent, AfterActionNodeOnFinish afterAddNode) {
this(context, db, oldE, newParent, afterAddNode, false);
}
public CopyEntryRunnable(Context context, Database db, PwEntry oldE, PwGroup newParent, AfterActionNodeOnFinish afterAddNode, boolean dontSave) {
super(context, db, afterAddNode, dontSave);
this.mEntryToCopy = oldE;
this.mNewParent = newParent;
}
@Override
public void run() {
// Update entry with new values
mEntryCopied = mDb.copyEntry(mEntryToCopy, mNewParent);
if (mEntryCopied != null) {
mEntryCopied.touch(true, true);
// Commit to disk
super.run();
} else {
Log.e(TAG, "Unable to create a copy of the entry");
}
}
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
// If we fail to save, try to delete the copy
try {
mDb.deleteEntry(mEntryCopied);
} catch (Exception e) {
Log.i(TAG, "Unable to delete the copied entry");
}
}
callbackNodeAction(success, message, mEntryToCopy, mEntryCopied);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
public class DeleteEntryRunnable extends ActionNodeDatabaseRunnable {
private PwEntry mEntryToDelete;
private PwGroup mParent;
private boolean mRecycle;
public DeleteEntryRunnable(Context ctx, Database db, PwEntry entry, AfterActionNodeOnFinish finish) {
this(ctx, db, entry, finish, false);
}
public DeleteEntryRunnable(Context ctx, Database db, PwEntry entry, AfterActionNodeOnFinish finish, boolean dontSave) {
super(ctx, db, finish, dontSave);
this.mEntryToDelete = entry;
}
@Override
public void run() {
mParent = mEntryToDelete.getParent();
// Remove Entry from parent
mRecycle = mDb.canRecycle(mEntryToDelete);
if (mRecycle) {
mDb.recycle(mEntryToDelete);
}
else {
mDb.deleteEntry(mEntryToDelete);
}
// Commit database
super.run();
}
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
if (mRecycle) {
mDb.undoRecycle(mEntryToDelete, mParent);
}
else {
mDb.undoDeleteEntry(mEntryToDelete, mParent);
}
}
callbackNodeAction(success, message, mEntryToDelete, null);
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import java.util.ArrayList;
import java.util.List;
public class DeleteGroupRunnable extends ActionNodeDatabaseRunnable {
private PwGroup<PwGroup, PwGroup, PwEntry> mGroupToDelete;
private PwGroup mParent;
private boolean mRecycle;
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish) {
this(ctx, db, group, finish, false);
}
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish, boolean dontSave) {
super(ctx, db, finish, dontSave);
mGroupToDelete = group;
}
@Override
public void run() {
mParent = mGroupToDelete.getParent();
// Remove Group from parent
mRecycle = mDb.canRecycle(mGroupToDelete);
if (mRecycle) {
mDb.recycle(mGroupToDelete);
}
else {
// TODO tests
// Remove child entries
List<PwEntry> childEnt = new ArrayList<>(mGroupToDelete.getChildEntries()); // TODO new Methods
for ( int i = 0; i < childEnt.size(); i++ ) {
DeleteEntryRunnable task = new DeleteEntryRunnable(mContext, mDb, childEnt.get(i), null, true);
task.run();
}
// Remove child groups
List<PwGroup> childGrp = new ArrayList<>(mGroupToDelete.getChildGroups());
for ( int i = 0; i < childGrp.size(); i++ ) {
DeleteGroupRunnable task = new DeleteGroupRunnable(mContext, mDb, childGrp.get(i), null, true);
task.run();
}
mDb.deleteGroup(mGroupToDelete);
// Remove from PwDatabaseV3
// TODO ENcapsulate
mDb.getPwDatabase().getGroups().remove(mGroupToDelete);
}
// Commit Database
super.run();
}
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
if (mRecycle) {
mDb.undoRecycle(mGroupToDelete, mParent);
}
else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
App.setShutdown();
// TODO TEST pm.undoDeleteGroup(mGroup, mParent);
}
}
callbackNodeAction(success, message, mGroupToDelete, null);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import android.util.Log;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
public class MoveEntryRunnable extends ActionNodeDatabaseRunnable {
private static final String TAG = MoveEntryRunnable.class.getName();
private PwEntry mEntryToMove;
private PwGroup mOldParent;
private PwGroup mNewParent;
public MoveEntryRunnable(Context context, Database db, PwEntry oldE, PwGroup newParent, AfterActionNodeOnFinish afterAddNode) {
this(context, db, oldE, newParent, afterAddNode, false);
}
public MoveEntryRunnable(Context context, Database db, PwEntry oldE, PwGroup newParent, AfterActionNodeOnFinish afterAddNode, boolean dontSave) {
super(context, db, afterAddNode, dontSave);
this.mEntryToMove = oldE;
this.mNewParent = newParent;
}
@Override
public void run() {
// Move entry in new parent
mOldParent = mEntryToMove.getParent();
mDb.moveEntry(mEntryToMove, mNewParent);
if (mEntryToMove != null) {
mEntryToMove.touch(true, true);
// Commit to disk
super.run();
} else {
Log.e(TAG, "Unable to create a copy of the entry");
}
}
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
// If we fail to save, try to remove in the first place
try {
mDb.moveEntry(mEntryToMove, mOldParent);
} catch (Exception e) {
Log.i(TAG, "Unable to replace the entry");
}
}
callbackNodeAction(success, message, null, mEntryToMove);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import android.util.Log;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
public class MoveGroupRunnable extends ActionNodeDatabaseRunnable {
private static final String TAG = MoveGroupRunnable.class.getName();
private PwGroup mGroupToMove;
private PwGroup mOldParent;
private PwGroup mNewParent;
public MoveGroupRunnable(Context context, Database db, PwGroup groupToMove, PwGroup newParent, AfterActionNodeOnFinish afterAddNode) {
this(context, db, groupToMove, newParent, afterAddNode, false);
}
public MoveGroupRunnable(Context context, Database db, PwGroup groupToMove, PwGroup newParent, AfterActionNodeOnFinish afterAddNode, boolean dontSave) {
super(context, db, afterAddNode, dontSave);
this.mGroupToMove = groupToMove;
this.mNewParent = newParent;
}
@Override
public void run() {
mOldParent = mGroupToMove.getParent();
// Move group in new parent if not in the current group
if (!mGroupToMove.equals(mNewParent)
&& !mNewParent.isContainedIn(mGroupToMove)) {
mDb.moveGroup(mGroupToMove, mNewParent);
if (mGroupToMove != null) {
mGroupToMove.touch(true, true);
// Commit to disk
super.run();
} else {
Log.e(TAG, "Unable to create a copy of the group");
}
} else {
// Only finish thread
mFinish.setResult(false);
String message = mContext.getString(R.string.error_move_folder_in_itself);
Log.e(TAG, message);
mFinish.setMessage(message);
mFinish.run();
}
}
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
// If we fail to save, try to move in the first place
try {
mDb.moveGroup(mGroupToMove, mOldParent);
} catch (Exception e) {
Log.i(TAG, "Unable to replace the group");
}
}
callbackNodeAction(success, message, null, mGroupToMove);
}
}

View File

@@ -17,68 +17,47 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action;
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
public class UpdateEntryRunnable extends RunnableOnFinish {
public class UpdateEntryRunnable extends ActionNodeDatabaseRunnable {
private Database mDb;
private PwEntry mOldE;
private PwEntry mNewE;
private Context ctx;
private boolean mDontSave;
private PwEntry mOldEntry;
private PwEntry mNewEntry;
private PwEntry mBackupEntry;
public UpdateEntryRunnable(Context ctx, Database db, PwEntry oldE, PwEntry newE, OnFinishRunnable finish) {
public UpdateEntryRunnable(Context ctx, Database db, PwEntry oldE, PwEntry newE, AfterActionNodeOnFinish finish) {
this(ctx, db, oldE, newE, finish, false);
}
public UpdateEntryRunnable(Context ctx, Database db, PwEntry oldE, PwEntry newE, OnFinishRunnable finish, boolean dontSave) {
super(finish);
this.mDb = db;
this.mOldE = oldE;
this.mNewE = newE;
this.ctx = ctx;
this.mDontSave = dontSave;
public UpdateEntryRunnable(Context ctx, Database db, PwEntry oldE, PwEntry newE, AfterActionNodeOnFinish finish, boolean dontSave) {
super(ctx, db, finish, dontSave);
this.mOldEntry = oldE;
this.mNewEntry = newE;
// Keep backup of original values in case save fails
PwEntry backup;
backup = mOldE.clone();
mFinish = new AfterUpdate(backup, finish);
this.mBackupEntry = mOldEntry.clone();
}
@Override
public void run() {
// Update entry with new values
mDb.updateEntry(mOldE, mNewE);
mOldE.touch(true, true);
mDb.updateEntry(mOldEntry, mNewEntry);
mOldEntry.touch(true, true);
// Commit to disk
SaveDBRunnable save = new SaveDBRunnable(ctx, mDb, mFinish, mDontSave);
save.run();
super.run();
}
private class AfterUpdate extends OnFinishRunnable {
private PwEntry mBackup;
AfterUpdate(PwEntry backup, OnFinishRunnable finish) {
super(finish);
mBackup = backup;
}
@Override
public void run() {
if ( !mSuccess ) {
// If we fail to save, back out changes to global structure
mDb.updateEntry(mOldE, mBackup);
}
// TODO Callback for update entry
super.run();
}
}
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
// If we fail to save, back out changes to global structure
mDb.updateEntry(mOldEntry, mBackupEntry);
}
callbackNodeAction(success, message, mOldEntry, mNewEntry);
}
}

View File

@@ -17,39 +17,30 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action;
package com.kunzisoft.keepass.database.action.node;
import android.content.Context;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
public class UpdateGroupRunnable extends RunnableOnFinish {
public class UpdateGroupRunnable extends ActionNodeDatabaseRunnable {
private Database mDb;
private PwGroup mOldGroup;
private PwGroup mNewGroup;
private Context ctx;
private boolean mDontSave;
private PwGroup mBackupGroup;
public UpdateGroupRunnable(Context ctx, Database db, PwGroup oldGroup, PwGroup newGroup, AfterActionNodeOnFinish finish) {
this(ctx, db, oldGroup, newGroup, finish, false);
}
public UpdateGroupRunnable(Context ctx, Database db, PwGroup oldGroup, PwGroup newGroup, AfterActionNodeOnFinish finish, boolean dontSave) {
super(finish);
super(ctx, db, finish, dontSave);
this.mDb = db;
this.mOldGroup = oldGroup;
this.mNewGroup = newGroup;
this.ctx = ctx;
this.mDontSave = dontSave;
// Keep backup of original values in case save fails
PwGroup backup;
backup = mOldGroup.clone();
this.mFinish = new AfterUpdate(backup, finish);
this.mBackupGroup = mOldGroup.clone();
}
@Override
@@ -59,30 +50,15 @@ public class UpdateGroupRunnable extends RunnableOnFinish {
mOldGroup.touch(true, true);
// Commit to disk
new SaveDBRunnable(ctx, mDb, mFinish, mDontSave).run();
super.run();
}
private class AfterUpdate extends OnFinishRunnable {
private PwGroup mBackup;
AfterUpdate(PwGroup backup, OnFinishRunnable finish) {
super(finish);
mBackup = backup;
}
@Override
public void run() {
if ( !mSuccess ) {
// If we fail to save, back out changes to global structure
mDb.updateGroup(mOldGroup, mBackup);
}
// TODO Better callback
AfterActionNodeOnFinish afterActionNodeOnFinish =
(AfterActionNodeOnFinish) super.mOnFinish;
afterActionNodeOnFinish.mSuccess = mSuccess;
afterActionNodeOnFinish.mMessage = mMessage;
afterActionNodeOnFinish.run(mOldGroup, mNewGroup);
@Override
protected void onFinish(boolean success, String message) {
if ( !success ) {
// If we fail to save, back out changes to global structure
mDb.updateGroup(mOldGroup, mBackupGroup);
}
callbackNodeAction(success, message, mOldGroup, mNewGroup);
}
}

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.search;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
@@ -36,9 +35,9 @@ import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.utils.MenuUtil;
public class SearchResultsActivity extends ListNodesActivity {
import javax.annotation.Nullable;
private RecyclerView listView;
public class SearchResultsActivity extends ListNodesActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -52,23 +51,24 @@ public class SearchResultsActivity extends ListNodesActivity {
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
groupNameView = findViewById(R.id.group_name);
attachFragmentToContentView();
listView = findViewById(R.id.nodes_list);
View notFoundView = findViewById(R.id.not_found_container);
View listContainer = findViewById(R.id.nodes_list_fragment_container);
if ( mCurrentGroup == null || mCurrentGroup.numbersOfChildEntries() < 1 ) {
listView.setVisibility(View.GONE);
listContainer.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listView.setVisibility(View.VISIBLE);
listContainer.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
setGroupTitle();
}
@Override
protected PwGroup initCurrentGroup() {
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
Database mDb = App.getDB();
// Likely the app has been killed exit the activity
if ( ! mDb.getLoaded() ) {
@@ -77,17 +77,11 @@ public class SearchResultsActivity extends ListNodesActivity {
return mDb.search(getSearchStr(getIntent()).trim());
}
@Override
protected RecyclerView defineNodeList() {
return listView;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.tree, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;

View File

@@ -112,8 +112,8 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
@Override
public void onNestedPreferenceSelected(NestedSettingsFragment.Screen key) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right)
.setCustomAnimations(R.anim.slide_alpha_in_right, R.anim.slide_alpha_out_left,
R.anim.slide_alpha_in_left, R.anim.slide_alpha_out_right)
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED)
.addToBackStack(TAG_NESTED)
.commit();

View File

@@ -24,7 +24,7 @@ import android.view.View;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.action.OnFinishRunnable;
import com.kunzisoft.keepass.database.action.SaveDBRunnable;
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable;
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
@@ -47,15 +47,15 @@ public abstract class DatabaseSavePreferenceDialogFragmentCompat extends InputP
assert getActivity() != null;
if (database != null && afterSaveDatabase != null) {
SaveDBRunnable saveDBRunnable = new SaveDBRunnable(getContext(),
SaveDatabaseRunnable saveDatabaseRunnable = new SaveDatabaseRunnable(getContext(),
database,
afterSaveDatabase);
saveDBRunnable.setUpdateProgressTaskStatus(
saveDatabaseRunnable.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(getContext(),
SaveDatabaseProgressTaskDialogFragment.start(
getActivity().getSupportFragmentManager())
));
new Thread(saveDBRunnable).start();
new Thread(saveDatabaseRunnable).start();
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stylish;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.support.v4.app.Fragment;
import android.support.v7.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
public abstract class StylishFragment extends Fragment {
protected @StyleRes int themeId;
protected Context contextThemed; // TODO small ref
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context != null) {
this.themeId = Stylish.getThemeId(context);
}
contextThemed = new ContextThemeWrapper(context, themeId);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// To fix status bar color
if (getActivity() != null
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getActivity().getWindow();
int[] attrColorPrimaryDark = {android.R.attr.colorPrimaryDark};
TypedArray taColorPrimaryDark = contextThemed.getTheme().obtainStyledAttributes(attrColorPrimaryDark);
window.setStatusBarColor(taColorPrimaryDark.getColor(0, Color.BLACK));
taColorPrimaryDark.recycle();
}
return super.onCreateView(inflater, container, savedInstanceState);
}
public Context getContextThemed() {
return contextThemed;
}
}

View File

@@ -26,7 +26,6 @@ import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -124,20 +123,14 @@ public class AddNodeButtonView extends RelativeLayout {
return super.onTouchEvent(event);
}
public RecyclerView.OnScrollListener hideButtonOnScrollListener() {
return new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (state.equals(State.CLOSE)) {
if (dy > 0 && addButtonView.getVisibility() == View.VISIBLE) {
hideButton();
} else if (dy < 0 && addButtonView.getVisibility() != View.VISIBLE) {
showButton();
}
}
public void hideButtonOnScrollListener(int dy) {
if (state.equals(State.CLOSE)) {
if (dy > 0 && addButtonView.getVisibility() == View.VISIBLE) {
hideButton();
} else if (dy < 0 && addButtonView.getVisibility() != View.VISIBLE) {
showButton();
}
};
}
}
public void showButton() {

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-50%p" android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_right.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="50%p" android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_out_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="-50%p"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_out_right.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="50%p"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -19,8 +19,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-50%p" android:toXDelta="0"
<translate android:fromXDelta="-100%p" android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -19,8 +19,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="50%p" android:toXDelta="0"
<translate android:fromXDelta="100%p" android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -19,8 +19,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="-50%p"
<translate android:fromXDelta="0" android:toXDelta="-100%p"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -19,8 +19,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="50%p"
<translate android:fromXDelta="0" android:toXDelta="100%p"
android:duration="@android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="@android:integer/config_mediumAnimTime" />
</set>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14,5.5l-6,6 6,6z"/>
</vector>

View File

@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:fillColor="#FFFFFFFF"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9.64,7.64c0.23,-0.5 0.36,-1.05 0.36,-1.64 0,-2.21 -1.79,-4 -4,-4S2,3.79 2,6s1.79,4 4,4c0.59,0 1.14,-0.13 1.64,-0.36L10,12l-2.36,2.36C7.14,14.13 6.59,14 6,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4c0,-0.59 -0.13,-1.14 -0.36,-1.64L12,14l7,7h3v-1L9.64,7.64zM6,8c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM6,20c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM12,12.5c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5 0.5,0.22 0.5,0.5 -0.22,0.5 -0.5,0.5zM19,3l-6,6 2,2 7,-7L22,3z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@@ -43,6 +43,6 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/title"
android:src="@drawable/ic_content_copy_black_24dp"
android:src="@drawable/ic_content_copy_white_24dp"
android:tint="?attr/colorAccent" />
</RelativeLayout>

View File

@@ -52,7 +52,7 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/entry_user_name_label"
android:src="@drawable/ic_content_copy_black_24dp"
android:src="@drawable/ic_content_copy_white_24dp"
android:tint="?attr/colorAccent" />
</RelativeLayout>
@@ -91,7 +91,7 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/entry_password_label"
android:src="@drawable/ic_content_copy_black_24dp"
android:src="@drawable/ic_content_copy_white_24dp"
android:tint="?attr/colorAccent" />
</RelativeLayout>

View File

@@ -47,7 +47,6 @@
android:layout_weight="1"
android:text="@string/root"
android:maxLines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nodes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground" />

View File

@@ -17,63 +17,90 @@
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<android.support.design.widget.CoordinatorLayout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:titleEnabled="false"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar"
android:title="@string/app_name"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:theme="?attr/toolbarAppearance"
app:popupTheme="?attr/toolbarPopupAppearance"
android:elevation="4dp"
tools:targetApi="lollipop">
<com.kunzisoft.keepass.view.GroupHeaderView
android:id="@+id/group_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/toolbar" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/nodes_list"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar" />
android:layout_above="@+id/expandable_toolbar_paste_layout">
<com.kunzisoft.keepass.view.AddNodeButtonView
android:id="@+id/add_node_button"
android:layout_width="wrap_content"
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:titleEnabled="false"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar"
android:title="@string/app_name"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:theme="?attr/toolbarAppearance"
app:popupTheme="?attr/toolbarPopupAppearance"
android:elevation="4dp"
tools:targetApi="lollipop">
<com.kunzisoft.keepass.view.GroupHeaderView
android:id="@+id/group_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/toolbar" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/nodes_list_fragment_container"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar" />
<com.kunzisoft.keepass.view.AddNodeButtonView
android:id="@+id/add_node_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@+id/nodes_list"
app:layout_anchorGravity="right|bottom" />
</android.support.design.widget.CoordinatorLayout>
<net.cachapa.expandablelayout.ExpandableLayout
android:id="@+id/expandable_toolbar_paste_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_anchor="@+id/nodes_list"
app:layout_anchorGravity="right|bottom" />
</android.support.design.widget.CoordinatorLayout>
android:layout_alignParentBottom="true"
app:el_duration="300"
app:el_expanded="false"
app:el_parallax="0.5">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_paste"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
app:theme="?attr/toolbarBottomAppearance"
android:background="?attr/colorAccent"
tools:targetApi="lollipop" />
</net.cachapa.expandablelayout.ExpandableLayout>
</RelativeLayout>

View File

@@ -47,11 +47,10 @@
android:layout_height="wrap_content"
android:text="@string/no_results"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView android:id="@+id/nodes_list"
<FrameLayout
android:id="@+id/nodes_list_fragment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:dividerHeight="0dp"/>
android:layout_height="match_parent" />
</FrameLayout>
</RelativeLayout>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePass DX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_open"
android:icon="@drawable/ic_launch_white_24dp"
android:title="@string/menu_open"
android:orderInCategory="1010"
app:showAsAction="ifRoom" />
<item android:id="@+id/menu_edit"
android:icon="@drawable/ic_mode_edit_white_24dp"
android:title="@string/menu_edit"
android:orderInCategory="1020"
app:showAsAction="ifRoom" />
<item android:id="@+id/menu_copy"
android:icon="@drawable/ic_content_copy_white_24dp"
android:title="@string/menu_copy"
android:orderInCategory="1040"
app:showAsAction="ifRoom" />
<item android:id="@+id/menu_move"
android:icon="@drawable/ic_content_cut_white_24dp"
android:title="@string/menu_move"
android:orderInCategory="1030"
app:showAsAction="ifRoom" />
<item android:id="@+id/menu_delete"
android:icon="@drawable/ic_content_delete_white_24dp"
android:title="@string/menu_delete"
android:orderInCategory="1050"
app:showAsAction="ifRoom" />
</menu>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
Copyright 2018 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
@@ -17,16 +17,10 @@
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar_default" />
<android.support.v7.widget.RecyclerView android:id="@+id/nodes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar" />
</RelativeLayout>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_paste"
android:title="@string/menu_paste"
android:orderInCategory="1090"
app:showAsAction="ifRoom|withText" />
</menu>

View File

@@ -19,6 +19,7 @@
-->
<resources>
<attr name="toolbarAppearance" format="reference" />
<attr name="toolbarBottomAppearance" format="reference" />
<attr name="toolbarPopupAppearance" format="reference" />
<attr name="colorAccentCompat" format="reference|color" />

View File

@@ -21,6 +21,7 @@
<color name="transparent">#00ffffff</color>
<color name="white">#ffffff</color>
<color name="black">#000000</color>
<color name="dark">#0e0e0e</color>
<color name="orange_light">#ffa726</color>
<color name="orange">#fb8c00</color>

View File

@@ -87,6 +87,7 @@
<string name="error_title_required">A title is required.</string>
<string name="error_wrong_length">Enter a positive integer on length field</string>
<string name="error_autofill_enable_service">Autofill service can\'t be enabled.</string>
<string name="error_move_folder_in_itself">Unable to move a group in itself.</string>
<string name="field_name">Field Name</string>
<string name="field_value">Field value</string>
<string name="file_not_found">File not found.</string>
@@ -121,9 +122,13 @@
<string name="menu_app_settings">Application settings</string>
<string name="menu_form_filling_settings">Form filling</string>
<string name="menu_db_settings">Database settings</string>
<string name="menu_delete">Delete</string>
<string name="menu_donate">Donate</string>
<string name="menu_edit">Edit</string>
<string name="menu_copy">Copy</string>
<string name="menu_move">Move</string>
<string name="menu_paste">Paste</string>
<string name="menu_delete">Delete</string>
<string name="menu_cancel">Cancel</string>
<string name="menu_hide_password">Hide Pass</string>
<string name="menu_lock">Lock Database</string>
<string name="menu_open">Open</string>

View File

@@ -29,6 +29,8 @@
<item name="android:textColorHintInverse">@color/blue_lighter</item>
<item name="android:windowBackground">@color/background_light</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Blue</item>
<item name="toolbarBottomAppearance">@style/KeepassDXStyle.Toolbar.Bottom.Blue</item>
<item name="actionModeStyle">@style/KeepassDXStyle.ActionMode.Blue</item>
</style>
<!-- Toolbar Style Blue -->
<style name="KeepassDXStyle.Toolbar.Blue" parent="KeepassDXStyle.Blue">
@@ -39,6 +41,13 @@
<item name="android:editTextColor">@color/colorTextInverse</item>
<item name="android:textColorHint">@color/blue_lighter</item>
</style>
<style name="KeepassDXStyle.Toolbar.Bottom.Blue" parent="KeepassDXStyle.Toolbar.Blue">
<item name="actionMenuTextColor">@color/colorTextInverse</item>
</style>
<!-- Contextual Action Bar Blue -->
<style name="KeepassDXStyle.ActionMode.Blue" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/blue_dark</item>
</style>
<!-- File Picker Theme -->
<style name="KeepassDXStyle.FilePickerStyle.Blue" parent="KeepassDXStyle.FilePickerStyle">
<item name="colorPrimary">@color/blue</item>

View File

@@ -21,7 +21,7 @@
<!-- Dark Style -->
<style name="KeepassDXStyle.Dark" parent="KeepassDXStyle.Night.v21" >
<item name="colorPrimary">#212121</item>
<item name="colorPrimaryDark">#0e0e0e</item>
<item name="colorPrimaryDark">@color/dark</item>
<item name="colorAccent">#26a69a</item>
<item name="colorAccentCompat">#26a69a</item>
<item name="colorControlActivated">#80cbc4</item>
@@ -29,8 +29,10 @@
<item name="android:textColorHintInverse">#80cbc4</item>
<item name="android:windowBackground">@color/background_dark</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Dark</item>
<item name="toolbarBottomAppearance">@style/KeepassDXStyle.Toolbar.Dark</item>
<item name="android:alertDialogTheme">@style/KeepassDXStyle.Dark.Dialog</item>
<item name="alertDialogTheme">@style/KeepassDXStyle.Dark.Dialog</item>
<item name="actionModeStyle">@style/KeepassDXStyle.ActionMode.Dark</item>
</style>
<!-- Toolbar Style Dark -->
<style name="KeepassDXStyle.Toolbar.Dark" parent="KeepassDXStyle.Dark">
@@ -38,6 +40,10 @@
<item name="android:textColorPrimary">@color/colorTextInverse</item>
<item name="android:textColorSecondary">@color/colorTextSecondary</item>
</style>
<!-- Contextual Action Bar Dark -->
<style name="KeepassDXStyle.ActionMode.Dark" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/dark</item>
</style>
<!-- Dialog -->
<style name="KeepassDXStyle.Dark.Dialog" parent="KeepassDXStyle.Night.Dialog">
<item name="colorAccent">#fefefe</item>

View File

@@ -31,6 +31,8 @@
<item name="android:textColorHintInverse">@color/purple_lighter</item>
<item name="android:windowBackground">@color/background_purple</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Purple</item>
<item name="toolbarBottomAppearance">@style/KeepassDXStyle.Toolbar.Bottom.Purple</item>
<item name="actionModeStyle">@style/KeepassDXStyle.ActionMode.Purple</item>
<item name="iconPreferenceColor">@color/purple_light</item>
</style>
<!-- Toolbar Style Purple -->
@@ -41,6 +43,13 @@
<item name="android:editTextColor">@color/colorTextInverse</item>
<item name="android:textColorHint">@color/purple_lighter</item>
</style>
<style name="KeepassDXStyle.Toolbar.Bottom.Purple" parent="KeepassDXStyle.Toolbar.Purple">
<item name="actionMenuTextColor">@color/colorTextInverse</item>
</style>
<!-- Contextual Action Bar Purple -->
<style name="KeepassDXStyle.ActionMode.Purple" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/purple_dark</item>
</style>
<!-- File Picker Theme -->
<style name="KeepassDXStyle.FilePickerStyle.Purple" parent="KeepassDXStyle.FilePickerStyle">
<item name="colorPrimary">@color/purple</item>

View File

@@ -29,6 +29,8 @@
<item name="android:textColorHintInverse">@color/red_lighter</item>
<item name="android:windowBackground">@color/background_night</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Red</item>
<item name="toolbarBottomAppearance">@style/KeepassDXStyle.Toolbar.Red</item>
<item name="actionModeStyle">@style/KeepassDXStyle.ActionMode.Red</item>
</style>
<!-- Toolbar Style Red -->
<style name="KeepassDXStyle.Toolbar.Red" parent="KeepassDXStyle.Red">
@@ -39,6 +41,10 @@
<item name="android:editTextColor">@color/colorTextInverse</item>
<item name="android:textColorHint">@color/red_lighter</item>
</style>
<!-- Contextual Action Bar Red -->
<style name="KeepassDXStyle.ActionMode.Red" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/red_dark</item>
</style>
<!-- File Picker Theme -->
<style name="KeepassDXStyle.FilePickerStyle.Red" parent="KeepassDXStyle.FilePickerStyle">
<item name="colorPrimary">@color/red</item>

View File

@@ -29,9 +29,7 @@
<!-- Base application theme. -->
<!-- For setting encapsulation -->
<style name="KeepassDXStyle.Light.v21" parent="Theme.AppCompat.Light">
<!-- TODO Activate after navigation drawer
<item name="android:windowAnimationStyle">@style/KeepassDXStyle.ActivityAnimation</item>
-->
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
@@ -73,14 +71,14 @@
<!-- Toolbar -->
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Light</item>
<item name="toolbarPopupAppearance">@style/KeepassDXStyle.Light.Toolbar.Popup</item>
<item name="toolbarBottomAppearance">@style/KeepassDXStyle.Toolbar.Night</item>
<item name="actionModeStyle">@style/KeepassDXStyle.ActionMode</item>
<!-- White FAB -->
<item name="whiteFab">@style/KeepassDXStyle.Fab.White</item>
</style>
<style name="KeepassDXStyle.Night.v21" parent="Theme.AppCompat">
<!--
<item name="android:windowAnimationStyle">@style/KeepassDXStyle.ActivityAnimation</item>
-->
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
@@ -127,6 +125,8 @@
<!-- Toolbar -->
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Night</item>
<item name="toolbarPopupAppearance">@style/KeepassDXStyle.Night.Toolbar.Popup</item>
<item name="toolbarBottomAppearance">@style/KeepassDXStyle.Toolbar.Night</item>
<item name="actionModeStyle">@style/KeepassDXStyle.ActionMode</item>
<!-- White FAB -->
<item name="whiteFab">@style/KeepassDXStyle.Fab.White</item>
@@ -148,10 +148,10 @@
<!-- Activity Animation -->
<style name="KeepassDXStyle.ActivityAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/slide_in_right</item>
<item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
<item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
<item name="android:activityOpenEnterAnimation">@anim/slide_alpha_in_right</item>
<item name="android:activityOpenExitAnimation">@anim/slide_alpha_out_left</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_alpha_in_left</item>
<item name="android:activityCloseExitAnimation">@anim/slide_alpha_out_right</item>
</style>
<!-- Toolbar Style Light Green -->
@@ -172,6 +172,11 @@
<item name="android:textColorHint">@color/green_lighter</item>
</style>
<!-- Contextual Action Bar -->
<style name="KeepassDXStyle.ActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/green_dark</item>
</style>
<!-- Dialog -->
<style name="KeepassDXStyle.Night.Dialog" parent="Theme.AppCompat.Dialog.Alert">
<item name="colorAccent">@color/orange</item>